OpenCV中Hough直线检测和圆检测

霍夫变换是图像处理中的一个检测直线、圆等简单几何形状的方法。它最初是用于在二值化的图像中进行直线检测的。


1、霍夫直线检测

(1)基本理论

Hough直线检测的基本理论是二值图像中的任何点都可能是一些候选直线集合的一部分,所选的参数方式是每一行代表极坐标中的一个点,并且隐含的直线是通过象征点的,垂直于原点到此点的半径,即:检测的过程可以看成从图像中的一个个像素点出发,寻找每个点成为直线一部分的可能,再把这条线上可能的点连起来形成直线。在实际检测中,当一条线出现凹陷或是弯曲度时,也会检测出直线,只是不是一条完整长度直线,而是断断续续重叠相近的很多直线。

而对于图像中的一条直线而言,利用直角坐标系,可以表示为:的形式。那么,该直线上任意一点(x,y)变换到k-b参数空间将变成一个“点”。也就是说,将图像空间中所有的非零像素转换到k-b参数空间,那么它们将聚焦在一个点上。如此一来,参数空间中的一个局部峰值点就很有可能对应着原图像空间中的一条直线。不过,由于直线的斜率可能为无穷大,或者无穷小,那么,在k-b参数空间就不便于对直线进行刻画和描述。所以,研究人员提出采用极坐标参数空间进行直线检测。在极坐标系中,直线可以表述为以下形式:


(2)OpenCV的实现

OpenCV中(2.0版本以下)的函数cvHoughLines2()实现了用于直线(线段)检测的霍夫变换(Hough transform)方法,将极坐标空间中局部峰值点予以返回。它支持两种不同的霍夫直线变换:标准霍夫变换(Standard Hough Transform,SHT)和累计概率霍夫变换(Progressive ProbabStandard Hough Transform,PPHT)。其中标准霍夫变换就是刚才所述的在极坐标空间进行参数表示的方法。而PPHTSHT的改进,它是在一定的范围内进行霍夫变换,从而减少计算量,缩短计算时间。cvHoughLines2()函数原型如下:

CvSeq* cvHoughLines2(CvArr* image,  void* lineStorage,  int method,  double rho,   double theta,  int threshold,  double param1=0,  double param2=0);

参数的说明如下:

image

输入 8-比特、单通道 (二值) 图像,当用CV_HOUGH_PROBABILISTIC方法检测的时候其内容会被函数改变。

line_storage

检测到的线段存储仓. 可以是内存存储仓 (此种情况下,一个线段序列在存储仓中被创建,并且由函数返回),或者是包含线段参数的特殊类型(见下面)的具有单行/单列的矩阵(CvMat*)。矩阵头为函数所修改,使得它的 cols/rows 将包含一组检测到的线段。如果 line_storage 是矩阵,而实际线段的数目超过矩阵尺寸,那么最大可能数目的线段被返回(线段没有按照长度、可信度或其它指标排序)。

method

Hough 变换变量,是下面变量的其中之一:

CV_HOUGH_STANDARD - 传统或标准 Hough 变换(SHT)。每一个线段由两个浮点数 (ρ, θ) 表示,其中 ρ 是直线与原点 (0,0) 之间的距离,θ 线段与 x-轴之间的夹角。因此,矩阵类型必须是 CV_32FC2 type;

CV_HOUGH_PROBABILISTIC- 概率 Hough 变换(PPHT)。如果图像包含一些长的线性分割,则效率更高。它返回线段分割而不是整个线段。每个分割用起点和终点来表示,所以矩阵(或创建的序列)类型是 CV_32SC4 type;

CV_HOUGH_MULTI_SCALE- 传统 Hough 变换的多尺度变种。线段的编码方式与 CV_HOUGH_STANDARD 的一致。

SHT相对而言,找的线参数比较准,但是画的位置不准。具体表现为检测的线端点和图片实际端点有一定偏差;PPHT检测结果相对SHT来说,更靠近图片中的线位置,具体表现为PPHT检测的起始端点比SHT检测的更靠近图片线端点。
假如知道图片中线的端点,你把它带入SHT内的参数,更准。

rho

设置极坐标系的精度:与像素相关的距离精度(单位为像素)。

theta

设置极坐标系的精度:弧度测量的角度精度(单位为弧度)。

threshold

阈值参数。如果相应的累计值大于 threshold, 则函数返回的这个线段。

param1

第一个方法相关的参数:

对传统 Hough 变换,不使用(0);

对概率 Hough 变换,它是最小线段长度;

对多尺度 Hough 变换,它是距离精度 rho 的分母。首先根据用户设定的rhotheta来检测直线,之后按照param1param2的值对检测结果进行优化,即:大致的距离精度是 rho,而精确的应该是 rho / param1。

param2

第二个方法相关参数:

对传统 Hough 变换,不使用 (0);

对概率 Hough 变换,这个参数表示在同一条直线上进行碎线段连接的最大间隔值(gap), 即当同一条直线上的两条碎线段之间的间隔小于param2时,将其合二为一;

对多尺度 Hough 变换,它是角度精度 theta 的分母。首先根据用户设定的rhotheta来检测直线,之后按照param1param2的值对检测结果进行优化,即:大致的角度精度是 theta,而精确的角度应该是 theta / param2。

返回值说明如下:

返回的是CvSeq*类型,存储了检测到的直线参数序列。其中在使用cvLine()函数绘制直线时有个line[0]和line[1],选择不同形式的hough变换直线检测类型,line[0]和line[1]表示的意思是不同的。

a.在标准hough变换中,对于返回的每条直线:

float* line = ( float* ) cvGetSeqElem( lines,i);
float rho = line[0];
float theta = line[1];

line[ 0 ] 表示矢量长度(rho_x来表示),line[1]表示直线的倾斜角(theta_x来表示)

b.在概率hough变换中,line[0]和line[1]表示返回直线的两个端点:

CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);
cvLine( color_dst, line[0], line[1], CV_RGB(255,0,0), 3, CV_AA, 0 );

代码参考如下:

#define CV_NO_BACKWARD_COMPATIBILITY

/* This is a standalone program. Pass an image name as a first parameter of the program.
   Switch between standard and probabilistic Hough transform by changing "#if 1" to "#if 0" and back */
#include <cv.h>
#include <highgui.h>
#include <math.h>

int main(int argc, char** argv)
{
    const char* filename = argc >= 2 ? argv[1] : ".\\pic1.png";
    IplImage* src = cvLoadImage( filename, 0 );
    IplImage* dst;
    IplImage* color_dst;
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq* lines = 0;
    int i;

    if( !src )
	{
		printf("Load image error!\n");
        return -1;
	}

    dst = cvCreateImage( cvGetSize(src), 8, 1 );
    color_dst = cvCreateImage( cvGetSize(src), 8, 3 );

    cvCanny( src, dst, 50, 200, 3 );
	//cvThreshold(src, dst, 50, 255, CV_THRESH_BINARY_INV);

    cvCvtColor( dst, color_dst, CV_GRAY2BGR );
#if 0	//标准hough变换
	printf("标准hough变换\n");
    lines = cvHoughLines2( dst, storage, CV_HOUGH_STANDARD, 1, CV_PI/180, 100, 0, 0 );

    for( i = 0; i < MIN(lines->total,100); 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);
        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));
        cvLine( color_dst, pt1, pt2, CV_RGB(255,0,0), 3, CV_AA, 0 );
    }
#else	//累积概率hough变换
	printf("累积概率hough变换\n");
    lines = cvHoughLines2( dst, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 50, 50, 10 );
    for( i = 0; i < lines->total; i++ )
    {
        CvPoint* line = (CvPoint*)cvGetSeqElem(lines,i);
        cvLine( color_dst, line[0], line[1], CV_RGB(255,0,0), 3, CV_AA, 0 );
    }
#endif
    cvNamedWindow( "Source", 1 );
    cvShowImage( "Source", src );

    cvNamedWindow( "Hough", 1 );
    cvShowImage( "Hough", color_dst );

    cvWaitKey(0);

    return 0;
}

结果如下:



2、霍夫圆检测

(1)基本理论

已知圆的一般方程为:(x - a)^2 + (y - b)^2 = r^2。其中,(a, b)为圆心,r为圆的半径。把X-Y平面上的圆转换到a-b-r参数空间,则图像空间中过(x, y)点圆对应参数空间中,高度r变化下的一个三维锥面,如图:


同理,过图像空间中任意一点的圆对应于参数空间中的一个三维锥面。因此,过图像空间上同一圆上的点,对应的参数空间中的三维锥面,在r高度必然相交于一点(a, b, r)。这样通过检测这一点可以得到圆的参数,相应的圆也可求得了。图像平面的方程转化为参数平面上的示意图如图:


相比霍夫直线检测,圆形检测的过程很类似:只是参数方程有变化,而且参数空间增加了一个维度(圆心坐标x,y和半径r)。霍夫变换的一个好处就是不需要图像中出现完整的圆,只要落在一个圆上的像素数量足够多,就能正确识别。同理,霍夫变换检测椭圆如果使用椭圆的标准式,那么将会有五个参数,它们通过标准式相关,检测圆就已经相当耗时了,如果再用这中方程形式处理势必失去实际用途。

(2)OpenCV实现

由于比直线检测多出一个维度,使得标准的霍夫圆检测需要大量内存且速度较慢。出于对运算效率的考虑, OpenCV实现的霍夫圆检测是一个比标准霍夫圆变换更为灵活的检测方法:霍夫梯度法,也叫2-1霍夫变换(21HT)。原理是首先对图像进行canny边缘检测,然后考虑边缘图像中的每一个非0点的局部梯度,通过cvSobel()函数计算x,y方向上的sobel一阶导数得到梯度,利用得到的梯度,由斜率指定直线上的每一个点都在累加器中被累加,然后从累加器中这些点中选择候选的中心画圆。

(来自www.opencv.org.cn的解释没看太懂:它的原理依据是圆心一定是在圆上的每个点的模向量上,这些圆上点模向量的交点就是圆心,霍夫梯度法的第一步就是找到这些圆心,这样三维的累加平面就又转化为二维累加平面;第二步是根据所有候选中心的边缘非0像素对其的支持程度来确定半径。)

OpenCV中(2.0版本以下)的函数cvHoughCircles()实现了上述方法。cvHoughCircles()函数原型如下:

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)和半径r。

method

Hough 变换方式,目前只支持CV_HOUGH_GRADIENT(即:21HT)。

dp

累加器图像的分辨率。这个参数允许创建一个比输入图像分辨率低的累加器。(这样做是因为有理由认为图像中存在的圆会自然降低到与图像宽高相同数量的范畴)。如果dp设置为1,则分辨率是相同的;如果设置为更大的值(比如2),累加器的分辨率受此影响会变小(此情况下为一半)。dp的值不能比1小,但也不要太大,越大越易误判,最好为2。

min_dist

该参数是让算法能明显区分的两个不同圆之间的最小距离。

param1

用于Canny的边缘阀值上限,下限被置为上限的一半。根据图像总体灰度情况确定。

param2

累加器的阀值。较大,只检测较大的圆;较小,小圆也检测。

min_radius

最小圆半径。

max_radius

最大圆半径。

返回值说明如下:

返回的是CvSeq*类型,存储了检测到的圆的参数序列。

float* p = ( float* )cvGetSeqElem( results, i );
这里能够得到p[0],p[1]和p[2],分别代表圆心的x坐标,y坐标和半径r。

代码参考如下:

#include <cv.h>
#include <highgui.h>
#include <math.h>

int main(int argc, char* argv[])
{
	const char* filename = argc >= 2 ? argv[1] : "..\\board.jpg";

	IplImage* src = cvLoadImage(filename, 0);
	cvSmooth(src, src, CV_GAUSSIAN, 5, 5);		//降噪,不然误检测太高
	IplImage* dst = cvCreateImage(cvGetSize(src), src->depth, 3);
	cvCvtColor(src, dst, CV_GRAY2BGR);
	
	CvMemStorage* storage = cvCreateMemStorage(0);
	CvSeq* results = cvHoughCircles(
		//cvHoughCircles函数需要估计每一个像素梯度的方向,
		//因此会在内部自动调用cvSobel,而二值边缘图像的处理是比较难的
		src,
		storage,
		CV_HOUGH_GRADIENT,
		1,  //累加器图像的分辨率
		10,
		100,
		30,
		1,30);	//改变着两个参数检测不同大小的圆

	for( int i = 0; i < results->total; i++ )
	{
		float* p = ( float* )cvGetSeqElem( results, i );
		//霍夫圆变换
		CvPoint pt = cvPoint( cvRound( p[0] ), cvRound( p[1] ) );
		cvCircle(
			dst,
			pt,  //确定圆心
			cvRound( p[2] ),  //确定半径
			CV_RGB( 255, 0, 0 )
			);  //画圆函数
	}
	cvNamedWindow( "cvHoughCircles", 1 );
	cvShowImage( "cvHoughCircles", dst );
	cvNamedWindow( "source", 1 );
	cvShowImage( "source", src );
	cvWaitKey(0);

	cvReleaseMemStorage(&storage);

	return 0;
}
结果如下:


效果不好,但是opencv2.0以上版本的demo(\samples\cpp下面的houghcircles.cpp)效果不错:



ref:

改进的霍夫圆检测参考:OpenCV实现Hough变换检测圆形 - JohnHany的黑板 - 博客频道 - CSDN.NET



  • 0
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenCV的霍夫变换可以用来检测直线。下面分别介绍一下如何使用霍夫变换检测直线。 1. 检测直线 首先,需要将图像转换为灰度图像,并进行边缘检测。然后,使用cv2.HoughLines函数进行霍夫变换检测直线,该函数的参数包括输入图像、霍夫变换类型、直线的距离和角度分辨率、阈值等。具体代码如下: ``` import cv2 import numpy as np # 读取图像并转换为灰度图像 img = cv2.imread('test.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 边缘检测 edges = cv2.Canny(gray, 50, 150, apertureSize=3) # 霍夫变换检测直线 lines = cv2.HoughLines(edges, 1, np.pi/180, 200) # 绘制直线 for line in lines: rho, theta = line[0] a = np.cos(theta) b = np.sin(theta) x0 = a*rho y0 = b*rho x1 = int(x0 + 1000*(-b)) y1 = int(y0 + 1000*a) x2 = int(x0 - 1000*(-b)) y2 = int(y0 - 1000*a) cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2) # 显示图像 cv2.imshow('image', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 2. 检测 同样地,需要将图像转换为灰度图像,并进行边缘检测。然后,使用cv2.HoughCircles函数进行霍夫变换检测,该函数的参数包括输入图像、霍夫变换类型、心和半径的最小值和最大值等。具体代码如下: ``` import cv2 import numpy as np # 读取图像并转换为灰度图像 img = cv2.imread('test.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 边缘检测 edges = cv2.Canny(gray, 50, 150, apertureSize=3) # 霍夫变换检测 circles = cv2.HoughCircles(edges, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=30, minRadius=0, maxRadius=0) # 绘制 if circles is not None: circles = np.round(circles[0, :]).astype("int") for (x, y, r) in circles: cv2.circle(img, (x, y), r, (0, 255, 0), 2) # 显示图像 cv2.imshow('image', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 以上就是使用霍夫变换检测直线的方法,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值