OpenCV2编程手册笔记之 7.3使用霍夫变换检测直线

来源:互联网 发布:linux bz2 解压 编辑:程序博客网 时间:2024/05/16 14:24

    在检测边缘了解后,我们介绍一种用来检测直线的方法,那就是霍夫变换,这是一个检测直线的经典算法。

    霍夫变换的数学原理较为复杂,我们先在应用层面介绍霍夫变换:

  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 );
    我们可以看到,霍夫变换有四个初始化参数,那么我们就只需要确定前几个就可以了

    霍夫变换的输入图像是包含一组点的二值图像,我们一般通过Canny算子来得到这幅图像

  cv::Canny(image, contours, 125, 350);
    这里的contours就是HoughLines的输入图像

    函数的输出是一组向量,每个向量都储存着检测到的直线的信息,也就是一个n*2的数组

    第三个和第四个参数代表着搜索时的步进尺寸,我们一般定义:

    rho为1,theta为CV_PI / 180

    第五个参数为阈值,推荐使用大一些的数,比如超过200,因为如果小了opencv就直接报错了。

    但是这里需要根据图像进行自我调整,比如我们的例子中100就是比较合适的数据

    这样,我们就得到了目标图像上的所有直线,下一步就是把他们画在图像上


-----------------------下面是画直线--------------------------


    我们通过使用迭代器遍历向量Lines_Hough的方法来进行可视化直线操作

    由于返回的数据是一个n*2数组,我们采用如下的迭代器:

  vector<cv::Vec2f>::const_iterator it = lines_Hough.begin();
    通过it的自加一操作,就可以得到所有的向量数据

    在这里,我们需要了解一下霍夫变换中蕴含的数学原理,这样,我们才能正确的编写代码


---------------------原理-----------------------

   

    这是电脑中图像的坐标系

    A是目标直线

    B是目标直线的垂线

    rho是垂直距离

    theta是偏角,范围是0~2*PI

    在极坐标系下,所有的直线都可以表示为


    这样,我们就可以知道霍夫变换中得到的就是所有可能情况下的rho和theta

    依据这两个参数,我们就可以算出所有可能情况的直线


----------------------原理-------------------------


    在迭代器遍历时,我们将theta分为[0, PI/4]并[3*PI/4, PI]和[ PI/4,3*PI/4]两部分

    这样,在theta为[0, PI/4]并[3*PI/4, PI]时,直线和图像第一行的交点为


    直线和图像最后一行交点为


    这样,我们使用cv::line就可以做出直线


    对于theta在另外的取值范围内的情况,基本同理

   

    这种简单情况下的霍夫变换得到的结果为


    我们不难发现,这种结果产生了错误的直线检测,并且由于cv::line方法绘出的是直线,造成了直线的失真,因此,我们采取改进的概率霍夫变换来解决上述出现的问题。

   

    我们先来了解一下概率霍夫变换 HoughLineP以及他的数学原理:

   
    数学原理:

    概率霍夫变换将会检测图像上所有的点,并识别出经过每个点的所有直线,并标记这条直线,记为每条直线的投票数。如果一条直线被标记了多次,也就是这条直线的投票数超过我们规定的值,那么我们就认为这是一条可以选定的直线,这就是概率霍夫变换的作用原理。

    opencv函数实现:

  void HoughLinesP( InputArray image, OutputArray lines,                               double rho, double theta, int threshold,                               double minLineLength = 0, double maxLineGap = 0 );
    概率霍夫变换和普通霍夫变换的输入是一样的,都是由Canny算子得到的二值图像

    返回的是一个Vec4i类的向量,这是一个n*4的数组,数组的第一个和第二个数据是直线一个端点的x、y;第三个和第四个数据是直线另一个端点的x、y数据

    rho和theta的定义与普通霍夫变换一样

    threshold就是投票数阈值

    minLineLength是直线的最小长度

    maxLineGap是沿直线方向的最大缺口


    为了更好的使用概率霍夫变换,我们将这个方法封装到一个LineFinder类中

class LineFinder{private:    //原图    cv::Mat img;    //检测到的直线的端点    vector<cv::Vec4i>lines;    //投票累加器的分辨率    double deltaRho;    double deltaTheta;    //直线合法的最小投票数    int minVote;    //直线的最小长度    double minLength;    //沿直线的最大缺口    double maxGap;public:    //重定义    LineFinder()    {        deltaRho = 1;        deltaTheta = CV_PI / 180;        minVote = 10;        minLength = 0.0;        maxGap = 0.0;    }    //数据重定义    void setAccResolution(double dRho, double dTheta);    void setMinVote(int minv);    void setLineLengthAndGap(double length, double gap);    //寻找合法直线    std::vector<cv::Vec4i> findLines(cv::Mat& binary);    //画出直线    void drawDetectedLines(cv::Mat &image, cv::Scalar color = cv::Scalar(0, 255, 0));};
    这个类中初始化了所有需要的数值,并且定义了几种需要的方法

    在霍夫变换给出的结果中(Lines),我们就得到了直线段的所有数据,这样,我们就可以在原图中画出确定的直线。

   

    普通霍夫变换HoughLines:

int main(){cv::Mat image = cv::imread("F:\\Image\\road.jpg");if (!image.data){exit(1);}cv::Mat contours;cv::Canny(image, contours, 125, 350);vector<cv::Vec2f>lines_Hough;cv::HoughLines(contours, lines_Hough, 1, CV_PI / 180, 100);vector<cv::Vec2f>::const_iterator it = lines_Hough.begin();while (it != lines_Hough.end()){float rho = (*it)[0];float theta = (*it)[1];if (theta < CV_PI / 4. || theta > 3.*CV_PI / 4.){//直线和第一行的交点cv::Point pt1(rho / cos(theta), 0);//直线和最后一行的交点cv::Point pt2((rho - contours.rows*sin(theta)) / cos(theta), contours.rows);cv::line(image, pt1, pt2, cv::Scalar(255), 1);}else{//直线和第一列的交点cv::Point pt1(0, rho / sin(theta));//直线和最后一列交点cv::Point pt2(contours.cols, (rho - contours.cols*cos(theta)) / sin(theta));cv::line(image, pt1, pt2, cv::Scalar(255), 1);}std::cout << "line: (" << rho << "," << theta << ")\n";++it;}cv::imshow("11", image);cv::waitKey(0);    return 0;}


    概率霍夫变换:

LineFinder类#pragma once#include "stdafx.h"#include <opencv2\opencv.hpp>using namespace std;class LineFinder{private://原图cv::Mat img;//检测到的直线的端点vector<cv::Vec4i>lines;//投票累加器的分辨率double deltaRho;double deltaTheta;//直线合法的最小投票数int minVote;//直线的最小长度double minLength;//沿直线的最大缺口double maxGap;public://重定义LineFinder(){deltaRho = 1;deltaTheta = CV_PI / 180;minVote = 10;minLength = 0.0;maxGap = 0.0;}//数据重定义void setAccResolution(double dRho, double dTheta);void setMinVote(int minv);void setLineLengthAndGap(double length, double gap);//寻找合法直线std::vector<cv::Vec4i> findLines(cv::Mat& binary);//画出直线void drawDetectedLines(cv::Mat &image, cv::Scalar color = cv::Scalar(0, 255, 0));};

#include "stdafx.h"#include "LineFinder.h"void LineFinder::setAccResolution(double dRho, double dTheta){deltaRho = dRho;deltaTheta = dTheta;}void LineFinder::setMinVote(int minv){minVote = minv;}void LineFinder::setLineLengthAndGap(double length, double gap){minLength = length;maxGap = gap;}std::vector<cv::Vec4i> LineFinder::findLines(cv::Mat& binary){lines.clear();cv::HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);return lines;}void LineFinder::drawDetectedLines(cv::Mat &image, cv::Scalar color){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);++it2;}}

cv::Mat image = cv::imread("F:\\Image\\road.jpg");if (!image.data){exit(1);}cv::Mat contours;cv::Canny(image, contours, 125, 350);LineFinder finder;finder.setLineLengthAndGap(100, 20);finder.setMinVote(80);vector<cv::Vec4i>lines = finder.findLines(contours);finder.drawDetectedLines(image, cv::Scalar(255, 0, 0));cv::imshow("123", image);cv::waitKey(0);    return 0;


    这样,我们就得到了改进的概率霍夫变换图像



阅读全文
0 0