一、Hough变换的定义
霍夫变换是一种从图像空间到参数空间的变换。对于直线检测问题,参数空间是一个二维直角坐标系(ρ, θ),其中ρ表示原点到直线的距离,θ表示该直线与水平轴的夹角。对于圆检测问题,参数空间则是径向距离和极角(r, θ)组成的三维空间。霍夫变换通过统计图像空间中每个像素点对
二、霍夫变换的原理
在直线检测中,对于图像中的每一个点(x, y),可以找出所有可能经过这个点的直线在参数空间(ρ, θ)中的表示。当图像中有大量共线的像素点时,它们在参数空间中对应的(ρ, θ)值会在某一点(或一条线上)积累大量的投票,从而形成了明显的峰值,这个峰值就对应于图像中的直线参数。
具体步骤如下:
- 对图像中的每个像素点进行遍历。
- 对于每个像素点,计算出所有可能经过该点的直线在参数空间(ρ, θ)中的坐标。
- 在参数空间中对应的坐标位置进行累加计数,形成累积直方图。
- 查找累积直方图中的峰值,这些峰值对应的参数(ρ, θ)就是图像中潜在的直线参数。
霍夫变换的优点在于它对图像中的断裂、缺失、噪声等情况有一定的鲁棒性,因为即使直线在图像中被打断,只要打断的部分仍属于同一直线,它们在参数空间中累积的计数仍然会在一起形成峰值。
三、Hough变换实现尺寸测量
尺寸测量的整体流程
(1)输入原始图像
输入一张图像,图像左上角为3mm的标度尺
(2)Sobel边缘检测
边缘检测模块使用Sobel算子,Sobel算子是一种用于图像边缘检测的微分算子,它通过计算图像中像素灰度值在水平和垂直方向上的梯度来检测图像中的边缘。Sobel算子在水平、垂直方向上的模板是一个3x3的卷积核,其系数如图所示。
计算结果表示的是该像素点处图像水平方向上的梯度强度,即左右两边像素灰度值的变化程度。当像素点处于水平边缘时,由于两侧像素灰度差异较大,所以该点的水平梯度值将会很高。同样地,垂直方向上的模板应用于图像时,也是对每个像素点与其上下邻域内的像素进行加权求和,反映的是该像素点处图像垂直方向上的梯度强度,即上下两边像素灰度值的变化情况。当像素点处于垂直边缘时,其垂直梯度值将显著增大。综合这两个方向的结果,可以得到一个像素点的梯度幅度(通常采用欧氏距离或者幅角的方式来表征),以及可能的方向信息。最终通过设定阈值可以确定哪些像素点位于边缘,并根据梯度方向得到边缘的方向。得到边缘检测的图像:
MATLAB自编函数实现Sobel边缘检测
% 边缘检测 - Sobel 算法
for i = 2:rows-1
for j = 2:cols-1
% 提取当前像素周围的区域
region = gray(i-1:i+1, j-1:j+1);
% 计算梯度
gradient_x = sum(sum(Gx .* region)); % 二维卷积 .*只能与双精度类型或者同类的整数间使用
gradient_y = sum(sum(Gy .* region));
% 计算梯度幅度
gradient(i, j) = sqrt(gradient_x^2 + gradient_y^2);
end
end
% 阈值处理 - 仅保留大于阈值的梯度
% 可以根据需要调整此阈值
threshold_bw = 120;
edges = gradient > threshold_bw;
BW = edges;
% 显示边缘检测后的图像
figure(4); imshow(edges); title('边缘检测后的图像');
% 结束计时并获取处理时间
processingTime = toc;
% 输出处理时间
fprintf('边缘检测用时: %.3f 秒\n', processingTime);
(3)Hough变换检测直线
自编Hough变换函数,首先输入图像必须是二值图像:函数首先使用assert函数确保输入图像是二值图像,即只包含黑白两种颜色的图像。函数定义了极坐标参数的范围。theta表示角度,范围为-90到89度,共180个角度。rho表示极径,范围为从负的最大极径到正的最大极径,共2倍的最大极径个极径值。接着,函数创建一个大小为length(rho) × length(theta)的全零矩阵作为Hough累加器。函数使用两个嵌套的循环遍历输入图像的每个像素点,判断像素点是否是边缘点,如果当前像素点是边缘点(即像素值为真),则执行下面的操作;否则,跳过该像素点。对每个角度进行遍历,函数使用一个循环遍历每个角度。根据当前像素点的坐标和角度,计算出对应的极径值。将计算得到的rho值加上最大极径,并四舍五入得到在Hough累加器中的索引。将Hough累加器中对应索引位置的值加1,表示在该位置上有一个极坐标点通过。最终,函数返回Hough累加器矩阵H、角度数组theta和极径数组rho。Hough累加器矩阵H记录了在输入图像中检测到的直线在Hough空间中的累加情况,可以用于后续的直线检测和分析。
MATLAB自编函数实现Hough变换
function [H, theta, rho] = simple_hough_transform(I)
% 确保输入图像是二值图像
assert(islogical(I), 'Input image must be a binary image.');
% 图像尺寸
[rows, cols] = size(I);
% 极坐标参数范围
theta = linspace(-90, 89, 180);
rho_max = hypot(rows, cols);
rho = linspace(-rho_max, rho_max, 2*rho_max);
% 初始化Hough累加器
H = zeros(length(rho), length(theta));
% 对于每个像素点
for x = 1:cols
for y = 1:rows
% 如果像素点是边缘点
if I(y, x)
% 对于每个角度
for theta_idx = 1:length(theta)
% 计算对应的rho值
rho_val = x * cosd(theta(theta_idx)) + y * sind(theta(theta_idx));
rho_idx = round(rho_val + rho_max);
% 累加Hough空间
H(rho_idx, theta_idx) = H(rho_idx, theta_idx) + 1;
end
end
end
end
end
MATLAB自编函数实现寻找ρ,θ域峰值
寻找ρ、Θ域峰值,初始化峰值数组,函数创建一个大小为numpeaks × 2的全零矩阵作为峰值数组,用于存储检测到的霍夫峰值的极径和角度索引。创建H的副本:函数将输入的Hough累加器矩阵H复制给一个临时变量tempH,以便在后续的操作中修改tempH而不影响原始的H。对于每个峰值进行遍历:函数使用一个循环遍历numpeaks次,即检测指定数量的峰值。找到最大值:在tempH中找到最大值及其索引。max函数返回tempH中的最大值max_val和最大值的线性索引max_idx。判断最大值是否小于阈值:如果最大值max_val小于设定的阈值threshold,则表示没有更多的峰值满足条件,停止检测。将最大值的索引转换为极径和角度索引:使用ind2sub函数将最大值的线性索引max_idx转换为对应的极径索引rho_idx和角度索引theta_idx。将峰值的极径和角度索引存储到峰值数组中:将当前峰值的极径索引rho_idx和角度索引theta_idx存储到峰值数组的第i行。抑制局部最大值附近的区域:将tempH中最大值的位置及其周围的8个邻域位置置零,以避免检测到相邻的峰值。最终,函数返回峰值数组peaks,其中存储了检测到的霍夫峰值的极径和角度索引。这些峰值可以用于后续的直线提取和分析。
function peaks = simple_houghpeaks(H, numpeaks, threshold)
peaks = zeros(numpeaks, 2); % 初始化峰值数组
tempH = H; % 创建H的副本,以便修改
for i = 1:numpeaks
% 找到最大值
[max_val, max_idx] = max(tempH(:));
if max_val < threshold
% 如果最大值小于阈值,则停止
peaks = peaks(1:i-1, :);
break;
end
[rho_idx, theta_idx] = ind2sub(size(H), max_idx);
peaks(i, :) = [rho_idx, theta_idx];
% 抑制局部最大值附近的区域
tempH(max(rho_idx-1, 1):min(rho_idx+1, size(H, 1)), ...
max(theta_idx-1, 1):min(theta_idx+1, size(H, 2))) = 0;
end
end
MATLAB自编函数实现Hough变换实现直线提取
自编函数实现直线提取,初始化输出变量lines:函数创建一个空的结构体数组lines,用于存储检测到的直线的起点、终点、角度和距离。遍历检测到的峰值点:函数使用一个循环遍历检测到的所有峰值点。获取当前峰值点的距离和角度:根据当前遍历到的峰值点在rho和theta中的索引,获取对应的距离值rho_peak和角度值theta_peak。创建x轴坐标序列并计算y坐标值:根据直线的极坐标方程,创建一个x轴坐标序列,并根据该直线方程计算出可能位于该直线上的所有y坐标值。筛选有效的y坐标索引:根据图像的有效高度范围,筛选出位于有效范围内的y坐标索引。提取直线的起点和终点:如果存在有效的y坐标,提取这些坐标对应的x坐标值,并将第一个坐标作为直线的起点,最后一个坐标作为直线的终点。将直线的起点、终点、角度和距离添加到结果结构体数组lines:将计算得到的直线的起点、终点、角度和距离添加到结果结构体数组lines的末尾。最终,函数返回存储了检测到的直线信息的结构体数组lines。每个元素包含直线的起点、终点、角度和距离。这些直线可以用于后续的直线绘制和分析。
function lines = simple_houghlines(I, theta, rho, peaks)
%定义一个名为simple_houghlines的函数,输入参数包括:图像矩阵I、
% 角度向量theta、距离向量rho以及检测到的峰值点集合peaks
% 初始化输出变量lines为一个结构体数组,每个元素分别包含直线
% 起点(point1)、终点(point2)、对应的极坐标角度(theta)和距离(rho)
lines = struct('point1', {}, 'point2', {}, 'theta', {}, 'rho', {});
% 遍历检测到的所有峰值点,peaks(i,:)表示第i个峰值点在rho和theta中的索引
for i = 1:size(peaks, 1)
% 获取当前遍历到的峰值点对应的距离值rho_peak和角度值theta_peak
rho_peak = rho(peaks(i, 1));
theta_peak = theta(peaks(i, 2));
% 创建x轴坐标序列,并根据直线的极坐标方程计算出可能位于该直线上的所有y坐标值
x = 1:size(I, 2);
y = (rho_peak - x * cosd(theta_peak)) / sind(theta_peak);
y = round(y);
% 计算并筛选出位于图像有效高度范围内的y坐标索引
valid_idx = y > 0 & y <= size(I, 1);
% 如果存在有效的y坐标,则提取这些坐标对应的x坐标值
if any(valid_idx)
x = x(valid_idx);
y = y(valid_idx);
lines(end+1).point1 = [x(1), y(1)];
lines(end).point2 = [x(end), y(end)];
% 将计算得到的第一对(x, y)坐标作为直线起点(point1),
% 最后一对(x, y)坐标作为直线终点(point2),并将它们添加到结果结构体数组lines的末尾
lines(end).theta = theta_peak;
lines(end).rho = rho_peak;
end
end
end
绘制出直线在ρ,θ域的图像
(4)对直线进行去重操作
详细理解见代码
(5)计算去重后的直线两两之间的交点
自编函数实现获取两条直线之间的交点,该函数名为lineIntersection,其作用是计算两条直线的交点坐标。函数接收四个参数,分别是直线1上的两个点(lineone_pointone和lineone_pointtwo)以及直线2上的两个点(linetwo_pointone和linetwo_pointtwo)。每个点都是一个包含x坐标和y坐标的二维向量。函数首先提取这两条直线的端点坐标值,并分别存储在变量x1, y1, x2, y2, x3, y3, x4, y4中。接下来,函数利用直线方程的一般形式通过解析几何的方法来计算交点坐标:计算交点的x坐标值:使用两直线线段的系数交叉相乘后除以两线段斜率之差。计算交点的y坐标值:同样采用类似的方法,但针对的是y坐标相关的表达式。最后,将计算得到的交点坐标x和y封装到一个新的二维向量intersection中,并将其作为函数的返回值输出。如果两条直线相交,则返回交点坐标;若平行或重合,则结果可能没有实际意义,需要额外检查这种情况。
function vertices = computeRectangleVertices(lineone_pointone, lineone_pointtwo, linetwo_pointone, ...
linetwo_pointtwo, linethree_pointone, linethree_pointtwo, linefour_pointone,linefour_pointtwo)
% 计算直线1和直线2的交点
intersection1 = lineIntersection(lineone_pointone, lineone_pointtwo, linetwo_pointone,linetwo_pointtwo);
% 计算直线2和直线3的交点
intersection2 = lineIntersection(linetwo_pointone, linetwo_pointtwo, linethree_pointone,linethree_pointtwo);
% 计算直线3和直线4的交点
intersection3 = lineIntersection(linethree_pointone,linethree_pointtwo, linefour_pointone, linefour_pointtwo);
% 计算直线4和直线1的交点
intersection4 = lineIntersection(linefour_pointone, linefour_pointtwo, lineone_pointone, lineone_pointtwo);
% 确定矩形的四个顶点
vertices = [intersection1; intersection2; intersection3; intersection4];
end
function intersection = lineIntersection(lineone_pointone, lineone_pointtwo, linetwo_pointone,linetwo_pointtwo)
% 提取直线1和直线2的点坐标
x1 = lineone_pointone(1);
y1 = lineone_pointone(2);
x2 = lineone_pointtwo(1);
y2 = lineone_pointtwo(2);
x3 = linetwo_pointone(1);
y3 = linetwo_pointone(2);
x4 = linetwo_pointtwo(1);
y4 = linetwo_pointtwo(2);
% 计算直线1和直线2的交点坐标
x = ((x1*y2 - y1*x2)*(x3 - x4) - (x1 - x2)*(x3*y4 - y3*x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4));
y = ((x1*y2 - y1*x2)*(y3 - y4) - (y1 - y2)*(x3*y4 - y3*x4)) / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4));
% 返回交点坐标
intersection = [x, y];
end
% 计算两个点之间的距离
function distance = calculateDistance(x1, y1, x2, y2)
distance = sqrt((x2 - x1)^2 + (y2 - y1)^2);
end
对矩形的顶点以及标尺的左右顶点定位
(6)利用欧式距离公式计算矩形的长和宽
将检测到的可能的直线在边缘检测二值图像钟画出来,并求出这几条线的交点,在图像中将这些点标记出来,最后就是通过标尺的实际长度除以标尺计算得到的长度,通过该操作得到一个像素点代表的实际长度,将得到的这一比例换算信息分别乘以计算得到的直线交点之间的距离,即得到矩形的长和宽。结果输出如图所示。
(7)标尺的比例转换
对于标尺,我们利用扫黑白跳变点的方法找到标尺的两个端点,并将其标记为红色:对于矩形,我们直接根据直线求出四个交点即矩形顶点,也标记为红色,如图3.5所示,将标记出来的点的坐标打印在GUI显示界面,然后计算绿色线段上红色圆点两点之间的长度。根据图3.4结果可知,此程序可以精确地找出标尺的左右端点的坐标,进而计算出标尺的实际长度与坐标长度之间的比例,得到比例尺,以标尺长度为基准,根据定位的矩形物体的四个顶点的坐标,利用欧氏距离计算得到矩形物体四个边在坐标中的长度,通过比例尺的转换,得到矩形物体边缘的测量长度如图3.6,物体实际长度:85mm,测量长度:85.880mm,物体实际宽度:52mm,测量宽度:52.922mm,可以看到,计算结果与物体的实际长度基本一致,误差在允许范围之内,实验结果达到预期。
四、总结
本项目的实验方法具有一些优点,例如精度较高,代码简单易行。这些优点使我们能够获得较为准确的结果,并且可以在短时间内完成实验。我们的实验方法也有一些限制和缺点,如图像的清晰度,光照条件等。这些因素可能会导致结果的误差或不准确性。