先看提取轮廓的代码:
1 Mat image = imread("D:/picture/images/binaryGroup.bmp",0); 2 if(!image.data) 3 return -1; 4 imshow("源图像",image); 5 6 //获取轮廓 7 std::vector<std::vector<Point>> contours; 8 //获取轮廓: 9 findContours(image, //图像 10 contours, //轮廓点 11 //包含图像拓扑结构的信息(可选参数,这里没有选) 12 CV_RETR_EXTERNAL, //获取轮廓的方法(这里获取外围轮廓) 13 CV_CHAIN_APPROX_NONE); //轮廓近似的方法(这里不近似,获取全部轮廓) 14 //打印轮廓信息 15 std::cout<<"共有外围轮廓:"<<contours.size()<<"条"<<std::endl; 16 std::vector<std::vector<Point>>::const_iterator itContours = contours.begin(); 17 for(;itContours != contours.end();++itContours) 18 { 19 std::cout<<"每个轮廓的长度: "<<itContours->size()<<std::endl; 20 }
注意到轮廓的存储格式为std::vector<std::vector<Point>>,他说明整个轮廓是若干条轮廓按一定顺序组成的,而每个轮廓中的点也是有顺序的。
画出轮廓就比较简单了:
1 //画出轮廓 2 Mat result(image.size(),CV_8U,Scalar(255)); 3 //画出轮廓,参数为:画板,轮廓,轮廓指示(这里画出所有轮廓),颜色,线粗 4 drawContours(result,contours,-1,Scalar(0),2); 5 imshow("提取外围轮廓",result);
还要注意提取轮廓的方法还有很多种,比如CV_RETR_LIST代表所有轮廓
findContours(image, //图像 contours, //轮廓点 //包含图像拓扑结构的信息(可选参数,这里没有选) CV_RETR_LIST, //获取轮廓的方法(这里获取所有轮廓) CV_CHAIN_APPROX_NONE); //轮廓近似的方法(这里不近似,获取全部轮廓 //画出轮廓 drawContours(result,contours,-1,Scalar(0),2); imshow("提取所有轮廓",result);
通常,这样提取的轮廓包含一些我们不希望的轮廓(比如一些小洞),或者假如我们知道我们感兴趣的物体轮廓的大概范围时,我们就可以用下面的办法缩小目标范围:
1 //除去太长或者太短的轮廓 2 int cmin = 100; 3 int cmax = 1000; 4 std::vector<std::vector<Point>>::const_iterator itc = contours.begin(); 5 while(itc != contours.end()) 6 { 7 if(itc->size() < cmin || itc->size() > cmax) 8 itc = contours.erase(itc); 9 else 10 ++itc; 11 } 12 13 //把结果画在源图像上: 14 Mat original = imread("D:/picture/images/group.jpg"); 15 if(!original.data) 16 return -1; 17 drawContours(original,contours,-1,Scalar(255,255,255),2); 18 imshow("动物的轮廓",original); 19 20 //将轮廓重绘于白板上 21 result.setTo(Scalar(255)); 22 drawContours(result,contours,-1,Scalar(0),1);
怎么提取轮廓的特征呢?OpenCV提供了很多函数,我们展示其中的几个:
1 //轮廓的形状描述子 2 //外接矩形 3 Rect r0 = boundingRect(Mat(contours[0])); 4 rectangle(result,r0,Scalar(0),2); 5 6 //最小外接圆 7 float radius; 8 Point2f center; 9 minEnclosingCircle(Mat(contours[1]),center,radius); 10 circle(result,Point(center),static_cast<int>(radius),Scalar(0),2); 11 12 //多边形估计 13 std::vector<Point> poly; 14 //参数为:输入图像的2维点集,输出结果,估计精度,是否闭合 15 approxPolyDP(Mat(contours[2]),poly,5,true); 16 std::cout<<"多边形大小:"<<poly.size()<<std::endl; 17 //画出结果 18 std::vector<Point>::const_iterator itp = poly.begin(); 19 while(itp != poly.end()-1) 20 { 21 line(result,*itp,*(itp+1),Scalar(0),2); 22 ++itp; 23 } 24 //将第一个点和最后一点连起来 25 line(result,*(poly.begin()),*(poly.end()-1),Scalar(128),2); 26 27 28 //计算凸包 29 std::vector<Point> hull; 30 convexHull(Mat(contours[3]),hull); 31 std::vector<cv::Point>::const_iterator it= hull.begin(); 32 while(it != (hull.end()-1)) 33 { 34 line(result,*it,*(it+1),Scalar(0),2); 35 ++it; 36 } 37 line(result,*(hull.begin()),*(hull.end()-1),Scalar(0),2); 38 39 40 //计算矩信息 41 itc = contours.begin(); 42 while(itc != contours.end()) 43 { 44 //计算所有的距 45 Moments mom = moments(Mat(*itc++)); 46 //计算并画出质心 47 circle(result,Point(mom.m10/mom.m00,mom.m01/mom.m00),2,Scalar(2),2); 48 } 49 imshow("形状描述子",result);
我们再次看到,轮廓的确是有顺序的。值得注意的是矩信息:OpenCV提供了一个结构体Moments,它的元素就是计算好的矩信息,里面存放了常用的距。
其实,OpenCV还提供了许多其他的形状描述子,比如函数cv::minAreaRect计算了最小外界倾斜的矩形。函数cv::contourArea估计轮廓区域的面积(里面的像素数)。函数cv::pointPolygonTest计算一个点是否在轮廓内,cv::matchShapes测量了2两个轮廓的相似程度等等。这里就不一一介绍了。