/************************************************************************/
/* 提取轮廓两种方法对比及绘制轮廓'最大等级'分析 */
/************************************************************************/
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
int main()
{
IplImage* img = cvLoadImage("lena.jpg", CV_LOAD_IMAGE_GRAYSCALE);
IplImage* img_temp = cvCreateImage(cvGetSize(img), 8, 1);
cvThreshold(img, img, 128, 255, CV_THRESH_BINARY);
CvMemStorage* mem_storage = cvCreateMemStorage(0);
CvSeq *first_contour = NULL, *c = NULL;
//////////////////////////////////////////////////////////////////////////
// 1、
cvNamedWindow("contour1");
cvCopyImage(img, img_temp);
double t = (double)cvGetTickCount();
cvFindContours(img_temp, mem_storage, &first_contour);
cvZero(img_temp);
cvDrawContours(
img_temp,
first_contour,
cvScalar(100),
cvScalar(100),
1
);
t = (double)cvGetTickCount() - t;
cvShowImage("contour1", img_temp);
printf("run1 = %gms\n", t/(cvGetTickFrequency()*1000.));
cvClearMemStorage(mem_storage);
//////////////////////////////////////////////////////////////////////////
// 2、
cvNamedWindow("contour2");
cvCopyImage(img, img_temp);
t = (double)cvGetTickCount();
CvContourScanner scanner = cvStartFindContours(img_temp, mem_storage);
while (cvFindNextContour(scanner));
first_contour = cvEndFindContours(&scanner);
cvZero(img_temp);
cvDrawContours(
img_temp,
first_contour,
cvScalar(100),
cvScalar(100),
1
);
t = (double)cvGetTickCount() - t;
cvShowImage("contour2", img_temp);
printf("run2 = %gms\n", t/(cvGetTickFrequency()*1000.));
cvClearMemStorage(mem_storage);
cvReleaseImage(&img);
cvReleaseImage(&img_temp);
cvWaitKey();
/************************************************************************/
/* 经测试 run1 = 16.1431ms run2 = 15.8677ms (参考)
不过可以肯定这两中算法时间复杂度是相同的 */
/************************************************************************/
//////////////////////////////////////////////////////////////////////////
// 上述两种方法完成了对轮廓的提取,如想绘制轮廓都得配合cvDrawContours来使用
// 而cvDrawContours 函数第5个参数为 max_level 经查ICVL含义如下:
//
// 绘制轮廓的最大等级。如果等级为0,绘制单独的轮廓。如果为1,绘制轮廓及在其后的相同的级别下轮廓。
// 如果值为2,所有的轮廓。如果等级为2,绘制所有同级轮廓及所有低一级轮廓,诸此种种。如果值为负数,
// 函数不绘制同级轮廓,但会升序绘制直到级别为abs(max_level)-1的子轮廓。
//
// 相信好多读者初次都无法理解等级的含义,而且测试时候输入>=1 的整数效果几乎一样
// 只有提取轮廓时候的提取模式设为 CV_RETR_CCOMP CV_RETR_TREE 时这个参数才有意义
//
// 经查FindContours 函数里面这样介绍提取模式(mode)的这两个参数:
// CV_RETR_CCOMP - 提取所有轮廓,并且将其组织为两层的 hierarchy: 顶层为连通域的外围边界,次层为洞的内层边界。
// CV_RETR_TREE - 提取所有轮廓,并且重构嵌套轮廓的全部 hierarchy
//
// 下面用第一种方法进行测试
cvNamedWindow("contour_test");
cvNamedWindow("contour_raw");
img = cvLoadImage("contour.jpg", CV_LOAD_IMAGE_GRAYSCALE);
cvShowImage("contour_raw", img);
cvThreshold(img, img, 128, 255, CV_THRESH_BINARY);
img_temp = cvCloneImage(img);
cvFindContours(
img_temp,
mem_storage,
&first_contour,
sizeof(CvContour),
CV_RETR_CCOMP //#1 需更改区域
);
cvZero(img_temp);
cvDrawContours(
img_temp,
first_contour,
cvScalar(100),
cvScalar(100),
1 //#2 需更改区域
);
cvShowImage("contour_test", img_temp);
/************************************************************************/
/* (1, 2) = (CV_RETR_CCOMP, 1) 如图1
(1, 2) = (CV_RETR_CCOMP, 2) 如图2
(1, 2) = (CV_RETR_TREE, 1) 如图3
(1, 2) = (CV_RETR_TREE, 2) 如图4
(1, 2) = (CV_RETR_TREE, 6) 如图5
经分析CV_RETR_CCOMP 只把图像分为两个层次,顶层和次层,一等级轮廓只匹配与其最接近
的内侧轮廓即2等级
CV_RETR_TREE 则从轮廓外到内按等级1 - n 全部分配
CV_RETR_LIST 全部轮廓均为1级 */
/************************************************************************/
cvWaitKey();
cvReleaseImage(&img);
cvReleaseImage(&img_temp);
cvReleaseMemStorage(&mem_storage);
cvDestroyAllWindows();
return 0;
}
原图
图一
图二
图三
图四
图五
opencv中常用的跟轮廓相关的操作有:findContours()查找轮廓;drawContours()画轮廓;轮廓填充;计算轮廓的面积和周长;提取轮廓凸包,矩形,最小外接矩形,外接圆等。它们都有相应的函数可以直接调用,那么任意形状怎么取呢?
方法1:点乘,将其形状与图像进行点乘,求其形状对应的图像形状;
方法2:用findContours函数得对应的形状区域,其边缘显示类型可以通过设置参数可以控制;
//===============================对应灰度图的区域segImage==============================================================
// 遍历图像 对每个非零像素值赋值为对应灰度图的像素值
Mat zeroImage(Size(rioImage.cols, rioImage.rows), CV_8U, Scalar(0));//建立全0矩阵
for (int i = 0; i < closeImg.rows; i++)//行遍历
{
unsigned char* ptr = (unsigned char*)closeImg.data + closeImg.step*i;
for (int j = 0; j < closeImg.cols; j++)//列遍历
{
int intensity = ptr[j];
if (intensity != 0)
{
zeroImage.at(i, j) = rioImage.at(i, j);//对应位置赋值为灰度图像素
}
}
}
zeroImage.copyTo(segImage);
//imshow("segImage.jpg", segImage);
//waitKey(0);
}
void findContours (
InputOutputArray image,//输入图像,必须是8位单通道二值图像
OutputArrayOfArrays contours,//检测到的轮廓,每个轮廓被表示成一个point向量
OutputArray hierarchy,//可选的输出向量,包含图像的拓扑信息。其中元素的个数和检测到的轮廓的数量相等
int mode,//说明需要的轮廓类型和希望的返回值方式
int method,//轮廓近似方法
Point offset = Point()
)
参数说明:
mode:
①mode的值决定把找到的轮廓如何挂到轮廓树节点变量(h_prev, h_next, v_prev, v_next)上,拓扑结构图如下;
②每种情况下,结构都可以看成是被横向连接(h_prev, h_next)联系和被纵向连接(v_prev, v_next)不同层次。
③CV_RETR_EXTERNAL:只检测出最外轮廓即c0;
CV_RETR_LIST:检测出所有的轮廓并将他们保存到表(list)中;
CV_RETR_COMP:检测出所有的轮廓并将他们组织成双层的结构,第一层是外部轮廓边界,第二层边界是孔的边界;
CV_RETR_TREE:检测出所有轮廓并且重新建立网状的轮廓结构;
④参数method:
CV_CHAIN_CODE:用freeman链码输出轮廓,其他方法输出多边形(顶点的序列);
CV_CHAIN_APPROX_NONE:将链码编码中的所有点转换为点;
CV_CHAIN_APPROX_SIMPLE:压缩水平,垂直或斜的部分,只保存最后一个点;
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_QPPROX_TC89_KCOS:使用Teh-Chin链逼近算法中的一个。
CV_LINK_RUNS:与上述的算法完全不同,连接所有的水平层次的轮廓。
【注】:findContours()查找时,这个图像会被直接涂改,因此如果是以后有用的图像,应该复制之后再进行查找;
void drawContours( InputOutputArray image,//要绘制轮廓的图像 InputArrayOfArrays contours,//所有输入的轮廓,每个轮廓被保存成一个point向量 int contourIdx,//指定要绘制轮廓的编号,如果是负数,则绘制所有的轮廓 const Scalar& color,//绘制轮廓所用的颜色 int thickness = 1, //绘制轮廓的线的粗细,如果是负数,则轮廓内部被填充 int lineType = 8, /绘制轮廓线的连通性 InputArray hierarchy = noArray(),//关于层级的可选参数,只有绘制部分轮廓时才会用到 int maxLevel = INT_MAX,//绘制轮廓的最高级别,这个参数只有hierarchy有效的时候才有效 Point offset = Point() ) |
【注】:
①maxLevel=0,绘制与输入轮廓属于同一等级的所有轮廓即输入轮廓和与其相邻的轮廓
maxLevel=1, 绘制与输入轮廓同一等级的所有轮廓与其子节点。
maxLevel=2,绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点
步骤:
a) 依次遍历轮廓点,将点绘制到img上;
b) 使用floodFill以及一个种子点进行填充;
两种方法:
法一:自己编写程序;使用drawContours()函数;
void drawMaxAreaLine(Mat& dst, vector maxAreaPoints)
{
int step = dst.step;
auto data = dst.data;
for (int i = 0; i < maxAreaPoints.size(); ++i)
{
*(data + maxAreaPoints[i].x + maxAreaPoints[i].y * step) = 255;
}
}
法二:孔洞填充算法
void fillHole(Mat src_Bw, Mat &dst_Bw)
{
Size m_Size = src_Bw.size();
Mat Temp=Mat::zeros(m_Size.height+10,m_Size.width+10,src_Bw.type());
src_Bw.copyTo(Temp(Range(5, m_Size.height + 5), Range(5, m_Size.width + 5)));
floodFill(Temp, Point(0, 0), Scalar(255));
Mat cutImg;
Temp(Range(5, m_Size.height + 5), Range(5, m_Size.width + 5)).copyTo(cutImg);
dst_Bw = src_Bw | (~cutImg);
}
【注】:这里常会碰到种子点不好选取的问题,因为有时候所选择的种子点不能保证对所有轮廓都适用。也就是查找一个在轮廓内的点是存在一定难度的。
使用drawContours()就会很方便:
vector > contours;
contours.push_back(currentFrameEdge);
Mat savedGrayMat = Mat::zeros(RectData[0].rows, RectData[0].cols, CV_8UC1);
//drawMaxAreaLine(savedGrayMat, currentFrameEdge);
//floodFill(savedGrayMat, Point(currentFrameEdge[0].x + 2, currentFrameEdge[0].y + 2), 255);
drawContours(savedGrayMat, contours, 0, Scalar(255), CV_FILLED);
imshow("savedGrayMat", savedGrayMat);
waitKey();
double contourArea(InputArray contour, bool oriented=false )
InputArray contour:输入的点,一般是图像的轮廓点;
bool oriented=false: 表示某一个方向上轮廓的的面积值,顺时针或者逆时针,一般选择默认false;
double arcLength(InputArray curve, bool closed)
InputArray curve:表示图像的轮廓;
bool closed:表示轮廓是否封闭的;
【注】:
①contourArea计算整个或部分轮廓的面积;
在计算部分轮廓的情况时,由轮廓弧线和连接两端点的弦围成的区域总面积被计算,如图;
②轮廓的位置将影响区域面积的符号,因此函数范围的有可能是负值。可以在运行时使用fabs()来得到面积的绝对值;
格式:
void convexhul(InputArray points,//要求凸包的点集 OutputArray hull,//输出的凸包点,可以为vector,此时返回的是凸包点在原轮廓点集中的索引,也可以为vector,此时存放的是凸包点的位置 bool clockwise=false,//一个bool变量,表示求得的凸包是顺时针方向还是逆时针方向,true是顺时针方向; bool returnPoints=true)//表示第二个参数的返回类型是vector还是vector,可以忽略; |
格式:
Rect boundingRect(InputArray points); |
输入:二维点集,点的序列或向量 (Mat)
返回:Rect
//寻找外包矩阵
Rect maxRect = boundingRect(contours[m_count]); |
//绘制外包矩阵
rectangle(contours_img_1, maxRect, Scalar(0, 255, 0)); |
主要求包含点集最小面积的矩形,这个矩形是可以有偏转角度的,可以与图像的边界不平行;
格式:
RotatedRect minAreaRect(InputArray points); |
输入:二维点集,点的序列或向量 (Mat)
返回:RotatedRect
格式:
void minEnclosingcircle(InputArray points,Point2f& center,float& radius); |
输入:
二维点集:点的序列vector< point >或向量 (Mat) ,
圆心坐标;
半径;
https://jingyan.baidu.com/article/4d58d54179ecfb9dd4e9c092.html