本次范例通过霍夫变换检测直线和圆,讲解霍夫线变换和霍夫圆变换的原理,代码实现,和演示结果,使用霍夫线变换之前, 首先要对图像进行边缘检测的处理,也即霍夫线变换的直接输入只能是边缘二值图像。而霍夫圆变换则只要输入灰度图像即可,因为在霍夫圆变换的过程中已经用到了canny边缘检测。
1、原理
霍夫线变换:
-
众所周知, 一条直线在图像二维空间可由两个变量表示. 例如:
- 在 笛卡尔坐标系: 可由参数: 斜率和截距表示.
- 在 极坐标系: 可由参数: 极径和极角表示
对于霍夫变换, 我们将用 极坐标系 来表示直线. 因此, 直线的表达式可为:
化简得:
-
一般来说对于点 , 我们可以将通过这个点的一族直线统一定义为:
这就意味着每一对 代表一条通过点 的直线.
-
如果对于一个给定点 我们在极坐标对极径极角平面绘出所有通过它的直线, 将得到一条正弦曲线. 例如, 对于给定点 and 我们可以绘出下图 (在平面 - ):
只绘出满足下列条件的点 and .
-
我们可以对图像中所有的点进行上述操作. 如果两个不同点进行上述操作后得到的曲线在平面 - 相交, 这就意味着它们通过同一条直线. 例如, 接上面的例子我们继续对点: , 和点 , 绘图, 得到下图:
这三条曲线在 - 平面相交于点 , 坐标表示的是参数对 () 或者是说点 , 点 和点 组成的平面内的的直线.
-
那么以上的材料要说明什么呢? 这意味着一般来说, 一条直线能够通过在平面 - 寻找交于一点的曲线数量来 检测. 越多曲线交于一点也就意味着这个交点表示的直线由更多的点组成. 一般来说我们可以通过设置直线上点的 阈值 来定义多少条曲线交于一点我们才认为 检测 到了一条直线.
-
这就是霍夫线变换要做的. 它追踪图像中每个点对应曲线间的交点. 如果交于一点的曲线的数量超过了 阈值, 那么可以认为这个交点所代表的参数对 在原图像中为一条直线.
- 标准霍夫线变换
- 原理在上面的部分已经说明了. 它能给我们提供一组参数对 的集合来表示检测到的直线
- 在OpenCV 中通过函数 HoughLines 来实现
- 统计概率霍夫线变换
- 这是执行起来效率更高的霍夫线变换. 它输出检测到的直线的端点
- 在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空间中
<span style="font-family:SimSun;font-size:18px;">④、对于每个ab空间中的点,进行累加 ⑤、提取数量最多的点作为圆点 </span>
<span style="font-family:SimSun;font-size:18px;">在上面提到R是假设已知的,那么当R是未知的情况怎么办?其实很简单,就是将R设置为1、2、3……这样子已知下去,慢慢试,再对R和圆心数量进行阈值就可以了。</span>
2、代码实现
<span style="font-family:SimSun;font-size:18px;"></span><pre class="cpp" code_snippet_id="250656" snippet_file_name="blog_20140322_3_1861283" name="code">#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; }
4、总结
<span style="font-family:SimSun;font-size:18px;">霍夫线变</span><span style="font-family:SimSun;font-size:18px;BACKGROUND-COLOR: rgb(240,240,240)">换</span><span style="font-family:SimSun;font-size:18px;">需要对图片线进行边缘提取,图像二值化处理,而霍夫圆变换则不用,只需输入灰度图像即可;霍夫圆变换不能检测出同心圆的多个圆,只能标记出其中的一个,而且通过阈值来选择哪个圆,大家可以通过上面的滑动条来动态的检验结果。检测到的结果一般都用vector容器来存储。</span>
5、用到的类和函数
HoughLines:
<span style="font-family:SimSun;font-size:18px;">功能:利用 Hough 变换在二值图像中找到直线</span>
<span style="font-family:SimSun;font-size:18px;">结构:</span>
<span style="font-family:SimSun;font-size:18px;"></span><pre class="cpp" code_snippet_id="250656" snippet_file_name="blog_20140322_14_6209800" name="code">void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
<span style="font-family:SimSun;font-size:18px;"> </span>image :输入 8-比特、单通道 (二值) 图像
lines :输出的角度和r长度
rho :与象素相关单位的距离精度
theta :弧度测量的角度精度
threshold :阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段.
srn 、stn :多尺度变换,距离精度 rho 的分母,角度精度 theta 的分母。
HoughLinesP:
<span style="font-family:SimSun;font-size:18px;">功能:通过统计概率的霍夫线变换找到线段</span>
<span style="font-family:SimSun;font-size:18px;">结构:</span>
<span style="font-family:SimSun;font-size:18px;"></span><pre class="cpp" code_snippet_id="250656" snippet_file_name="blog_20140322_18_8913042" name="code">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)
<span style="font-family:SimSun;font-size:18px;"></span><pre style="PADDING-BOTTOM: 0px; LINE-HEIGHT: 24px; BACKGROUND-COLOR: rgb(255,252,246); MARGIN-TOP: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; WORD-WRAP: break-word; WHITE-SPACE: pre-wrap; MARGIN-BOTTOM: 10px; COLOR: rgb(51,51,51); PADDING-TOP: 0px" id="best-content-1248887378" class="best-text mb-10" code_snippet_id="250656" snippet_file_name="blog_20140322_19_58095" name="code"><span style="font-family:SimSun;font-size:18px;">rho :与象素相关单位的距离精度 theta :弧度测量的角度精度</span>
<span style="font-family:SimSun;font-size:18px;">threshold :阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段.</span>minLineLength :最小的线段长度
maxLineGap :这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直线上的两条碎线段之间的间隔小于maxLineGap时 ,将其合二为一。
HoughCircles:
<span style="font-family:SimSun;font-size:18px;">功能:利用 Hough 变换在灰度图像中找圆</span>
<span style="font-family:SimSun;font-size:18px;">结构:</span>
<span style="font-family:SimSun;font-size:18px;"></span><pre class="cpp" code_snippet_id="250656" snippet_file_name="blog_20140322_23_8824622" name="code">void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0 )
<span style="font-family:SimSun;font-size:18px;"> </span>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)
<span style="font-family:SimSun;font-size:18px;"> </span>