<span style="font-family: Arial, Helvetica, sans-serif;">#include<cv.h></span>
#include<highgui.h>
#include<math.h>
int main(int argc,char **argv)
{
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);
cvCvtColor(dst,color_dst,CV_GRAY2BGR);
#if 1
lines = cvHoughLines2(dst,storage,CV_HOUGH_STANDARD,1,CV_PI/180.150,0,0);
for(i=0;i<lines->total;i++)
{
float *line = (float *)cvGetSeqElem(lines,i);
float rho = line[0];
float theta = line[1];
CvPoint pt1,pt2;
double a = cos(theta),b = sin(theta);
if(fabs(a)<0.001)
{
pt1.x=pt2.x = cvRound(rho);
pt1.y=0;
pt2.y=color_dst->height;
}
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);
pt2.x=cvRound(rho/a);
pt2.y=0;
}
cvLine(color_dst,pt1,pt2,CV_RGB(255,0,0),3,8);
}
#else
lines = cvHoughLines2(dst,storage,CV_HOUGH_PROBABILISTIC,1,CV_PI/180,80,30,10);
for(i=0;i<line->total;i++)
{
CvPoint*line = (CvPoint*)cvGetSeqElem(lines,i);
cvline(color_dst,line[0],line[1],CV_RGB(255,0,0),3,8);
}
#endif
cvNamedWindow("Source",1);
cvShowImage("Source",src);
cvNamedWindow("Hough",1);
cvShowImage("Hough",color_dst);
cvWaitKey;
}
}
从这个例程我们接触霍夫变换
HOUGH变换概念
Hough变换是一种利用图像的全局特征将特定形状的边缘连接起来,形成连续平滑边缘的一种方法。它通过将源图像上的点影射到用于累加的参数空间,实现对已知解析式曲线的识别。由于它利用了图像全局特性,所以受噪声和边界间断的影响较小,比较鲁棒。 Hough变换常用来对图像中的直线和圆进行识别。
最简单的霍夫变换是在图像中识别直线。在平面直角坐标系(x-y)中,一条直线可以用下式表示。
对于直线上一个确定的点(x_0,y_0),有:y_0=kx_0+b
这表示参数平面(k-b)中的一条直线。因此,图像中的一个点对应参数平面中的一条直线,图像中的一条直线对应参数平面中的一个点。对图像上所有的点作霍夫变换,最终所要检测的直线对应的一定是参数平面中直线相交最多的那个点。这样就在图像中检测出了直线。在实际应用中,直线通常采用参数方程
Opencv里有以下函数检测直线(最基本的霍夫变换):
void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
我们再看一个程序:
#include "opencv2/opencv.hpp"
#define PI 3.1415926
int main(int argc, char *argv[])
{
cv::Mat image = cv::imread ("src.jpg");
cv::Mat result;
cv::cvtColor (image,result,CV_BGRA2GRAY);
cv::Mat contours;
//边缘检测
cv::Canny (result,contours,125,350);
std::vector<cv::Vec2f> lines;
//霍夫变换,获得一组极坐标参数(rho,theta),每一对对应一条直线,保存到lines
//第3,4个参数表示在(rho,theta)坐标系里横纵坐标的最小单位,即步长
cv::HoughLines (contours,lines,1,PI/180,80);
std::vector<cv::Vec2f>::const_iterator it = lines.begin ();
std::cout<<lines.size ()<<std::endl;
while(it != lines.end()){
float rho = (*it)[0];
float theta = (*it)[1];
if(theta<PI/4.||theta>3.*PI/4){
//画交点在上下两边的直线
cv::Point pt1(rho/cos(theta),0);
cv::Point pt2((rho-result.rows*sin(theta))/cos(theta),result.rows);
cv::line(image,pt1,pt2,cv::Scalar(255),1);
}
else {
//画交点在左右两边的直线
cv::Point pt1(0,rho/sin(theta));
cv::Point pt2(result.cols,(rho-result.cols*cos(theta)/sin(theta)));
cv::line(image,pt1,pt2,cv::Scalar(255),1);
}
++it;
}
cv::namedWindow ("hough");
cv::imshow("hough",image);
cv::waitKey (0);
}
输出结果对比可以看到: :
另外,可以看出,上面的直线检测存在以下问题:
void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )
这个方法是通过概率霍夫变换实现的:
1)随机获取边缘图片上的前景点,映射到级坐标系画曲线;
2)当极坐标系里面有交点达到最小投票数,将该点对应x-y坐标系的直线L找出来;
3)搜索边缘图片上前景点,在直线L上的点(且点与点之间距离小于maxLineGap的)连成线段,然后这些点全部删除,并且记录该线段的参数,就是起始点和终止点啦~~~~~~~~~~~~~~~~~~~(当然这里线段长度要满足最小长度的,否则就不用记录了)
4)重复1),2),3)
#include "opencv2/opencv.hpp"
#define PI 3.1415926
class LineFinder{
private:
// 直线对应的点参数向量
std::vector<cv::Vec4i> lines;
//步长
double deltaRho;
double deltaTheta;
// 判断是直线的最小投票数
int minVote;
// 判断是直线的最小长度
double minLength;
// 同一条直线上点之间的距离容忍度
double maxGap;
public:
//初始化
LineFinder() : deltaRho(1), deltaTheta(PI/180),
minVote(10), minLength(0.), maxGap(0.) {}
// 设置步长
void setAccResolution(double dRho, double dTheta) {
deltaRho= dRho;
deltaTheta= dTheta;
}
// 设置最小投票数
void setMinVote(int minv) {
minVote= minv;
}
// 设置最小线段长度和线段间距容忍度
void setLineLengthAndGap(double length, double gap) {
minLength= length;
maxGap= gap;
}
//寻找线段
std::vector<cv::Vec4i> findLines(cv::Mat& binary) {
lines.clear();
cv::HoughLinesP(binary,lines, deltaRho, deltaTheta, minVote,minLength, maxGap);
return lines;
}
// 画线段
void drawDetectedLines(cv::Mat &image, cv::Scalar color=cv::Scalar(255,255,255)) {
std::vector<cv::Vec4i>::const_iterator it2=lines.begin();
while (it2!=lines.end()) {
cv::Point pt1((*it2)[0],(*it2)[1]);
cv::Point pt2((*it2)[2],(*it2)[3]);
cv::line( image, pt1, pt2, color);
++it2;
}
}
};
int main(int argc, char *argv[])
{
cv::Mat image = cv::imread ("road.jpg");
cv::Mat result;
cv::cvtColor (image,result,CV_BGRA2GRAY);
cv::Mat contours;
//边缘检测
cv::Canny (result,contours,125,350);
LineFinder finder;
finder.setMinVote (80);
finder.setLineLengthAndGap (100,20);
finder.findLines (contours);
finder.drawDetectedLines (image);
cv::namedWindow ("hough");
cv::imshow("hough",image);
cv::waitKey (0);
}
图像空间到参数空间的转换
对于图像中共线的点集{(x0,y0), (x1,y1), ...}都经过直线y=kx+b,先在我们换一个说法,“斜率为k,截距为b的直线y=kx+b包含了所有在该直线上的点”。一种强调的是图像中的点集,另一种强调的是直线的参数k和b,通过直线的点集去描述这条直线明显没有直接通过k,b两个参数去描述那样直接方便。而Hough变换就是将我们“点共线”的思维转化到参数空间{k,b}进行描述,图像空间中所有经过y=kx+b的点经过Hough变换后在参数空间都会相交于点(k,b),这样,通过Hough变换,就可以将图像空间中直线的检测转化为参数空间中对点的检测。我们不妨将y=kx+b进行一下变形:
这就是Hough变换将图像空间坐标(x,y)转化为参数空间(k,b)的Hough变换式。
Hough变换的步骤(执行过程)
-
在参数空间中建立一个二维(分别对应k,b)计数器,实际就是二维数组kbcnt,k维度为图像中直线斜率可能范围,b维度为图像中截距可能范围;数组中所有值都初始化为0;
-
扫描图像空间中的所有点(xi,yi),Hough变换式进行图像空间到参数空间的变换(ki,bi),计数kbcnt(ki,bi)++
-
设定阈值thr(图像中有多少个点共线才认为存在直线),kbcnt(ki,bi)>thr的ki,bi组成图像中的直线y=ki*x+bi
然而,上面的检测直线的方案貌似还有些问题:如果图像中存在竖直的直线呢,那kbcnt的k维度岂不是要无穷大!因此,才有了另一种参数空间的方案:利用极坐标参数而非“斜率-截距式”描述直线。
极坐标中的直线方程为
将其改写成Hough变换式,即自变量(x,y)到参数变量(r,theta)的映射:
使用极坐标参数空间,Hough变换的步骤不变,只不过将kbcnt替换成rthcnt,r范围是图像对角线的长度,th范围是0~2*pi。因为图像是离散的,所以r和th都有一个步进值dr和dth。
Hough变换除了检测直线,还可用来检测任何能用数学表达式表示的形状,如最常见的圆、椭圆,基本原理都是将图像空间的像素转变到参数空间,然后在参数空间中对共线/圆/椭圆的点进行统计,最后通过阈值判决是否是符合要求的形状。
几个常用函数
1.CvSeq* cvHoughLines2(CvArr* image,void* line_storage,int mehtod,double rho,double theta,int threshold,double param1 =0,double param2 =0);
image为输入图像,要求是8位单通道图像
lines为输出的直线向量,每条线用4个元素表示,即直线的两个端点的4个坐标值
rho和theta分别为距离和角度的分辨率
threshold为阈值,即步骤3中的阈值
minLineLength为最小直线长度,在步骤5中要用到,即如果小于该值,则不被认为是一条直线
maxLineGap为最大直线间隙,在步骤4中要用到,即如果有两条线段是在一条直线上,但它们之间因为有间隙,所以被认为是两个线段,如果这个间隙大于该值,则被认为是两条线段,否则是一条。
3.CvSeq* cvHoughCircles( CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1=100, double param2=100, int min_radius=0, int max_radius=0 );
-
image: 输入 8-比特、单通道灰度图像.
circle_storage: 检测到的圆存储仓. 可以是内存存储仓 (此种情况下,一个线段序列在存储仓中被创建,并且由函数返回)或者是包含圆参数的特殊类型的具有单行/单列的CV_32FC3型矩阵(CvMat*). 矩阵头为函数所修改,使得它的 cols/rows 将包含一组检测到的圆。如果 circle_storage 是矩阵,而实际圆的数目超过矩阵尺寸,那么最大可能数目的圆被返回.
每个圆由三个浮点数表示:圆心坐标(x,y)和半径.
-
method: Hough变换方式,目前只支持CV_HOUGH_GRADIENT, which is basically 21HT, described in [Yuen03].
dp: 累加器图像的分辨率。这个参数允许创建一个比输入图像分辨率低的累加器。(这样做是因为有理由认为图像中存在的圆会自然降低到与图像宽高相同数量的范畴)。如果dp设置为1,则分辨率是相同的;如果设置为更大的值(比如2),累加器的分辨率受此影响会变小(此情况下为一半)。dp的值不能比1小。
-
min_dist: 该参数是让算法能明显区分的两个不同圆之间的最小距离。
-
param1:
用于Canny的边缘阀值上限,下限被置为上限的一半。
-
param2:
累加器的阀值。
-
min_radius:
最小圆半径。
-
max_radius:
最大圆半径。
具体的程序可以参考http://blog.csdn.net/hitwengqi/article/details/6883299