OpenCV开发笔记(四十二):红胖子8分钟带你深入了解标准霍夫线变换(图文并茂+浅显易懂+程序源码)

若该文为原创文章,未经允许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105527284
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究

目录

前言

Demo

霍夫变换

概述

标准霍夫线变换

概述

原理

1.一条直线的图像二维空间可由两个变量表示

2.一般来说,对于点(x0,y0),可以通过这个点的一组直线统一定义

3.对于一个给定点(x0,y0),在极坐标对极径和极角平面会出所有通过他的直线将得到一条正弦曲线

4.对图像中所有的点进行上述操作,得到曲线图集合

5.从以上得出,一条直线能够通过在平面θ-r寻找交于一点的曲线数量来检测。

标准霍夫变换函数原型

补充数据函数原型

Demo源码

工程模板:对应版本号v1.37.0

参考博文


OpenCV开发专栏(点击传送门)

    OpenCV开发笔记(四十二):红胖子8分钟带你深入了解标准霍夫线变换(图文并茂+浅显易懂+程序源码)

前言

      红胖子来也!!!

去噪、边缘检测之后,就是特征提取了,识别图形的基本方法之一---霍夫变换,霍夫变换是图像处理中的一种特征提取技术,本篇章主要详解讲解了霍夫线变换。

Demo

霍夫变换

概述

      霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,改过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果。

      经典的霍夫变换用来检测图像中的直线,后来霍夫变换扩展到任意形状物体的识别,多为圆和椭圆。

      霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同的形状的曲线或直线映射到另一个坐标控件的一个点上形成峰值,从而把检测任何形状的问题转化为统计峰值问题。

      OpenCV中的霍夫变换分为两大类型,线变换下又分三种类型,如下图:

 

标准霍夫线变换

概述

      霍夫线变换,从名字就可以知道其实针对直线,显而易见就是用来寻找直线的方法,此处特别注意,使用霍夫线变换之前,肯定是需要对图片进行预处理:降噪、边缘检测处理,霍夫线变换只寻找直线,只能识别边缘二值图像,所以输入也就只能是二值化的图像了。

二值化的图像需要使用二值化的函数,之前边缘检测,得出的颜色是灰度的,还需要使用二值化的函数对灰度图像进行二值化。

霍夫线变换分为三种,如下图:

      霍夫线变换会找出大量的线,但是有些线其实是无用的。

原理

1.一条直线的图像二维空间可由两个变量表示

  • 在笛卡尔坐标系:可有参数斜率和截距(m,b)表示;

  • 在极坐标系(霍夫变换采用的方式):可有参数极径和极角(r,θ)表示;

      霍夫变换采用极坐标的方式来表示直线。

      因此直线的表达式是:

      得出r公式为:

2.一般来说,对于点(x0,y0),可以通过这个点的一组直线统一定义

      rθ= x0 * cosθ + y0 * sinθ

      每一对(rθ,θ)代表一条通过点(x0,y0)的直线。

3.对于一个给定点(x0,y0),在极坐标对极径和极角平面会出所有通过他的直线将得到一条正弦曲线

例如,对于给定点x0=8和y0=6,计算原理如下:

可以会出如下图曲线:

只绘出满足特定条件,如r>0和0<θ<2π,(注意:0表示垂直线,π/2度表示水平线);

4.对图像中所有的点进行上述操作,得到曲线图集合

如果两个不同点进行上述操作后得到的曲线在平面θ-r相交,这就意味着他们通过同一条直线。

例如,接上面的例子继续对点x1=9,y1=4和x2=12,y2=3绘图如下:

这三条曲线在平面相较于点0.925, 9.6,坐标表示的是参数对θ-r或者是说点(x0, y0)、(x1, y1)和(x2, y2)组成的平面内的直线,所以其实是计算平面内的每个点的每个角度的距离,绘制成曲线后,如果3个点交叉了,那么3个点就是在一条直线上,示意如下图:

(交于一点的曲线的数量超过了阈值(在同一条直线上点的数量),比如上面的示意图,就认为3个点就可以组成一条直线,那么他们可以检测出一跳直线)

5.从以上得出,一条直线能够通过在平面θ-r寻找交于一点的曲线数量来检测。

越多曲线交于一点也就意味着这个点焦点表示的直线有更多的点组成。一般来说我们可以通过设置直线上点的阈值来定义多少条曲线交于一点,这样才认为检测到了一条直线。

以上就是霍夫变换所做的,它追踪图像中每个点对应曲线的交点,如果交于一点的曲线的数量超过了阈值(在同一条直线上点的数量阈值),那么可以认为这个交点所代表的参数对(θ,rθ)在原图像中为一条直线。

标准霍夫变换函数原型

void 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 );
  • 参数一:InputArray类型的image,源图像8位,单通道二进制图像。可以将任意的原图载入进来,并由函数修改成此格式后,再填这里;
  • 参数二:OutputArray类型的lines,经过调用HoughLines函数后存储了霍夫变换检测到线条的输出矢量。每一条线由两个元素的矢量(r,θ),r代表离坐标原点的距离,θ是弧度线条旋转角度(0表示垂直线,π/2度表示水平线);
  • 参数三:double类型的rho,rho是直线的距离,若为1个像素,那么检测1->2->3长度的直线,若为2则是2->4->6忽略1和3了,可以设为小数忽略参数六
  • 参数四:double类型的theta,theta是弧度,哪怕所有的都是线,那么线与线之间的弧度精度,若360度一个点,弧度为π,则表示为从0°开始,每隔π弧度(180°,检测一根线),可以设为小数忽略参数七;
  • 参数五:int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中;
  • 参数六:double类型的srn,有默认值0。对于多尺度的霍夫变换,这是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,而精确的累加器进步尺寸为rho/srn(还不如直接rho设置为小数),细化检测出的结果;
  • 参数七:double类型的stn,有默认值0,对于多尺度霍夫变换,srn表示第四个参数进步尺寸的单位角度theta的除数距离(还不如直接rho设置为小数),细化检测出的结果。(注意:如果srnstn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数)
  • 参数八:double类型的min_theta,对于标准和多尺度Hough变换,检查线条的最小角度。必须介于0max_theta之间,检测出线条的角度范围;
  • 参数九:double类型的max_theta,对于标准和多尺度Hough变换,检查线条的最大角度。必须介于min_thetaCV_PI之间,检测出线条的角度范围;

补充数据函数原型

cvRound():返回跟参数最接近的整数值,即四舍五入;
cvFloor():返回不大于参数的最大整数值,即向下取整;
cvCeil():返回不小于参数的最小整数值,即向上取整;

Demo源码

oid OpenCVManager::testHoughLines()
{
    QString fileName1 =
            "E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/16.jpg";
    cv::Mat srcMat = cv::imread(fileName1.toStdString());
    int width = 400;
    int height = 300;

    cv::resize(srcMat, srcMat, cv::Size(width, height));
    cv::Mat colorMat = srcMat.clone();

    cv::String windowName = _windowTitle.toStdString();
    cvui::init(windowName);

    cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
                                srcMat.type());

    cv::cvtColor(srcMat, srcMat, CV_BGR2GRAY);

    int threshold1 = 200;
    int threshold2 = 100;
    int apertureSize = 1;

    int rh0 = 1;                            // 默认1像素
    int theta = 1;                          // 默认1°
    int threshold = 100;                    // 默认检测到同一直线的100个点

    while(true)
    {
        qDebug() << __FILE__ << __LINE__;
        windowMat = cv::Scalar(0, 0, 0);

        cv::Mat mat;
        cv::Mat dstMat;
        cv::Mat grayMat;

        // 转换为灰度图像
        // 原图先copy到左边
        cv::Mat leftMat = windowMat(cv::Range(0, srcMat.rows),
                                    cv::Range(0, srcMat.cols));
        cv::cvtColor(srcMat, grayMat, CV_GRAY2BGR);
        cv::addWeighted(leftMat, 0.0f, grayMat, 1.0f, 0.0f, leftMat);

        {
            cvui::printf(windowMat,
                         srcMat.rows * 1 + 100,
                         srcMat.cols * 0 + 20,
                         "threshold1");
            cvui::trackbar(windowMat,
                           srcMat.rows * 1 + 100,
                           srcMat.cols * 0 + 50,
                           200,
                           &threshold1,
                           0,
                           255);
            cvui::printf(windowMat,
                         srcMat.rows * 1 + 100,
                         srcMat.cols * 0 + 100, "threshold2");
            cvui::trackbar(windowMat,
                           srcMat.rows * 1 + 100,
                           srcMat.cols * 0 + 130,
                           200,
                           &threshold2,
                           0,
                           255);
            cv::Canny(srcMat, dstMat, threshold1, threshold2, apertureSize * 2 + 1);
            // copy
            mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                            cv::Range(srcMat.cols * 0, srcMat.cols * 1));

            cv::cvtColor(dstMat, grayMat, CV_GRAY2BGR);
            cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);

            cvui::printf(windowMat,
                         srcMat.rows * 1 + 100,
                         srcMat.cols * 1 + 20,
                         "rho");
            cvui::trackbar(windowMat,
                           srcMat.rows * 1 + 100,
                           srcMat.cols * 1 + 50,
                           200,
                           &rh0,
                           1,
                           255);
            cvui::printf(windowMat,
                         srcMat.rows * 1 + 100,
                         srcMat.cols * 1 + 100, "theta = value / 2");
            cvui::trackbar(windowMat,
                           srcMat.rows * 1 + 100,
                           srcMat.cols * 1 + 130,
                           200,
                           &theta,
                           1,
                           720);
            cvui::printf(windowMat,
                         srcMat.rows * 1 + 100,
                         srcMat.cols * 1 + 180, "min points");
            cvui::trackbar(windowMat,
                           srcMat.rows * 1 + 100,
                           srcMat.cols * 1 + 210,
                           200,
                           &threshold,
                           2,
                           300);

            // 边缘检测后,进行霍夫线检测
            // 使用霍夫线变化检测所有角度范围内的直线
            std::vector<cv::Vec2f> lines;
            cv::HoughLines(dstMat,          // 输入8位
                           lines,           // 输出线 std::vector<std::Vec2f>
                           rh0,             // 初步像素精度
                           theta / 720.0 * CV_PI,   // 初步偏移角度精度
                           threshold,       // 必须达到的点的数量
                           0,               // 标准霍夫变换,为0
                           0,               // 标准霍夫变换,为0
                           0,               // 检测角度范围最小为0
                           CV_PI);          // 检测角度范围最大为π,即360°
            // 在图中绘制出每条线段
            dstMat = colorMat.clone();
            qDebug() << __FILE__ << __LINE__ << lines.size();
            for(int index = 0; index < lines.size(); index++)
            {
                float rho = lines[index][0];
                float theta = lines[index][1];
                cv::Point pt1;
                cv::Point pt2;
                double a = cos(theta);
                double b = sin(theta);
                double x0 = a * rho;
                double y0 = b * rho;
                // 计算出点的坐标
                pt1.x = cvRound(x0 + 1000 * (-b));
                pt1.y = cvRound(y0 + 1000 * (a));
                pt2.x = cvRound(x0 - 1000 * (-b));
                pt2.y = cvRound(y0 - 1000 * (a));
                // 画线
                cv::line(dstMat, pt1, pt2, cv::Scalar(0, 0, 255), 1, cv::LINE_AA);
            }
            // copy
            mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                            cv::Range(srcMat.cols * 0, srcMat.cols * 1));
            cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
        }
        // 更新
        cvui::update();
        // 显示
        cv::imshow(windowName, windowMat);
        // esc键退出
        if(cv::waitKey(25) == 27)
        {
            break;
        }
    }
}

工程模板:对应版本号v1.37.0

      对应版本号v1.37.0

参考博文

https://blog.csdn.net/shenziheng1/article/details/75307410

 

原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105527284

发布了264 篇原创文章 · 获赞 440 · 访问量 53万+

猜你喜欢

转载自blog.csdn.net/qq21497936/article/details/105527284
今日推荐