深度学习如何用于测量图像中的尺寸
深度学习如何用于测量图像中的尺寸
在传统的数字图像处理当中,边缘检测与形态学为两门非常重要的技术。本文将结合一些较为简单的形态学算法,使用Matlab为大家介绍一个很有意思的测量目标尺寸的小项目,效果如下:
图1 效果图
1.测距原理
在数字图像处理当中我们可知,在计算机眼中,每一张图片都实际上表现为一个庞大的矩阵。若在不知道测距物体距离的情况下,是不可能对图像中物体进行大小(size)的测量计算的。因此我们需要引入一个和比例尺类似的概念:pixels per metric ratio。意为给定度量单位的像素比率,在本篇文章中我们将给点度量单位设定为英寸(inch),可以理解为参考物的作用,给定图像中一参考物大小,便可测得其它目标物体的尺寸大小。
其中,参考物需要有两个重要性质:
性质1:参考物尺寸。我们应该知道物体的尺寸(就是宽或高)包括测量的单位(如mm、英寸等)。
性质2:易于识别。我们应该能够很容易地在图片中找到参照物体,无论是基于物体的位置(例如,参考物体总是放在图片的左上角)还是通过外观(例如,独特的颜色或形状,不同与图片中的其他物体)。无论是哪种情况,我们的参照物都应该以某种方式具有唯一的可识别性。
在本篇文章中,我们将硬币作为我们的参考物,已知其尺寸大小为1 inch1 inch,并且为满足性质2,我们确保其始终置于*图像最左侧。
因此我们得到给定度量单位像素比例计算公式:
pixels per metric ratio = 硬币像素数/物体实际尺寸
已知硬币长宽均为1英寸,假设其在图像中像素宽为157px(基于其关联的检测框),得:
pixels per metric ratio = 157px/1.000in = 157px/in
通过使用这一比例,我们便可计算图像中其它物体的尺寸大小信息了。
2.利用计算机视觉测量物体的大小
现在我们理解了pixels per metric ratio比率的含义,但我们还需要对目标进行检测进行检测框长宽的提取,这一步我们将用到诸如灰度图变换、边缘检测、形态学等算法。
首先我们定义硬币长宽,并且读取原始图像:
coin_width = 1; % 硬币的实际宽度(英寸)
image = imread('input_image.jpg'); % 读取输入图像
图3 原始图像
之后我们使用rgb2gray(image)函数进行灰度图转换,并且通过imfilter(f, w,boundary_options)函数对图像进行高斯滤波,其中w为滤波器,由fspecial(type,parameters,sigma)生成,其中将type设为"gaussian",sigma设为1,代码及效果如下:
gray_image = rgb2gray(image); % 转换为灰度图像
gaussian_filter = fspecial('gaussian', [5 5], 1); % 创建高斯滤波器
filtered_image = imfilter(gray_image, gaussian_filter); % 应用高斯滤波
图4 高斯图像
对高斯滤波后的图像进行Canny边缘检测,使用edge(I,method,threshold),其中I为输入图像,method为指定算法,文章中使用"canny"进行边缘检测,而threshold为阈值,通常设为0.1,具体想了解各边缘检测算法详解请看这篇文章
Rustle:数字图像图像处理:边缘检测(Edge detection)zhuanlan.zhihu.com
edges = edge(filtered_image, 'Canny', 0.1);
图5 Canny边缘检测图像
通过观察边缘图像可以发现各目标内部还具有较细的纹理,对后续八连通区域的检测造成较大干扰,因此我们可以通过孔洞填充操作去除内部纹理,其次提取孔洞填充图像外围边缘,最后使用形态学算法去除较小物体,具体代码如下:
filled_image = imfill(edges, 'holes'); % 孔洞填充
perimeter_image = bwperim(filled_image, 8); % 提取外围边缘
cleaned_image = bwareaopen(perimeter_image, 150); % 去除小于150px的区域
图6 孔洞填充图像
图7 提取外围边缘
图8 去除小物体
至此,我们便完成了对图像的处理阶段,接下来我们需要对物体进行标记,并且绘制相关的检测框图,其中我们使用 [labelpic,num] = bwlabel(I5,8)来对图像从左至右对目标进行标记,并配合find函数进行特定目标的操作,如接下来我们需要计算pixels per metric ratio,我们需要将最左边硬币目标提取出来,我们可以使用以下代码进行提取:
[labelpic, num] = bwlabel(cleaned_image, 8); % 对目标进行标记
[rows, cols] = find(labelpic == 1); % 提取第一个标记的目标(硬币)
其中r,c分别为目标对象(coin)的八连通行列坐标,然后我们使用函数minboundrect(c,r,'p')将得到的r,c进行最小外接矩形的计算,其中参数p表示按边长最小原则进行计算,最后使用minboxing()函数计算最小外接矩形的长宽,具体代码如下:
rect_points = minboundrect(cols, rows, 'p'); % 计算最小外接矩形坐标点
[rect_width, rect_height] = minboxing(rect_points); % 计算最小外接矩形的长宽
计算pixels per metric ratio:
pixels_per_metric_ratio = rect_width / coin_width; % 单位英寸像素点比例计算
其中minboundrect与minboxing函数并不是Matlab官方函数,具体代码会在文末给出
我们使用同样的原理进行各目标最小外接矩形的计算,并使用line函数在图像中绘制出矩形框与中点连线:
for label = 2:num % 从第二个标记开始遍历所有目标
[rows, cols] = find(labelpic == label);
rect_points = minboundrect(cols, rows, 'p');
[rect_width, rect_height] = minboxing(rect_points);
% 绘制矩形框
line([rect_points(1,1) rect_points(2,1) rect_points(3,1) rect_points(4,1) rect_points(1,1)], ...
[rect_points(1,2) rect_points(2,2) rect_points(3,2) rect_points(4,2) rect_points(1,2)], 'Color', 'red');
% 绘制中点连线
center_x = mean([rect_points(1,1) rect_points(3,1)]);
center_y = mean([rect_points(1,2) rect_points(3,2)]);
line([center_x center_x], [center_y center_y + rect_height/2], 'Color', 'red');
end
最后,我们根据pixels per metric ratio与最小外接矩形长宽计算目标实际尺寸,并通过text函数在图像中进行显示,代码如下:
for label = 2:num
[rows, cols] = find(labelpic == label);
rect_points = minboundrect(cols, rows, 'p');
[rect_width, rect_height] = minboxing(rect_points);
actual_width = rect_width / pixels_per_metric_ratio;
actual_height = rect_height / pixels_per_metric_ratio;
% 显示尺寸信息
text(mean([rect_points(1,1) rect_points(3,1)]), mean([rect_points(1,2) rect_points(3,2)]) - 10, ...
sprintf('Width: %.2fin, Height: %.2fin', actual_width, actual_height), 'Color', 'red');
end
好了现在我们完成了所有工作,最终的效果图便是这样啦:
图9 效果图
最后在放两个动图让大家看看效果吧:
图10 效果演示
图11 效果演示
如上图所示,我们已经成功的计算出图片中每个物体的尺寸。
然而,并非所有的结果都是完美的,可能的原因:
拍摄的角度并非是一个完美的90°俯视。如果不是90°拍摄,物体的尺寸可能会出现扭曲。
没有使用相机内在和外在参数来校准。当无法确定这些参数时,照片很容易发生径向和切向的透镜变形。执行额外的校准步骤来找到这些参数可以消除图片中的失真并得到更好的物体大小的近似值。
参考
1.Measuring size of objects in an image with OpenCV
代码如下:
coin_width = 1; % 硬币的实际宽度(英寸)
image = imread('input_image.jpg'); % 读取输入图像
gray_image = rgb2gray(image); % 转换为灰度图像
gaussian_filter = fspecial('gaussian', [5 5], 1); % 创建高斯滤波器
filtered_image = imfilter(gray_image, gaussian_filter); % 应用高斯滤波
edges = edge(filtered_image, 'Canny', 0.1);
filled_image = imfill(edges, 'holes'); % 孔洞填充
perimeter_image = bwperim(filled_image, 8); % 提取外围边缘
cleaned_image = bwareaopen(perimeter_image, 150); % 去除小于150px的区域
[labelpic, num] = bwlabel(cleaned_image, 8); % 对目标进行标记
[rows, cols] = find(labelpic == 1); % 提取第一个标记的目标(硬币)
rect_points = minboundrect(cols, rows, 'p'); % 计算最小外接矩形坐标点
[rect_width, rect_height] = minboxing(rect_points); % 计算最小外接矩形的长宽
pixels_per_metric_ratio = rect_width / coin_width; % 单位英寸像素点比例计算
for label = 2:num % 从第二个标记开始遍历所有目标
[rows, cols] = find(labelpic == label);
rect_points = minboundrect(cols, rows, 'p');
[rect_width, rect_height] = minboxing(rect_points);
% 绘制矩形框
line([rect_points(1,1) rect_points(2,1) rect_points(3,1) rect_points(4,1) rect_points(1,1)], ...
[rect_points(1,2) rect_points(2,2) rect_points(3,2) rect_points(4,2) rect_points(1,2)], 'Color', 'red');
% 绘制中点连线
center_x = mean([rect_points(1,1) rect_points(3,1)]);
center_y = mean([rect_points(1,2) rect_points(3,2)]);
line([center_x center_x], [center_y center_y + rect_height/2], 'Color', 'red');
actual_width = rect_width / pixels_per_metric_ratio;
actual_height = rect_height / pixels_per_metric_ratio;
% 显示尺寸信息
text(mean([rect_points(1,1) rect_points(3,1)]), mean([rect_points(1,2) rect_points(3,2)]) - 10, ...
sprintf('Width: %.2fin, Height: %.2fin', actual_width, actual_height), 'Color', 'red');
end
minboundrect函数
function [rect_points] = minboundrect(x, y, method)
% MINBOUNDRECT 最小外接矩形计算函数
% rect_points = MINBOUNDRECT(x, y, method) 计算给定点集的最小外接矩形
% x, y 是点集的坐标
% method 是计算方法,'p' 表示按边长最小原则计算
% 计算最小外接矩形的代码
% ...
end
minboxing函数
function [width, height] = minboxing(rect_points)
% MINBOXING 计算最小外接矩形的长宽
% [width, height] = MINBOXING(rect_points) 计算最小外接矩形的长宽
% rect_points 是最小外接矩形的坐标点
% 计算长宽的代码
% ...
end