一、凸包检测
1、凸包检测原理
**凸包:**用来描述一组点的最小凸多边形包围结构。
给定一组平面点集,凸包就是"用一根橡皮筋把所有点外侧紧紧包住后形成的形状"。
2、凸包检测函数
cpp
/* 用途:用于计算点集或轮廓的凸包,
即能够完全包围目标的最小凸多边形 */
void cv::convexHull( InputArray points, OutputArray hull,
bool clockwise = false, bool returnPoints = true );
/*
points:输入的2D点集
hull:输出凸包的顶点
clockwise:方向标志,当参数为true时,凸包顺序为顺时针方向,否则为逆时针方向
returnPoints:输出数据的类型标志,当参数为true时第二个参数输出的结果是凸包顶点的坐标,
否则第二个参数输出的结果是凸包顶点的索引
*/
3、示例代码
cpp
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img = imread(s_imgPath + "/hand.png");
if (img.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
/*二值化*/
Mat gray, binary;
cvtColor(img, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 105, 255, THRESH_BINARY);
/*开运算消除细小区域*/
Mat k = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, binary, MORPH_OPEN, k);
imshow("binary", binary);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, 0, 2, Point());
for (int n = 0; n < contours.size(); n++)
{
/*计算凸包*/
vector<Point> hull;
convexHull(contours[n], hull);
/*绘制凸包*/
for (int i = 0; i < hull.size(); i++)
{
/*绘制凸包顶点*/
circle(img, hull[i], 4, Scalar(255, 0, 0), 2, 8, 0);
/*连接凸包*/
if (i == hull.size() - 1)
{
line(img, hull[i], hull[0], Scalar(0, 0, 255), 2, 8, 0);
break;
}
line(img, hull[i], hull[i + 1], Scalar(0, 0, 255), 2, 8, 0);
}
}
imshow("hull", img);
waitKey(0);
destroyAllWindows();
二、直线检测
1、检测直线的霍夫变换原理
霍夫变换(Hough Transform)是一种用于检测图像中几何形状的方法,最常用于直线检测。
其核心思想是:
将图像空间中的点转换到参数空间中进行投票,当多个点在参数空间中的曲线交汇于同一点时,则认为这些点属于同一条直线。
2、相关函数
检测直线流程:
- 将参数空间的坐标轴离散化
- 将图像中每个非0像素通过映射关系求取在参数空间通过的方格
- 统计参数空间内每个方格出现的次数,选取次数大于某一阈值的方格作为表示直线的方格
- 将参数空间中表示直线的方格的参数作为图像中直线的参数
cpp
/* 用途:用于通过霍夫变换检测图像中的直线,
能够从边缘图像中提取满足条件的直线参数 */
void cv::HoughLines( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double srn = 0, double stn = 0,
double min_theta = 0, double max_theta = CV_PI );
/*
image:输入图像,必须为8位单通道二值图像,通常为Canny边缘检测结果
lines:检测到的直线参数输出,每条直线由(rho, theta)表示
rho:距离分辨率,单位为像素
theta:角度分辨率,单位为弧度
threshold:直线检测阈值,
累加器中投票数超过该值才认为是直线
srn:多尺度霍夫变换的rho分辨率,默认0表示标准霍夫变换
stn:多尺度霍夫变换的theta分辨率
min_theta:检测直线的最小角度
max_theta:检测直线的最大角度
*/
cpp
/* 用途:用于通过概率霍夫变换检测图像中的线段。
与HoughLines()不同,
HoughLinesP()直接返回线段两个端点坐标,
而不是返回无限延长直线的参数 */
void cv::HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength = 0, double maxLineGap = 0 );
/*
image:输入图像,必须是CV_8C的单通道二值图像
lines:检测到的线段结果,每条线段由(x1,y1,x2,y2)表示
rho:距离分辨率,单位为像素
theta:角度分辨率,单位为弧度
threshold:霍夫累加器阈值,
投票数超过该值才认为检测到线段
minLineLength:最小线段长度,
小于该长度的线段将被忽略
maxLineGap:同一直线上两点之间允许的最大间隔,
小于该间隔的断裂线段会被连接
*/
3、示例代码
cpp
void drawLine(Mat& img,/*要标记直线的图像*/
vector<Vec2f> lines,/*检测的直线数据*/
double rows,/*原图像的行数(高)*/
double cols,/*原图像的列数(宽)*/
Scalar scalar,/*绘制直线的颜色*/
int n)/*绘制直线的线宽*/
{
Point pt1, pt2;
for (int i = 0; i < lines.size(); i++)
{
float rho = lines[i][0];/*直线距离坐标原点的距离*/
float theta = lines[i][1];/*直线过坐标原点垂线与x轴夹角*/
double a = cos(theta);/*夹角的余弦值*/
double b = sin(theta);/*夹角的正弦值*/
double x0 = a * rho, y0 = b * rho;/*直线与过坐标原点的垂线的交点*/
double length = max(rows, cols);/*图像高宽的最大值*/
/*计算直线上的一点*/
pt1.x = cvRound(x0 + length * (-b));
pt1.y = cvRound(y0 + length * (a));
/*计算直线上的另一点*/
pt2.x = cvRound(x0 - length * (-b));
pt2.y = cvRound(y0 - length * (a));
/*两点绘制一条直线*/
line(img, pt1, pt2, scalar, n);
}
}
void HoughLinesStudy()
{
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img = imread(s_imgPath + "/HoughLines.png");
if (img.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
Mat edge;
/*检测边缘图像,并二值化*/
Canny(img, edge, 80, 180, 3, false);
threshold(edge, edge, 170, 255, THRESH_BINARY);
/*用不同的累加器进行检测直线*/
vector<Vec2f> lines1, lines2;
HoughLines(edge, lines1, 1, CV_PI / 180, 50, 0, 0);
HoughLines(edge, lines2, 1, CV_PI / 180, 150, 0, 0);
/*在原图像中绘制直线*/
Mat img1, img2;
img.copyTo(img1);
img.copyTo(img2);
drawLine(img1, lines1, edge.rows, edge.cols, Scalar(255), 2);
drawLine(img2, lines2, edge.rows, edge.cols, Scalar(255), 2);
imshow("img", img);
imshow("edge", edge);
imshow("img1", img1);
imshow("img2", img2);
waitKey(0);
destroyAllWindows();
}
cpp
QString imgPath = QApplication::applicationDirPath() + "/Images";
cv::String s_imgPath = imgPath.toLocal8Bit().data();
Mat img = imread(s_imgPath + "/HoughLines.png");
if (img.empty())
{
qDebug() << "图片加载失败, 请确认图像文件名称是否正确";
return;
}
Mat edge;
/*检测边缘图像,并二值化*/
Canny(img, edge, 80, 180, 3, false);
/*用不同的累加器进行检测直线*/
vector<Vec4i> linesP1, linesP2;
HoughLinesP(edge, linesP1, 1, CV_PI / 180, 150, 30, 10);/*两个点连接最大距离10*/
HoughLinesP(edge, linesP2, 1, CV_PI / 180, 150, 30, 30);/*两个点连接最大距离30*/
/*绘制两个点连接最大距离10 直线检测结果*/
Mat img1;
img.copyTo(img1);
for (int i = 0; i < linesP1.size(); i++)
{
line(img1, Point(linesP1[i][0], linesP1[i][1]),
Point(linesP1[i][2], linesP1[i][3]), Scalar(255), 3);
}
/*绘制两个点连接最大距离10 直线检测结果*/
Mat img2;
img.copyTo(img2);
for (int i = 0; i < linesP2.size(); i++)
{
line(img2, Point(linesP2[i][0], linesP2[i][1]),
Point(linesP2[i][2], linesP2[i][3]), Scalar(255), 3);
}
imshow("img", img);
imshow("edge", edge);
imshow("img1", img1);
imshow("img2", img2);
waitKey(0);
destroyAllWindows();
三、点集拟合
1、点集拟合的含义
点集拟合是指:根据一组离散的点数据,寻找一个最能表示这些点分布规律的几何模型或数学函数。

2、直线拟合函数
cpp
/* 用途:用于根据输入点集进行直线拟合,
计算最能代表这些点整体趋势的一条直线 */
void cv::fitLine( InputArray points, OutputArray line, int distType,
double param, double reps, double aeps );
/*
points:输入待拟合直线直线的2D或者3D点集
line:输出拟合直线的参数,2D点集描述参数为Vec4f类型,2D点集描述参数为Vec6f类型
distType:M-estimator算法使用的距离类型标志
param:某些类型距离的数值参数(C),如果数值为0,则自动选择最佳值
reps:直线坐标原点与直线之间的距离精度,数值0表示选择自适应参数,一般常选择0.01
aeps:直线角度精度,数值0表示选择自适应参数,一般常选择0.01
*/
| 标志参数 | 简记 | 距离计算公式 |
|---|---|---|
| DIST_L1 | 1 | |
| DIST_L2 | 2 | |
| DIST_L12 | 4 | |
| DIST_FAIR | 5 | |
| DIST_WEILSCH | 6 | |
| DIST_L1HUBER | 7 | ![\rho (r)= \begin{cases} \frac{r^2}{2}, & |
| [距离类型选择标志] |
3、圆形拟合
cpp
/* 用途:用于计算能够完全包围输入点集的最小外接圆,
即在保证包含所有点的前提下,
面积最小的圆形区域 */
void cv::minEnclosingCircle( InputArray points,
CV_OUT Point2f& center, CV_OUT float& radius );
/*
points:输入二维点集,通常为轮廓点集合
center:输出最小外接圆的圆心坐标
radius:输出最小外接圆的半径
*/
4、三角形拟合
cpp
/* 返回值:最小外接三角形的面积
用途:用于计算能够完全包围输入点集的最小面积三角形,
即在保证包含所有点的前提下,
面积最小的外接三角形 */
double cv::minEnclosingTriangle( InputArray points, CV_OUT OutputArray triangle );
/*
points:输入二维点集,通常为轮廓点集合
triangle:输出最小外接三角形的三个顶点坐标
*/
5、示例代码
cpp
Vec4f lines;/*存放拟合后的直线*/
vector<Point2f> point;/*待检测是否存在直线的所有点*/
const static float Points[20][2] = {
{0.0f,0.0f},{10.0f,11.0f},{21.0f,20.0f},{30.0f,30.0f},
{40.0f,42.0f},{50.0f,50.0f},{60.0f,60.0f},{70.0f,70.0f},
{80.0f,80.0f},{90.0f,92.0f},{100.0f,100.0f},{110.0f,110.0f},
{120.0f,120.0f},{136.0f,130.0f},{138.0f,140.0f},{150.0f,150.0f},
{160.0f,163.0f},{175.0f,170.0f},{181.0f,180.0f},{200.0f,190.0f}
};
/*将所有点存放在vector中,用于输入函数中*/
for (int i = 0; i < 20; i++)
{
point.push_back(Point2f(Points[i][0], Points[i][1]));
}
/*参数设置*/
double param = 0;/*距离模型中的数值参数C*/
double reps = 0.01;/*坐标原点与直线之间的距离精度*/
double aeps = 0.01;/*角度精度*/
fitLine(point, lines, DIST_L1, 0, 0.01, 0.01);
double k = lines[1] / lines[0];/*直线斜率*/
cout << "Line Slope: " << k << endl;
cout << "Point on the Line Coordinates x: " << lines[2] << ",y: " << lines[3] << endl;
cout << "Line Equation: y=" << k << "(x-" << lines[2] << ")+" << lines[3] << endl;
waitKey(0);
Mat img(500, 500, CV_8UC3, Scalar::all(0));
RNG& rng = theRNG();/*生成随机点*/
while (true)
{
int count = rng.uniform(1, 101);
vector<Point2f> points;
/*生成随机点*/
for (int i = 0; i < count; i++)
{
Point pt;
pt.x = rng.uniform(img.cols / 4, img.cols * 3 / 4);
pt.y = rng.uniform(img.rows / 4, img.rows * 3 / 4);
points.push_back(pt);
}
/*寻找包围点集的三角形*/
vector<Point2f> triangle;
double area = minEnclosingTriangle(points, triangle);
/*寻找包围点集的圆形*/
Point2f center;
float radius = 0;
minEnclosingCircle(points, center, radius);
/*创建两个图片用于输出结果*/
img = Scalar::all(0);
Mat img2;
img.copyTo(img2);
/*在图像中绘制坐标点*/
for (int i = 0; i < count; i++)
{
circle(img, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
circle(img2, points[i], 3, Scalar(255, 255, 255), FILLED, LINE_AA);
}
/*绘制三角形*/
for (int i = 0; i < 3; i++)
{
if (i == 2)
{
line(img, triangle[i], triangle[0], Scalar(255, 255, 255), 1, 16);
break;
}
line(img, triangle[i], triangle[i + 1], Scalar(255, 255, 255), 1, 16);
}
/*绘制圆形*/
circle(img2, center, cvRound(radius), Scalar(255, 255, 255), 1, LINE_AA);
imshow("triangle", img);
imshow("circle", img2);
/*按q键或者ESC键退出*/
char key = (char)waitKey();
if (key == 27 || key == 'q' || key == 'Q')
{
break;
}
}
waitKey(0);
destroyAllWindows();