想要看懂Opencv源码,打算从findContours()开始。
源文件: C:\***\opencv\sources\modules\imgproc\src\contours.cpp
环境:opencv2.4.9 + VS2010
一、findContours() 接口函数
void cv::findContours( InputOutputArray _image, OutputArrayOfArrays _contours,
OutputArray _hierarchy, int mode, int method, Point offset )
Parameters:
image--待处理图,8bit单通道,非零像素被当成1,零像素仍然是0,因此该图被当成二值图来处理(实际在源码中发现,CV_8UC1和CV_32SC1都是允许的)。注意,该函数在提取轮廓的过程中会修改原始图像。(因此,在执行findContours之前将原图备份。)
contours--检测到的轮廓集合,每一个轮廓存储为 a vector of points。
hierarchy--可选的输出向量,包含图像拓扑结构信息。该向量的元素个数等于轮廓的总个数。对于每个轮廓contours[i], hierarchy[i][0], hierarchy[i][1], hierarchy[i][2], hierarchy[i][3]分别代表同级轮廓的后一个轮廓(next contour)、前一个轮廓(previous contour)、第一个子轮廓(first child contour)、母轮廓(paraent contour)。这些轮廓用基于0的序列号表示。如果没有相应的轮廓,则hierarchy[i][j]将被设置为负值。
mode--轮廓跟踪模式。(关于轮廓之间的层次关系)
- — CV_RETR_EXTERNAL 只提取最外层轮廓。所有轮廓的hierarchy[i][2] = hierarchy[i][3] =-1.
- — CV_RETR_LIST 提取所有轮廓但不建立任何层次关系。
- — CV_RETR_CCOMP 提取所有轮廓,把它们组织成两级结构。第一级是连通域的外边界(external boundaries),第二级是孔边界(boundaries of holes)。如果在孔中间还有另外的连通域,则被当成另一个外边界。
- — CV_RETR_TREE 提取所有轮廓并把它们组织成完整的层次结构。
- — CV_CHAIN_CODE (手册上没有标出)采用Freeman链码的方式存储轮廓。
- — CV_CHAIN_APPROX_NONE 将Freeman链码转换成点集,并存储轮廓上所有的点。
- — CV_CHAIN_APPROX_SIMPLE 压缩水平、垂直和斜对角方向的元素,只保留线段端点的那些点。比如说,一个矩形,总共四条线段,只保留四个顶点,顶点之间的点就省略掉,所以最终该矩形轮廓只用4个点表示。
- — CV_CHAIN_APPROX_TC89_L1, CV_CHAIN_APPPRX_TC89_KCOS 使用Teh-Chin 链近似方法。
- — CV_LINK_RUNS 通过连接为1的水平碎片使用完全不同的轮廓提取算法。仅有CV_RETR_LIST 提取模式可以在本方法中使用.
手册中给出了该函数参考的算法原始论文[Suzuki85] 。
- Suzuki, S. and Abe, K., Topological Structural Analysis of Digitized Binary Images by Border Following.CVGIP 30 1, pp 32-46 (1985)
论文翻译博客地址:http://blog.youkuaiyun.com/yiqiudream/article/details/76864722
论文原文及翻译下载地址:
看论文仍然糊里糊涂的,不知道链码跟踪的具体算法是什么。而contours.cpp中的函数较多,内容有点复杂,只能一个一个看,比如单个轮廓的具体跟踪方法,在 icvFetchContours() 。最后对该函数单步调试,列出了每一步执行的步骤,终于明白一点。所以记录下来。
二、icvFetchContours() 源码
为了便于理解代码,先把用到的一些宏定义贴出来。
/* initializes 8-element array for fast access to 3x3 neighborhood of a pixel */
#define CV_INIT_3X3_DELTAS( deltas, step, nch ) \
((deltas)[0] = (nch), (deltas)[1] = -(step) + (nch), \
(deltas)[2] = -(step), (deltas)[3] = -(step) - (nch), \
(deltas)[4] = -(nch), (deltas)[5] = (step) - (nch), \
(deltas)[6] = (step), (deltas)[7] = (step) + (nch))
注释:定义了一个3X3 的邻域位置快速查找表。因为在内存中,Mat数据存储的时候是一行一行连续存储的,从像素点(i,j)到像素点(i+1,j),指针偏移了一行的数据,也就是 step的距离。这里step 是指一行像素点所占的字节数,nch是步进距离,后面设置为1,也就是指针偏移一个Byte. deltas[8]是一个数组,共8个元素,分别代表8个方向。如图。
3 | 2 | 1 |
4 | 0 | |
5 | 6 | 7 |
static const CvPoint icvCodeDeltas[8] =
{ {1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1} };
注释:deltas[8]存的是指针的偏移量,icvCodeDeltas[8] 存的是8个方向相对于中心点坐标的偏移量。
/* Contour retrieval modes */
enum