网站首页 > 技术文章 正文
该篇文章是基于opencv图像识别的AI五子棋系列的第二篇文章。通过该系列文章,你可以进阶opencv图像识别,熟悉图像识别与处理的大致流程,熟悉AI智能......
上一篇文章对五子棋棋盘进行了预处理操作,该篇文章,则在上一篇文章的基础上进行棋盘轮廓的查找,查找到轮廓后方便我们进行后续处理。
首先看一下处理的效果图
效果示意图
程序具体实现过程
我们的目标就是寻找到棋盘的最外边轮廓,那么如何寻找呢,我们可以利用opencv的一个函数:
findContours(srcImage, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);//找轮廓 CV_RETR_EXTERNAL,表示最外层轮廓
这个函数的详细解释如下:
函数原型
findContours(InputOutputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode, int method, Point offset = Point());
- 参数1:单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;
- 参数2:contours定义为“vector<vector<Point>> contours”,是一个双重向量(向量内每个元素保存了一组由连续的Point构成的点的集合的向量),每一组点集就是一个轮廓,有多少轮廓,contours就有多少元素;
- 参数3:hierarchy定义为“vector<Vec4i> hierarchy”,Vec4i的定义:typedef Vec<int, 4> Vec4i;(向量内每个元素都包含了4个int型变量),所以从定义上看,hierarchy是一个向量,向量内每个元素都是一个包含4个int型的数组。向量hierarchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。hierarchy内每个元素的4个int型变量是hierarchy[i][0] ~ hierarchy[i][3],分别表示当前轮廓 i 的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的编号索引。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓,则相应的hierarchy[i][*]被置为-1。
- 参数4:定义轮廓的检索模式,取值如下:
CV_RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
CV_RETR_LIST:检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到;
CV_RETR_CCOMP: 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;
CV_RETR_TREE: 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
- 参数5:定义轮廓的近似方法,取值如下:
CV_CHAIN_APPROX_NONE:保存物体边界上所有连续的轮廓点到contours向量内;
CV_CHAIN_APPROX_SIMPLE:仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;
CV_CHAIN_APPROX_TC89_L1:使用teh-Chinl chain 近似算法;
CV_CHAIN_APPROX_TC89_KCOS:使用teh-Chinl chain 近似算法。
- 参数6:Point偏移量,所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可以是负值!
注意:本文中参数3和参数6被省略。
寻找轮廓的函数看似简单,但想用好,还需要不断地进行优化和过滤,因为这个函数最开始找到的轮廓可能有十几个。因此我们需要根据经验,将其他轮廓过滤掉,这里我们利用了轮廓的长度进行了过滤,在摄像头的位置和图像分辨率确定后,我们图像的轮廓长度应该是在某个特定范围内的。
下面是通过长度过滤的代码片段:
for (int i = 0; i < contours.size(); i++)
{//历遍所有的轮廓
length[i] = arcLength(contours[i], true);
if (length[i] >2000 && length[i] <12000)
{//通过长度匹配滤除小轮廓
convexHull(Mat(contours[i]), hull[i], false);//把凸包找出来
Area_contours[i] = contourArea(contours[i]);
Area_hull[i] = contourArea(hull[i]);
Rectangularity[i] = Area_contours[i] / Area_hull[i];
printf("过滤后轮廓长度为 length=%5f\n", length[i]);
RectContours.push_back(hull[i]);//把提取出来的方框导入到新的轮廓组
drawContours(drawing, hull, i, Scalar(0, 0, 0), 1);//得到方框
}
}
通过上述代码的处理,我们初步找到了轮廓,如下图:
我们可以看到上图总已经大体找到了棋盘的轮廓,但是美中不足的是,图像轮廓有两个,这是什么原因呢?原来通过观察原图,我们发现图像中间有条黑色的线,使得图像有些分离,那么我们该如何找到一个完美的轮廓呢?最简单的办法是将两个轮廓连通。
这里我利用了一个小技巧:
1、找到两个轮廓中心。
2、然后在两个轮廓中心画线,连通两个轮廓。
3、再进行查找最外边轮廓找到最终的轮廓。
下面是代码的具体实现过程:
vector<Point2f> zhongxin_zuobiao;
zhongxin_zuobiao = find_lunkuo_zhongxin(RectContours);
if (zhongxin_zuobiao.size() >= 2)
{
for (int i = 0; i < zhongxin_zuobiao.size() - 1; i++)
{
for (int j = i + 1; j < zhongxin_zuobiao.size(); j++)
{
float dis_juxin = getDistance_1(zhongxin_zuobiao[i], zhongxin_zuobiao[j]);
//if (dis_juxin <65)
{
line(drawing, zhongxin_zuobiao[i], zhongxin_zuobiao[j], Scalar(0), 40, LINE_AA);
}
}
}
}
vector<vector<Point>> contours1;
findContours(drawing, contours1, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//找轮廓 CV_RETR_EXTERNAL,表示最外层轮廓
Mat drawing1(drawing.size(), CV_8UC3, cv::Scalar(255, 255, 255));
vector<vector<Point>> hull1(contours1.size());//用于存放凸包
for (int i = 0; i < contours1.size(); i++)
{//历遍所有的轮廓
//drawContours(drawing1, contours1, i, Scalar(0, 255, 0), 1);//得到方框
convexHull(Mat(contours1[i]), hull1[i], false);//把凸包找出来
RectContours_fainal.push_back(hull1[i]);//把提取出来的方框导入到新的轮廓组
drawContours(drawing1, hull1, i, Scalar(0, 0, 0), 4);//得到方框
}
查找轮廓中点的部分,我封装成了一个函数。
/*******************************************************************************************
*函数功能 : 找每个轮廓的中心坐标
*输入参数 :轮廓或者凸包
*返 回 值 : 点向量
*编写时间 : 2018.8.9
*作 者 : diyun
********************************************************************************************/
vector< Point2f> find_lunkuo_zhongxin(vector<vector<Point>> dangban_RectContours)
{
vector<Point2f> zhongxin_zuobiao;
/// 计算矩
vector<Moments> mu(dangban_RectContours.size());
for (int i = 0; i < dangban_RectContours.size(); i++)
{
mu[i] = moments(dangban_RectContours[i], false);
}
/// 计算中心矩:
vector<Point2f> mc(dangban_RectContours.size());
for (int i = 0; i < dangban_RectContours.size(); i++)
{
mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
zhongxin_zuobiao.push_back(mc[i]);
}
return zhongxin_zuobiao;
}
通过上面的处理我们最终完成了棋盘轮廓的查找:
完整代码
/*****************************************************************************************
五子棋棋盘棋子识别检测
1、灰度化,二值化
2、查找棋盘最外边轮廓
*****************************************************************************************/
#include<opencv2/opencv.hpp>
#include <iostream>
#include <fstream>
#include <stdlib.h> //srand()和rand()函数
#include <time.h> //time()函数
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/ml/ml.hpp>
#include<opencv2\opencv.hpp>
#include <opencv2\imgproc\types_c.h>
#include<windows.h>
using namespace std;
using namespace cv;
using namespace ml;
/*******************************************************************************************
*函数功能 : 找每个轮廓的中心坐标
*输入参数 :轮廓或者凸包
*返 回 值 : 点向量
*编写时间 : 2018.8.9
*作 者 : diyun
********************************************************************************************/
vector< Point2f> find_lunkuo_zhongxin(vector<vector<Point>> dangban_RectContours)
{
vector<Point2f> zhongxin_zuobiao;
/// 计算矩
vector<Moments> mu(dangban_RectContours.size());
for (int i = 0; i < dangban_RectContours.size(); i++)
{
mu[i] = moments(dangban_RectContours[i], false);
}
/// 计算中心矩:
vector<Point2f> mc(dangban_RectContours.size());
for (int i = 0; i < dangban_RectContours.size(); i++)
{
mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);
zhongxin_zuobiao.push_back(mc[i]);
}
return zhongxin_zuobiao;
}
//两点之间距离
float getDistance_1(Point pointO, Point pointA)
{
float distance;
distance = powf((pointO.x - pointA.x), 2) + powf((pointO.y - pointA.y), 2);
distance = sqrtf(distance);
return distance;
}
int main()
{
float ret = 0;
Mat srcImage0 = imread("1.jpg");//读取图片
//Mat srcImage0 = imread("app/1.jpg");//读取图片
if (srcImage0.empty())
{
cout << " 待预测图像不存在: " << endl;
printf("[ALG ERROR][函数:%s][行号:%d],图片未正常读取,请检查输入路径十分正确 \n", __FUNCTION__, __LINE__, 1);
cout << " 待预测图像不存在: " << endl;
}
Mat srcImage, srcImage1;
resize(srcImage0, srcImage0, Size(1920, 1080));
cvtColor(srcImage0, srcImage1, CV_BGR2GRAY);
namedWindow("灰度化", 0);
imshow("灰度化", srcImage1);
waitKey(2);
srcImage = srcImage1 > 150; // 二值化
namedWindow("二值化", 0);
imshow("二值化", srcImage);
waitKey(2);
/*****1、输入二值化图像返回有用的轮廓*****/
/*一般通过长度滤除剩下两个,选择内部那个*/
vector<vector<Point>> contours, RectContours, RectContours_fainal;
int height = srcImage.rows;
findContours(srcImage, contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);//找轮廓 CV_RETR_EXTERNAL,表示最外层轮廓
vector<vector<Point>> hull(contours.size());//用于存放凸包
vector<float> length(contours.size());
vector<float> Area_contours(contours.size()), Area_hull(contours.size()), Rectangularity(contours.size()), circularity(contours.size());
Mat drawing(srcImage.size(), CV_8UC3, cv::Scalar(255, 255, 255));
for (int i = 0; i < contours.size(); i++)
{//历遍所有的轮廓
length[i] = arcLength(contours[i], true);
if (length[i] >2000 && length[i] <12000)
{//通过长度匹配滤除小轮廓
convexHull(Mat(contours[i]), hull[i], false);//把凸包找出来
Area_contours[i] = contourArea(contours[i]);
Area_hull[i] = contourArea(hull[i]);
Rectangularity[i] = Area_contours[i] / Area_hull[i];
printf("过滤后轮廓长度为 length=%5f\n", length[i]);
circularity[i] = (4 * 3.1415*Area_contours[i]) / (length[i] * length[i]);
//drawContours(drawing, contours, i, (255, 0, 0), 2);//得到方框
//if (Rectangularity[i]>0.9&&circularity[i]<0.8&&Area_hull[i]>8000 && Area_hull[i]<50000)
{//通过凸包面积滤除不对的凸包,找到最终的四边形
RectContours.push_back(hull[i]);//把提取出来的方框导入到新的轮廓组
drawContours(drawing, hull, i, Scalar(0, 0, 0), 1);//得到方框
}
}
}
if (0 == RectContours.size())
{
printf("[ALG ERROR][函数:%s][行号:%d],RectContours.size=0,当前帧未找到轮廓,检查仪表二值化是否正常\n", __FUNCTION__, __LINE__);
}
/*识别结果显示,调试用*/
//namedWindow("找轮廓结果", 0);
//imshow("找轮廓结果", drawing);
//waitKey(2);
/*将多个轮廓,弄成一个*/
cvtColor(drawing, drawing, CV_BGR2GRAY);
drawing = drawing<150; // 二值化
//imshow("二值化", drawing);
//将线连接起来
vector<Point2f> zhongxin_zuobiao;
zhongxin_zuobiao = find_lunkuo_zhongxin(RectContours);
if (zhongxin_zuobiao.size() >= 2)
{
for (int i = 0; i < zhongxin_zuobiao.size() - 1; i++)
{
for (int j = i + 1; j < zhongxin_zuobiao.size(); j++)
{
float dis_juxin = getDistance_1(zhongxin_zuobiao[i], zhongxin_zuobiao[j]);
//if (dis_juxin <65)
{
line(drawing, zhongxin_zuobiao[i], zhongxin_zuobiao[j], Scalar(0), 40, LINE_AA);
}
}
}
}
vector<vector<Point>> contours1;
findContours(drawing, contours1, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//找轮廓 CV_RETR_EXTERNAL,表示最外层轮廓
Mat drawing1(drawing.size(), CV_8UC3, cv::Scalar(255, 255, 255));
vector<vector<Point>> hull1(contours1.size());//用于存放凸包
for (int i = 0; i < contours1.size(); i++)
{//历遍所有的轮廓
//drawContours(drawing1, contours1, i, Scalar(0, 255, 0), 1);//得到方框
convexHull(Mat(contours1[i]), hull1[i], false);//把凸包找出来
RectContours_fainal.push_back(hull1[i]);//把提取出来的方框导入到新的轮廓组
drawContours(drawing1, hull1, i, Scalar(0, 0, 0), 4);//得到方框
}
namedWindow("最终轮廓", 0);
imshow("最终轮廓", drawing1);
waitKey(0);
return 0;
}
下篇文章讲述根据最外边轮廓寻找棋盘的四个顶点。
猜你喜欢
- 2024-09-30 OPENCV-python 第一天 python opencv教程
- 2024-09-30 Python帮您十步搞定人脸检测 人脸检测 python
- 2024-09-30 OpenCV学习笔记(一)之图像金字塔-上采样与降采样与DOG
- 2024-09-30 Ubuntu18.04LTS下OpenCV的配置 ubuntu opencv4
- 2024-09-30 计算机视觉之Opencv(1)——基本操作
- 2024-09-30 OpenCV系列教程_03 opencv官方教程
- 2024-09-30 CV之 HOG特征描述算子-行人检测 卜算子 黄州定慧院寓居作
- 2024-09-30 OpenCV SURF特征点检测和匹配 opencv特征提取方法
- 2024-09-30 Opencv从零开始 - [启蒙篇] - 读取、几何变换
- 2024-09-30 密码忘记了?没事,我早就用Python给你监听了
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)