learning OpenCV(5)霍夫变换

霍夫变换的原理

我们先看这样一个问题:设已知一黑白图像上画了一条直线,要求出这条直线所在的位置。我们知道,直线的方程可以用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)。

这个性质就为我们解决问题提供了方法:
首先,我们初始化一块缓冲区,对应于参数平面,将其所有数据置为0.
对于图像上每一前景点,求出参数平面对应的直线,把这直线上的所有点的值都加1。最后,找到参数平面上最大点的位置,这个位置就是原图像上直线的参数。上面就是霍夫变换的基本思想。就是把图像平面上的点对应到参数平面上的线,最后通过统计特性来解决问题。假如图像平面上有两条直线,那么最终在参数平面上就会看到两个峰值点,依此类推。
在实际应用中,y=k*x+b形式的 直线方程没有办法表示x=c形式的直线(这时候, 直线的斜率为无穷大)。所以实际应用中,是采用 参数方程p=x*cos(theta)+y*sin(theta)。这样,图像平面上的一个点就对应到参数p---theta平面上的一条 曲线上。其它的还是一样。

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;
}

霍夫圆变换

原理:

对直线来说, 一条直线能由参数极径极角 (r, \theta) 表示. 而对圆来说, 我们需要三个参数来表示一个圆, 如上文所说现在原图像的边缘图像的任意点对应的经过这个点的所有可能圆是在三维空间有下面这三个参数来表示了,其对应一条三维空间的曲线. 那么与二维的霍夫线变换同样的道理, 对于多个边缘点越多这些点对应的三维空间曲线交于一点那么他们经过的共同圆上的点就越多,类似的我们也就可以用同样的阈值的方法来判断一个圆是否被检测到, 这就是标准霍夫圆变换的原理, 但也正是在三维空间的计算量大大增加的原因, 标准霍夫圆变化很难被应用到实际中:

C : ( x_{center}, y_{center}, r )

这里的 (x_{center}, y_{center}) 表示圆心的位置 (下图中的绿点) 而 r 表示半径, 这样我们就能唯一的定义一个圆了 


出于上面提到的对运算效率的考虑, 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中具体对算法的具体实现有详细描述并讨论了霍夫梯度法的局限性.

局限性:

  1. 梯度计算相当于求局部切线,不是一个数值稳定做法
  2. 整个非零像素集被认为是每个中心的候选,如果把累加器的阈值设置偏低,算法将要消耗比较长的时间
  3. 如果有同心圆,只能选择其中的一个
  4. 中心被认为是按照与其关联的累加器的值升值排序的,如果新的中心过于接近以前接受的中心将不被保留????
CvSeq * cvHoughCircles(CvArr* image, void* circle_storage, int method, double dp, double min_dist, double param1 = 100, double param2 = 300, int min_radius = 0, int max_radius = 0);

  • image: 输入图像 (灰度图)
  • circles_storage: 存储下面三个参数: x_{c}, y_{c}, r 集合的容器来表示每个检测到的圆.
  • 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图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值