3.20 在图像中寻找轮廓
使用
findContours 函数寻找轮廓。
使用
drawContours 函数绘出轮廓。
Mat canny_output;
vector > contours;
vector 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, 2, 8, hierarchy, 0, Point() );
}
findContours 函数参数说明:
canny_output:输入,源图像,8位单通道图像,非零像素看作1,零值像素仍为0(其实就是二值图像)。
contours:输出,检测到的轮廓,每一个轮廓都存储在一个 vector
中.
hierarchy:可选的输出数组,包含了图像的拓扑信息。它有尽可能多的轮廓数目的元素。记录了轮廓的层次信息。
mode:轮廓的检测模式。
CV_RETR_EXTERNAL:仅检测外轮廓。
CV_RETR_LIST:检测所有轮廓,但不建立任何层次关系。
CV_RETR_CCOMP:检测所有轮廓并把它们组织成两级层次。顶层是外轮廓,第二层是内孔的边界。如果在孔内还有轮廓,则放置在顶层。
CV_RETR_TREE:检测所有轮廓并重构一个完整的嵌套式层次结构轮廓。
method:轮廓逼近方法。
CV_CHAIN_APPROX_NONE:存储所有的轮廓点,任意两个连续的轮廓点之间的关系是垂直、水平或对角线相邻。
CV_CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,只留下它们的端点。
CV_CHAIN_APPROX_TC89_L1和CV_CHAIN_APPROX_TC89_KCOS:适用于 Tech-Chin 链逼近算法的一种。
offset:可选偏移量。如果轮廓从图像的ROI中提取,轮廓坐标是ROI中的位置,而分析应针对整幅图像,则得到的轮廓坐标应该进行一定的偏移,移回原图像的坐标位置。
drawContours 函数参数说明:
drawing:输出目标图像。
contours:输入的所有轮廓,每个轮廓存储为vector
i:要画的轮廓线的索引。如果为负数,则绘制所有轮廓。
color:轮廓线的颜色。
2:轮廓线的粗细。
8:连接线的类型。
hierarchy:关于层次的可选信息。
0:绘制轮廓的最大水平。如果是0,只绘制指定的轮廓。如果是1,绘制指定轮廓和被嵌套的轮廓。如果是2,绘制指定轮廓,被嵌套轮廓和迭代嵌套的轮廓。只有上一个参数hierarchy有用时才考虑使用该参数。
Point():可选偏移量。解释同上一个函数的offset。
3.21 计算凸包 - Convex Hull
凸包(Convex Hull)是一个计算几何(图形学)中的概念。用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它能包含点集中所有点的。严谨的定义和相关概念参见维基百科:凸包。
使用
convexHull 函数计算凸包。
在进行凸包计算之前,先要将图像转换到灰度图并进行滤波处理。
Mat src_copy = src.clone();
Mat threshold_output;
vector > contours;
vector hierarchy;
/// 对图像进行二值化
threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
/// 寻找轮廓
findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
/// 对每个轮廓计算其凸包
vector >hull( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{ convexHull( Mat(contours[i]), hull[i], false ); }
/// 绘出轮廓及其凸包
Mat drawing = Mat::zeros( threshold_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, vector(), 0, Point() );
drawContours( drawing, hull, i, color, 1, 8, vector(), 0, Point() );
}
convexHull 函数参数说明:
Mat(contours[i]):输入的2维点集,存储在 vector 或 Mat 中。
hull[i]:输出的凸包。同轮廓的存储方式类似,存储构成凸包的点集。它也可以存储输入的原始点集数组的索引。
false:标志位。为true时返回构成凸包的点。为false时返回凸包点的索引。当hull是vector时,该标志位被忽略,输出取决于vector的具体类型:vector
则返回点集,vector则返回索引(reference_manual上这样说的,感觉说反了,存疑)。
3.22 创建包围轮廓的矩形和圆形边界框
使用
boundingRect 函数计算包围轮廓的矩形框。
使用
minEnclosingCircle 函数计算完全包围已有轮廓的最小圆。
/** @thresh_callback 函数 */
void thresh_callback(int, void* )
{
Mat threshold_output;
vector > contours;
vector hierarchy;
/// 使用Threshold检测边缘
threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
/// 找到轮廓
findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
/// 多边形逼近轮廓 + 获取矩形和圆形边界框
vector > contours_poly( contours.size() );
vector boundRect( contours.size() );
vectorcenter( contours.size() );
vectorradius( 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( threshold_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_poly, i, color, 1, 8, vector(), 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 );
}
boundingRect 函数参数说明:
Mat(contours_poly[i]):输入的点集数组,计算出恰好完全包围该点集的矩形。返回描述矩形的Rect类型。
minEnclosingCircle 函数参数说明:
contours_poly[i]:输入一组2维点集,存储在 vector 或 Mat 中。
center[i]:输出圆心。
radius[i]:输出圆的半径。
3.23 为轮廓创建可倾斜的边界框和椭圆
使用
minAreaRect 函数计算包围轮廓的最小矩形框(可能是倾斜的)。
使用
fitEllipse 函数计算包围轮廓的最小椭圆形。
Mat threshold_output;
vector > contours;
vector hierarchy;
/// 阈值化检测边界
threshold( src_gray, threshold_output, thresh, 255, THRESH_BINARY );
/// 寻找轮廓
findContours( threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
/// 对每个找到的轮廓创建可倾斜的边界框和椭圆
vector minRect( contours.size() );
vector minEllipse( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{ minRect[i] = minAreaRect( Mat(contours[i]) );
if( contours[i].size() > 5 )
{ minEllipse[i] = fitEllipse( Mat(contours[i]) ); }
}
/// 绘出轮廓及其可倾斜的边界框和边界椭圆
Mat drawing = Mat::zeros( threshold_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) );
// contour
drawContours( drawing, contours, i, color, 1, 8, vector(), 0, Point() );
// ellipse
ellipse( drawing, minEllipse[i], color, 2, 8 );
// rotated rectangle
Point2f rect_points[4]; minRect[i].points( rect_points );
for( int j = 0; j < 4; j++ )
line( drawing, rect_points[j], rect_points[(j+1)%4], color, 1, 8 );
}
minAreaRect 函数参数说明:
Mat(contours[i]):输入的一组点集。返回包围该点集的最小矩形,矩形可能是旋转倾斜的情况。
fitEllipse 函数参数说明:
Mat(contours[i]):输入的一组点集。计算包围该点集的最小平方意义上的椭圆,返回的是
该椭圆的外接矩形。