OpenCV联合C++/Qt 学习笔记(十七)----凸包检测、直线检测及点集拟合

一、凸包检测

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、相关函数

检测直线流程:

  1. 将参数空间的坐标轴离散化
  2. 将图像中每个非0像素通过映射关系求取在参数空间通过的方格
  3. 统计参数空间内每个方格出现的次数,选取次数大于某一阈值的方格作为表示直线的方格
  4. 将参数空间中表示直线的方格的参数作为图像中直线的参数
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 ,其中C=1.3998
DIST_WEILSCH 6 ,其中C=2.9846
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();
相关推荐
basketball6161 小时前
C++ Lambda 表达式完全指南
开发语言·c++·算法
不知名的老吴1 小时前
C++中emplace函数的不适场景总结(三)
开发语言·c++·算法
2401_892070982 小时前
C++ 缓存线程池(CachedThreadPool):原理、实现、对比
c++·缓存·缓存线程池
南境十里·墨染春水2 小时前
linux学习进展 网络编程——HTTPS (补充)
linux·网络·学习
是喵斯特ya2 小时前
红日内网靶场1环境搭建
笔记
玉树临风ives2 小时前
atcoder ABC 457 题解
数据结构·c++·算法
中屹指纹浏览器2 小时前
2026浏览器插件扩展安全风险溯源与环境隔离防护规范
经验分享·笔记
吃好睡好便好2 小时前
说说损伤膝盖的行为和保护膝盖的方法
学习
ComputerInBook2 小时前
数字图像处理——倍频(octave)图像
人工智能·深度学习·计算机视觉·倍频图像