在OpenCV中,轮廓(contours)是图像处理中的一个重要概念,通常用于形状分析、物体检测等任务。OpenCV提供了多种与轮廓相关的API,可以在C++中使用。
一.常用的与轮廓相关的操作及其对应的API函数
1.查找轮廓
findContours 函数用于在二值图像中查找轮廓。
void findContours(InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point() );
image: 输入的二值图像(通常为灰度图并经过阈值处理)。
contours: 检测到的轮廓,以std::vector<std::vector<Point>>形式存储。
hierarchy: 轮廓的拓扑信息,以std::vector<Vec4i>形式存储。
mode: 轮廓检索模式(如RETR_EXTERNAL, RETR_LIST, RETR_CCOMP, RETR_TREE)。
method: 轮廓逼近方法(如CHAIN_APPROX_SIMPLE, CHAIN_APPROX_TC89_L1, CHAIN_APPROX_TC89_KCOS, CHAIN_APPROX_NONE)。
offset: 可选的轮廓偏移量。
2.绘制轮廓
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: 轮廓,以std::vector<std::vector<Point>>形式存储。
contourIdx: 指定要绘制的轮廓索引,-1表示绘制所有轮廓。
color: 轮廓的颜色。
thickness: 轮廓线的厚度。负值表示填充轮廓。
lineType: 轮廓线的类型。
hierarchy: 可选的轮廓拓扑信息,用于指定绘制的轮廓层次。
maxLevel: 绘制轮廓的最大层次深度。
offset: 可选的轮廓偏移量。
3.轮廓近似
approxPolyDP 函数用于对轮廓进行多边形逼近。
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
curve: 输入的轮廓。
approxCurve: 输出的逼近多边形。
epsilon: 逼近精度,表示轮廓到逼近多边形的最大距离。
closed: 指示逼近多边形是否闭合。
4.轮廓面积和周长
contourArea 和 arcLength 函数分别用于计算轮廓的面积和周长。
double contourArea(InputArray contour, bool oriented = false);
double arcLength(InputArray curve, bool closed);
contour: 输入的轮廓。
oriented: 当为true时,返回有符号面积(正值表示逆时针方向,负值表示顺时针方向)。
curve: 输入的轮廓或曲线。
closed: 指示曲线是否闭合。
5.轮廓矩
moments 函数用于计算轮廓的矩。
Moments moments(InputArray array, bool binaryImage = false);
array: 输入的轮廓或图像。
binaryImage: 当为true时,假定输入图像为二值图像。
Moments结构体包含各种矩,如m00, m10, m01, m20, m11, m02等,可以用来计算轮廓的几何属性,具体有:
a.空间矩(Spatial Moments)
空间矩是图像像素位置与其灰度值的加权和,它们描述了图像的整体分布。在 cv::Moments 结构体中,空间矩包括:
m00:零阶矩,表示图像的总亮度(或质量)。
m10:一阶矩关于 x 轴的分量,描述了图像在 x 方向上的亮度分布。
m01:一阶矩关于 y 轴的分量,描述了图像在 y 方向上的亮度分布。
m20、m11、m02:二阶矩,分别描述了图像在 x2 方向上的亮度分布。
m30、m21、m12、m03:三阶矩,进一步描述了图像的高阶亮度分布特性。
b.中心矩(Central Moments)
中心矩是相对于图像重心的矩,它们描述了图像形状相对于重心的分布。在 cv::Moments 结构体中,中心矩包括:
mu20、mu11、mu02:二阶中心矩,分别描述了图像形状在 x2 方向上的相对于重心的分布。
mu30、mu21、mu12、mu03:三阶中心矩,进一步描述了图像形状的高阶相对于重心的分布特性。
c.中心归一化矩(Normalized Central Moments)
中心归一化矩是通过将中心矩除以零阶中心矩的适当幂次来得到的,它们具有尺度不变性。在 cv::Moments 结构体中,虽然直接没有列出归一化中心矩的成员变量,
但可以通过中心矩和零阶中心矩(实际上在 cv::Moments 中是 m00,但注意 m00 不是中心矩,这里只是为了说明归一化的概念)来计算得到。
归一化中心矩的一般形式为:
nu_pq = mu_pq / (m00^((p+q)/2 + 1))
其中,nu_pq 表示 p+q 阶归一化中心矩,mu_pq 表示 p+q 阶中心矩,m00 表示零阶矩。
6.获取轮廓的凸包
通过cv::convexHull()获取轮廓的凸包。
void convexHull( InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true );
points即输入的二维点集;
hull为输出的二维点集;
clockwise决定出来的轮廓是否为顺时针方向,为true时为顺时针方向;
returnPoints标志,为true时,hull将返回点集,此时为std::vector<cv::Point>类型,为false时,hull将返回返回对应于外壳点的轮廓点的索引。
二.代码示例
1.打开一个图片
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 读取图像
cv::Mat src = cv::imread("path_to_image.jpg", cv::IMREAD_GRAYSCALE);
if (src.empty()) {
std::cerr << "Could not open or find the image!" << std::endl;
return -1;
}
// 阈值处理
cv::Mat binary;
cv::threshold(src, binary, 127, 255, cv::THRESH_BINARY);
// 查找轮廓
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
// 绘制轮廓
cv::Mat result;
cv::cvtColor(src, result, cv::COLOR_GRAY2BGR);
for (size_t i = 0; i < contours.size(); i++) {
cv::drawContours(result, contours, (int)i, cv::Scalar(0, 255, 0), 2, cv::LINE_8, hierarchy, 0);
}
// 显示结果
cv::imshow("Contours", result);
cv::waitKey(0);
return 0;
}
2.生成一个图像
#include <iostream>
#include <opencv2/opencv.hpp>
int main() {
// 创建一个空白的图像
cv::Mat image = cv::Mat::zeros(300, 300, CV_8UC1); // 单通道8位图像
// 在图像中间画一个白色的圆形作为轮廓
cv::circle(image, cv::Point(150, 150), 50, cv::Scalar(255), -1);
// 查找图像中的轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(image, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
// 计算轮廓的矩
cv::Moments moments = cv::moments(contours[0]);
// 从矩中计算质心
double cx = moments.m10 / moments.m00;
double cy = moments.m01 / moments.m00;
// 计算面积
double area = moments.m00;
// 在图像上标记质心
cv::circle(image, cv::Point(static_cast<int>(cx), static_cast<int>(cy)), 5, cv::Scalar(128), -1);
// 输出质心坐标和面积
std::cout << "Centroid at (" << cx << ", " << cy << ")" << std::endl;
std::cout << "Area: " << area << std::endl;
// 显示图像
cv::imshow("Contour with Centroid", image);
cv::waitKey(0);
return 0;
}