16.1 轮廓发现
16.1.1 概述
轮廓发现(find contour)是基于图像边缘提取的基础寻找对象轮廓的方法。所以边缘提取的阈值选定会影响最终轮廓的发现结果。
16.1.2 相关API
findContours:发现轮廓
·void findContours( InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
参数 | 含义 | |
作用 | 在二值图像上,检测出物体轮廓 | |
输入 | image | 单通道图像矩阵,可以是灰度图,但更常用的是二值图像,一般是经过Canny、拉普拉斯等边缘检测算子处理过的二值图像;8bit |
contours | contours定义为“vector<vector> contours”,是一个双重向量(向量内每个元素保存了一组由连续的Point构成的点的集合的向量),每一组点集就是一个轮廓,有多少轮廓,contours就有多少元素; | |
hierarchy | 定义为“vector<Vec4i> hierarchy ”,Vec4i的定义:typedef Vec<int, 4> Vec4i; (向量内每个元素都包含了4个int型变量),分别表示当前轮廓 i 的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的编号索引。 | |
mode | 检索轮廓的模式: RETR_EXTERNAL:只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略; | |
method | 定义轮廓的近似方法: CHAIN_APPROX_NONE:保存物体边界上所有连续的轮廓点到contours向量内; | |
offset | 轮廓像素的位移,默认(0,0)没有位移 | |
返回值 | void | 无 |
drawContours:绘制轮廓
·void drawContours( InputOutputArray image, OutputArrayOfArrays contours, int contourIdx, const Scalar &color, int thickness=1, int lineType=LINE_8,InputArray hierarchy=noArray(),int maxLevel=INT_MAX, Point offset=Point())
参数 | 含义 | |
作用 | 绘制轮廓 | |
输入 | image | 输入输出图像 |
contours | contours定义为“vector<vector> contours”,是一个双重向量(向量内每个元素保存了一组由连续的Point构成的点的集合的向量),每一组点集就是一个轮廓,有多少轮廓,contours就有多少元素; | |
contourIdx | 轮廓索引号,如果为负值则绘制所有输入轮廓 | |
color | 轮廓颜色 | |
thickness | 绘制轮廓所用线条粗细度,如果值为负值,则在轮廓内部绘制 | |
lineType | 线条类型,有默认值LINE_8 | |
hierarchy | 定义为“vector<Vec4i> hierarchy ”,Vec4i的定义:typedef Vec<int, 4> Vec4i; (向量内每个元素都包含了4个int型变量),分别表示当前轮廓 i 的后一个轮廓、前一个轮廓、父轮廓和内嵌轮廓的编号索引。 | |
maxLevel | 最大层数,0只绘制当前的,1绘制当前及其内嵌的轮廓 | |
offset | 轮廓像素的位移,默认(0,0)没有位移 | |
返回值 | void | 无 |
16.1.3 寻找轮廓方法介绍
在findContours中,不同的mode+method会有不同的效果
1)检测最外层轮廓,并且保存轮廓上所有点
mode取值“RETR_EXTERNAL”,method取值“CHAIN_APPROX_NONE”,即只检测最外层轮廓,并且保存轮廓上所有点;如下图只检测了最外层的轮廓,内层的轮廓被忽略。
轮廓:
点集:将轮廓上的所有点均保存下来
2)检测所有轮廓,但各轮廓之间彼此独立,不建立等级关系,并且仅保存轮廓上拐点信息
mode取值“RETR_LIST”,method取值“CHAIN_APPROX_SIMPLE”,即检测所有轮廓,但各轮廓之间彼此独立,不建立等级关系,并且仅保存轮廓上拐点信息:
轮廓:
点集:
3)检测所有轮廓,轮廓间建立外层、内层的等级关系,并且保存轮廓上所有点
mode取值“RETR_TREE”,method取值“CHAIN_APPROX_NONE”,即检测所有轮廓,轮廓间建立外层、内层的等级关系,并且保存轮廓上所有点。
轮廓:
点集:
16.1.4 代码示例
步骤:
- 将输入图像转为灰度图cvtColor
- 使用Canny进行轮廓提取,得到二值图
- 使用findContours寻找轮廓
- 使用drawContours绘制轮廓
Mat src,dst;
const char* OUTPUTWIN = "outputImg";
int thresholdValue = 100;
int thresholdMax = 255;
RNG rng(12345);
void CannyFindContours(int, void*)
{
Mat brinaryImg;
//canny边缘检测
Canny(src, brinaryImg, thresholdValue, thresholdValue * 2,3,false);
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
//对边缘检测后的图像进行轮廓寻找
findContours(brinaryImg, contours, hierachy, RETR_TREE, CHAIN_APPROX_NONE,Point(0,0));
//绘制轮廓
dst = Mat::zeros(src.size(),CV_8UC3);
for (int i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours,i, color,2,8, hierachy,0, Point(0, 0));
}
imshow(OUTPUTWIN, dst);
}
int main(int argc, char** argv)
{
//源图像
src = imread("D:\\testimg\\CSet12\\yingbi.png");
if (src.empty())
{
printf("Could not load the image...\n");
return -1;
}
else;
namedWindow("inputImg", WINDOW_AUTOSIZE);
imshow("inputImg", src);
namedWindow(OUTPUTWIN, WINDOW_AUTOSIZE);
cvtColor(src,src,COLOR_BGR2GRAY);
const char* trackbarTitle = "threshold";
createTrackbar(trackbarTitle, OUTPUTWIN, &thresholdValue, thresholdMax, CannyFindContours);
CannyFindContours(0, 0);
waitKey(0);
return 0;
}
16.2 凸包
16.2.1 概述
凸包(Convex Hull)是指在一个多边形边缘或者内部任意两个点的连线都包含在多边形边界或者内部。包含点集合S中所有点的最小凸边形称为凸包。
16.2.2 Graham扫描检测算法
算法介绍:
- 首先选择Y方向最低的点作为起始点P0;
- 从P0开始极坐标扫描,依次添加P1....Pn(根据极坐标的角度大小,逆时针方向排序)
- 对每个点Pi来说,吐过添加Pi点到凸包中导致一个左转向(逆时针方法)则添加该点到凸包,繁殖是一个右转向(顺时针方向)不添加到凸包中
16.2.3 相关API
·void convexHull( InputArray points, OutputArray hull, bool clockwise=false,bool returnPoints=true)
参数 | 含义 | |
作用 | 查找图像中物体的凸包 | |
输入 | points | 输入的二维点集,Mat类型数据即可(可以从findContours中获取) |
hull | 输出参数,用于输出函数调用后找到的凸包 | |
clockwise | 操作方向,当标识符为真时,输出凸包为顺时针方向,否则为逆时针方向。 | |
returnPoints | 操作标识符,默认值为true,此时返回各凸包的各个点,否则返回凸包各点的指数,当输出数组时std::vector时,此标识被忽略。 | |
返回值 | void | 无 |
16.2.4 代码示例
Mat src, cannyImg,dst;
const char* OUTPUTWIN = "outputImg";
int thresholdValue = 100;
int thresholdMax = 255;
RNG rng(12345);
void CannyFindContours(int, void*)
{
Mat brinaryImg;
//canny边缘检测,(此外可以使用阈值分割等)
//轮廓寻找取决于边缘检测的效果
Canny(cannyImg, brinaryImg, thresholdValue, thresholdValue * 2,3,false);
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
//对边缘检测后的图像进行轮廓寻找
findContours(brinaryImg, contours, hierachy, RETR_TREE, CHAIN_APPROX_NONE,Point(0,0));
//计算凸包,每个轮廓独立计算
vector<vector<Point>> hull(contours.size());
for (int i = 0; i < contours.size(); i++)
{
convexHull(contours[i], hull[i], false, true);
}
for (int i = 0; i < hull.size(); i++)
{
Scalar color = Scalar(rng.uniform(0,255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(src, hull, i, color, 2, 8, hierachy, 0, Point(0, 0));
}
imshow(OUTPUTWIN, src);
}
int main(int argc, char** argv)
{
//源图像
src = imread("D:\\testimg\\CSet12\\yingbi.png");
if (src.empty())
{
printf("Could not load the image...\n");
return -1;
}
else;
namedWindow("inputImg", WINDOW_AUTOSIZE);
imshow("inputImg", src);
namedWindow(OUTPUTWIN, WINDOW_AUTOSIZE);
cvtColor(src, cannyImg,COLOR_BGR2GRAY);
const char* trackbarTitle = "threshold";
createTrackbar(trackbarTitle, OUTPUTWIN, &thresholdValue, thresholdMax, CannyFindContours);
CannyFindContours(0, 0);
waitKey(0);
return 0;
}
需要调整算法预处理,得到更好的边缘检测结果。