图像分割——霍夫变换检测直线

目录

1. 基本原理

2.设计思路:

3. 代码实现:

4. 运行结果:

5. 总结


1. 基本原理

霍夫变换的基本原理是将原空间中的问题转换到它的对偶空间去求解,在对偶空间问题会变得相对简单。霍夫变换的基本步骤通过将图像中的点对应到对偶空间中的线,图像中的线对应对偶空间的点,接着对每个对偶空间中的点进行投票,没有一条直线穿过该点,则给该点增加一票,最终选择投票数最多的若干个点(根据图像中的大概直线数决定),这若干个点就是原空间中最可能是直线的部分,将这些点转换为直线,这些直线就是我们所需要分割的部分。

在图像空间(x,y)中,一条直线的方程可以表示为y = px + q,我们将px移到左边去,则能得到直线方程的另一种表示方式:q = y – px,在这种表示方式中,该直线是由直线斜率与直线截距的关系表示的,所在空间为(p,q),此空间定义为参数空间PQ。

在图像空间中,两个点可以对应一条直线,两个不同的x,y值对应同一个p,q值,在它的对偶空间(参数空间中),是两条不同的直线对应一个相同的点。在参数空间中,每条直线的y和x都是固定的,变化的是q和p,那么就可以将这条直线看作是图像空间中的一个点,同样,参数空间中每个点的p和q都是一样的,可以将其看作是图像空间中的一条直线。

根据霍夫变换的实现原理,我们要求的是图像空间中每一条可能的直线(可能存在的直线是我们根据图像的情况预估的,通常是预估不同斜率的)上有多少个点,根据点的多少来判断这条可能的直线是否是存在的,在图像空间中我们很难去计算每条可能的直线上有多少个点,因为每条直线的斜率的截距的取值有很多,二者的组合将会有非常多的可能,对每种可能都进行计算是非常繁琐的,还会计算到很多没有经过任何点的无用直线。

但是在参数空间中,我们只需要预先给定一组可能的p值,就能计算得到每个点上所穿过的直线数,对应的就是图像空间中每条可能的直线上有多少个点。这样我们就只需要根据图像中可能的直线的斜率范围给出一组离散的p值,每个点再对每个p值判断对应在那条直线上,再将对应直线上的点个数累加,最终就能得到每条可能的直线上的点的数量了。

但是这样使用斜率和截距表示直线有局限性,就是在直线的非常接近垂直时,斜率p会接近无穷大,在有很多竖直的直线的图像中,使用这种方法则需要将斜率的取值范围设置的非常大,导致运算的时间非常长,为了解决这个问题,我们可以将图像空间变换为极坐标空间而不是参数空间。极坐标空间中直线的表示方式是:ρ=xcosθ+ysinθ

则问题改为求θ对应的ρ ,其他步骤不变。

2.设计思路:

根据上面所述的霍夫变换的基本原理,我们可以得到霍夫变换算法实现步骤:

(1)对图像进行边缘检测,得到图像中的所有边缘点。

(2)预估θ ρ 的范围,并给出一组离散的θ ρ 取值分别保存在数组thetalist和rholist中,创建一个m x n(m和n分别为thetalist和rholist的长度)的累计数组A记录每条直线所经过的点的数量,初始化A中所有数据为0。

(3)遍历每一个边缘点,每个点对thetalist中的所有θ 值分别计算对应的ρ ,使用θ 所在thetalist的位置j和ρ 所在rholist中的位置k)作为索引,在累加数组A中该直线的值加一,即A(j,k)+ 1

(4)预估图像中大概的直线数量t,在累计数组中寻找前t个最大值,得出对应的t条最可能存在的直线。

3. 代码实现:

根据上面的步骤,我们首先对图像进行边缘检测,得到图像中的所有边缘点。

这里使用的是sobel算子对图像进行边缘检测

sobel_x = [-1 0 1; -2 0 2; -1 0 1];
sobel_y = [-1 -2 -1; 0 0 0; 1 2 1];

上面是两个sobel算子,分别用于检测水平和竖直方向上的边缘点。

分别使用上面两个算子对原图像进行卷积运算

Gx = conv2(double(gray_imag), sobel_x, 'same');
Gy = conv2(double(gray_imag), sobel_y, 'same');

上面最后一个参数’same’是保留图像的原大小,因为使用长度大于2的卷积核对图像进行卷积操作得到的输出图像通常会比原图像小一点。

再根据两个边缘检测结果得到总的结果

output_imag = sqrt(Gx.^2 + Gy.^2);

使用一张简单的图片测试一下边缘检测的结果:

原图像(灰度图像):

检测结果:

可以看到边缘检测的结果还是很好的。

接着从上面得到的输出图像中取出边缘点,经过边缘检测后得到的图像中边缘点灰度值为非零值,其他地方都是零,要取出边缘点只需从输出图像中取出非零的元素即可。取出边缘点后遍历每个边缘点,每个边缘点对plist中的所有斜率p计算对应的截距q,在累计数组中将该直线的值加一。注意rho的值可能会取负数,直接用rho的值作为索引可能会报错,需要将rho的值整体右移到非零然后才能作为索引。

imag = imread("C:\Users\林梓烯\Pictures\练习图片\直线.jpg");
gray_imag = rgb2gray(imag);
[h, w] = size(gray_imag);
thetalist = 0 : 1 : 180;
rholist = -sqrt(h ^ 2 + w ^ 2) : 1 : sqrt(h ^ 2 + w ^ 2);
A = zeros(length(rholist), length(thetalist));
sobel_x = [-1 0 1; -2 0 2; -1 0 1];
sobel_y = [-1 -2 -1; 0 0 0; 1 2 1];
% 卷积计算过滤出竖直和水平边缘点
Gx = conv2(double(gray_imag), sobel_x, 'same');
Gy = conv2(double(gray_imag), sobel_y, 'same');
output_imag = sqrt(Gx.^2 + Gy.^2);
% 对图像中的每个点遍历
for x = 1 : w
    for y = 1 : h
        % disp(output_imag(x, y));
        % 判断该点是不是边缘点
        if output_imag(y, x) > 0
            for j = 1 : length(thetalist)
                 % 计算对应的rho值
                rho = x * cosd(thetalist(j)) + y * sind(thetalist(j));
                 % 将rho进行扩大,使其所在范围移动到1到length(thetalist),否则非0的索引无法保存
                rhoIdx = round(rho + length(rholist) / 2);
                 % 判断rho索引是否在范围内,在的话才进行累加,避免索引异常
                if rhoIdx > 0 && rhoIdx <= length(rholist)
                    A(rhoIdx, j) = A(rhoIdx, j) + 1;
                end
            end
        end
    end
end

运行完上面的代码后我们就能获得计算完毕的累加数组A,接着我们对累加数组进行倒序排序,选择我们要检测出的直线数量n(提前预估图像中可能存在的直线数量),从排序后的累加数组中取出n个值的索引(theta和rho)

nLines = 50;
[A_sorted, sorted_idx] = sort(A(:), 'descend');
rhoIdx = zeros(nLines, 1);
thetaIdx = zeros(nLines, 1);
for i = 1 : nLines
    [rhoIdx(i), thetaIdx(i)] = ind2sub(size(A), sorted_idx(i));
end

这里选择的直线数量为50,使用sort函数对A进行排序,获得排序后的索引,接着从排序后的索引中取出前nLines个直线的行和列索引(对应rho和theta),因为A是二维的,所以要用ind2sub转换为二维的索引。

接着我们再将索引转换为实际的rho和theta值,通过索引分别在rholist和thetalist中取出对应的rho和theta值即可

detectedRho = rholist(rhoIdx);
detectedTheta = thetalist(thetaIdx);

最后使用plot函数画出这nlines个直线。

figure, imshow(gray_imag), hold on
x = 1:w;
for i = 1:length(detectedRho)
    y = (detectedRho(i) - x * cosd(detectedTheta(i))) / sind(detectedTheta(i));
    plot(x, y, 'LineWidth',2,'Color','red');
end
hold off

如上述函数首先输出原图像,然后使用hold on函数可以保持住当前图像便于直接在当前图像上绘制直线,最后对每条直线,给出指定范围的x值,使用theta和rho计算出对应的y,使用这一组x,y值画出这条直线。

4. 运行结果:

首先选择绘制图像中最可能存在的10条直线,观察结果:

可以看出这个结果是非常差的,再尝试绘制出更多条的直线观察是否能检测出更多的直线:

绘制50条直线:

可以看到检测出了更多的直线,但是还有很多的直线没有检测出来。这可能是因为边缘检测出了问题,上面自己实现的sobel算子过于简陋,没有再进一步进行处理,下面改为输出边缘检测后的图像进行观察:

可以看到这个边缘检测的结果是很差的,因为上面实现的边缘检测没有对边缘图像做进一步的处理,得出的结果是很粗糙的,对霍夫变换检测直线的影响很大。使用matlab自带的边缘检测函数以sobel算子进行检测,看看得出的结果是否变好:

output_imag = edge(gray_imag, 'sobel');

可以看出,matlab内置函数的边缘检测效果是非常好的,接着再使用这个边缘图像进行霍夫变换检测直线观察结果。

从上图可以看出,所有的直线都被检测出来了,说明出问题的确实是边缘检测步骤。

因为之前使用自己实现的边缘检测函数检测出的边缘图像很难检测出直线,所以使用了这个比较简单,边缘很明显的图像进行检测。使用了matlab内置的边缘检测函数,检测结果好了很多,现在再对复杂的图像进行检测,观察是否也能达到很好的结果。

原图像:

检测结果:

这里nLines选择的是100,可以看到检测出的结果还是很好的,只不过由于我是直接绘制直线的而不是线段,因此看上去比较杂乱。继续增加nLines的数量还能检测出更多的直线,不过也会出现很多非常接近的直线混杂在一起的情况。

使用matlab内置函数hough、houghpeaks、houghlines可以更好地检测出图像中的直线,而且绘制出来的是线段,看上去更加美观。

完整代码:

imag = imread("C:\Users\林梓烯\Pictures\练习图片\房子.jpg");
gray_imag = rgb2gray(imag);
[h, w] = size(gray_imag);
thetalist = 0 : 1 : 180;
rholist = -sqrt(h ^ 2 + w ^ 2) : 1 : sqrt(h ^ 2 + w ^ 2);
A = zeros(length(rholist), length(thetalist));
output_imag = edge(gray_imag, 'sobel');
% 对图像中的每个点遍历
for x = 1 : w
    for y = 1 : h
        % 判断该点是不是边缘点
        if output_imag(y, x) > 0
            for j = 1 : length(thetalist)
		        % 计算对应的rho值
                rho = x * cosd(thetalist(j)) + y * sind(thetalist(j));
		        % 将rho进行扩大,使其所在范围移动到1到length(thetalist),否则非0的索引无法保存
                rhoIdx = round(rho + length(rholist) / 2);
		        % 判断rho索引是否在范围内,在的话才进行累加,避免索引异常
                if rhoIdx > 0 && rhoIdx <= length(rholist)
                    A(rhoIdx, j) = A(rhoIdx, j) + 1;
                end
            end
        end
    end
end
nLines = 100;
[A_sorted, sorted_idx] = sort(A(:), 'descend');
rhoIdx = zeros(nLines, 1);
thetaIdx = zeros(nLines, 1);
for i = 1 : nLines
    [rhoIdx(i), thetaIdx(i)] = ind2sub(size(A), sorted_idx(i));
end
% rho = (-sqrt(h^2+w^2):1:sqrt(h^2+w^2));
% theta = (0:1:180);
detectedRho = rholist(rhoIdx);
detectedTheta = thetalist(thetaIdx);
figure, imshow(gray_imag), hold on
x = 1:w;
for i = 1:length(detectedRho)
    y = (detectedRho(i) - x * cosd(detectedTheta(i))) / sind(detectedTheta(i));
    plot(x, y, 'LineWidth',2,'Color','red');
end
hold off

5. 总结

       霍夫变换检测直线的基本思路就是给出一些可能存在的直线,计算每条直线经过的边缘点,经过的边缘点越多就说明这条直线越可能存在,通过投票的机制选择票数最多的直线,这些直线就是最可能存在的直线,霍夫变换的变换就体现在它不是在图像空间中求解的,而是转换到对偶空间(参数空间、极坐标空间)中进行求解,因为在这些空间中求解能使得问题变得简单。霍夫变换实现起来还是比较简单的,主要问题在于将确定直线所需要的两个参数(p、q或者极坐标空间中的theta和rho)对应到数组的索引上,索引不能是复数,而p、q(或theta和rho)可能是负数,需要进行一定的操作解决。

        在上面的实现中取出直线是预先给定一个n值,从累加数组中取出n个值最大的直线,这种方式有比较大的局限性,一些复杂的图像我们不能很准确的预估图像可能存在的直线数,我们可以通过设定阈值,取出超过阈值的直线输出,阈值的选择通常依据累加数组中的最大值来确定。

        上面实现的霍夫变换是个很简陋很粗糙的算法,检测出来的直线并不全,而且绘制出来的直线也很不美观,但自己实现霍夫变换只是为了让我们对霍夫变换更加了解,在具体实现时我们只需要使用matlab内置的函数来实现即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值