OpenCV获取图像轮廓信息
在计算机视觉领域,识别和分析图像中的对象形状是一项基本任务。OpenCV 库提供了一个强大的工具——轮廓检测(Contour Detection),它能够帮助我们精确地定位对象的边界。这篇博文将带你入门 OpenCV 的轮廓检测,理解其原理,并学会如何在 C++ 中使用它。
1.什么是轮廓?
简单来说,轮廓可以看作是一条连接所有具有相同颜色或灰度值的连续点(沿着边界)的曲线。
更准确地说,轮廓是图像中强度或颜色发生显著变化的区域的边界。在 OpenCV 中,轮廓检测通常作用于二值图像(只有黑色和白色像素的图像)。算法会寻找白色(或非零)区域的边界,并将这些边界表示为一系列点的坐标。
通过检测轮廓,我们可以实现目标分割、形状分析、物体计数、特征提取及对象检测和识别等任务。
如上图1,2,3 轮廓还有层次,有最外的轮廓1依次往里包括2,3轮廓。
2. OpenCV 轮廓检测函数
2.1 findContours 函数
findContours
是 OpenCV 中最常用的轮廓检测函数,其函数原型如下:
void findContours(InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy,
int mode, int method, Point offset = Point());
- image:输入的二值图像(非零值表示前景)。
- contours:输出轮廓,存储为
std::vector<std::vector<Point>>
。 - hierarchy:输出轮廓的层级信息,描述轮廓之间的嵌套关系。
- mode:轮廓检索模式,如
RETR_EXTERNAL
(只检测最外层轮廓)、RETR_LIST
(检测所有轮廓但不建立层级关系)、RETR_TREE
(检测所有轮廓并重构完整的层级结构)。 - method:轮廓近似方法,如
CHAIN_APPROX_SIMPLE
(压缩水平、垂直和对角冗余点)和CHAIN_APPROX_NONE
(存储所有点)。 - offset:可选参数,用于给输出轮廓的所有点加上偏移值。
2.3 drawContours 函数
drawContours
函数用于在图像上绘制检测到的轮廓。它可以帮助直观展示轮廓检测和形状分析的结果。函数原型如下:
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx,
const Scalar& color, int thickness = 1, int lineType = LINE_8,
InputArray hierarchy = noArray(), int maxLevel = INT_MAX, Point offset = Point());
- image:输入输出图像,即在该图像上绘制轮廓。
- contours:轮廓集合,通常由
findContours
得到,类型为std::vector<std::vector<Point>>
。 - contourIdx:指定绘制哪一个轮廓;若设为 -1,则绘制所有轮廓。
- color:绘制轮廓的颜色,如
Scalar(0,255,0)
表示绿色。 - thickness:轮廓线的粗细;若为负值,则填充轮廓内部。
- lineType:线型,默认
LINE_8
。 - hierarchy:轮廓的层级信息,可选。
- maxLevel:绘制轮廓的最大层级,默认
INT_MAX
。 - offset:绘制时添加的偏移量。
drawContours
常用于可视化轮廓检测结果,结合 findContours
使用可以将检测到的目标区域在图像上直观标记出来。
2.3 轮廓检测的基本步骤
使用 OpenCV C++ 进行轮廓检测通常遵循以下步骤:
- 读取图像 (Read Image): 使用
cv::imread
加载你想要处理的源图像到cv::Mat
对象。 - 转换为灰度图 (Convert to Grayscale): 使用
cv::cvtColor
将彩色图像转换为灰度图,以简化图像信息。 - 二值化 (Thresholding): 使用
cv::threshold
将灰度图像转换为二值图像。这是轮廓检测的关键步骤,因为findContours
函数通常需要二值图像作为输入。 - 查找轮廓 (Find Contours): 使用
cv::findContours()
函数来检测图像中的所有轮廓。 - 绘制轮廓 (Draw Contours): (可选)使用
cv::drawContours()
函数将检测到的轮廓绘制在原始图像或新的画布上,以便可视化。
2.4 参考代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取灰度图像
Mat src = imread("E:/image/test.png");
if (src.empty()) {
cerr << "图像加载失败!" << endl;
return -1;
}
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gray, Size(5, 5), 0);
Mat binary;
double otsu_thresh_val = threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
cout << "Otsu 自动选择的阈值为:" << otsu_thresh_val << endl;
imshow("原始灰度图像", gray);
imshow("otsu 二值化", binary);
//查找轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
cout << hierarchy.size();
//绘制所有轮廓
Mat contourImage = src.clone();
drawContours(contourImage, contours, -1, Scalar(0, 0, 255), 1);
imshow("contour", contourImage);
waitKey(0);
destroyAllWindows();
return 0;
}
如果只想要最外面的轮廓只需要把参数RETR_TREE改为**RETR_EXTERNAL
**
3. 计算并绘制轮廓几何特征
在检测到轮廓后,我们可以计算并绘制轮廓的几何特征,例如:
- 面积 (
contourArea
) - 周长 (
arcLength
) - 重心
- 最小外接矩形 (
boundingRect
) - 最小外接圆 (
minEnclosingCircle
)
3.1 参考代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 读取图像并转换为灰度图
Mat src = imread("E:/image/test1.png");
if (src.empty()) {
cerr << "图像加载失败!" << endl;
return -1;
}
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
// 应用阈值处理得到二值图像
Mat binary;
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
// 检测轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 创建用于显示轮廓的图像
Mat contourImage = src.clone();
for (size_t i = 0; i < contours.size(); i++) {
// 绘制轮廓
drawContours(contourImage, contours, static_cast<int>(i), Scalar(0, 255, 0), 2);
// 计算轮廓面积和周长
double area = contourArea(contours[i]);
double perimeter = arcLength(contours[i], true);
if (area < 500) continue;
// 计算重心
Moments M = moments(contours[i]);
int cx = static_cast<int>(M.m10 / M.m00);
int cy = static_cast<int>(M.m01 / M.m00);
circle(contourImage, Point(cx, cy), 5, Scalar(255, 0, 0), -1);
// 计算外接矩形
Rect boundingBox = boundingRect(contours[i]);
rectangle(contourImage, boundingBox, Scalar(0, 0, 255), 2);
//最小外接矩形
RotatedRect box2 = minAreaRect(contours[i]);
Point2f vertices[4];
box2.points(vertices);
for (int j = 0; j < 4; j++) {
line(contourImage, vertices[j], vertices[(j + 1) % 4], Scalar(0, 255, 255), 1);
}
// 计算最小外接圆
Point2f center;
float radius;
minEnclosingCircle(contours[i], center, radius);
circle(contourImage, center, static_cast<int>(radius), Scalar(255, 255, 0), 2);
// 在图像上标注几何特征
putText(contourImage, "Area: " + to_string(static_cast<int>(area)),
Point(boundingBox.x, boundingBox.y - 10), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 1);
putText(contourImage, "Perimeter: " + to_string(static_cast<int>(perimeter)),
Point(boundingBox.x, boundingBox.y + boundingBox.height + 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 1);
}
// 显示结果
imshow("轮廓检测与几何特征", contourImage);
waitKey(0);
return 0;
}
代码简介
- 轮廓检测:使用
findContours
提取二值图像中的所有轮廓。 - 绘制轮廓:使用
drawContours
绘制检测到的轮廓。 - 计算几何特征:
- 面积和周长:使用
contourArea
和arcLength
计算。 - 重心:通过
moments
计算轮廓的中心点,并用circle
绘制。 - 最小外接矩形:使用
boundingRect
计算,并用rectangle
绘制。 - 最小外接圆:使用
minEnclosingCircle
计算,并用circle
绘制。
- 面积和周长:使用
- 标注特征信息:使用
putText
在图像上显示面积和周长。 - 面积筛选:代码中过滤不显示面积小于500的轮廓
4. 应用场景
轮廓检测技术在多个领域都有广泛应用,包括但不限于:
-
目标分割和计数
利用轮廓检测可以分割图像中的独立目标,并统计数量,如细胞计数、车牌识别等。
-
形状分析
分析轮廓的几何特征(如周长、面积、凸包、旋转角度等),用于目标识别和匹配。
-
运动跟踪
在视频处理中,通过检测运动目标的轮廓,进一步实现对象跟踪和行为分析。
-
OCR 前处理
对文档图像进行轮廓检测,提取文字区域,为光学字符识别(OCR)提供准备工作。