Ex6.1 霍夫变换

实验6.1 霍夫变换

实验过程

基本遍历算法:

Hough Transform for Circles
For every edge pixel (x,y) :

​ For each possible radius value r:
​ For each possible gradient direction θ:
​ // or use estimated gradient
​ a = x – r cos(θ)
​ b = y + r sin(θ)

​ H[a,b,r] += 1
​ end
end

code:

首先要使用hough变换要对输入图片预处理:转化成灰度图、边界检测:

	cvtColor(ori, copy_img, COLOR_RGB2GRAY);
	Canny(copy_img, cannying, 20, 255, 3, false);

hough_count是三维数组存储圆心半径的投票;a/b对应hough空间的圆心,hough和源空间一致对应的就是源空间的圆心;

	for (int ro = 0; ro < rows; ro++) {
		//cout << "r:" << ro << "\n";
		for (int co = 0; co < cols; co++) {
			if (cannying.at<uchar>(ro, co)!=0) {//ptr error
				//cout << "ro:" << ro << " co:" << co << "cannying.ptr<uchar>(ro, co):" << int(cannying.ptr<uchar>(ro, co) )<< "\n";
				edgenum++;
				for (int r = min_r; r <= max_r; r++) {
					for (float theta = 0; theta <2*pi; theta += 0.1) {//2pi 范围内投票更准确
						int a = co - r * cos(theta), b = ro + r * sin(theta);
						//cout << "a:" << a << " b:" << b << " r:" << r << "\n";
						if (a > 0 && a < cols && b>0 && b < rows) {
							hough_count[a][b][r]++;
						}
					}
				}
			}
		}
	}

错误使用指针,直接用来指针ptr判断是否为0,野指针不代表指针内容,应该是*ptr

这里是 θ \theta θ 选择0–2pi更准确;

然后通过得分结果选择阈值显示圆:

	createTrackbar("alpha", newwin, &alpha, 80, thresh_draw);
    
	for (int ro = 0; ro < rows; ro++) {
		for (int co = 0; co < cols; co++) {
			for (int r = 0; r <= max_r; r++) {
				if (hough_count[co][ro][r] >= alpha) {
					//cout << hough_count[co][ro][r] << "\n";
					circlenum++;
					Point center(co, ro);
					circle(new_img, center, 3, Scalar(0, 255, 0), -1, 8, 0);
					circle(new_img, Point(co, ro), r, Scalar(0, 0, 255),3,8,0);//Point (x,y)
				}
			}
		}
	}

Point (x,y) ,x与column对应,y与row对应

结果:显示结果不错;

然后使用标准的HoughCircles绘制,查阅其中的参数使用:

HoughCircles( InputArray image, OutputArray circles,int method, double dp, double minDist,(认为是同一圆心的距离)double param1 = 100,(canny相关) double param2 = 100,(圆心投票相关)int minRadius = 0, int maxRadius = 0 );

dp:表示将原图按照1:2,或者1:1进行操作,直接使用1;

minDist:表示在这个距离内算作同一个圆心;

其他参数为具体过程投票的阈值;

minRidus、maxRadius表示半径表示的阈值。

这种方法通过调节参数能取得不错的效果,改变其中的minR、maxR运行时间长,遍历运算量大;

梯度引入

考虑使用梯度可以直接确定一个大致的圆心所在的方向,使用sobel算子通过梯度确定圆心的方向。

do_hough_gradient();
//sobel 计算梯度
	Sobel(cannying, img_dx, CV_32F, 1, 0);
	Sobel(cannying, img_dy, CV_32F, 0, 1);
//创建三个调节条
	createTrackbar("min_r", gradientwin, &min_r, 200, sobel_gradient);
	createTrackbar("max_r", gradientwin, &max_r, 400, sobel_gradient);
	createTrackbar("alpha", gradientwin, &alpha, 80, thresh_draw_gradient);

opencv的sobel算子:image-20211030235545560方向和图像的xy方向一致,对于梯度直接计算:每次计算清除原本记录矩阵的内容,然后遍历通过x、y两个方向dx,dy确定梯度相对于x正方向的偏向角然后遍历半径范围记录:

void sobel_gradient(int, void*) {
	memset(hough_gradient, 0, sizeof(hough_gradient));//清零
	for (int ro = 0; ro < rows; ro++) {
		for (int co = 0; co < cols; co++) {
			if (cannying.at<uchar>(ro, co) != 0) {
				float cur_dx = img_dx.at<float>(ro, co), cur_dy = img_dy.at<float>(ro, co);
				double theta0 =cur_dx!=0? atan(cur_dy / cur_dx):pi/2;//cur_dx-->theta=nan error
			//	cout << theta0 << "gradient angle \n";
				for (float theta = theta0 - 0.5; theta < theta0 + 0.5; theta += 0.1) {
					for (int r = min_r; r <= max_r; r++) {
						int a = co + r * cos(theta), b = ro + r * sin(theta);
						if (a > 0 && a < cols && b>0 && b < rows) {
							hough_gradient[a][b][r]++;
						}
					}
				}
			}
		}
	}
	thresh_draw_gradient(alpha,0);//阈值相同处理

数组起始清零,alpha的开始不能设置太小否则画的圆太多也会在起始有一定卡顿,得到的图效果不好

image-20211031000305843一开始得到这种图,以为是坐标变换不对,实际是没有对dx=0特殊处理出现的角度大部分是nan;

更新错误:上面错误是由于sobel滤波之后存储浮点数,这里存储的是cv_16s是整数,错误;修改后效果不错,在梯度周围较小范围内投票效果较好。

Tips:

在进行霍夫变换之前做滤波:但是通过调阈值基本可以解决,没看出有太大区别,只是在canny之后的边界中去除了很多噪音。

image-20211031001618761

gassian后:

image-20211031001638828

image-20211031001913056

这张图右边的硬币总是检测不到,但是轮廓是比较清楚的,我就想是坐标变换后rows和cols某个地方边界判断错误,果真是a,b边界写反了导致那里没检测到,调整后:

image-20211031002115638

没用滤波实验效果会好点,考虑滤波消除掉某些特征。

基本暴力更多测试例子:

Snipaste_2021-10-29_21-14-08

Snipaste_2021-10-29_21-59-06

Snipaste_2021-10-29_22-14-06

Snipaste_2021-10-29_22-26-35

Snipaste_2021-10-29_22-35-35

Snipaste_2021-10-30_09-34-23

对于半径相差较大的准确率不会很高,在自带的函数里面存在同样的问题,考虑算法本身的minDist参数,minR,maxR参数限制了R差别较大的问题;上图分辨率较高,修改大小:

resize(ori, ori, Size(500, 500), 0, 0, INTER_LINEAR);

Snipaste_2021-10-30_09-39-43

Snipaste_2021-10-30_09-41-10

Snipaste_2021-10-30_09-42-05

虽然有上面的结论,但是这张图效果却不错,考虑自己实现没有使用minDist参数。

Snipaste_2021-10-30_09-51-14

Snipaste_2021-10-30_09-54-41

Snipaste_2021-10-30_10-01-22

使用含有梯度的:

Snipaste_2021-10-30_22-24-25

Snipaste_2021-10-30_22-28-50

Snipaste_2021-10-30_22-32-17

其中theta的值不是很准确表示梯度方向,要对theta取一定范围的值来解,复杂度还是比原来降低了,调节bar也流畅了,但是准确率和速度成反比关系,取值越多,也趋向于枚举做法,越精准,但速度也变慢。

实验总结

  • 枚举求解
  • 实验细节实现,多cout输出
  • sobel算子梯度进一步使用
  • opencv本是实现更巧妙

霍夫梯度法的原理

  • 估计圆心

1.把原图做一次 Canny 边缘检测,得到边缘检测的二值图。

2.对原始图像执行一次 Sobel 算子,计算出所有像素的邻域梯度值。

3.初始化圆心空间 N(a,b),令所有的 N(a,b)=0。

4.遍历 Canny 边缘二值图中的所有非零像素点,沿着梯度方向 ( 切线的垂直方向 )画线,将线段经过的所有累加器中的点 (a,b) 的 N(a,b)+=1。

5.统计排序 N(a,b),得到可能的圆心(N(a,b) 越大,越有可能是圆心)

  • 估计半径(针对某一个圆心 (a,b))

1.计算 Canny 图中所有非 0 点距离圆心的距离。

2.距离从小到大排序,根据阈值,选取合适的可能半径(比如 3 和 3.5 都被划为半径值 3 中)。

3.初始化半径空间 r, N®=0。

4.遍历 Canny 图中的非 0 点,N( 距离 )+=1。

5.统计得到可能的半径值(N® 越大,说明这个距离值出现次数越多,越有可能是半径值)。

  • 估计半径(针对某一个圆心 (a,b))

1.计算 Canny 图中所有非 0 点距离圆心的距离。

2.距离从小到大排序,根据阈值,选取合适的可能半径(比如 3 和 3.5 都被划为半径值 3 中)。

3.初始化半径空间 r, N®=0。

4.遍历 Canny 图中的非 0 点,N( 距离 )+=1。

5.统计得到可能的半径值(N® 越大,说明这个距离值出现次数越多,越有可能是半径值)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值