Hough 變換,對圖像中直線的殘缺部分、噪聲、以及其它的共存結構不敏感,因此,具有很強的魯棒性。
它常用來檢測 直線和曲線 (圓形),識別圖像中的幾何形狀,甚至可用來分割重疊或有部分遮擋的物體。
1 平面坐標和極坐標
1) 平面坐標的點 <=> 極坐標(平面化)的曲線
所謂極坐標平面化是指, 將 ρ-θ 的關系像 x-y 那樣在平面內展開。
公式推導: x-y坐標中的點 (x0, y0), 代入極坐標 ρ-θ 中得
$\quad \begin{align*}\rho = x_{0}\: cos\theta + y_{0}\: sin\theta = \sqrt{x_{0}^{2}+y_{0}^{2}}\:\left (\frac{x_{0}}{\sqrt{x_{0}^{2}+y_{0}^{2}}}\: cos\theta + \frac{y_{0}}{\sqrt{x_{0}^{2}+y_{0}^{2}}}\:sin\theta\right )
= \sqrt{x_{0}^{2}+y_{0}^{2}} \; sin(\varphi_{0} +\theta ) \end{align*} $
其中, $sin\varphi_{0} = \frac{x_{0}}{\sqrt{x_{0}^{2}+y_{0}^{2}}}$
由上述公式可以明顯的看出ρ與θ之間的函數關系。
2) 極坐標的點 <=> 平面坐標的直線
公式推導: ρ-θ 極坐標中的點 (ρ0, θ0), 代入平面坐標 x-y 中得
$\quad y = ( -\tfrac{cos\theta_{0} }{sin\theta_{0}} )\:x + \tfrac{\rho_{0} }{sin\theta_{0}}$
由此可知, 極坐標中的一個定點(ρ0, θ0), 對應於平面坐標內的一條直線。若極坐標中有N條曲線相交於一點(如下左圖), 則說明在平面坐標中, 該點對應的直線包含N條曲線對應的N個點, 也即這N個點組成了一條直線。
上面左圖中, 是平面坐標中的三個點 (8,6), (4, 9), (12,3) 分別對應的三條曲線。
因此, 霍夫變換的目的是將平面坐標內的直線檢測, 轉換為極坐標內的交點檢測, 顯然尋找一個交點要比尋找直線(實際上是尋找許多點)容易得多了。
2 OpenCV中的函數
1) HoughLines
void cv::HoughLines(
InputArray image, // 輸入圖像
OutputArray lines, // 輸出位直線數組
double rho, // 用像素個數表示的長度分辨率
double theta, // 用像素個數表示的角度分辨率
int threshold, // 累加器閾值 (只有大於該閾值的才有足夠的投票)
double srn = 0, // 若 srn = stn = 0,則使用的是經典霍夫變換
double stn =0,
double min_theta = 0, // 取值為 0 ~ max_theta
double max_theta = CV_PI // 取值為 min_theta ~ CV_PI
);
未完待續-源代碼留待以后剖析:

/* Here image is an input raster; step is it's step; size characterizes it's ROI; rho and theta are discretization steps (in pixels and radians correspondingly). threshold is the minimum number of pixels in the feature for it to be a candidate for line. lines is the output array of (rho, theta) pairs. linesMax is the buffer size (number of pairs). Functions return the actual number of found lines. */ static void HoughLinesStandard( const Mat& img, float rho, float theta, int threshold, std::vector<Vec2f>& lines, int linesMax, double min_theta, double max_theta ) { int i, j; float irho = 1 / rho; CV_Assert( img.type() == CV_8UC1 ); const uchar* image = img.ptr(); int step = (int)img.step; int width = img.cols; int height = img.rows; if (max_theta < min_theta ) { CV_Error( CV_StsBadArg, "max_theta must be greater than min_theta" ); } int numangle = cvRound((max_theta - min_theta) / theta); int numrho = cvRound(((width + height) * 2 + 1) / rho); #if defined HAVE_IPP && !defined(HAVE_IPP_ICV_ONLY) && IPP_VERSION_X100 >= 810 && IPP_DISABLE_BLOCK CV_IPP_CHECK() { IppiSize srcSize = { width, height }; IppPointPolar delta = { rho, theta }; IppPointPolar dstRoi[2] = {{(Ipp32f) -(width + height), (Ipp32f) min_theta},{(Ipp32f) (width + height), (Ipp32f) max_theta}}; int bufferSize; int nz = countNonZero(img); int ipp_linesMax = std::min(linesMax, nz*numangle/threshold); int linesCount = 0; lines.resize(ipp_linesMax); IppStatus ok = ippiHoughLineGetSize_8u_C1R(srcSize, delta, ipp_linesMax, &bufferSize); Ipp8u* buffer = ippsMalloc_8u(bufferSize); if (ok >= 0) ok = ippiHoughLine_Region_8u32f_C1R(image, step, srcSize, (IppPointPolar*) &lines[0], dstRoi, ipp_linesMax, &linesCount, delta, threshold, buffer); ippsFree(buffer); if (ok >= 0) { lines.resize(linesCount); CV_IMPL_ADD(CV_IMPL_IPP); return; } lines.clear(); setIppErrorStatus(); } #endif AutoBuffer<int> _accum((numangle+2) * (numrho+2)); std::vector<int> _sort_buf; AutoBuffer<float> _tabSin(numangle); AutoBuffer<float> _tabCos(numangle); int *accum = _accum; float *tabSin = _tabSin, *tabCos = _tabCos; memset( accum, 0, sizeof(accum[0]) * (numangle+2) * (numrho+2) ); float ang = static_cast<float>(min_theta); for(int n = 0; n < numangle; ang += theta, n++ ) { tabSin[n] = (float)(sin((double)ang) * irho); tabCos[n] = (float)(cos((double)ang) * irho); } // stage 1. fill accumulator for( i = 0; i < height; i++ ) for( j = 0; j < width; j++ ) { if( image[i * step + j] != 0 ) for(int n = 0; n < numangle; n++ ) { int r = cvRound( j * tabCos[n] + i * tabSin[n] ); r += (numrho - 1) / 2; accum[(n+1) * (numrho+2) + r+1]++; } } // stage 2. find local maximums for(int r = 0; r < numrho; r++ ) for(int n = 0; n < numangle; n++ ) { int base = (n+1) * (numrho+2) + r+1; if( accum[base] > threshold && accum[base] > accum[base - 1] && accum[base] >= accum[base + 1] && accum[base] > accum[base - numrho - 2] && accum[base] >= accum[base + numrho + 2] ) _sort_buf.push_back(base); } // stage 3. sort the detected lines by accumulator value std::sort(_sort_buf.begin(), _sort_buf.end(), hough_cmp_gt(accum)); // stage 4. store the first min(total,linesMax) lines to the output buffer linesMax = std::min(linesMax, (int)_sort_buf.size()); double scale = 1./(numrho+2); for( i = 0; i < linesMax; i++ ) { LinePolar line; int idx = _sort_buf[i]; int n = cvFloor(idx*scale) - 1; int r = idx - (n+1)*(numrho+2) - 1; line.rho = (r - (numrho - 1)*0.5f) * rho; line.angle = static_cast<float>(min_theta) + n * theta; lines.push_back(Vec2f(line.rho, line.angle)); } }
2) HoughLinesP

void cv::HoughLinesP ( InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 ) /* 1) image 8-bit, single-channel binary source image. The image may be modified by the function. 2) lines Output vector of lines. Each line is represented by a 4-element vector x1, y1, x2,y2), where (x1, y1) and (x2, y2) are the ending points of each detected line segment. 3) rho Distance resolution of the accumulator in pixels. 4) theta Angle resolution of the accumulator in radians. 5) threshold Accumulator threshold parameter. Only those lines are returned that get enough votes(>threshold). 6) minLineLength Minimum line length. Line segments shorter than that are rejected. 7) MaxLineGap Maximum allowed gap between points on the same line to link them. */
Progressive Probabilistic Hough Transform Algorithm Outline:

/* 摘自文獻 J. Matas, Robust Detection of Lines Using the Progressive Probabilistic Hough Transform 1. Check the input image; if it is empty then finish. 2. Update the accumulator with a single pixel randomly selected from the input image. 3. Remove the selected pixel from input image. 4. Check if the highest peak in the accumulator that was modified by the new pixel is higher than threshold thr(N). If not then goto 1. 5. Look along a corridor specified by the peak in the accumulator, and find the longest segment that either is continuous or exhibits a gap not exceeding a given threshold. 6. Remove the pixels in the segment from input image. 7. “Unvote” from the accumulator all the pixels from the line that have previously voted. 8. If the line segment is longer than the minimum length add it into the output list. 9. Goto 1. */
3) HoughCircles

void cv::HoughCircles ( InputArray image, OutputArray circles, int method, double dp, double minDist, double param1 = 100, double param2 = 100, int minRadius = 0, int maxRadius = 0) /* 1) image 8-bit, single-channel, grayscale input image. 2) circles Output vector of found circles. Each vector is encoded as a 3-element floating-point vector (x, y, radius). 3) method Detection method, see cv::HoughModes. Currently, the only implemented method is HOUGH_GRADIENT 4) dp Inverse ratio of the accumulator resolution to the image resolution. For example, if dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has half as big width and height. 5) minDist Minimum distance between the centers of the detected circles. If the parameter is too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is too large, some circles may be missed. 6) param1 First method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the higher threshold of the two passed to the Canny edge detector (the lower one is twice smaller). 7) param2 Second method-specific parameter. In case of CV_HOUGH_GRADIENT , it is the accumulator threshold for the circle centers at the detection stage. The smaller it is, the more false circles may be detected. Circles, corresponding to the larger accumulator values, will be returned first. 8) minRadius Minimum circle radius. 9) maxRadius Maximum circle radius. */
3 代碼示例
在進行霍夫變換之前,首先要對圖像進行"邊緣檢測"的預處理。
3.1 檢測直線 (帶滑動條)
#include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" using namespace cv; using namespace std; // 全局變量 Mat src, src_gray, edges; Mat standard_hough, probabilistic_hough; int min_threshold = 50; int max_trackbar = 150; const char* probabilistic_name = "Probabilistic Hough Lines"; int p_trackbar = max_trackbar; void Probabilistic_Hough( int, void* ); // 函數聲明
int main( int, char** argv ) { // 讀圖 src = imread("line_demo.png"); if(src.empty()) return -1; // 灰度圖 cvtColor(src, src_gray, COLOR_RGB2GRAY); // cany 邊緣檢測 Canny(src_gray, edges, 50, 200, 3); // 閾值滑動條 char thresh_label[50]; sprintf( thresh_label, "Thres: %d + input", min_threshold ); namedWindow(probabilistic_name, WINDOW_AUTOSIZE ); createTrackbar(thresh_label, probabilistic_name, &p_trackbar, max_trackbar, Probabilistic_Hough); // 回調函數 Probabilistic_Hough(0, 0); waitKey(0); } void Probabilistic_Hough(int, void*) { vector<Vec4i> p_lines; // gray -> rgb cvtColor(edges, probabilistic_hough, COLOR_GRAY2BGR); // Probabilistic Hough Transform HoughLinesP(edges, p_lines, 1, CV_PI/180, min_threshold + p_trackbar, 30, 10 ); // 顯示 for( size_t i = 0; i < p_lines.size(); i++ ) { Vec4i l = p_lines[i]; line(probabilistic_hough, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(255,0,0), 3, LINE_AA); } imshow( probabilistic_name, probabilistic_hough ); }
3.2 檢測圓形 (不帶滑動條)
#include <opencv2/imgproc.hpp> #include <opencv2/highgui.hpp> using namespace cv; using namespace std; int main(int argc, char** argv) { // 讀圖 Mat src, src_gray; src = imread("circle_demo.png"); if(src.empty()) return -1; // 灰度圖 + 高斯濾波 cvtColor(src, src_gray, COLOR_BGR2GRAY); GaussianBlur(src_gray, src_gray, Size(9, 9), 2, 2 ); // 畫圓 vector<Vec3f> circles; HoughCircles(src_gray, circles, HOUGH_GRADIENT, 2, src_gray.rows/4, 200, 100 ); 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(src, center, 3, Scalar(0,255,0), -1, 8, 0 ); // 圓心 circle(src, center, radius, Scalar(0,0,255), 3, 8, 0 ); // 圓形 } // 窗體 namedWindow("circles", WINDOW_AUTOSIZE); imshow("circles", src); waitKey(0); }
參考資料:
<圖像處理、分析與機器視覺> 第4版, 6.2.6 Hough 變換
OpenCV Tutorials / Image Processing (imgproc module) / Hough Line Transform
OpenCV Tutorials / Image Processing (imgproc module) / Hough Circle Transform