• OpenCV2马拉松第22圈——Hough变换直线检測原理与实现


    计算机视觉讨论群162501053

    收入囊中
    • Hough变换
    • 概率Hough变换
    • 自己实现Hough变换直线检測

    葵花宝典
    先看一下我实现的效果图


    以下,我们进入Hough变换的原理解说。

    看上图,我们知道,经过一点(x0,y0)的直线能够表示成y0 = mox + b0
    反过来看方程,b = –x0m + y0 ,于是我们从原来的坐标系转移到了Hough空间,m是横坐标,b是纵坐标


    刚才提到,经过(x0,y0)的直线具有的特征是b = –x0m + y0,在Hough空间下也是一条直线,
    那么经过(x1,y1)的直线具有的特征是b = -x1m + y1,在Hough空间下是还有一条直线。
    两条直线的相交点的(m,b)就是经过(x0,y0)(x1,y1)的直线,这个应该能够理解吧。
    于是就有了一个简单的想法,对于每个点,在Hough空间中都画出一条直线,对于每条直线经过的点,都填充在例如以下的 Hough空间中,看哪交点多,就能确定。我们用一个二维数组表示Hough空间,例如以下。最后就变成数哪些格子的值比較高。

    可是,用m和b有局限性。由于m是能够取到无穷大的,所以这个特征仅仅在理论上可行...实际上我们不可能申请一个无限大的二维数组。

    自然而然,我们想到了极坐标,在极坐标下,就没有这个限制了。
    Line variables在极坐标下,我们的直线能够写成:y = left ( -dfrac{cos 	heta}{sin 	heta} 
ight ) x + left ( dfrac{r}{sin 	heta} 
ight )
    也就是:r = x cos 	heta + y sin 	heta
    经过点(x0,y0)的直线:r_{	heta} = x_{0} cdot cos 	heta  + y_{0} cdot sin 	heta
    当x0 = 8, y0 = 6,我们有这种图
    Polar plot of a the family of lines of a point我们在以下仅仅考虑r > 0 而且 0< 	heta < 2 pi.

    我们还有2个点,x_{1} = 9y_{1} = 4       x_{2} = 12y_{2} = 3,就能够绘制出以下的图形
    Polar plot of the family of lines for three points这3条直线相交于(0.925, 9.6), 也就是说 (	heta, r) = (0.925, 9.6) 是这3个点 (x_{0}, y_{0})(x_{1}, y_{1})  (x_{2}, y_{2})共同经过的直线!

    因此,我们有了算法雏形                                       

    • 初始化H( Hough空间的二维数组)全为0
    • 遍历图片的 (x,y) 
    For θ = 0 to 360
          ρ = xcos θ + y sin θ
          H(θ,
    ρ) = H(θ,ρ) + 1
        end
    end
    • Find the value(s) of (θ, ρ)where H(θ, ρ)is a local maximum
    • Thedetected line in the image is given by  ρ = xcos θ + y sin θ

    看以下的图片,当都一条直线时,Hough空间的某个区域就会非常亮,取局部极大值就能够



    一张更复杂的图片



    初识API
    C++: void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
     
    • image – 8-bit,单通道二值图(有可能被函数改变)
    • lines – 输出vector,是 (
ho, 	heta) 的vector. 
ho 是距离原点距离(0,0) (图片左上角[0,0]处). 	heta  ( 0 sim 	extrm{vertical line}, pi/2 sim 	extrm{horizontal line} ).
    • rho – 累加器的半径resolution
    • theta – 累加器的theta resulution
    • threshold – 返回Hough空间中 ( >	exttt{threshold} ).的点
    • srn – For the multi-scale Hough transform, it is a divisor for the distance resolution rho . The coarse accumulator distance resolution is rho and the accurate accumulator resolution is rho/srn . If both srn=0 and stn=0 , the classical Hough transform is used. Otherwise, both these parameters should be positive.
    • stn – For the multi-scale Hough transform, it is a divisor for the distance resolution theta.


    C++: void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, doublemaxLineGap=0 )
     
    • image –8-bit,单通道二值图(有可能被函数改变)
    • lines – 输出向量是4-element vector (x_1, y_1, x_2, y_2) ,  (x_1,y_1) 是起点 (x_2, y_2) 是终点
    • rho – Distance resolution of the accumulator in pixels.
    • theta – Angle resolution of the accumulator in radians.
    • threshold – Accumulator threshold parameter. Only those lines are returned that get enough votes ( >	exttt{threshold} ).
    • minLineLength – 最小长度,小于这个值不被觉得是线段
    • maxLineGap – 两个点之间最大的gap,当小于这个值两个点就被觉得是同一线段的点


    荷枪实弹
    还是先贴出官方sample
    #include <cv.h>
    #include <highgui.h>
    #include <math.h>
    
    using namespace cv;
    
    int main(int argc, char** argv)
    {
        Mat src, dst, color_dst;
        if( argc != 2 || !(src=imread(argv[1], 0)).data)
            return -1;
    
        Canny( src, dst, 50, 200, 3 );
        cvtColor( dst, color_dst, CV_GRAY2BGR );
    
    #if 0
        vector<Vec2f> lines;
        HoughLines( dst, lines, 1, CV_PI/180, 100 );
    
        for( size_t i = 0; i < lines.size(); i++ )
        {
            float rho = lines[i][0];
            float theta = lines[i][1];
            double a = cos(theta), b = sin(theta);
            double x0 = a*rho, y0 = b*rho;
            Point pt1(cvRound(x0 + 1000*(-b)),
                      cvRound(y0 + 1000*(a)));
            Point pt2(cvRound(x0 - 1000*(-b)),
                      cvRound(y0 - 1000*(a)));
            line( color_dst, pt1, pt2, Scalar(0,0,255), 3, 8 );
        }
    #else
        vector<Vec4i> lines;
        HoughLinesP( dst, lines, 1, CV_PI/180, 80, 30, 10 );
        for( size_t i = 0; i < lines.size(); i++ )
        {
            line( color_dst, Point(lines[i][0], lines[i][1]),
                Point(lines[i][2], lines[i][3]), Scalar(0,0,255), 3, 8 );
        }
    #endif
        namedWindow( "Source", 1 );
        imshow( "Source", src );
    
        namedWindow( "Detected Lines", 1 );
        imshow( "Detected Lines", color_dst );
    
        waitKey(0);
        return 0;
    }

    假如我们想检測直线,就能够用第一个API,由于这个API返回的是直线的两个參数
    假设想检測图片中的线段,就用第二个API,由于这个 API返回的是起点和终点

    以下看下我自己的实现,首先是弧度及结构体的定义
    const double pi = 3.1415926f;
    const double RADIAN = 180.0/pi; 
    
    struct line
    {
    	int theta;
    	int r;
    };

    接下来是变换到Hough空间,填充二维数组
    vector<struct line> houghLine(Mat &img, int threshold)
    {
    	vector<struct line> lines;
    	int diagonal = floor(sqrt(img.rows*img.rows + img.cols*img.cols));
    	vector< vector<int> >p(360 ,vector<int>(diagonal));
    	
    	for( int j = 0; j < img.rows ; j++ ) { 
    		for( int i = 0; i < img.cols; i++ ) {
                if( img.at<unsigned char>(j,i) > 0)
    			{
    				for(int theta = 0;theta < 360;theta++)
    				{
    					int r = floor(i*cos(theta/RADIAN) + j*sin(theta/RADIAN));
    					if(r < 0)
    						continue;
    					p[theta][r]++;
    				}
    			}
    		}
    	}
    
    	//get local maximum
    	for( int theta = 0;theta < 360;theta++)
    	{
    		for( int r = 0;r < diagonal;r++)
    		{
    			int thetaLeft = max(0,theta-1);
    			int thetaRight = min(359,theta+1);
    			int rLeft = max(0,r-1);
    			int rRight = min(diagonal-1,r+1);
    			int tmp = p[theta][r];
    			if( tmp > threshold 
    				&& tmp > p[thetaLeft][rLeft] && tmp > p[thetaLeft][r] && tmp > p[thetaLeft][rRight]
    				&& tmp > p[theta][rLeft] && tmp > p[theta][rRight]
    				&& tmp > p[thetaRight][rLeft] && tmp > p[thetaRight][r] && tmp > p[thetaRight][rRight])
    			{
    				struct line newline;
    				newline.theta = theta;
    				newline.r = r;
    				lines.push_back(newline);
    			}
    		}
    	}
    
    	return lines;
    }
    

    最后是画直线的函数
    void drawLines(Mat &img, const vector<struct line> &lines)
    {
    	for(int i = 0;i < lines.size();i++)
    	{
    		vector<Point> points;
    		int theta = lines[i].theta;
    		int r = lines[i].r;
    
    		double ct = cos(theta/RADIAN);
    		double st = sin(theta/RADIAN);
    		
    		//r = x*ct + y*st
    		//left
    		int y = int(r/st);
    		if(y >= 0 && y < img.rows){
    			Point p(0, y);
    			points.push_back(p);
    		}
    		//right
    		y = int((r-ct*(img.cols-1))/st);
    		if(y >= 0 && y < img.rows){
    			Point p(img.cols-1, y);
    			points.push_back(p);
    		}
    		//top
    		int x = int(r/ct);
    		if(x >= 0 && x < img.cols){
    			Point p(x, 0);
    			points.push_back(p);
    		}
    		//down
    		x = int((r-st*(img.rows-1))/ct);
    		if(x >= 0 && x < img.cols){
    			Point p(x, img.rows-1);
    			points.push_back(p);
    		}
    		
    		cv::line( img, points[0], points[1], Scalar(0,0,255), 1, CV_AA);
    	}
    }

    完整代码
    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <iostream>
    #include <vector>
    #include <cmath>
    using namespace cv;
    using namespace std;
    
    const double pi = 3.1415926f;
    const double RADIAN = 180.0/pi; 
    
    struct line
    {
    	int theta;
    	int r;
    };
    
    /*
     * r = xcos(theta) + ysin(theta)
     */
    vector<struct line> houghLine(Mat &img, int threshold)
    {
    	vector<struct line> lines;
    	int diagonal = floor(sqrt(img.rows*img.rows + img.cols*img.cols));
    	vector< vector<int> >p(360 ,vector<int>(diagonal));
    	
    	for( int j = 0; j < img.rows ; j++ ) { 
    		for( int i = 0; i < img.cols; i++ ) {
                if( img.at<unsigned char>(j,i) > 0)
    			{
    				for(int theta = 0;theta < 360;theta++)
    				{
    					int r = floor(i*cos(theta/RADIAN) + j*sin(theta/RADIAN));
    					if(r < 0)
    						continue;
    					p[theta][r]++;
    				}
    			}
    		}
    	}
    
    	//get local maximum
    	for( int theta = 0;theta < 360;theta++)
    	{
    		for( int r = 0;r < diagonal;r++)
    		{
    			int thetaLeft = max(0,theta-1);
    			int thetaRight = min(359,theta+1);
    			int rLeft = max(0,r-1);
    			int rRight = min(diagonal-1,r+1);
    			int tmp = p[theta][r];
    			if( tmp > threshold 
    				&& tmp > p[thetaLeft][rLeft] && tmp > p[thetaLeft][r] && tmp > p[thetaLeft][rRight]
    				&& tmp > p[theta][rLeft] && tmp > p[theta][rRight]
    				&& tmp > p[thetaRight][rLeft] && tmp > p[thetaRight][r] && tmp > p[thetaRight][rRight])
    			{
    				struct line newline;
    				newline.theta = theta;
    				newline.r = r;
    				lines.push_back(newline);
    			}
    		}
    	}
    
    	return lines;
    }
    
    void drawLines(Mat &img, const vector<struct line> &lines)
    {
    	for(int i = 0;i < lines.size();i++)
    	{
    		vector<Point> points;
    		int theta = lines[i].theta;
    		int r = lines[i].r;
    
    		double ct = cos(theta/RADIAN);
    		double st = sin(theta/RADIAN);
    		
    		//r = x*ct + y*st
    		//left
    		int y = int(r/st);
    		if(y >= 0 && y < img.rows){
    			Point p(0, y);
    			points.push_back(p);
    		}
    		//right
    		y = int((r-ct*(img.cols-1))/st);
    		if(y >= 0 && y < img.rows){
    			Point p(img.cols-1, y);
    			points.push_back(p);
    		}
    		//top
    		int x = int(r/ct);
    		if(x >= 0 && x < img.cols){
    			Point p(x, 0);
    			points.push_back(p);
    		}
    		//down
    		x = int((r-st*(img.rows-1))/ct);
    		if(x >= 0 && x < img.cols){
    			Point p(x, img.rows-1);
    			points.push_back(p);
    		}
    		
    		cv::line( img, points[0], points[1], Scalar(0,0,255), 1, CV_AA);
    	}
    }
    
    int main( int, char** argv )
    {
    	Mat src,src_gray,edge;
      	src = imread( argv[1] );
      	cvtColor( src, src_gray, CV_BGR2GRAY );
    
      	blur( src_gray, src_gray, Size(3,3) );
      	Canny( src_gray, edge, 50, 200);
    	vector<struct line> lines = houghLine(edge, 90);
    	drawLines(src, lines);
    	
      	namedWindow("result", 1); 
        imshow("result", src);
        waitKey();
        
      	return 0;
    }



    举一反三

    概率Hough变换在基本算法上添加了比較少的改动。之前是逐行扫描,如今则是随机选点。每当累加器的一个条目达到指定的最小值,就沿这条直线的方向扫描,并且将通过它的全部点删除(即使它们还没有參与投票)。并且该扫描还确定被接受的线段的长度。为此,该算法定义了两个附加參数。一个是被接受线段的最小长度,而还有一个是被同意以形成连续的段的最大距离。这个附加步骤添加了算法的复杂性,可是复杂性带来的效率损失被较少的点会參与投票过程补偿。

    我们再来看一看其它形状在二维 Hough空间的样子



    我们再考虑一下噪声的影响


    噪声使得峰值定位非常难

    噪声程度越厉害,结果越不准确

    解决噪声问题的算法:

    这个算法也不复杂,对于一个点,我们曾经要遍历[0,360]的角度,可是如今,这个角度就直接被我们取出来了,速度也有非常大的提升,非常不错的算法。
  • 相关阅读:
    Linux Sever简单笔记(第十二堂课)之linux下的系统故障分析和排查
    Linux Sever简单笔记(第十一堂课)之linux下的备份和恢复及rsync还有inotify和dump以及restore
    Linux Sever简单笔记(第十堂课)之linux下的任务计划及相关的命令
    ubuntu18.04设置apt源(国内)
    shell简单常用脚本实例
    装完ubuntu系统之后,不能ssh正常连接
    mysql主从复制以及读写分离
    复习计划
    linux下dhcp的安装及配置
    日常问题
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/3788388.html
Copyright © 2020-2023  润新知