zoukankan      html  css  js  c++  java
  • 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]的角度,可是如今,这个角度就直接被我们取出来了,速度也有非常大的提升,非常不错的算法。
  • 相关阅读:
    【乱侃】How do they look them ?
    【softeware】Messy code,some bug of Youdao notebook in EN win7
    【随谈】designing the login page of our project
    【web】Ad in security code, making good use of resource
    SQL数据库内存设置篇
    关系数据库的查询优化策略
    利用SQL未公开的存储过程实现分页
    sql语句总结
    sql中使用cmd命令注销登录用户
    SQLServer 分页存储过程
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/3788388.html
Copyright © 2011-2022 走看看