OpenCV 学习(Hough 变换提取直线)

在机器视觉应用中,我们经常要提取图像中的各种特征,最基本的特征就是图像中的线条、拐角等。这篇笔记就来讲讲如何提取图像中的直线。这里使用的方法叫做 Hough 变换。

Hough 变换这个名称最早是在 Richard Duda 和 Peter Hart 两人于 1972 年合写的发表于 Comm. ACM 文章 《Use of the Hough Transformation to Detect Lines and Curves in Pictures》 中提出的。 大家可能会好奇,这俩人没一个叫 Hough,为啥这个变换叫 Hough 变换呢。这还要追溯到更早的年代,1962 年 Paul Hough 申请了一个美国专利,专利的名称叫做 《Method and means for recognizing complex patterns》,这个专利中提出了 Hough 变换基本方法。不过 1962 年那时还没有所谓的机器视觉这个学科,计算机也不是一般人能见到的。所以这个专利并没有受到特别的重视。 Richard Duda 和 Peter Hart 不知是如何翻到这个 10 年前的专利,并敏锐的发现了它的价值,并将其用于机器视觉领域。从此就有了大名鼎鼎的 Hough 变换。

关于 Hough 更详细的历史发展大家可以参考:

https://en.wikipedia.org/wiki/Hough_transform

Hough 变换的原理介绍也可以参考上面的 wiki。简单的说 Hough 变换采用的是一种证据收集的方式,遍历一幅图像上所有的直线位置,哪条直线上的特征点(证据)更多,哪条直线就更可能是我们希望找到的直线。

这里不准备详细介绍Hough 变换的原理。但是Hough 变换如何表示图像中的直线还是要介绍的。否则,我们都不知道如何使用获得的结果。

Hough 变换时,我们采用参数方程来表示直线。



ρ=xcosθ+ysinθ



ρ 的几何含义是直线到图像原点的距离。 θ 是直线的法向方向与 x 轴的夹角。 θ=0 表示的是垂直的直线,例如下图中直线 1。 θ=π/2 表示的是水平的直线,例如下图中直线 5。 θ 的取值范围是 0 到 π。由于限制了 θ的取值范围, ρ 既可以为正也可以为负。比如下图中直线2, θ=0.8π , ρ 为负。

opencv 直线以内全白 opencv找直线_#include

OpenCV 中提供了两个Hough变换提取直线的函数。

  1. cv::HoughLines 函数
  2. cv::HoughLinesP 函数

下面分别介绍。

cv::HoughLines 函数

这个函数采用最原始的Hough 变换来计算直线的位置。

void HoughLines( InputArray image, 
                 OutputArray lines,
                 double rho,  // rho 的步长
                 double theta, // 角度步长
                 int threshold, // 阈值
                 double srn=0, 
                 double stn=0 );

输入图像必须是单通道的。输出的直线存在一个

std::vector<cv::Vec2f> lines;

首先给出一个简单的测试图片。这个图片上有四条直线。没有其他的干扰物体。这属于最基本的情形。

opencv 直线以内全白 opencv找直线_OpenCV_02

下面是个测试代码。

#include <QCoreApplication>
#include <math.h>
#define PI 3.14159265358979
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"  

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    cv::Mat image = cv::imread("c:\\test.png");
    cv::Mat contours;
    cv::cvtColor(image, contours, cv::COLOR_BGR2GRAY);
    cv::bitwise_not(contours, contours);
    //cv::Canny(image, contours, 155, 350);
    std::vector<cv::Vec2f> lines;
    cv::HoughLines(contours, lines, 1, PI/180, 180);
    //cv::imshow("cany",contours );
    std::vector<cv::Vec2f>::const_iterator it= lines.begin();
    while (it!=lines.end())
    {
        float rho= (*it)[0]; // first element is distance rho
        float theta= (*it)[1]; // second element is angle theta
        if (theta < PI/4. || theta > 3.*PI/4.)// ~vertical line
        {
            // point of intersection of the line with first row
            cv::Point pt1(rho/cos(theta), 0);
            // point of intersection of the line with last row
            cv::Point pt2((rho - image.rows * sin(theta))/cos(theta), image.rows);
            // draw a white line
            cv::line( image, pt1, pt2, cv::Scalar(255), 1);
        }
        else
        { // ~horizontal line
            // point of intersection of the
            // line with first column
            cv::Point pt1(0,rho/sin(theta));
            // point of intersection of the line with last column
            cv::Point pt2(image.cols, (rho - image.cols * cos(theta))/sin(theta));
            // draw a white line
            cv::line(image, pt1, pt2, cv::Scalar(255), 1);
        }
        ++it;
    }
    cv::imshow("", image);
    return a.exec();
}

输出结果如下:

opencv 直线以内全白 opencv找直线_OpenCV_03

这几条线找的还是蛮准的。

cv::HoughLinesP 函数

与 cv::HoughLines函数不同, cv::HoughLinesP 函数可以提取线段。
输出的直线存在一个

std::vector<cv::Vec4i> lines;

中。

cv::Vec4i 的四个整数分别是线段的起点和终点坐标。

void HoughLinesP( InputArray image, 
    OutputArray lines,
    double rho,  // rho 的步长
    double theta,  // 角度的步长,单位是度
    int threshold, // 阈值
    double minLineLength=0,  // 线段的最小长度
    double maxLineGap=0 );  // 线段之间的最小距离

下面把 HoughLinesP 函数封装到一个类中。

class LineFinder
{
private:
    cv::Mat img; // original image
    std::vector<cv::Vec4i> lines;
    double deltaRho;
    double deltaTheta;
    int minVote;

    double minLength; // min length for a line
    double maxGap; // max allowed gap along the line
public:
    // Default accumulator resolution is 1 pixel by 1 degree
    // no gap, no mimimum length
    LineFinder() : deltaRho(1),
        deltaTheta(PI/180),
        minVote(10),
        minLength(0.),
        maxGap(0.) {}
    // Set the resolution of the accumulator
    void setAccResolution(double dRho, double dTheta)
    {
        deltaRho= dRho;
        deltaTheta= dTheta;
    }
    // Set the minimum number of votes
    void setMinVote(int minv)
    {
        minVote= minv;
    }
    // Set line length and gap
    void setLineLengthAndGap(double length, double gap)
    {
        minLength= length;
        maxGap= gap;
    }
    // Apply probabilistic Hough Transform
    std::vector<cv::Vec4i> findLines(cv::Mat& binary)
    {
        lines.clear();
        cv::HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);
        return lines;
    }
    // Draw the detected lines on an image
    void drawDetectedLines(cv::Mat &image, cv::Scalar color = cv::Scalar(255, 255, 255))
    {
        // Draw the lines
        std::vector<cv::Vec4i>::const_iterator it2 = lines.begin();
        while (it2 != lines.end())
        {
            cv::Point pt1((*it2)[0],(*it2)[1]);
            cv::Point pt2((*it2)[2],(*it2)[3]);
            cv::line( image, pt1, pt2, color, 2);
            ++it2;
        }
    }
};

用这个类实现图中线段的检测。

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    cv::Mat image = cv::imread("c:\\test.png");
    cv::Mat contours;
    cv::cvtColor(image, contours, cv::COLOR_BGR2GRAY);
    cv::bitwise_not(contours, contours);
    //cv::Canny(image, contours, 155, 350);
    LineFinder finder;
    // Set probabilistic Hough parameters
    finder.setLineLengthAndGap(100, 20);
    finder.setMinVote(80);
    // Detect lines and draw them
    std::vector<cv::Vec4i> lines = finder.findLines(contours);
    finder.drawDetectedLines(image, cv::Scalar(0, 0, 255));
    cv::namedWindow("Detected Lines with HoughP");
    cv::imshow("Detected Lines with HoughP",image);
    return a.exec();
}

opencv 直线以内全白 opencv找直线_OpenCV_04