Opencv2.4学习::霍夫变换(2)圆变换

霍夫圆变换


基本原理 

关于基本原理,其思想大概跟霍夫线变换相似,但是有两种说法。

第一种

在霍夫线变换中,笛卡尔X-Y直角坐标系中的直线,变换到霍夫空间中则为1个点

因此类比可得,笛卡尔X-Y直角坐标系中的圆,变换到abr空间中,则为一条曲线,具体如下:

X-Y直角坐标系下圆方程:

对应的参数方程为:

所以在abr组成的三维坐标系中,一个点可以唯一确定一个圆。

那么,当我们固定(x,y),选取(a,b,r) 的不同组合,可以得到xy直角坐标系中经过某点(X,Y)的所有圆对应,

而在笛卡尔的xy坐标系中经过某一点的所有圆映射到abr坐标系中就是一条三维的曲线。【这句话应该是不严谨的,正确的说法看后面的第二种叙述】

个人认为,上面的话,应该是对于某点(x,y),

固定X时,选取不同(a,r)组合,可以得到一条(a,r)坐标系下的曲线

固定Y时,选取不同(b,r)组合,可以得到一条(b,r)坐标系下的曲线 

这里,我们先继续认为上面的叙述没有问题,即经过点(X,Y)的所有圆变换到(a,b,r)空间下是一条空间曲线。那么这条曲线上的一点,则对应一个圆。

                              

这时候,看(a,b,r)坐标系,若该坐标系中有某点(a,b,r)固定,那么经过该点的空间曲线越多,说明对应笛卡尔X-Y直角坐标系中共圆的点越多。当设定一个阈值,则可判断X-Y坐标系中是否有圆了。


 第二种

X-Y直角坐标系下圆方程:

现假设(x,y)是参数,即(x,y)固定,假设为(1,1)

那么以(a,b,r)为变量的方程,对应的空间图形是一个这样一个曲面,貌似是圆锥?

matlab画图代码:

>> a=-8:0.5:8;
>> b=a;
>> [A,B]=meshgrid(a,b);
>> R=sqrt((1-A).^2+(1-B).^2);
>> mesh(A,B,R);

这就跟上面第一种说法的叙述有矛盾了,这里表明在笛卡尔的xy坐标系中经过某一点的所有圆映射到abr坐标系中是一个圆锥体

那么,在这个曲面上的每一个点,则对应 X-Y坐标系下的一个经过点(x,y)的圆。

也就是说,在(a,b,r)坐标系中,假设有固定的某点(a0,b0,r0),若经过该点的曲面越多,则表示对应X-Y直角坐标系中有多个点共这个圆。

原理部分就先到这里,希望有大神指出纰漏。 


 

霍夫梯度法


上述描述的是标准霍夫圆变换的原理,由于三维空间的计算量大大增大的原因, 标准霍夫圆变化很难被应用到实际中。

实际上,一般用霍夫梯度法来解决这个问题。

基本原理:依据是圆心一定是在圆上的每个点的模向量上(圆上该点切线的法向量), 这些圆上点模向量的交点就是圆心, 霍夫梯度法的

第一步就是找到这些圆心, (圆心包含了圆心处的x和y坐标)这样三维的累加平面就又转化为二维累加平面.

第二步根据所有候选中心的边缘非0像素对其的支持程度来确定半径。简单来说,就是从圆心到圆周上的任意一点的距离(即半径)是相同,只要确定一个阈值,只要相同距离的数量大于该阈值,我们就认为该距离就是该圆心所对应的圆半径,该方法只需要计算半径直方图,不使用霍夫空间。

下引用给出两个版本解释的霍夫梯度法操作具体过程:

引用参考:https://blog.csdn.net/qq_37059483/article/details/77916655

version 1:

2-1霍夫变换的具体步骤为:

1)首先对图像进行边缘检测,调用opencv自带的cvCanny()函数,将图像二值化,得到边缘图像。
2)对边缘图像上的每一个非零点【即边缘点】。采用cvSobel()函数,计算x方向导数和y方向的导数,从而得到梯度。从边缘点,沿着梯度和梯度的反方向,对参数指定的min_radius到max_radium的每一个像素,在累加器中被累加。同时记下边缘图像中每一个非0点的位置。
3)从(二维)累加器中这些点中选择候选中心。这些中心都大于给定的阈值和其相邻的四个邻域点的累加值。
4)对于这些候选中心按照累加值降序排序,以便于最支持的像素的中心首次出现。
5)对于每一个中心,考虑到所有的非0像素(非0,梯度不为0),这些像素按照与其中心的距离排序,从最大支持的中心的最小距离算起,选择非零像素最支持的一条半径。
6)如果一个中心受到边缘图像非0像素的充分支持,并且到前期被选择的中心有足够的距离。则将圆心和半径压入到序列中,得以保留。
 

version 2:

第一阶段:检测圆心

1.1、对输入图像边缘检测;

1.2、考虑其局部梯度,即用Sobel()函数计算x和y方向的Sobel一阶导数得到梯度,即得到切线的法向量【指向圆心】

(这里容易把偏导、切线、梯度、法向量混淆,为此我还回去看了几天的高数下册,在下面给出一些归纳)

1.3、在二维霍夫空间内,绘出所有图形的梯度直线,某坐标点上累加和的值越大,说明在该点上直线相交的次数越多,也就是越有可能是圆心;(备注:这只是直观的想法,实际源码并没有划线)

1.4、在霍夫空间的4邻域内进行非最大值抑制;

1.5、设定一个阈值,霍夫空间内累加和大于该阈值的点就对应于圆心。

第二阶段:检测圆半径

2.1、计算某一个圆心到所有圆周线(假定为边缘检测出来的边缘)的距离,这些距离中就有该圆心所对应的圆的半径的值,这些半径值当然是相等的,并且这些圆半径的数量要远远大于其他距离值相等的数量

2.2、设定两个阈值,定义为最大半径和最小半径,保留距离在这两个半径之间的值,这意味着我们检测的圆不能太大,也不能太小

2.3、对保留下来的距离进行排序

2.4、找到距离相同的那些值,并计算相同值的数量

2.5、设定一个阈值,只有相同值的数量大于该阈值,才认为该值是该圆心对应的圆半径

2.6、对每一个圆心,完成上面的2.1~2.5步骤,得到所有的圆半径



番外:切向量&&法向量

先给出切向量的大概推导:

意思是:对于2维下参数方程,某点处的切向量为分量对参数求导(x'(t0),y'(t0))

由此,可以得到以下归纳:

在非参数方程的归纳中,将x看作是参数,将问题转化为参数方程下的切向量和法向量问题,

最终加上隐函数求导法则,可以推出2维下某点的梯度即为该点处的法向量

另外还可以参见:https://wenku.baidu.com/view/7cd9ee6d7e21af45b307a8a1.html

不过该文章全篇基本用的是偏导和隐函数求导,与上述归纳有所出入,实质是一样的。



核心函数:

void HoughCircles( InputArray image, OutputArray circles,
                               int method, double dp, double minDist,
                               double param1=100, double param2=100,
                               int minRadius=0, int maxRadius=0 );
  • 第一个参数image是输入图像矩阵,要求是灰度图像
  • 第二个参数 circles是输出,是一个包含检测到的圆的信息的向量,向量内第一个元素是圆的横坐标,第二个是纵坐标,第三个是半径大小;
  • 第三个参数 methodmethod是所使用的圆检测算法,目前只有CV_HOUGH_GRADIENT一个可选;
  • 第四个参数 dp是累加面与原始图像相比的分辨率的反比参数,dp=2时累计面分辨率是元素图像的一半,宽高都缩减为原来的一半,dp=1时,两者相同。(关于这个分辨率的概念没有理解透,按道理低分辨率应该意味着更快的检测速度,然而实测恰恰相反)
  • 第五个参数 minDist定义了两个圆心之间的最小距离;
  • 第六个参数param1是Canny边缘检测的高阈值,低阈值被自动置为高阈值的一半;
  • 第七个参数param2是累加平面对是否是圆的判定阈值;
  • 第八和第九个参数定义了检测到的圆的半径的最大值和最小值;

测试代码:

#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
#include<stdio.h>
using namespace std;
using namespace cv;

void main()
{
	Mat srcImage = imread("F:\\opencv_re_learn\\circle.jpg");
	if (!srcImage.data){
		cout << "failed to read" << endl;
		system("pause");
		return;
	}
	imshow("srcImage", srcImage);
	//转换为灰度图像
	Mat src_gray;
	cvtColor(srcImage, src_gray, CV_BGR2GRAY);
	//高斯滤波
	GaussianBlur(src_gray, src_gray, Size(9, 9), 2, 2);
	vector<Vec3f>circles;
	//霍夫圆检测
	HoughCircles(src_gray, circles, CV_HOUGH_GRADIENT, 1, src_gray.rows / 8,
		200, 100, 0, 0);
	//将得到的结果绘图
	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(srcImage, center, 3, Scalar(0, 255, 0),
			-1, 8, 0);
		//绘制圆
		circle(srcImage, center, radius, Scalar(0, 0, 255), 3, 8, 0);
	}
	imshow("HoughResult", srcImage);
	waitKey(0);
}

实现效果: 


快速调参版:

#include<opencv2/core/core.hpp>  
#include<opencv2/highgui/highgui.hpp>  
#include<opencv2/imgproc/imgproc.hpp>  
#include<iostream>  
#include<string>  
using namespace std;
using namespace cv;
//bgr图像  
Mat bgr;
Mat srcImage;
Mat srcGray;
//HSV图像  
Mat hsv;
//分辨率  
int px = 1;//分辨率取值
int px_Max = 5; //分辨率可取的最大值
//圆心最小距离  
int center_distance = 10;//圆心最小距离取值
int center_distance_Max = 200; //圆心最小距离取值可取的最大值
//Canny边缘检测高阈值  【值越大,找到的边缘越少】 
int Canny_value = 200;//
int Canny_value_Max = 300;//Canny边缘检测高阈值可取的最大值
//圆的判定阈值 
int acc_circle = 100;
int acc_circle_Max = 300;//圆的判定阈值可取的最大值
//圆的半径最小值
int r_min = 0;
int r_min_Max = 300;//圆的半径最小值可取的最大值
//圆的半径最大值
int r_max= 0;
int r_max_Max = 500;//圆的半径最大值可取的最大值

//显示原图的窗口  
string windowName = "src";
//输出图像的显示窗口  
string dstName = "dst";
//输出图像  
Mat dst;
//回调函数  
void callBack(int, void*)
{
	bgr = srcImage.clone();
	vector<Vec3f>circles;
	//霍夫圆检测
	HoughCircles(srcGray, circles, CV_HOUGH_GRADIENT, px, center_distance,
		Canny_value, acc_circle, r_min, r_max);
	//将得到的结果绘图
	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(bgr, center, 3, Scalar(0, 255, 0),
			-1, 8, 0);
		//绘制圆
		circle(bgr, center, radius, Scalar(0, 0, 255), 3, 8, 0);
	}
	imshow(dstName, bgr);
}
int main()
{
	//输入图像  
	srcImage = imread("F:\\opencv_re_learn\\circle2.jpg");
	if (!srcImage.data){
		cout << "falied to read" << endl;
		system("pause");
		return -1;
	}
	imshow(windowName, srcImage);
	//颜色空间转换  
	cvtColor(srcImage, srcGray, CV_BGR2GRAY);
	//高斯滤波
	GaussianBlur(srcGray, srcGray, Size(9, 9), 2, 2);
	//定义输出图像的显示窗口  
	namedWindow(dstName, CV_WINDOW_AUTOSIZE);
	//分辨率 
	createTrackbar("px", dstName, &px, px_Max, callBack);
	//圆心最小距离  
	createTrackbar("distance", dstName, &center_distance, center_distance_Max, callBack);
	//Canny边缘检测高阈值
	createTrackbar("Canny_value", dstName, &Canny_value, Canny_value_Max, callBack);
	//圆的判定阈值 
	createTrackbar("acc_circle", dstName, &acc_circle, acc_circle_Max, callBack);
	//圆的半径最小值
	createTrackbar("r_min", dstName, &r_min, r_min_Max, callBack);
	//圆的半径最大值
	createTrackbar("r_max", dstName, &r_max, r_max_Max, callBack);
	callBack(0, 0);
	waitKey(0);
	return 0;
}


参考文章:

https://blog.csdn.net/xia316104/article/details/44781157 

https://blog.csdn.net/qq_37059483/article/details/77916655

https://blog.csdn.net/dcrmg/article/details/52506538

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值