學習OpenCV範例(十五)——霍夫變換

本次範例通過霍夫變換檢測直線和圓,講解霍夫線變換和霍夫圓變換的原理,代碼實現,和演示結果,使用霍夫線變換之前, 首先要對圖像進行邊緣檢測的處理,也即霍夫線變換的直接輸入只能是邊緣二值圖像。而霍夫圓變換則只要輸入灰度圖像即可,因爲在霍夫圓變換的過程中已經用到了canny邊緣檢測。

1、原理

霍夫線變換:

  1. 衆所周知, 一條直線在圖像二維空間可由兩個變量表示. 例如:

    1. 在 笛卡爾座標系: 可由參數: (m,b) 斜率和截距表示.
    2. 在 極座標系: 可由參數: (r,\theta) 極徑和極角表示
    Line variables

    對於霍夫變換, 我們將用 極座標系 來表示直線. 因此, 直線的表達式可爲:

    y = \left ( -\dfrac{\cos \theta}{\sin \theta} \right ) x + \left ( \dfrac{r}{\sin \theta} \right )

    化簡得: r = x \cos \theta + y \sin \theta

  2. 一般來說對於點 (x_{0}, y_{0}), 我們可以將通過這個點的一族直線統一定義爲:

    r_{\theta} = x_{0} \cdot \cos \theta  + y_{0} \cdot \sin \theta

    這就意味着每一對 (r_{\theta},\theta) 代表一條通過點 (x_{0}, y_{0}) 的直線.

  3. 如果對於一個給定點 (x_{0}, y_{0}) 我們在極座標對極徑極角平面繪出所有通過它的直線, 將得到一條正弦曲線. 例如, 對於給定點 x_{0} = 8 and y_{0} = 6我們可以繪出下圖 (在平面 \theta - r):

    Polar plot of a the family of lines of a point

    只繪出滿足下列條件的點 r > 0 and 0< \theta < 2 \pi.

  4. 我們可以對圖像中所有的點進行上述操作. 如果兩個不同點進行上述操作後得到的曲線在平面 \theta - r 相交, 這就意味着它們通過同一條直線. 例如, 接上面的例子我們繼續對點: x_{1} = 9y_{1} = 4 和點 x_{2} = 12y_{2} = 3 繪圖, 得到下圖:

    Polar plot of the family of lines for three points

    這三條曲線在 \theta - r 平面相交於點 (0.925, 9.6), 座標表示的是參數對 (\theta, r) 或者是說點 (x_{0}, y_{0}), 點 (x_{1}, y_{1}) 和點 (x_{2}, y_{2}) 組成的平面內的的直線.

  5. 那麼以上的材料要說明什麼呢? 這意味着一般來說, 一條直線能夠通過在平面 \theta - r 尋找交於一點的曲線數量來 檢測. 越多曲線交於一點也就意味着這個交點表示的直線由更多的點組成. 一般來說我們可以通過設置直線上點的 閾值 來定義多少條曲線交於一點我們才認爲 檢測 到了一條直線.

  6. 這就是霍夫線變換要做的. 它追蹤圖像中每個點對應曲線間的交點. 如果交於一點的曲線的數量超過了 閾值, 那麼可以認爲這個交點所代表的參數對 (\theta, r_{\theta}) 在原圖像中爲一條直線. 

  標準霍夫線變換和統計概率霍夫線變換
  OpenCV實現了以下兩種霍夫線變換:
  1. 標準霍夫線變換
  • 原理在上面的部分已經說明了. 它能給我們提供一組參數對 (\theta, r_{\theta}) 的集合來表示檢測到的直線
  • 在OpenCV 中通過函數 HoughLines 來實現
  1. 統計概率霍夫線變換
  • 這是執行起來效率更高的霍夫線變換. 它輸出檢測到的直線的端點 (x_{0}, y_{0}, x_{1}, y_{1})
  • 在OpenCV 中它通過函數 HoughLinesP 來實現

霍夫圓變換:

霍夫圓變換可以根據霍夫線變換來實現

通過極座標來表示圓(a,b)表示圓心,R表示半徑,則圓表示爲:

x = a + Rcosθ

y = b + Rsinθ

θ的值爲0-360

一開始我們假設R是已知的,那麼我們就可以把x,y空間的公式變換爲關於a、b空間的公式:

a = x1 – Rcosθ

b = y1 – Rsinθ

x1、y1爲x、y空間中的每一個非零像素點,如此一來,後面的變換就可以跟霍夫線變換一樣操作了。

具體操作步驟爲:

①、讀取一副圖像

②、檢測邊緣,產生一副二值圖像

③、對於每個非零像素轉換到ab空間中

④、對於每個ab空間中的點,進行累加
⑤、提取數量最多的點作爲圓點
在上面提到R是假設已知的,那麼當R是未知的情況怎麼辦?其實很簡單,就是將R設置爲1、2、3……這樣子已知下去,慢慢試,再對R和圓心數量進行閾值就可以了。

2、代碼實現

#include "stdafx.h"

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>

using namespace cv;
using namespace std;

Mat src, dst, cdst;
const char* filename = "hough.png";
const char *winname="hough";
const char *trackbarname="1.houghlines\n2.houghlinep\n3.houghcircle";
int savevalue=50,houghtype;
const int maxthreshold=150;

void help()
{
	cout << "\nThis program demonstrates line finding with the Hough transform.\n"
		"Usage:\n"
		"./houghlines <image_name>, Default is pic1.jpg\n" << endl;
}
void choisehoughlines()
{
 
	vector<Vec2f> lines;
	Canny(src, dst,50, 200, 3);
	cvtColor(dst, cdst, CV_GRAY2BGR);
	HoughLines(dst, lines, 1, CV_PI/180, savevalue+10, 0, 0 );

	for( size_t i = 0; i < lines.size(); i++ )
	{
		float rho = lines[i][0], theta = lines[i][1];
		Point pt1, pt2;
		double a = cos(theta), b = sin(theta);
		double x0 = a*rho, y0 = b*rho;
		pt1.x = cvRound(x0 + 1000*(-b));
		pt1.y = cvRound(y0 + 1000*(a));
		pt2.x = cvRound(x0 - 1000*(-b));
		pt2.y = cvRound(y0 - 1000*(a));
		line( cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA);
	}
	imshow(winname, cdst); 
}
void choisehoughlinep()
{
	vector<Vec4i> lines;
	Canny(src, dst,50, 200, 3);
	cvtColor(dst, cdst, CV_GRAY2BGR);
	HoughLinesP(dst, lines, 1, CV_PI/180, savevalue+10, savevalue+10, 10 );
	for( size_t i = 0; i < lines.size(); i++ )
	{
		Vec4i l = lines[i];
		line( cdst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0,0,255), 3, CV_AA);
	}
	imshow(winname, cdst);

}
void choisehoughcircle()
{
	vector<Vec3f> circles;
	cvtColor(src, cdst, CV_GRAY2BGR);
	/// Apply the Hough Transform to find the circles
	HoughCircles( src, circles, CV_HOUGH_GRADIENT, 1, dst.rows/10, 200, savevalue+10, 0, 0 );
	/// Draw the circles detected
	printf("%d",circles.size());
	for( size_t i = 0; i < circles.size(); i++ )
	{
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		int radius = cvRound(circles[i][2]);
		// circle center
		circle( cdst, center, 3, Scalar(0,255,0), -1, 8, 0 );
		// circle outline
		circle( cdst, center, radius, Scalar(0,0,255), 3, 8, 0 );
	}
		imshow(winname, cdst);
}
void choice(int,void *)
{
	switch (houghtype)
	{
	case 0:choisehoughlines();break;
	case 1:choisehoughlinep();break;
	case 2:choisehoughcircle();break;
	}
}
int main(int argc, char** argv)
{

    src = imread(filename, 0);
	if(src.empty())
	{
		help();
		cout << "can not open " << filename << endl;
		return -1;
	}
	namedWindow(winname);
	createTrackbar(trackbarname,winname,&houghtype,3,choice);
	createTrackbar("thresholdvalue",winname,&savevalue,maxthreshold,choice);
	imshow("source", src);
	choice(0,0);
	waitKey();

	return 0;
}
3、運行結果


                         圖1、原圖
                                圖2、houghlines
                              圖3、houghlinep
                              圖4、houghcircle

4、總結

霍夫線變需要對圖片線進行邊緣提取,圖像二值化處理,而霍夫圓變換則不用,只需輸入灰度圖像即可;霍夫圓變換不能檢測出同心圓的多個圓,只能標記出其中的一個,而且通過閾值來選擇哪個圓,大家可以通過上面的滑動條來動態的檢驗結果。檢測到的結果一般都用vector容器來存儲。

5、用到的類和函數

HoughLines:

功能:利用 Hough 變換在二值圖像中找到直線
結構:
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )

image :輸入 8-比特、單通道 (二值) 圖像
lines :輸出的角度和r長度
rho :與象素相關單位的距離精度
theta :弧度測量的角度精度
threshold :閾值參數。如果相應的累計值大於 threshold, 則函數返回的這個線段.
srn 、stn :多尺度變換,距離精度 rho 的分母,角度精度 theta 的分母。

HoughLinesP:

功能:通過統計概率的霍夫線變換找到線段
結構:
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
image :輸入 8-比特、單通道 (二值) 圖像
lines :輸出線段的兩個端點,保存爲(x_1, y_1, x_2, y_2)

rho :與象素相關單位的距離精度
theta :弧度測量的角度精度
threshold :閾值參數。如果相應的累計值大於 threshold, 則函數返回的這個線段.
minLineLength :最小的線段長度
maxLineGap :這個參數表示在同一條直線上進行碎線段連接的最大間隔值(gap), 即當同一條直線上的兩條碎線段之間的間隔小於maxLineGap時,將其合二爲一。

HoughCircles:

功能:利用 Hough 變換在灰度圖像中找圓
結構:
void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0 )

image :輸入 8-比特、單通道 (二值) 圖像
circles :輸出圓心座標(x、y),和半徑r
method : Hough 變換方式,目前只支持CV_HOUGH_GRADIENT
dp :累加器圖像的分辨率。如果dp設置爲1,則分辨率是相同的;如果設置爲更大的值(比如2),累加器的分辨率受此影響會變小(此情況下爲一半)。dp的值不能比1小。
minDist :讓算法能明顯區分的兩個不同圓之間的最小距離。
param1 :用於Canny的邊緣閥值上限,下限被置爲上限的一半。
param2 :累加器的閥值。
minRadius :最小圓半徑
maxRadius :最大圓半徑,默認爲最大值 max(image_width, image_height)




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章