1、图像轮廓
1.1图像轮廓与API函数
轮廓是一系列相连的点组成的曲线,代表了物体的基本外形,相对于边缘,轮廓是连续的,边缘并不全部连续。一般地,获取图像轮廓要经过下面几个步骤:
1) 读取图片。
2) 将彩色图像转换成灰度图像。
3) 将灰度图像转换成二值图形并查找其二值图像边缘即可(如canny边缘检测)。
4) 显示轮廓边缘。
findContours寻找轮廓函数,原型为:
CV_EXPORTS_W void findContours( InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point()); /** @overload */ CV_EXPORTS void findContours( InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point());
1)image:图像,单通道灰度图。
2)contours:检测到的轮廓,包含对象边界点(x,y)的坐标,每个轮廓存储为一个点向量可用point类型的vector存储。
3)hierarchy:轮廓的拓扑信息,每个轮廓contours[i]包含4个hierarchy[i]元素,hierarchy[i][0]-- hierarchy[i][3],分别代表后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的编号。
4)mode:轮廓检索模式。
RETR_EXTERNAL :只检索最外面的轮廓;
RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,次层是空洞的内层边界;
RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;
返回值的含义
5)method:轮廓逼近方法。
CHAIN_APPROX_NONE:输出轮廓的每个像素。
CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,函数只保留他们的终点坐标。
drawContours绘制轮廓函数,原型为:
CV_EXPORTS_W void drawContours( InputOutputArray image, InputArrayOfArrays 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 - 所有的输入轮廓,每个轮廓为点矢量(a point vector)/点向量形式,与findcontours中的contours 形式一致。
contourIdx - 指定轮廓列表的索引 ID(将被绘制),若为负数,则所有的轮廓将会被绘制。
color - 绘制轮廓的颜色。
thickness - 绘制轮廓线条的宽度,若为负值或CV.FILLED则将填充轮廓内部区域。
lineType – 线条的类型,8连通型或4连通型。
hierarchy - 层次结构信息,与函数findcontours()的hierarchy有关
maxLevel - 绘制轮廓的最高级别。若为0,则绘制指定轮廓;若为1,则绘制该轮廓和所有嵌套轮廓(nested contours);若为2,则绘制该轮廓、嵌套轮廓(nested contours)/子轮廓和嵌套-嵌套轮廓(all the nested-to-nested contours)/孙轮廓,等等。该参数只有在层级结构时才用到。
offset - 按照偏移量移动所有的轮廓(点坐标)。
1.2图像轮廓实例
测试代码如下。
int main() { RNG rng(12345); Mat src_gray; Mat src = imread("D:\WORK\5.OpenCV\LeanOpenCV\pic_src\pic9.bmp"); imshow("原图", src); /// 转成灰度并模糊化降噪 cvtColor(src, src_gray, CV_BGR2GRAY); blur(src_gray, src_gray, Size(3, 3)); int thresh = 100; int max_thresh = 255; Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// 用Canny算子检测边缘 Canny(src_gray, canny_output, thresh, thresh * 2, 3); /// 寻找轮廓 findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); /// 绘出轮廓 Mat drawing = Mat::zeros(canny_output.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(drawing, contours, i, color, 1, 8, hierarchy, 0, Point()); } /// 在窗体中显示结果 namedWindow("Contours", CV_WINDOW_AUTOSIZE); imshow("Contours", drawing); waitKey(0); }
输出结果为:
测试2,结果如下图。
2、凸包
2.1凸包与API函数
在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。X的凸包可以用X内所有点(X1,…Xn)的凸组合来构造。简单来讲,对于一个二维空间的点集,这个点集当中的一些点总可以形成一个凸多边形,而这个凸多边形之内恰好可以包括除了组成凸包这个凸多边以外的所有点,而这个凸多边形就是凸包。凸包可以想象成一条刚好包住所有点的橡皮圈,对于给定二维平面上的点集,凸包常常就是将最外层的点连接起来构成的凸多边形,它能包含点击中所有的点。
物体的凸包检测常应用在物体识别、手势识别及边界检测等领域。理解物体形状或轮廓的一种方法是计算物体的凸包,然后计算其凸缺陷。下图人手图像画图了凸包线轮廓,然后标出了凸缺陷A—H。黑色的轮廓线为convexity hull, 而convexity hull与手掌之间的部分为convexity defects. 每个convexity defect区域有四个特征量:起始点(startPoint),结束点(endPoint),距离convexity hull最远点(farPoint),最远点到convexity hull的距离(depth)。
OpenCV使用convexHull函数做物体轮廓凸包检测:
CV_EXPORTS_W void convexHull( InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true );
point:输入的二维点集,可储存在向量或矩阵Mat中,代表轮廓点
hull:输出凸包,这是一个整数索引的载体或点的矢量,可以是vector<vector<Point>>和vector<vector<int>>
clockwise:方向标志位,true为顺时针,false为逆时针方向。
return Point :操作标准位,默认true,表示返回凸包的各点,否则返回凸包各点的指数。
OpenCV使用convexityDefects函数做轮廓凸包缺陷检测:
CV_EXPORTS_W void convexityDefects( InputArray contour, InputArray convexhull, OutputArray convexityDefects );
coutour: 输入参数,检测到的轮廓,可以调用findContours函数得到;
convexhull: 输入参数,检测到的凸包,可以调用convexHull函数得到。注意,convexHull函数可以得到vector<vector<Point>>和vector<vector<int>>两种类型结果,这里的convexhull应该为vector<vector<int>>类型,否则通不过ASSERT检查;
convexityDefects:输出参数,检测到的最终结果,应为vector<vector<Vec4i>>类型,Vec4i存储了起始点(startPoint),结束点(endPoint),距离convexity hull最远点(farPoint)以及最远点到convexity hull的距离(depth)。
2.2图像凸包检测实例
图像轮廓与凸包实例测试代码如下。
int main() { RNG rng(12345); Mat src_gray; Mat src = imread("D:\WORK\5.OpenCV\LeanOpenCV\pic_src\pic9.bmp"); imshow("原图", src); /// 转成灰度并模糊化降噪 cvtColor(src, src_gray, CV_BGR2GRAY); //blur(src_gray, src_gray, Size(5, 5)); imshow("src_gray", src_gray); int thresh = 100; int max_thresh = 255; Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// 用Canny算子检测边缘 Canny(src_gray, canny_output, thresh, thresh * 2, 3); imshow("canny_output", canny_output); /// 寻找轮廓 findContours(canny_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); //通过发现轮廓得到的候选点来绘制凸包 vector<vector<Point>> convexs(contours.size()); for (size_t i = 0; i < contours.size(); i++) { convexHull(contours[i], convexs[i], false, true); } /// 绘出轮廓 Mat drawing = Mat::zeros(canny_output.size(), CV_8UC3); Mat drawing_convex = Mat::zeros(canny_output.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(drawing, contours, i, color, 1, 8, hierarchy, 0, Point()); //白色线画出凸包 drawContours(drawing_convex, convexs, i, Scalar(255, 255, 255), 1, LINE_8, noArray(), 0, Point()); } imshow("Contours", drawing); imshow("drawing_convex", drawing_convex); waitKey(0); }
输出结果为:
测试2,输出结果为:
3、使用多边形将轮廓包围
3.1多边形包围轮廓和API
当我们得到对象轮廓后,可用boundingRect()得到包覆此轮廓的最小正矩形,minAreaRect()得到包覆轮廓的最小斜矩形,minEnclosingCircle()得到包覆此轮廓的最小圆形。
CV_EXPORTS_W void approxPolyDP( InputArray curve, OutputArray approxCurve, double epsilon, bool closed ); CV_EXPORTS_W Rect boundingRect( InputArray points ); CV_EXPORTS_W void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius );
3.2多边形包围轮廓测试实例
测试代码如下:
int main() { RNG rng(12345); Mat src_gray; Mat src = imread("D:\WORK\5.OpenCV\LeanOpenCV\pic_src\img3.bmp"); imshow("原图", src); /// 转成灰度并模糊化降噪 cvtColor(src, src_gray, CV_BGR2GRAY); //blur(src_gray, src_gray, Size(5, 5)); imshow("src_gray", src_gray); int thresh = 130; int max_thresh = 255; Mat canny_output; vector<vector<Point> > contours; vector<Vec4i> hierarchy; /// 用Canny算子检测边缘 Canny(src_gray, canny_output, thresh, thresh * 2, 3); imshow("canny_output", canny_output); /// 寻找轮廓 findContours(canny_output, contours, hierarchy, RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); vector<vector<Point> > contours_poly(contours.size()); vector<Rect> boundRect(contours.size()); vector<Point2f>center(contours.size()); vector<float>radius(contours.size()); for (int i = 0; i < contours.size(); i++) { approxPolyDP(Mat(contours[i]), contours_poly[i], 3, true); boundRect[i] = boundingRect(Mat(contours_poly[i])); minEnclosingCircle(contours_poly[i], center[i], radius[i]); } /// 绘出轮廓 Mat drawing = Mat::zeros(canny_output.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(drawing, contours, i, color, 1, 8, hierarchy, 0, Point()); /// 画多边形轮廓 + 包围的矩形框 + 圆形框 rectangle(drawing, boundRect[i].tl(), boundRect[i].br(), color, 2, 8, 0); circle(drawing, center[i], (int)radius[i], color, 2, 8, 0); } imshow("Contours", drawing); waitKey(0); }
输出结果如下:
如果需要填充轮廓空心空白,其中drawContours的参数thicknes代表绘制轮廓线条的宽度,若为负值或CV.FILLED,表示填充轮廓内部区域。所以将此参数置-1就出现下面结果。
4、参考文献
1、《OpenCV3 编程入门》,电子工业出版社,毛星雨著
2、《学习OpenCV》,清华大学出版社,Gary Bradski, Adrian kaehler著
3、用opencv检测convexity defects
https://www.it610.com/article/4474290.htm
4、计算物体的凸包
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/shapedescriptors/hull/hull.html
5、提取轮廓的原理和代码实例
https://blog.csdn.net/qq_29796317/article/details/78297920
6、在图像中寻找轮廓
7、【计算几何/凸包】安德鲁算法(Andrew's Algorithm)详解
https://blog.csdn.net/peng0614/article/details/81193484
技术博客,转载请注明。