文章目录
基本原理
轮廓发现是图像处理中的一个重要步骤,用于检测物体的边界和形状。
-
图像预处理 :
轮廓发现通常在灰度图像上进行。因此,首先将图像转换为灰度图像。接下来,应用滤波器来减少噪声。常用的滤波器有高斯模糊(Gaussian Blur),它有助于平滑图像并减少噪声。
-
边缘检测 :
在预处理后的图像上应用边缘检测算法。常用的边缘检测算法是Canny边缘检测器,它能有效地检测出图像中的边缘。Canny边缘检测器使用梯度的方向和幅度来找到图像中的边缘。
-
轮廓提取 :
一旦得到二值化的边缘图像,就可以使用OpenCV的
findContours
函数来提取轮廓。findContours
函数将图像中的每一个边缘视为一个轮廓,并返回一个轮廓列表。每个轮廓都由一系列点组成,这些点定义了轮廓的形状。 -
轮廓的层次结构 :
findContours
函数不仅可以返回轮廓,还可以返回轮廓的层次结构。这对于包含内嵌轮廓(如嵌套在其他轮廓中的孔洞)的图像非常有用。层次结构信息存储了每个轮廓的父子关系。
关键函数和参数
-
cv2.findContours(image, mode, method)
:image
: 输入的二值图像(通常是边缘检测的结果)。mode
: 轮廓检索模式,如cv2.RETR_EXTERNAL
(只检测外轮廓)、cv2.RETR_TREE
(检测所有轮廓并构建层次结构)。method
: 轮廓逼近方法,如cv2.CHAIN_APPROX_SIMPLE
(只保存轮廓的必要点)、cv2.CHAIN_APPROX_NONE
(保存所有轮廓点)。
-
cv2.drawContours(image, contours, contourIdx, color, thickness)
:用于在图像上绘制轮廓。
注意事项
-
图像的预处理 :
轮廓发现对输入图像的质量非常敏感。良好的预处理(如去噪、对比度增强等)可以显著提高轮廓检测的效果。
-
边缘检测器的选择 :
边缘检测器的参数(如Canny边缘检测器的阈值)需要根据图像的特征进行调整。
-
轮廓的近似和表示 :
对于复杂的形状,可以使用多边形逼近(如Douglas-Peucker算法)来简化轮廓。
轮廓发现技术广泛应用于对象检测、形状分析、图像分割等领域。在这些应用中,轮廓的精确提取和表示对于后续处理和分析至关重要。
示例代码
在OpenCV中,使用C++进行轮廓发现通常包括以下主要步骤:读取图像、灰度化、边缘检测、轮廓发现和绘制轮廓。以下是一个基本的C++代码示例,展示如何使用OpenCV进行这些操作:
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取图像
Mat src = imread("image.jpg");
if (src.empty()) {
cout << "无法加载图像!" << endl;
return -1;
}
// 转换为灰度图像
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
// 应用高斯模糊以去除噪声
Mat blurred;
GaussianBlur(gray, blurred, Size(5, 5), 1.5);
// 进行Canny边缘检测
Mat edges;
Canny(blurred, edges, 100, 200);
// 发现轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(edges, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
// 在原图上绘制轮廓
Mat drawing = Mat::zeros(edges.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(255, 0, 0); // 轮廓的颜色
drawContours(drawing, contours, (int)i, color, 2, 8, hierarchy, 0);
}
// 显示结果
imshow("轮廓", drawing);
waitKey(0);
return 0;
}
示例效果
代码详解
-
读取图像:
cppMat src = imread("image.jpg");
使用
imread
函数加载图像。 -
灰度化:
cppMat gray; cvtColor(src, gray, COLOR_BGR2GRAY);
使用
cvtColor
函数将彩色图像转换为灰度图像。 -
高斯模糊:
cppMat blurred; GaussianBlur(gray, blurred, Size(5, 5), 1.5);
使用
GaussianBlur
函数对灰度图像进行平滑处理,以减少噪声。 -
Canny边缘检测:
cppMat edges; Canny(blurred, edges, 100, 200);
使用
Canny
函数进行边缘检测。这里100
和200
是低和高阈值,用于控制边缘的检测。 -
发现轮廓:
cppvector<vector<Point>> contours; vector<Vec4i> hierarchy; findContours(edges, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
使用
findContours
函数提取图像中的轮廓。RETR_TREE
参数用于获取轮廓的层次结构,CHAIN_APPROX_SIMPLE
用于压缩水平、垂直和对角直线段,只保留它们的终点。 -
绘制轮廓:
cppMat drawing = Mat::zeros(edges.size(), CV_8UC3); for (size_t i = 0; i < contours.size(); i++) { Scalar color = Scalar(255, 0, 0); // 轮廓的颜色 drawContours(drawing, contours, (int)i, color, 2, 8, hierarchy, 0); }
使用
drawContours
函数在图像上绘制检测到的轮廓。 -
显示结果:
cppimshow("轮廓", drawing); waitKey(0);
使用
imshow
函数显示绘制好的图像,并使用waitKey
等待用户按键。
在实际应用中,可以根据具体需求调整模糊参数、Canny边缘检测的阈值,以及findContours
的模式和方法参数。
findContours 函数原型
OpenCV 中的 findContours
函数用于检测图像中的轮廓。其函数原型如下:
cpp
void findContours(
InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
);
参数详解
-
image :
InputOutputArray
- 输入图像,通常为二值化图像(如通过边缘检测得到的图像)。该图像会被修改,因此如果需要保留原图像,应该传递其副本。
- 类型通常为
CV_8UC1
,即单通道8位无符号整数。
-
contours :
OutputArrayOfArrays
- 检测到的轮廓列表,每个轮廓是一个点的向量(即
std::vector<Point>
)。每个点表示轮廓的一部分。 - 具体类型为
std::vector<std::vector<Point>>
。
- 检测到的轮廓列表,每个轮廓是一个点的向量(即
-
hierarchy :
OutputArray
- 可选的层次结构输出向量。对于每个轮廓,
hierarchy[i][0]
表示下一个轮廓的索引,hierarchy[i][1]
表示前一个轮廓的索引,hierarchy[i][2]
表示第一个子轮廓的索引,hierarchy[i][3]
表示父轮廓的索引。 - 如果不需要层次结构,可以传递
noArray()
或一个空的Mat
。
- 可选的层次结构输出向量。对于每个轮廓,
-
mode :
int
- 轮廓检索模式,决定如何提取轮廓以及如何处理它们之间的关系。可选值有:
RETR_EXTERNAL
: 只检索最外层的轮廓。RETR_LIST
: 检索所有轮廓,不建立层次关系。RETR_CCOMP
: 检索所有轮廓,组织为两级结构:顶层是连通分量的外边界,次层是孔的边界。RETR_TREE
: 检索所有轮廓,并重建完整的嵌套轮廓。
- 轮廓检索模式,决定如何提取轮廓以及如何处理它们之间的关系。可选值有:
-
method :
int
- 轮廓逼近方法,指定如何对轮廓点进行存储。可选值有:
CHAIN_APPROX_NONE
: 存储所有的轮廓点。CHAIN_APPROX_SIMPLE
: 压缩水平、垂直和对角线段,只保留这些线段的终点。CHAIN_APPROX_TC89_L1
和CHAIN_APPROX_TC89_KCOS
: 使用 Teh-Chin 链逼近算法。
- 轮廓逼近方法,指定如何对轮廓点进行存储。可选值有:
-
offset :
Point
(默认值为Point()
)- 偏移量,用于所有轮廓点坐标的偏移。这在对图像中的ROI区域进行轮廓检测时尤其有用。
findContours函数变体
它不要求输出层次结构的层次信息。这种简化版的函数原型对于只关心检测到的轮廓而不需要它们之间的层次结构关系的情况是有用的。其具体定义如下:
CV_EXPORTS void findContours(
InputArray image,
OutputArrayOfArrays contours,
int mode,
int method,
Point offset = Point()
);
参数详解
- image :
InputArray
- 输入图像,通常是一个二值图像(如通过边缘检测得到的图像)。该图像会被修改,因此如果需要保留原图像,应该传递其副本。
- 类型通常为
CV_8UC1
,即单通道8位无符号整数。
- contours :
OutputArrayOfArrays
- 检测到的轮廓列表,每个轮廓是一个点的向量(即
std::vector<Point>
)。每个点表示轮廓的一部分。 - 具体类型为
std::vector<std::vector<Point>>
。
- 检测到的轮廓列表,每个轮廓是一个点的向量(即
- mode :
int
- 轮廓检索模式,决定如何提取轮廓以及如何处理它们之间的关系。可选值包括:
RETR_EXTERNAL
: 只检索最外层的轮廓。RETR_LIST
: 检索所有轮廓,不建立层次关系。RETR_CCOMP
: 检索所有轮廓,组织为两级结构:顶层是连通分量的外边界,次层是孔的边界。RETR_TREE
: 检索所有轮廓,并重建完整的嵌套轮廓。
- 轮廓检索模式,决定如何提取轮廓以及如何处理它们之间的关系。可选值包括:
- method :
int
- 轮廓逼近方法,指定如何对轮廓点进行存储。可选值包括:
CHAIN_APPROX_NONE
: 存储所有的轮廓点。CHAIN_APPROX_SIMPLE
: 压缩水平、垂直和对角线段,只保留这些线段的终点。CHAIN_APPROX_TC89_L1
和CHAIN_APPROX_TC89_KCOS
: 使用 Teh-Chin 链逼近算法。
- 轮廓逼近方法,指定如何对轮廓点进行存储。可选值包括:
- offset :
Point
(默认值为Point()
)- 偏移量,用于所有轮廓点坐标的偏移。这在对图像中的ROI(感兴趣区域)进行轮廓检测时尤其有用。
使用场景
这个版本的findContours
函数适用于简单的轮廓检测任务,尤其是当不需要关心轮廓之间的层次关系时。例如,在一些形状分析或对象检测的应用中,只需要获取所有的轮廓而不关心它们的嵌套关系,此时可以使用这个简化的函数原型。