OpenCV 轮廓算法

findContours() 作为常用的轮廓分割函数,通常在 Canny() 边缘提取之后或者二值化之后获取物体轮廓。下面简单记录其算法原理,并记录其 API 参数含义及使用方法。

1 算法原理

OpenCV 中 findcontours 的算法原型为 suzuki 算法,源码地址为 click。对于二值化图像,只有 0 和 1 两种,遍历过程前首先定义一个记录边界层次关系的值 NBD,并将其初始化为 1,遍历时就是需要改变前景像素层次关系的值来进行轮廓提取。

1.1 遍历图像

算法的第一步是寻找一个轮廓的起始点,方法就是通过定义核遍历图像。首先定义左边是 0 右边是 1 的 kernel (外部边界)及左边是 1 右边是 0 的 kernel(内部边界),使用这两个 kernel 从左到右从上到下遍历图像,找到第一个满足要求的点认为是该轮廓的起点,后面的步骤就是从这个起点出发进行轮廓追踪。

findcontours_1

1.2 轮廓追踪

找到轮廓起点后,将计数值 NBD 加一表示这是一个新的轮廓。进行轮廓追踪简单来讲就是围绕当前点进行顺时针或者逆时针的遍历,进而寻找到下一个轮廓点,之后不断循环找到该轮廓的所有点。当然,在围绕当前点顺时针或逆时针旋转时需要找到旋转的起点,这里也是遍历的重点(没太看懂流程,大概就是构造出<起点-中心点-新邻居>的结构,不断循环)。

findcontours_2

1.3 层级关系

简单来讲,算法中初始定义 NBD 为 1 可以理解为定义图像的边界为最外层轮廓,另外在遍历过程中还会记录上一个轮廓的编号 LNBD,然后根据下面表里的关系(表格记录当前轮廓 B 的父轮廓是谁)进行层级判断。

上一个轮廓B’类型 外部边界 内部边界
当前轮廓B类型 \ \ \
外部边界 \ B’的父轮廓 B’
内部边界 \ B’ B’的父轮廓

findcontours_3

2 函数API

1
2
3
4
5
6
7
void cv::findContours ( InputOutputArray 	image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)
参数 说明
InputOutputArray image 输入图像为8位单通道图像(图像中非零为1,被当作为二值图像),通常使用二值化或者 Canny 边缘检测等的结果。
OutputArrayOfArrays contours 输出为检测到的边缘集合,其中每个边缘都是一个点集。(一般设为std::vector< std::vector<cv::Point >>)
OutputArray hierarchy 输出边缘的层级关系,一般设置为std::vector<cv::Vec4i >,它的元素数量和轮廓数量相对应。对于第 i 个轮廓 contours[i],hierarchy的四个元素分别表示分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。
int mode 定义轮廓的检索模式。CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;CV_RETR_LIST检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,hierarchy向量内所有元素的第3、第4个分量都会被置为-1;CV_RETR_CCOMP检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;CV_RETR_TREE,检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
int method 定义轮廓的近似方法。CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内;CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours;CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用Teh-Chin chain算法。
Point offset = Point() 所有的轮廓信息相对于原始图像对应点的偏移量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
threshold(m_img, m_img, 0, 255, CV_THRESH_OTSU);

std::vector<std::vector<cv::Point>>m_contours;
vector<Vec4i> hierarchy;
findContours(m_img, m_contours, hierarchy, CV_RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
sort(m_contours.begin(), m_contours.end(), [](const vector<Point>& v1, const vector<Point>& v2)
{
return v1.size() > v2.size();
});
Mat obs = m_img.clone();//观察结果用
cvtColor(obs, obs, CV_GRAY2BGR);
drawContours(obs, m_contours, 0, Scalar(0, 0, 255));

return 0;
}

3 参考

  1. Suzuki’s Contour tracing algorithm OpenCV-Python
  2. Find Contours 算法原理以及 C++ 实现(图源)
  3. Opencv findcontours函数原理,以及python numpy实现
  4. suzuki论文