使用CImg利用霍夫变换检测图像中的直线和圆

检测直线

边缘检测

首先对图片进行处理,使用上次的代码生成边缘图。(参考上一篇博客)

在这里插入图片描述在这里插入图片描述
由于这次的目的是为了检测A4纸的边缘,所以不需要中间文字的小边缘,可以在计算sobel算子时增加亮度梯度阀值。
在这里插入图片描述
由于这次给的图片尺寸很大,这样计算太慢,所以我只采用了高斯模糊和sobel的步骤,并使用CImg的函数来进行灰度化和高斯模糊,并将sobel算子替换为更简单的Prewitt算子,并使用CImg内置的邻域矩阵CImg_3x3和循环cimg_for3x3来加快计算速度。

void Hough::Prewitt()
{
	gradnum = CImg<double>(img.width(), img.height(), 1, 1, 0);
	// 定义3*3领域矩阵I  
	CImg_3x3(I, double);
	// 遍历计算梯度值  
	cimg_for3x3(gFiltered, x, y, 0, 0, I, double) {
		const double ix = Inc - Ipc;
		const double iy = Icp - Icn;
		double grad = std::sqrt(ix*ix + iy * iy);
		// 梯度大于阈值才赋值  
		if (grad > gradLimit) {
			gradnum(x, y) = grad;
		}
	}
}

霍夫变换

声明一个霍夫空间,x轴为角度,值域0到PI,分为180份,纵轴为原图对角线的长度,这是由直角坐标系转极坐标系的关系得到的。注意这里theta并不需要0-2PI,因为0-PI就可以表示出直线的所有角度,用0-2PI的话反而会因为0和2PI表示同一条直线,但在霍夫空间的距离却很远,导致在相邻直线检测时无法很好区分。圆的话还是用2PI比较好。

houghImage = CImg(theta, max_length, 1, 1, 0);

对直角坐标系上的每个点,计算经过该点的直线在霍夫空间的坐标,对应像素值加一,这就是投票的过程。

void Hough::houghSpaceTransform()
{
	houghImage = CImg<int>(theta, max_length, 1, 1, 0);

	cimg_forXY(gradnum, x, y) {
		int temp = gradnum(x, y);
		if (temp != 0) {
			for (int i = 0; i < theta; ++i) {
				double r = x * cos(i*interval) + y * sin(i*interval);
				if (r >= 0 && r < max_length) {
					houghImage(i, r)++;		//voting
				}
			}
		}
	}
}

运行后发现经过简化处理后的边缘与原来的边缘差别不大。

由于霍夫变换后曲线的交点并不一定真的交于一点,所以一般的做法是取一个区域内投票数最多的点。

void Hough::houghLinesDetect()
{
	cimg_forXY(houghImage, x, y) {
		if (houghImage(x, y) > min_votes) {
			bool flag = false;
			for (auto& c : peaks) {
				if (distance(c.x - x, c.y - y) < min_distance) {
					flag = true;
					if (houghImage(x, y) > houghImage(c.x, c.y)) {
						c = Point(x, y);
					}
				}
			}
			if (!flag) {
				peaks.push_back(Point(x, y));
			}
		}
	}
	cout << peaks.size() << endl;

}

这里有两个参数

	int min_votes = 200;	//最小投票数
	int min_distance = 200;		//最小距离```

通过调整这两个参数,可以使得最后刚好检测出4条直线,如果检测出的直线十分靠近,可以考虑增加min_distance。

计算直角坐标

接下来就是根据得到的霍夫空间的坐标画出直线。这里要考虑两种情况,一是斜率不存在,二是斜率为0。所以最后写直线方程时我统一写成了ax+by+c=0的形式。

		if (peaks[i].x > 0&&peaks[i].x<=360) {	//斜率存在
			if (lines[i].k != 0) {	
				result.draw_line(x0, y_min, x1, y_max, lines_color);
				//cout << x0 << " " << y_min << " " << x1 << " " << y_max << endl;
			}
			else {	//斜率为0
				result.draw_line(x_min, y, x_max, y, lines_color);
			}
		}
		else {		//斜率不存在
			result.draw_line(x, y_min, x, y_max, lines_color);

			//cout << y_min << " " << x << endl;
		}

然后求解直线交点,这里要注意由于浮点数精度问题,无法准确表现出PI/2,在计算三角函数值时会出错,需要单独处理。

			double angle1 = peaks[j].x*interval;
			double a1 = cos(angle1), b1 = sin(angle1), c1 = -peaks[j].y;
			if (peaks[j].x == 90|| peaks[j].x == 270)
				a1 = 0;
			double D = a0 * b1 - a1*b0;
			//cout << "D " << D <<" "<< a0 * b1 << " " << a1 * b0 << endl;
			if (D != 0) {
				int x = (b0*c1 - b1 * c0) / D;
				int y = (a1*c0 - a0 * c1) / D;
				//cout <<"P"<< (b0*c1 - b1 * c0) / D << endl;
				if (x > 0 && x < width - 1 && y>0 && y < height - 1) {
					points.push_back(Point(x, y));
				}
			}

效果

在这里插入图片描述在这里插入图片描述

但是也有一些干扰比较大的图的识别效果不够好,就只好使用上次作业的边缘检测和边缘跟踪算法过滤掉干扰边缘。但是对于这样大小的图像时间可能需要几分钟,而使用简化的算法只需要数秒。

霍夫圆检测

原理

在这里插入图片描述
根据极坐标,圆上任意一点的坐标可以表示为如上形式。所以对于任意一个圆, 假设中心像素点p(x0, y0)像素点已知,圆半径已知,则旋转360°由极坐标方程可以得到每个点上的坐标。同样,如果只是知道图像上像素点, 圆半径,旋转360°则中心点处的坐标值必定最强。这正是霍夫变换检测圆的数学原理。

由于第二题的图片大小较小,并且干扰因素较多,所以可以直接用上次的代码进行边缘处理。
对于圆来说,霍夫空间的参数就是圆心的坐标,然后选择不同的半径进行投票,找到每次houghImage里面的最大投票数,这个投票数表示当前r的吻合程度,然后用投票数最大的r作为最好的r,票数超过阀值rLimit的就认为是一个圆的半径。

合理的选择r的区间(minR, maxR)可以有效减少遍历的次数,加快处理速度。

在确定了r之后,重新进行霍夫投票,将霍夫图像中投票数超过阀值的点对应圆心的坐标和值存入数组,这个值就代表了投票的结果,根据值进行排序,并判断检测出来的圆心坐标是否跟已检测的圆心坐标的距离,如果距离过小,默认是同个圆,最后输出结果。在这里,与圆形相关的像素就是圆心,用红色表示。

选择合适的参数可以加快运行速度,提高准确率,运行时间因图片大小不同在几分钟左右。

	//圆
	double minRadius = 200;
	int minR = 150, maxR = 300;
	int rLimit = 80;		//筛选半径
	int voteLimit = 40;		//筛选圆心

效果

在这里插入图片描述在这里插入图片描述

完整代码

https://github.com/CurryYuan/Computer-Vision/tree/master/Hough transform

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值