轮廓检测
轮廓检测
简介
-
图像轮廓是指图像中对象的边界,是图像目标的外部特征
-
通常,特定轮廓是指具有相同颜色和强度的边界像素
-
使用轮廓检测,我们可以检测物体的边界,并在图像中轻松定位它们。它通常是许多有趣应用的第一步,例如图像前景提取、简单的图像分割、检测和识别
-
在OpenCV中使用轮廓检测,只需按照以下步骤操作
- 读取图像并将其转换为灰度模式
- 应用图像的二值化。在寻找轮廓时,首先总是对灰度图像应用二进制阈值或 Canny 边缘检测
- 查找轮廓
- 在原始RGB图像上绘制轮廓
轮廓层次结构
简介
-
图像的轮廓不但能够提供物体的边缘,而且能够提供物体边缘之间的层次关系及拓补关系。可以将图像轮廓查找简单理解为带有结构关系的边缘检测,这种结构关系可以表明图像中连通域或者某些区域之间的关系
-
为了描述不同轮廓之间的结构关系,定义由外到内的轮廓级别越来越低,也就是高一层级的轮廓包围着较低层级的轮廓,被同一个轮廓包围的多个不互相包含的轮廓是同一层级轮廓
-
轮廓层次结构常表示为一个数组,该数组又包含四个值的数组,它表示为:
[Next, Previous, First_Child, Parent]
- Next:同层下一个轮廓索引。对于轮廓 1,同一层级的下一个轮廓即Next值是 2。若没有同级轮廓,则Next值为-1
- Previous:同层上一个轮廓索引。这意味着轮廓 1 的Previous值将始终为 -1
- First_Child:下一层第一个子轮廓索引
- Parent:上层父轮廓索引
轮廓的查找与绘制
查找
OpenCV 4 提供了可以在二值图像中检测图像所有轮廓并生成不同轮廓结构关系的findContours()
函数
findContours(image, contours, hierarchy, mode, method)
-
image:输入图像,数据类型为CV_8U的单通道灰度图或者二值化图像
-
contours:检测到的轮廓,每个轮廓中存放着像素的坐标,类型为 vector<vector<Point>>
-
hierarchy:轮廓结构关系描述向量,类型为 vector
-
mode:轮廓检索模式,可选参数如下
标志参数 简记 含义 RETR_EXTERNAL 0 只检测父子轮廓,而忽略任何子轮廓,对所有轮廓设置hierarchy[i][2] = -1 RETR_LIST 1 提取所有轮廓,并放置在list中。检测的轮廓不建立任何父子关系 RETR_CCOMP 2 提取所有轮廓,并将其组织为2级层次结构,即所有外部轮廓都将具有层次结构级别 1,所有内部轮廓都将具有层次结构级别 2 RETR_TREE 3 提取所有轮廓,并创建一个完整的层次结构 - RETR_LIST和RETR_EXTERNAL花费最少的时间来执行,因为RETR_LIST不定义任何层次结构,RETR_EXTERNAL只检索父轮廓
-
method:轮廓逼近方法,可选参数如下
标志参数 简记 含义 CHAIN_APPROX_NONE 1 获取每个轮廓的每个像素,相邻两个点的像素位置相差1 CHAIN_APPROX_SIMPLE 2 压缩水平方向、垂直方向和对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需要4个点来保持轮廓信息 - CHAIN_APPROX_SIMPLE算法不存储所有轮廓点,使用更少的内存,因此执行时间更短
注,mode指的是要检索的轮廓的类型,而method指的是存储了轮廓内的哪些点
补:有时,我们只需要检测图像的轮廓,并不关心轮廓之间的结构关系信息,此时轮廓之间的结构关系变量会造成内存资源的浪费,因此OpenCV4提供了findContours()
函数的另一种原型,即省略参数hierarchy
绘制
在提取图像轮廓后,为了能够直观地查看轮廓检测的结果,OpenCV 4 提供了显示轮廓的drawContours()
函数
drawContours(image, contours, contourIdx, color, thickness)
- image:绘制轮廓的RGB图像
- contours:所有将要绘制的轮廓,从
findContours()
函数中获取,类型为 vector<vector<Point>> - contourIdx:要绘制的轮廓点的数目。提供负值将绘制所有轮廓点
- color:绘制轮廓的颜色,使用 Scalar(x) 或 Scalar(x, y, z) 赋值
- thickness:轮廓点的粗细。如果为负数,将绘制轮廓的内部。默认参数为1
实现
示例代码:
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat_<Vec3b> img = imread("/home/kslas/OpenCV/contour.jpg");
imshow("Original", img);
Mat_<uchar> img_gray;
cvtColor(img, img_gray, COLOR_BGR2GRAY);
Mat_<uchar> thresh;
threshold(img_gray, thresh, 140, 255, THRESH_BINARY);
/* CHAIN_APPROX_NONE */
vector<vector<Point>> contours1;
vector<Vec4i> hierarchy1;
findContours(thresh, contours1, hierarchy1, RETR_TREE, CHAIN_APPROX_NONE);
Mat_<Vec3b> img_copy1 = img.clone();
drawContours(img_copy1, contours1, -1, Scalar(0, 255, 0), 2);
imshow("CHAIN_APPROX_NONE", img_copy1);
/* CHAIN_APPROX_SIMPLE */
vector<vector<Point>> contours2;
vector<Vec4i> hierarchy2;
findContours(thresh, contours2, hierarchy2, RETR_TREE, CHAIN_APPROX_SIMPLE);
Mat_<Vec3b> image_copy2 = img.clone();
drawContours(image_copy2, contours2, -1, Scalar(0, 255, 0), 2);
imshow("CHAIN_APPROX_SIMPLE", image_copy2);
Mat img_copy3 = img.clone();
for (int i = 0; i < contours2.size(); i++)
{
for (int j = 0; j < contours2[i].size(); j++)
{
circle(img_copy3, (contours2[i][j], contours2[i][j + 1]), 2, Scalar(0, 255, 0), 2);
}
}
imshow("CHAIN_APPROX_SIMPLE Point only", img_copy3);
waitKey(0);
destroyAllWindows();
return 0;
}
运行结果:
拾遗
-
当图像中的对象与其背景形成强烈对比时,我们可以清楚地识别与每个对象相关的轮廓
-
但是,当输入图像的背景杂乱无章或具有与感兴趣对象相同的像素强度时,轮廓检测算法便无法正确运行了
-
当图像中对象的背景充满线条时,轮廓检测也可能失败