OpenCV轮廓逼近与拟合
在计算机视觉中,轮廓是图像中边界或形状的重要表达形式。然而,直接从图像中提取的轮廓常常包含大量冗余点,且噪声较多。为了更好地描述图像中的形状,我们通常需要对轮廓进行逼近和拟合,从而降低数据维度、获得光滑的形状描述,并便于后续分析处理。本文将介绍轮廓逼近与拟合的基本原理,在 OpenCV 中如何使用相关函数(如 approxPolyDP
、fitLine
、boundingRect
、minEnclosingCircle
、fitEllipse
等)实现这些操作,并通过代码示例展示实际应用。
1. 轮廓逼近
1.1 基本原理
轮廓逼近的主要目的是用较少的点描述原始轮廓,大大降低数据量的同时,保留原始轮廓的主要形状特征。常用的方法为Douglas-Peucker 算法,通过设定容差(ε)来控制逼近精度。简单来说,算法会剔除那些不会显著改变轮廓形状的冗余点,从而生成一个更简洁的多边形。
1.2 approxPolyDP API
在 OpenCV 中,approxPolyDP
函数实现了 Douglas-Peucker 算法,其函数原型如下:
cpp
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
- curve:输入轮廓点集。
- approxCurve:输出逼近后的轮廓点集。
- epsilon:最大距离误差,通常设置为轮廓周长的一定比例(如 1% 或 2%)。
- closed:指示轮廓是否是闭合曲线。
1.3 参考代码
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat src = imread("E:/image/pic3.png");
if (src.empty())
{
cout << "无法加载图像!" << endl;
return -1;
}
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 创建一幅画布用于显示结果
Mat approxImage = src.clone();
// 依次对每个轮廓进行逼近
for (size_t i = 0; i < contours.size(); i++)
{
// epsilon 定义逼近精度,通常取轮廓弧长的一定比例
double epsilon = 0.01 * arcLength(contours[i], true);
vector<Point> approxCurve;
approxPolyDP(contours[i], approxCurve, epsilon, true);
// 绘制逼近后的多边形,颜色为红色
polylines(approxImage, approxCurve, true, Scalar(0, 0, 255), 2);
}
imshow("逼近轮廓", approxImage);
waitKey(0);
return 0;
}
说明
arcLength
计算轮廓的周长,然后用一定比例作为逼近精度epsilon
。approxPolyDP
根据指定精度将轮廓逼近为更简单的多边形。- 最后用
polylines
绘制逼近后的多边形轮廓。

cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
// 1. 读取输入图像,并转换为灰度图
Mat inputImg = imread("E:/image/pic4.png");
if (inputImg.empty()) {
cerr << "图像加载失败!" << endl;
return -1;
}
Mat grayInput;
cvtColor(inputImg, grayInput, COLOR_BGR2GRAY);
// 2. 对图像应用阈值处理得到二值图像
Mat binaryInput;
threshold(grayInput, binaryInput, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
vector<vector<Point>> contoursInput, contoursTemplate;
vector<Vec4i> hierarchy;
findContours(binaryInput, contoursInput, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 5. 遍历输入图像中的所有轮廓,计算与模板轮廓的匹配度
for (size_t i = 0; i < contoursInput.size(); i++)
{
//多边形逼近
Mat ret;
approxPolyDP(contoursInput[i], ret, 3, true);
double area = contourArea(contoursInput[i]);
double l = arcLength(contoursInput[i], true);
printf("corner %d ,area %.2f, length %.2f\n", ret.rows , area,l);
Moments m = moments(contoursInput[i]);
//drawContours(inputImg, contoursInput, static_cast<int>(i), Scalar(0, 255, 0), 2);
int cx = static_cast<int>(m.m10 / m.m00);
int cy = static_cast<int>(m.m01 / m.m00);
circle(inputImg, Point(cx, cy), 5, Scalar(0, 0, 255), -1);
if (ret.rows==6)
{
putText(inputImg, "poly", Point(cx+10, cy), FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0), 1);
}
else if (ret.rows == 5)
{
putText(inputImg, "pentagon", Point(cx + 10, cy), FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0), 1);
}
else if (ret.rows == 4)
{
putText(inputImg, "rectangle", Point(cx + 10, cy), FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0), 1);
}
else if (ret.rows == 3)
{
putText(inputImg, "triangle", Point(cx + 10, cy), FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0), 1);
}
else if (ret.rows > 10)
{
putText(inputImg, "circle", Point(cx + 10, cy), FONT_HERSHEY_PLAIN, 1, Scalar(0, 255, 0), 1);
}
}
//6. 显示结果\n
cv::imshow("轮廓逼近", ~inputImg);
cv::waitKey(0);
return 0;
}
通过判断逼近结果点的个数判断多边形的类型

2. 轮廓拟合
在某些应用中,我们不仅需要简化轮廓,还需要拟合出轮廓对应的几何形状(例如最小外接矩形、椭圆拟合、直线拟合等)。OpenCV 提供了多个拟合函数,下面分别介绍常用的几种。
2.1 拟合最小外接矩形
minAreaRect
可用于求解轮廓的最小外接矩形,其结果可以带旋转角度。示例代码如下:
cpp
// 针对当前轮廓拟合最小外接矩形
RotatedRect minRect = minAreaRect(contours[i]);
// 通过 boxPoints 得到矩形的四个顶点
Point2f rectPoints[4];
minRect.points(rectPoints);
for (int j = 0; j < 4; j++)
{
line(approxImage, rectPoints[j], rectPoints[(j+1)%4], Scalar(255, 0, 0), 2);
}
说明
- 返回的
RotatedRect
包含中心点、尺寸和旋转角度。 boxPoints
函数可以提取矩形的四个顶点,便于绘制.
2.2 拟合椭圆
对曲线较为平滑的轮廓,可以拟合椭圆。注意:输入点数量需大于 5。示例代码如下:
cpp
if (contours[i].size() > 5)
{
RotatedRect ellipseRect = fitEllipse(contours[i]);
ellipse(approxImage, ellipseRect, Scalar(0, 255, 255), 2);
}
说明
fitEllipse
返回一个RotatedRect
对象,描述拟合出来的椭圆。- 用
ellipse
函数绘制拟合好的椭圆,颜色可以自行设置。
2.3 拟合直线
有时希望从轮廓点中提取直线信息,这时可以使用 fitLine
。示例代码如下:
cpp
Vec4f lineParams;
fitLine(contours[i], lineParams, DIST_L2, 0, 0.01, 0.01);
// lineParams 格式为 (vx, vy, x0, y0),其中 (vx,vy) 为方向向量,(x0,y0) 为直线上一个点
// 根据方向向量确定直线的两个端点便于绘制
int imgWidth = src.cols, imgHeight = src.rows;
Point point0(lineParams[2], lineParams[3]);
Point point1;
point1.x = point0.x + (int)(lineParams[0] * imgWidth);
point1.y = point0.y + (int)(lineParams[1] * imgHeight);
line(approxImage, point0, point1, Scalar(255, 255, 0), 2);
说明
fitLine
返回直线参数,其中(vx, vy)
表示直线方向,(x0, y0)
表示直线上一个点。- 根据图像尺寸和方向向量,可以构造直线的端点用于绘制。
2.4 综合示例
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
// 读取图像
Mat src = imread("E:/image/pic2.png");
if (src.empty())
{
cout << "无法加载图像!" << endl;
return -1;
}
// 预处理:灰度与二值化
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
// 提取轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// 用于结果展示
Mat result = src.clone();
for (size_t i = 0; i < contours.size(); i++)
{
// 绘制原始轮廓(绿色)
drawContours(result, contours, i, Scalar(0, 255, 0), 2);
// 进行轮廓逼近
double epsilon = 0.01 * arcLength(contours[i], true);
vector<Point> approxCurve;
approxPolyDP(contours[i], approxCurve, epsilon, true);
polylines(result, approxCurve, true, Scalar(0, 0, 255), 2);
double area = contourArea(contours[i]);
double l = arcLength(contours[i], true);
printf("corner %d ,area %.2f, length %.2f\n", approxCurve.size(), area, l);
// 拟合最小外接矩形(蓝色)
RotatedRect minRect = minAreaRect(contours[i]);
Point2f rectPoints[4];
minRect.points(rectPoints);
for (int j = 0; j < 4; j++)
{
line(result, rectPoints[j], rectPoints[(j + 1) % 4], Scalar(255, 0, 0), 2);
}
// 拟合椭圆(黄色),要求轮廓至少有 5 个点
if (contours[i].size() > 5)
{
RotatedRect ellipseRect = fitEllipse(contours[i]);
ellipse(result, ellipseRect, Scalar(0, 255, 255), 2);
}
}
imshow("轮廓逼近与拟合", result);
waitKey(0);
return 0;
}

3. 总结
本文介绍了如何在 OpenCV C++ 中进行轮廓检测、逼近和拟合。
- 轮廓检测: 使用
findContours
提取图像中的轮廓; - 轮廓逼近: 通过
approxPolyDP
对复杂边缘进行简化; - 拟合: 利用
minAreaRect
、fitEllipse
和fitLine
分别求解最小外接矩形、椭圆和直线。
掌握上述方法后,你可以在各种图像分析与计算机视觉项目中更高效地处理和分析目标边界信息。