1. 程式人生 > >HOUGH變換檢測線段

HOUGH變換檢測線段



利用 Hough 變換在二值影象中找到直線

一.函式介紹
CvSeq* cvHoughLines2( CvArr* image, void* line_storage, int method,double rho, double theta, int threshold,double param1=0, double param2=0 );

其中
(1)image
輸入 8-位元、單通道 (二值) 影象,當用CV_HOUGH_PROBABILISTIC方法檢測的時候其內容會被函式改變
(2)line_storage
檢測到的線段儲存倉. 可以是記憶體儲存倉 (此種情況下,一個線段序列在儲存倉中被建立,並且由函式返回),或者是包含線段引數的特殊型別(見下面)的具有單行/單列的矩陣(CvMat*)。矩陣頭為函式所修改,使得它的cols/rows 將包含一組檢測到的線段。如果 line_storage 是矩陣,而實際線段的數目超過矩陣尺寸,那麼最大可能數目的線段被返回(對於標準hough變換,線段按照長度降序輸出).
(3)method
Hough 變換變數,是下面變數的其中之一:
CV_HOUGH_STANDARD - 傳統或標準 Hough 變換. 每一個線段由兩個浮點數 (ρ, θ) 表示,其中 ρ 是直線與原點 (0,0) 之間的距離,θ 線段與 x-軸之間的夾角。因此,矩陣型別必須是 CV_32FC2 type.
CV_HOUGH_PROBABILISTIC - 概率 Hough 變換(如果影象包含一些長的線性分割,則效率更高). 它返回線段分割而不是整個線段。每個分割用起點和終點來表示,所以矩陣(或建立的序列)型別是 CV_32SC4.
CV_HOUGH_MULTI_SCALE - 傳統 Hough 變換的多尺度變種。線段的編碼方式與 CV_HOUGH_STANDARD 的一致。
(4)theta
弧度測量的角度精度
(5)threshold
閾值引數。如果相應的累計值大於 threshold, 則函式返回的這個線段.

(6)param1
第一個方法相關的引數:
對傳統 Hough 變換,不使用(0).
對概率 Hough 變換,它是最小線段長度.
對多尺度 Hough 變換,它是距離精度 rho 的分母 (大致的距離精度是 rho 而精確的應該是 rho / param1 ).
(7)param2
第二個方法相關引數:
對傳統 Hough 變換,不使用 (0).
對概率 Hough 變換,這個引數表示在同一條直線上進行碎線段連線的最大間隔值(gap), 即當同一條直線上的兩條碎線段之間的間隔小於param2時,將其合二為一。
對多尺度 Hough 變換,它是角度精度 theta 的分母 (大致的角度精度是 theta 而精確的角度應該是 theta /param2).

二.檢測原理

(1)直線表示方式
對於平面中的一條直線,在笛卡爾座標系中,常見的有點斜式,兩點式兩種表示方法。然而在hough變換中,考慮的是另外一種表示方式:使用(r,theta)來表示一條直線。其中r為該直線到原點的距離,theta為該直線的垂線與x軸的夾角。如下圖所示。

hough變換是如何檢測出直線和圓的? - 鈺央 - 計算機視覺·影象處理 (2)識別各點是否在某條直線上
使用hough變換來檢測直線的思想就是:為每一個點假設n個方向的直線,通常n=180,此時檢測的直線的角度精度為1°,分別計算這n條直線的(r,theta)座標,得到n個座標點。如果要判斷的點共有N個,最終得到的(r,theta)座標有N*n個。有關這N*n個(r,theta)座標,其中theta是離散的角度,共有180個取值。
最重要的地方來了,如果多個點在一條直線上,那麼必有這多個點在theta=某個值theta_i時,這多個點的r近似相等於r_i。也就是說這多個點都在直線(r_i,theta_i)上。 三.程式舉例
本文是在《OpenCV教程基礎篇》的基礎上進行撰寫的。 (1)首先利用該書上的例題5-14的原始碼舉例(該程式中傳統型hough畫直線的程式碼有錯誤)
#include <cv.h>
#include <highgui.h>
#include <math.h>

int main(int argc, char** argv)
{
    argc=2;
	argv[1]="4.jpg";
	
	IplImage* src;
    if( argc == 2 && (src=cvLoadImage(argv[1], 0))!= 0)
    {
        IplImage* dst = cvCreateImage( cvGetSize(src), 8, 1 );
        IplImage* color_dst = cvCreateImage( cvGetSize(src), 8, 3 );
        CvMemStorage* storage = cvCreateMemStorage(0);//記憶體儲存器是一個可用來儲存諸如序列,輪廓,圖形,子劃分等動態增長資料結構的底層結構。
        CvSeq* lines = 0;//可動態增長元素序列
        int i;
        //cvCanny( src, dst, 50, 200, 3 );//邊緣檢測,變換為二值影象
		cvThreshold( src, dst, 200, 255, CV_THRESH_BINARY_INV );//變換為二值影象
        cvCvtColor( dst, color_dst, CV_GRAY2BGR );
		cvNamedWindow( "二值影象", 1 );
        cvShowImage( "二值影象", color_dst );

#if 1
		//輸入8位單通道二值影象,storage線段儲存倉,CV_HOUGH_PROBABILISTIC採用概率HOUGH變換,
		//1表示距離的畫素精度,CV_PI/180表示角度的弧度精度,80線段的累計返回閾值,
		//後兩個引數是在概率HOUGH變換和多尺度HOUGH變換時才用到,傳統(標準)HOUGH不使用,均設為0
        lines = cvHoughLines2( dst, storage, CV_HOUGH_STANDARD, 1, CV_PI/180, 100, 0, 0 );
		//利用傳統的hough變換時,得到的每個線段不是線段的起點和終點的座標,而是線段所在直線與原點的距離和X軸的夾角
		//故所得到的線段都是貫穿整個影象,顯示這些線段時要計算出它的起點和終點。
        for( i = 0; i < lines->total; i++ )
        {
            float* line = (float*)cvGetSeqElem(lines,i);////返回索引所指定的元素指標
            float rho = line[0];//線段所在直線與原點的距離
            float theta = line[1];//線段所在直線的垂線與X軸的夾角
            CvPoint pt1, pt2;
            double a = cos(theta), b = sin(theta);
			//當直線與X軸平行時
            if( fabs(a) < 0.001 )
            {
                pt1.x = pt2.x = cvRound(rho);//將直線與原點的距離轉換為整數
                pt1.y = 0;
                pt2.y = color_dst->height;
            }
			//當直線與Y軸平行時
            else if( fabs(b) < 0.001 )
            {
                pt1.y = pt2.y = cvRound(rho);
                pt1.x = 0;
                pt2.x = color_dst->width;
            }
            else
            {
                pt1.x = 0;
                pt1.y = cvRound(rho/b);//直角三角形中,rho/y1=sin(theta)
                pt2.x = cvRound(rho/a);//直角三角形中,rho/x2=cos(theta)
                pt2.y = 0;
            }
            cvLine( color_dst, pt1, pt2, CV_RGB(255,0,0), 1, 8 );
        }
#else
		//輸入8位單通道二值影象,storage線段儲存倉,CV_HOUGH_PROBABILISTIC採用概率HOUGH變換,
		//1表示距離的畫素精度,CV_PI/180表示角度的弧度精度,80線段的累計返回閾值,
		//30是最小線段長度,10表示同一條線段上碎線段連線的最大間隔值,當限度案件的間隔小於它時將這兩個線段合二為一
        lines = cvHoughLines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 80, 30, 10 );
        for( i = 0; i < lines->total; i++ )
        {
            CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);//返回索引所指定的元素指標
            cvLine( color_dst, line[0], line[1], CV_RGB(255,0,0), 3, 8 );//繪製連線兩個點的線段,line[0]、line[1]是線段的兩個端點
        }
#endif
        cvNamedWindow( "Source", 1 );
        cvShowImage( "Source", src );

        cvNamedWindow( "Hough", 1 );
        cvShowImage( "Hough", color_dst );

        cvWaitKey(0);
    }
	return 0;
}
程式中採用傳統型HOUGH變化進行檢測,其結果如下: 原圖: 二值影象(此處未採用canny,而是直接進行了固定閾值二值化): 檢測效果: 通過上圖可以看出,檢測結果不正確。原因在於:在畫出這些線段時,1.錯誤理解夾角的意義,theta為該直線的垂線與X軸的夾角,而不是該直線與X軸的夾角;2.未考慮線段所在直線影象的位置關係,導致畫直線所用到的兩個端點計算錯誤。 (2)下面是我在例題基礎上修改後的例子
#include <cv.h>
#include <highgui.h>
#include <math.h>
int main(int argc, char** argv)
{
    argc=2;
	//argv[1]="building.jpg";
	argv[1]="4.jpg";
	IplImage* src;
    if( argc == 2 && (src=cvLoadImage(argv[1], 0))!= 0)
    {
        IplImage* dst = cvCreateImage( cvGetSize(src), 8, 1 );
        IplImage* color_dst = cvCreateImage( cvGetSize(src), 8, 3 );
        CvMemStorage* storage = cvCreateMemStorage(0);//記憶體儲存器是一個可用來儲存諸如序列,輪廓,圖形,子劃分等動態增長資料結構的底層結構。
        CvSeq* lines = 0;//可動態增長元素序列
        int i;
        //cvCanny( src, dst, 50, 200, 3 );//邊緣檢測,變換為二值影象
		cvThreshold( src, dst, 200, 255, CV_THRESH_BINARY_INV );
        cvCvtColor( dst, color_dst, CV_GRAY2BGR );
		cvNamedWindow( "二值影象", 1 );
        cvShowImage( "二值影象", color_dst );
#if 1
		//輸入8位單通道二值影象,storage線段儲存倉,CV_HOUGH_PROBABILISTIC採用概率HOUGH變換,
		//1表示距離的畫素精度,CV_PI/180表示角度的弧度精度,80線段的累計返回閾值,
		//後兩個引數是在概率HOUGH變換和多尺度HOUGH變換時才用到,傳統(標準)HOUGH不使用,均設為0
        lines = cvHoughLines2( dst, storage, CV_HOUGH_STANDARD, 1, CV_PI/180, 100, 0, 0 );
		//利用傳統的hough變換時,得到的每個線段不是線段的起點和終點的座標,而是線段所在直線與原點的距離和Y軸的夾角
		//故所得到的線段都是貫穿整個影象,顯示這些線段時要計算出它的起點和終點。

        for( i = 0; i < lines->total; i++ )
        {
            float* line = (float*)cvGetSeqElem(lines,i);////返回索引所指定的元素指標
            float rho = line[0];//線段所在直線與原點的距離
            float theta = line[1];//線段所在直線的垂線與x軸的夾角與X軸的夾角
			double a = cos(theta), b = sin(theta);
			//printf("theta=%f\n",theta);
			//printf("rho=%f\n",rho);
            CvPoint pt1, pt2;

            if( fabs(b) < 0.001 )
            {
                pt1.x = pt2.x = cvRound(rho);//將直線與原點的距離轉換為整數
                pt1.y = 0;
                pt2.y = color_dst->height;
            }
            else if( fabs(a) < 0.001 )
            {
                pt1.y = pt2.y = cvRound(rho);
                pt1.x = 0;
                pt2.x = color_dst->width;
            }
            else if(theta>=CV_PI/2&&rho>=0)
            {
                pt1.x = 0;   
                pt1.y = cvRound(rho/b);                
                pt2.x = color_dst->width;
                pt2.y = cvRound(pt1.y+color_dst->width*fabs(a)/fabs(b));
            }
			
			else if(theta>=CV_PI/2&&rho<0)
            {
                pt1.x = cvRound(rho/a);   
                pt1.y = 0;                
                pt2.x = cvRound(pt1.x+color_dst->height*fabs(b)/fabs(a));
                pt2.y = color_dst->height;
            }
			
            else if(theta<CV_PI/2&&rho>=0)
            {
                pt1.x = cvRound(rho/a);   
                pt1.y = 0;                
                pt2.x = 0;
				pt2.y = cvRound(rho/b);

            }
			
            cvLine( color_dst, pt1, pt2, CV_RGB(255,0,0), 2, 8 );
        }

#else
		//輸入8位單通道二值影象,storage線段儲存倉,CV_HOUGH_PROBABILISTIC採用概率HOUGH變換,
		//1表示距離的畫素精度,CV_PI/180表示角度的弧度精度,80線段的累計返回閾值,
		//30是最小線段長度,10表示同一條線段上碎線段連線的最大間隔值,當限度案件的間隔小於它時將這兩個線段合二為一
        lines = cvHoughLines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 80, 30, 10 );
        for( i = 0; i < lines->total; i++ )
        {
            CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);//返回索引所指定的元素指標
            cvLine( color_dst, line[0], line[1], CV_RGB(255,0,0), 2, 8 );//繪製連線兩個點的線段,line[0]、line[1]是線段的兩個端點
        }
#endif
        //cvNamedWindow( "Source", 1 );
        //cvShowImage( "Source", src );

        cvNamedWindow( "Hough", 1 );
        cvShowImage( "Hough", color_dst );

        cvWaitKey(0);
    }
	return 0;
}
檢測結果如下: 二值影象: 檢測效果: (3)下面是不利用opencv自帶的cvHoughLines2函式,而是自己編寫函式進行直線檢測的例子
#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((float)(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,dst;
    //argv[1]="building.jpg";
	argv[1]="4.jpg";
  	src = imread( argv[1] );
  	cvtColor( src, src_gray, CV_BGR2GRAY );
	
  	blur( src_gray, src_gray, Size(3,3) );
  	Canny( src_gray, edge, 50, 200);
	//threshold( src_gray, edge, 250, 255, CV_THRESH_BINARY_INV );
	cvtColor( edge, dst, CV_GRAY2BGR );
    vector<struct line> lines = houghLine(edge, 100);
    drawLines(dst, lines);
  
  	namedWindow("result", 1); 
    imshow("result", dst);
    waitKey(0);
    
  	return 0;
}


檢測效果: