霍夫变换直线检测(Line Detection)原理及示例

给定一幅图像(一般为二值图像)中的点集合,如何检测直线?
一种解决方法:任选一对点,决定一条线,然后测试所有其他点是否接近这条线,从而得出接近这条特殊线的所有点的子集。该方法比较复杂。另外一种方法便是采用霍夫变换。

霍夫变换是图像处理必然接触到的一个算法,它通过一种投票算法检测具有特定形状的物体,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果,该方法可以进行圆,直线,椭圆等形状的检测。在车道线检测中,当初考虑的一个方案便是采用霍夫变换检测直线进行车道线提取。

目录

文章目录

  • 霍夫变换直线检测(Line Detection)原理及示例
  • 目录
  • 霍夫变换(Hough Transform)
  • 霍夫直线检测(Hough Line Detection)
  • 霍夫直线检测的基本原理
  • 关于对偶性
  • 参数空间的选择
  • 利用霍夫变换检测直线
  • 霍夫变换直线检测步骤示例
  • 霍夫直线检测的优缺点
  • 霍夫直线检测的OpenCV实现
  • 霍夫直线检测函数定义
  • OpenCV霍夫直线检测函数使用


霍夫变换(Hough Transform)

霍夫变换(Hough Transform)于1962年由Paul Hough 首次提出,后于1972年由Richard Duda和Peter Hart推广使用,是图像处理领域内从图像中检测几何形状的基本方法之一。经典霍夫变换用来检测图像中的直线,后来霍夫变换经过扩展可以进行任意形状物体的识别,例如圆和椭圆。
霍夫变换运用两个坐标空间之间的变换,将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题。

霍夫直线检测(Hough Line Detection)

Hough直线检测的基本原理在于利用点与线的对偶性,在我们的直线检测任务中,即图像空间中的直线与参数空间中的点是一一对应的,参数空间中的直线与图像空间中的点也是一一对应的。这意味着我们可以得出两个非常有用的结论:
1)图像空间中的每条直线在参数空间中都对应着单独一个点来表示;
2)图像空间中的直线上任何一部分线段在参数空间对应的是同一个点。
因此Hough直线检测算法就是把在图像空间中的直线检测问题转换到参数空间中对点的检测问题,通过在参数空间里寻找峰值来完成直线检测任务。

霍夫直线检测的基本原理

关于对偶性

1)图像空间中的点与参数空间中的直线一一对应
在图像空间x-y中,一条直线在直角坐标系下可以表示为:
opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_参数空间
其中k和b是参数,表示直线的斜率和截距。
过某一点opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_图像空间_02的所有直线的参数均满足方程opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_参数空间_03,即点opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_图像空间_02确定了一族直线.
如果我们将方程改写为:b=-kx0+y0

那么该方程在参数空间k-b中就对应了一条直线。以上过程如图1所示:

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_霍夫变换_05

也就是说,图像空间x-y中的点opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_图像空间_06对应了参数空间k-b中的直线opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_图像空间_07。因此可以得到结论,图像空间中的点与参数空间中的直线一一对应。
2)图像空间中的直线与参数空间中的点一一对应
我们在直线y=kx+b上再增加一个点opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_参数空间_08,那么点opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_霍夫变换_09在参数空间k-b中同样对应了一条直线,如图2所示:

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_图像空间_10

可以看到,图像空间x-y中的点A和点B在参数空间k-b中对应的直线相交于一点,这也就是说AB所确定的直线,在参数空间中对应着唯一一个点,而这个点的坐标值opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_图像空间_11也就是直线AB的参数。

以上就是在直线检测任务中关于对偶性的直观解释。这个性质也为我们解决直线检测任务提供了方法,也就是把图像空间中的直线对应到参数空间中的点,最后通过统计特性来解决问题。假如图像空间中有两条直线,那么最终在参数空间中就会对应到两个峰值点,依此类推。

如图3所示,图像空间中有5个点,将这5个点转换到参数空间k-b中对应着5条直线,每两条直线的交点可以确定图像空间中的一条直线,我们可以选择由尽可能多直线汇成的点,如A和B,将其参数空间中的坐标值作为图像空间中两条直线方程参数,由此便确定了两条直线,

总结来说,在参数k-b平面上相交直线最多的点,对应的x-y平面上的直线就是我们的解

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_霍夫变换_12

参数空间的选择

上述为了方便讲解对偶性和霍夫变换的基本原理,我们的参数空间也选择了笛卡尔直角坐标系。但在实际应用中,参数空间是不能选择直角坐标系的,因为原始图像直角坐标空间中的特殊直线x=c(垂直x轴,直线的斜率为无穷大)是没办法在基于直角坐标系的参数空间中表示的。

或者,直观理解,如下图4所示,为一条垂直于x轴的直线,将各点转换到参数空间k-b后的情况:

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_参数空间_13


可以看到,这些点在参数空间中的直线并没有交点。那么能否采用其他方式进行表示呢?是的,一般我们采用极坐标方式作为参数空间。

图5所示,为图像坐标系到极坐标系参数空间的转换过程:

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_霍夫变换_14


对于图像空间中的垂线来说,用极坐标可以表示为:

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_参数空间_15

从上面可以看到,参数空间的每个点(ρ,θ)都对应了图像空间的一条直线,或者说图像空间的一个点在参数空间中就对应为一条曲线。参数空间采用极坐标系,这样就可以在参数空间表示原始空间中的所有直线了。
注意,此时图像空间(直角坐标系x-y)上的一个点对应到参数空间(极坐标系ρ-θ)上是一条曲线,确切的说是一条正弦曲线。

如图6所示,为图像空间到极坐标参数空间的转换过程:

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_霍夫变换_16


opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_图像空间_17

这样就把在图像空间中检测直线的问题转化为在极坐标参数空间中找通过点(r,θ)的最多正弦曲线数的问题。霍夫空间中,曲线的交点次数越多,所代表的参数越确定,画出的图形越饱满。

利用霍夫变换检测直线

如前所述,霍夫直线检测就是把图像空间中的直线变换到参数空间中的点,通过统计特性来解决检测问题。具体来说,如果一幅图像中的像素构成一条直线,那么这些像素坐标值(x, y)在参数空间对应的曲线一定相交于一个点,所以我们只需要将图像中的所有像素点(坐标值)变换成参数空间的曲线,并在参数空间检测曲线交点就可以确定直线了。

在理论上,一个点对应无数条直线或者说任意方向的直线(在参数空间中坐标轴表示的斜率k或者说θ有无数个),但在实际应用中,我们必须限定直线的数量(即有限数量的方向)才能够进行计算。

因此,我们将直线的方向θ离散化为有限个等间距的离散值,参数ρ也就对应离散化为有限个值,于是参数空间不再是连续的,而是被离散量化为一个个等大小网格单元。将图像空间(直角坐标系)中每个像素点坐标值变换到参数空间(极坐标系)后,所得值会落在某个网格内,使该网格单元的累加计数器加1。当图像空间中所有的像素都经过霍夫变换后,对网格单元进行检查,累加计数值最大的网格,其坐标值(ρ0, θ0)就对应图像空间中所求的直线。如图7所示,为一个离散化过程。

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_参数空间_18

霍夫变换直线检测步骤示例

opencv 霍夫直线检测 python halcon霍夫变换直线检测原理_参数空间_19


总结:使用霍夫变换检测直线具体步骤:

1.彩色图像->灰度图

2.去噪(高斯核)

3.边缘提取(梯度算子、拉普拉斯算子、canny、sobel)

4.二值化(判断此处是否为边缘点,就看灰度值==255)

5.映射到霍夫空间(准备两个容器,一个用来展示hough-space概况,一个数组hough-space用来储存voting的值,因为投票过程往往有某个极大值超过阈值,多达几千,不能直接用灰度图来记录投票信息)

6.取局部极大值,设定阈值,过滤干扰直线

7.绘制直线、标定角点

霍夫直线检测的优缺点

优点:Hough直线检测的优点是抗干扰能力强,对图像中直线的殘缺部分、噪声以及其它共存的非直线结构不敏感,能容忍特征边界描述中的间隙,并且相对不受图像噪声的影响

缺点:Hough变换算法的特点导致其时间复杂度和空间复杂度都很高,并且在检测过程中只能确定直线方向,丢失了线段的长度信息。由于霍夫检测过程中进行了离散化,因此检测精度受参数离散间隔制约

霍夫直线检测的OpenCV实现

霍夫直线检测函数定义

OpenCV支持三种霍夫直线检测算法:
1)Standard Hough Transform(SHT,标准霍夫变换)
2)Multiscale Hough Transform(MSHT,多尺度霍夫变换)
3)Progressive Probability Houth Transform(PPHT,渐进概率式霍夫变换)
在OpenCV3.0及以上版本中,霍夫直线检测算法定义了两个函数:HoughLines、HoughLinesP,
1)HoughLines:标准霍夫变换、多尺度霍夫变换

CV_EXPORTS_W 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:检测到的线条参数集合。 
  //double rho:以像素为单位的距离步长。 
  //double theta:以弧度为单位的角度步长。 
  //int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。 
  //double srn:默认值为0,用于在多尺度霍夫变换中作为参数rho的除数,rho=rho/srn。 
  //double stn:默认值为0,用于在多尺度霍夫变换中作为参数theta的除数,theta=theta/stn。
  //如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换。

函数说明:
HoughLines函数输出检测到直线的矢量表示集合,每一条直线由具有两个元素的矢量(ρ, θ)表示,其中ρ表示直线距离原点(0, 0)的长度,θ表示直线的角度(以弧度为单位)。
HoughLines函数无法输出图像空间中线段的长度,这也是霍夫变换本身的弱点。

2)HoughLinesP:渐进概率式霍夫变换

CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines, 
  double rho, double theta, int threshold, 
  double minLineLength = 0, double maxLineGap = 0 ); 
  
  //InputArray image:输入图像,必须是8位单通道图像。 
  //OutputArray lines:检测到的线条参数集合。 
  //double rho:直线搜索时的距离步长,以像素为单位。 
  //double theta:直线搜索时的角度步长,以弧度为单位。 
  //int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。 
  //double minLineLength:默认值为0,表示最小线段长度阈值(像素)。 
  //double maxLineGap:线段上最近两点之间的阈值.默认值为0,表示直线断裂的最大间隔距离阈值。即如果有两条线段是在一条直线上,但它们之间有间隙,那么如果这个间隔距离小于该值,则被认为是一条线段,否则认为是两条线段。

HoughLinesP能够检测出线端,即能够检测出图像中直线的两个端点,确切地定位图像中的直线。
HoughLinesP函数输出检测到直线的矢量表示集合,每一条直线由具有四个元素的矢量(x1, y1, x2, y2)表示,其中(x1, y1)表示线段的起点,(x2, y2)表示线段的终点。

OpenCV霍夫直线检测函数使用

霍夫直线变换是一种用来在图像空间寻找直线的方法,输入图像要求是二值图像,同时为了提高检测直线的效率和准确率,在使用霍夫线变换之前,最好对图像进行边缘检测生成边缘二值图像,这样的检测效果是最好的。

原始图像如图9所示,分别进行HoughLines和HoughLinesP函数检测直线。


1)HoughLines函数

代码:

//
// Created by liheng on 2019/2/24.
// Program:霍夫直线检测示例
//Data:2019.2.24
//Author:liheng
//Version:V1.0
//
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>
int main()
{
    std::string img_path;
    cv::Mat mat_color;
    cv::Mat mat_gray;
    cv::Mat mat_binary;
    cv::Mat mat_canny;

    img_path = "../pictures/000177.png";
    mat_color = cv::imread(img_path, 1);
    mat_gray = cv::imread(img_path, 0);

    //形态学闭运算
    cv::Mat elementRect;
    elementRect = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(3,3),cv::Point(-1,-1));
    cv::morphologyEx(mat_gray,mat_gray,cv::MORPH_CLOSE,elementRect);

    // binary
    cv::threshold(mat_gray, mat_binary, 125, 255.0, cv::THRESH_BINARY);

    // detect edge
    cv::Canny(mat_binary, mat_canny, 50, 125, 3);

    // detect line
    std::vector<cv::Vec2f> lines;
    cv::HoughLines(mat_canny, lines, 1, CV_PI / 180, 150, 0, 0);

    // draw line
    std::cout << "line number: " << lines.size() << std::endl;
    for (size_t i = 0; i < lines.size(); i++)
    {
        cv::Vec2f linex = lines[i];
        std::cout << "radius: " << linex[0] <<", angle: " << 180 / CV_PI * linex[1] << std::endl;
        float rho = lines[i][0], theta = lines[i][1];
        cv::Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a * rho, 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));
        line(mat_color, pt1, pt2, cv::Scalar(255, 0, 0), 1);
    }
    cv::imshow("gray", mat_gray);
    cv::imshow("binary", mat_binary);
    cv::imshow("canny", mat_canny);
    cv::imshow("color", mat_color);
    cv::waitKey(0);

    return 0;
}

检测结果如图10所示:


从结果可以看到左侧车道线可以检测到,贴合实际车道线结果,但是右侧丢失,同时杂线比较多。

2)HoughLinesP函数

代码:

//
// Created by liheng on 2019/2/24.
// Program:霍夫P直线检测示例
//Data:2019.2.24
//Author:liheng
//Version:V1.0
//
#include <opencv2/opencv.hpp>
#include <vector>
#include <iostream>

int main()
{
    std::string img_path;
    cv::Mat mat_color;
    cv::Mat mat_gray;
    cv::Mat mat_binary;
    cv::Mat mat_canny;

    img_path = "../pictures/000177.png";
    mat_color = cv::imread(img_path, 1);
    mat_gray = cv::imread(img_path, 0);

    //形态学闭运算
    cv::Mat elementRect;
    elementRect = cv::getStructuringElement(cv::MORPH_RECT,cv::Size(3,3),cv::Point(-1,-1));
    cv::morphologyEx(mat_gray,mat_gray,cv::MORPH_CLOSE,elementRect);

    // binary
    cv::threshold(mat_gray, mat_binary, 125, 255.0, cv::THRESH_BINARY);

    // detect edge
    cv::Canny(mat_binary, mat_canny, 50, 125, 3);

    // detect line
    std::vector<cv::Vec4i> lines;
    HoughLinesP(mat_canny,lines,1,CV_PI/180,100,10,50);

    // draw line
    for (size_t i = 0; i < lines.size(); i++)
    {
        cv::Vec4i& linex = lines[i];
        int dx=linex[2]-linex[0];
        int dy=linex[2]-linex[1];
        double angle = atan2(double(dy),dx) * 180 /CV_PI;
        //if (abs(angle) <= 20)
        //    continue;

        line(mat_color, cv::Point(linex[0], linex[1]), cv::Point(linex[2], linex[3]), cv::Scalar(255, 0, 0), 1);
    }

    cv::imshow("gray",   mat_gray);
    cv::imshow("binary", mat_binary);
    cv::imshow("canny", mat_canny);
    cv::imshow("color", mat_color);
    cv::waitKey(0);


    return 0;
}

检测结果如图11所示:


其检测到左侧车道线的2条边线,但右侧同样没有检测到。

如果将HoughLines和HoughLinesP函数用于车道线检测,从上述结果来看,仍需一定的处理方法,以提高准确性。