霍夫变换的原理
我们先看这样一个问题:设已知一黑白图像上画了一条直线,要求出这条直线所在的位置。我们知道,直线的方程可以用y=k*x+b 来表示,其中k和b是参数,分别是斜率和截距。过某一点(x0,y0)的所有直线的参数都会满足方程y0=kx0+b。即点(x0,y0)确定了一组直线。方程y0=kx0+b在参数k--b平面上是一条直线,(你也可以是方程b=-x0*k+y0对应的直线)。这样,图像x--y平面上的一个前景像素点就对应到参数平面上的一条直线。我们举个例子说明解决前面那个问题的原理。设图像上的直线是y=x, 我们先取上面的三个点:A(0,0), B(1,1), C(2,2)。可以求出,过A点的直线的参数要满足方程b=0, 过B点的直线的参数要满足方程1=k+b, 过C点的直线的参数要满足方程2=2k+b, 这三个方程就对应着参数平面上的三条直线,而这三条直线会相交于一点(k=1,b=0)。 同理,原图像上直线y=x上的其它点(如(3,3),(4,4)等) 对应参数平面上的直线也会通过点(k=1,b=0)。
CvSeq * cvHoughLines2(CvArr* iamge, void* line_storage, int method, double rho, double theta, int threshold, double param1 = 0, double param2 = 0);
image输入图像,必须为8位布尔图像,
line_storage只想保存结果的指针,可以是内存块,也可以是N*1的矩阵数列
method可以是CV_HOUGH_STANDARD, CV_HUGH_PROBABILISTIC,或者CV_HOUGH_SCALE
rho, theta是用来设置直线所需要的分辨率
SHT中没有用到param1和param2参数,OPPHT中,p1设置将要返回线段的最小长度,p2设置为一条直线上分离线段不能连成一条直线的分隔像素点数。
函数返回类容依赖于调用方式,如果line_storage是矩阵数组,最终返回值为空。使用SHT和多尺度HT时,矩阵应该是CV_32FC2类型,两个通道分别是rho和θ。当使用PPHT时,矩阵为CV_32SC4类型,四个通道分别返回线段两端点的坐标。
如果line_storage指向的是内存块指针,返回的是cvseq指针,对于SHT和多尺度HT,line[0]和line[1]分别代表浮点类型的ρ和θ,对于PPHT,是线段终点的CVPOINT结构
#include "StdAfx.h"
#include<cv.h>
#include<highgui.h>
#include<math.h>
int main()
{
IplImage* sourceImage;
sourceImage= cvLoadImage("大中南.jpg",0);
IplImage* destImage=cvCreateImage(cvGetSize(sourceImage),8,1);
IplImage* colorImage=cvCreateImage(cvGetSize(sourceImage),8,3);
//分配空间,默认64KB
CvMemStorage* storage=cvCreateMemStorage(0);
CvSeq* lines=0; //指向所检测到的线的序列的第一条...
//首先对源图像进行边缘检测,结果以灰度图显示,
cvCanny(sourceImage,destImage,50,200,3);
cvCvtColor(destImage,colorImage,CV_GRAY2BGR);
lines=cvHoughLines2(//返回一个指向CvSeq序列结构的的指针,
destImage,//要进行霍夫检测的图像,必须是8位的,
storage, //保存结果位置的指针,
CV_HOUGH_PROBABILISTIC,
1, //这两个参数都是用来设置直线的分辨率的
CV_PI/180,
80, //一个阈值
30,
10 //表示支持所返回的直线的点的数量
);
int index; //index为直线的索引
for(index=0;index<lines->total;index++) //遍历每一条线
{
CvPoint* line=(CvPoint*)cvGetSeqElem(lines,index);
cvLine(colorImage,line[0],line[1],CV_RGB(255,0,0),3,8);
}
cvNamedWindow("SOURCEIMAGE",1);
cvShowImage("SOURCEIMAGE",sourceImage);
cvNamedWindow("COLORIMAGE",1);
cvShowImage("COLORIMAGE",colorImage);
cvNamedWindow("HOUGH",1);
cvShowImage("HOUGH",colorImage);
cvWaitKey(0);
return 0;
}
霍夫圆变换
原理:
对直线来说, 一条直线能由参数极径极角 表示. 而对圆来说, 我们需要三个参数来表示一个圆, 如上文所说现在原图像的边缘图像的任意点对应的经过这个点的所有可能圆是在三维空间有下面这三个参数来表示了,其对应一条三维空间的曲线. 那么与二维的霍夫线变换同样的道理, 对于多个边缘点越多这些点对应的三维空间曲线交于一点那么他们经过的共同圆上的点就越多,类似的我们也就可以用同样的阈值的方法来判断一个圆是否被检测到, 这就是标准霍夫圆变换的原理, 但也正是在三维空间的计算量大大增加的原因, 标准霍夫圆变化很难被应用到实际中:
这里的 表示圆心的位置 (下图中的绿点) 而 表示半径, 这样我们就能唯一的定义一个圆了
出于上面提到的对运算效率的考虑, OpenCV实现的是一个比标准霍夫圆变换更为灵活的检测方法: 霍夫梯度法, 也叫2-1霍夫变换(21HT), 它的原理依据是圆心一定是在圆上的每个点的模向量上, 这些圆上点模向量的交点就是圆心, 霍夫梯度法的第一步就是找到这些圆心, 这样三维的累加平面就又转化为二维累加平面. 第二部根据所有候选中心的边缘非0像素对其的支持程度来确定半径. 21HT方法最早在Illingworth的论文The Adaptive Hough Transform中提出并详细描述, 也可参照Yuen在1990年发表的A Comparative Study of Hough Transform Methods for Circle Finding, Bradski的《学习OpenCV》一书则对OpenCV中具体对算法的具体实现有详细描述并讨论了霍夫梯度法的局限性.
局限性:
- 梯度计算相当于求局部切线,不是一个数值稳定做法
- 整个非零像素集被认为是每个中心的候选,如果把累加器的阈值设置偏低,算法将要消耗比较长的时间
- 如果有同心圆,只能选择其中的一个
- 中心被认为是按照与其关联的累加器的值升值排序的,如果新的中心过于接近以前接受的中心将不被保留????
- image: 输入图像 (灰度图)
- circles_storage: 存储下面三个参数: 集合的容器来表示每个检测到的圆.
- method: 指定检测方法. 现在OpenCV中只有霍夫梯度法CV_HOUGH_GRADIENT
- dp = 1: 累加器图像的反比分辨率
- min_dist = src_gray.rows/8: 检测到圆心之间的最小距离
- param_1 = 200: Canny边缘函数的高阈值
- param_2 = 100: 圆心检测阈值.
- min_radius = 0: 能检测到的最小圆半径, 默认为0.
- max_radius = 0: 能检测到的最大圆半径, 默认为0
#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <stdio.h> using namespace cv; /** @function main */ int main(int argc, char** argv) { Mat src, src_gray; /// Read the image src = imread( argv[1], 1 ); if( !src.data ) { return -1; } /// Convert it to gray cvtColor( src, src_gray, CV_BGR2GRAY ); /// Reduce the noise so we avoid false circle detection GaussianBlur( src_gray, src_gray, Size(9, 9), 2, 2 ); vector<Vec3f> circles; /// Apply the Hough Transform to find the circles HoughCircles( src_gray, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows/8, 200, 100, 0, 0 ); /// Draw the circles detected 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( src, center, 3, Scalar(0,255,0), -1, 8, 0 ); // circle outline circle( src, center, radius, Scalar(0,0,255), 3, 8, 0 ); } /// Show your results namedWindow( "Hough Circle Transform Demo", CV_WINDOW_AUTOSIZE ); imshow( "Hough Circle Transform Demo", src ); waitKey(0); return 0; }
圆检测的输入图像必须为灰度图,而线性检测必须为bool图