问题重述
已知书本上右下角放一枚一元人民币(直径2.5厘米),请利用计算机视觉技术预测图片中目标的实际尺寸。
手动或自动地对已知物体进行标注和测量,得到其尺寸信息(例如像素大小)。 对图像进行预处理,包括灰度化、二值化、去除噪声等操作,以便更好地分割和识别图像中的物体。 利用物体检测或图像分割技术找到图像中待测物体的位置。 根据待测物体在图像中的像素大小和已知物体的像素大小之间的比例关系,计算出待测物体的实际尺寸。
先对图片进行预处理,后进行高斯模糊(通过测试设置合理的阈值,从而得到较好的效果),通过闭运算增加边缘连通性,用来填充物体内的小空洞,连接断开的轮廓线,由于边缘检测出来的轮廓线通常是不完整的,我们就可以通过这种方法来加强边缘,得到完整的边缘。为了获取到我们书本的轮廓,我们要提取最大的轮廓。然后提取通过霍夫变换检测直线,求出书本轮廓的四条直线的角点,也就是书本的四个角(求角点的时候要进行筛选,筛选出符合要求的点)。再通过角点以及透视变换对图片进行矫正,具体思路是先求出投影映射矩阵:根据4个基准坐标,然后反求出源图像坐标:根据映射矩阵内参数,求一个二元一次方程组。得到矫正后的图片后我们同样要进行高斯模糊。之后我们对图片使用圆形 Hough 变换查找圆,找出最符合的两个圆,从而能求出书本的宽高和手画的圆的直径。
书本边缘(显示角点):
透视变换后的图片:
Hough 变换查找圆,并画出来:
结果输出:
本次实验运用了较多计算机视觉相关知识,本人能通过课堂所学结合个人想法完成本次实验要求,较符合个人预期,能通过矫正图片等创新方法使得结果更加准确。但也存在一些不足,例如对手画圆的检测并不能十分精准,精度还有待提高。
[1]图像透视变换原理及现:https://blog.csdn.net/cuixing001/article/details/80261189
[2]透视变换的实现以及透视变换矩阵的构造: https://blog.csdn.net/overflow_1/article/details/80330835
[3]opencv实战–物体尺寸量:https://blog.csdn.net/mbtt00/article/details/107918654
% 读入图片并显示
img = imread('image.jpg');
%imshow(img);
% 缩小图片,方便处理
img = imresize(img, 0.5);
% 灰度化
grayImg = rgb2gray(img);
% 高斯模糊
blurImg = imgaussfilt(grayImg, 0.5); % 增加0.5个像素的模糊半径
% Canny边缘检测
edgeImg = edge(blurImg, 'Canny', [0.05, 0.15]); % 设置阈值0.05和0.15
%imshow(edgeImg);
% 闭运算,增加边缘连通性
se = strel('disk', 200); % 200像素大小的圆形结构元素
closedImg = imclose(edgeImg, se);
%imshow(closedImg);
% 提取轮廓
contourImg = bwperim(closedImg, 8); % 连续8个像素才能构成边缘
stats = regionprops(contourImg, 'BoundingBox', 'Area'); % 提取轮廓属性
% 霍夫变换检测直线
[H,T,R] = hough(contourImg);
P = houghpeaks(H, 50);
lines = houghlines(contourImg, T, R, P);
% 直线筛选
maxLength = 0;
longestLines = [];
for k = 1:length(lines)
xy = [lines(k).point1; lines(k).point2];
% 计算直线长度
lineLength = norm(xy(1,:) - xy(2,:));
% 判断是否为最长直线
if lineLength > maxLength
longestLines(end+1) = k;
if length(longestLines) > 4
[~, idx] = min(lineLength);
longestLines(idx) = [];
end
maxLength = lineLength;
end
end
% 计算直线交点
corners = zeros(4,2);
idx = 1;
for i = 1:length(longestLines)
for j = i+1:length(longestLines)
xy1 = [lines(longestLines(i)).point1; lines(longestLines(i)).point2];
xy2 = [lines(longestLines(j)).point1; lines(longestLines(j)).point2];
intPoint = lineIntersect(xy1, xy2);
if(intPoint(1)<640)
if(intPoint(2)>0)
corners(idx,:) = intPoint;
idx = idx + 1;
end
end
end
end
imshow(contourImg);title('书本边缘(显示角点)');
hold on;
plot(corners(:,1), corners(:,2), 'ro', 'MarkerSize', 10); % 显示角点
%plot(xy1(:,1),xy1(:,2),'LineWidth',1,'Color','red');
%plot(xy2(:,1),xy2(:,2),'LineWidth',1,'Color','green');
% 找到最大的轮廓,即书本的轮廓
[~, idx] = max([stats.Area]);
bookBox = stats(idx).BoundingBox;
disp(size(corners))
% 获取书本轮廓的图像并进行矫正
%bookContour = imcrop(img, bookBox);
% 显示矫正后的图像
%figure;
%imshow(bookContour);
[m,n] = size(grayImg);
dot=corners; %取四个点,依次是左上,右上,左下,右下
dot(:,[1,2])= dot(:,[2,1]); % 变换x y坐标,x=行 y=列
col=round(sqrt((dot(1,1)-dot(2,1))^2+(dot(1,2)-dot(2,2))^2)); %从原四边形获得新矩形宽
row=round(sqrt((dot(1,1)-dot(3,1))^2+(dot(1,2)-dot(3,2))^2)); %从原四边形获得新矩形高
new_img = ones(row,col);
% 原图四个基准点的坐标
x = [dot(1,1),dot(2,1),dot(3,1),dot(4,1)];
y = [dot(1,2),dot(2,2),dot(3,2),dot(4,2)];
% 新图四个基准点坐标
X = [1,1,row,row];
Y = [1,col,1,col];
% 列出投影关系 求出投影矩阵
A=[x(1),y(1),1,0,0,0,-X(1)*x(1),-X(1)*y(1);
0,0,0,x(1),y(1),1,-Y(1)*x(1),-Y(1)*y(1);
x(2),y(2),1,0,0,0,-X(2)*x(2),-X(2)*y(2);
0,0,0,x(2),y(2),1,-Y(2)*x(2),-Y(2)*y(2);
x(3),y(3),1,0,0,0,-X(3)*x(3),-X(3)*y(3);
0,0 ,0,x(3),y(3),1,-Y(3)*x(3),-Y(3)*y(3);
x(4),y(4),1,0,0,0,-X(4)*x(4),-X(4)*y(4);
0,0,0,x(4),y(4),1,-Y(4)*x(4),-Y(4)*y(4)];%求解变换矩阵的行列式
B = [X(1),Y(1),X(2),Y(2),X(3),Y(3),X(4),Y(4)]';
C = inv(A)*B;
D = [C(1),C(2),C(3);
C(4),C(5),C(6);
C(7),C(8),1]; % 变换矩阵3*3模式
inv_D = inv(D);
for i = 1:row
for j = 1:col
% 解二元一次方程组,根据目标图像坐标反求出原图坐标
pix = inv_D * [i j 1]';
pix1 = inv([C(7)*pix(1)-1 C(8)*pix(1);C(7)*pix(2) C(8)*pix(2)-1])*[-pix(1) -pix(2)]';
if pix1(1)<m && pix1(2)<n
new_img(i,j) = img(round(pix1(1)),round(pix1(2))); %最近邻插值
else
new_img(i,j) = 255;
end
end
end
figure;
new_img=flipud(new_img);%上下翻转
%图像归一化
Bmax=max(max(new_img));
Bmin=min(min(new_img));
new_img=(new_img-Bmin)/(Bmax-Bmin);
imshow(new_img);title('透视变换后')
new_img = imgaussfilt(new_img, 2);
new_img=edge(new_img, 'Canny', [0.05, 0.15]); % 设置阈值0.05和0.15
figure;
imshow(new_img);title('边缘检测')
[centers,radii,metric] = imfindcircles(new_img,[20,500])
centersStrong2 = centers(1:2,:);
radiiStrong2 = radii(1:2);
metricStrong2 = metric(1:2);
viscircles(centersStrong5, radiiStrong5,'EdgeColor','b');
%长为A,宽为B
[A,B]=size(new_img);
A=(A/radiiStrong2(1,1)*0.5)*2.5;
B=(B/radiiStrong2(1,1)*0.5)*2.5;
%外圆直径
W=(radiiStrong2(2,1)/radiiStrong2(1,1))*2.5;
disp('书本宽为:'+string(B)+'cm'+',书本长为:'+string(A)+'cm');
disp('铅笔所画的圆的外圆直径为:'+string(W)+'cm');
function intPoint = lineIntersect(line1, line2)
% 计算两条直线的交点
% line1: 直线1的两个端点坐标,[x1,y1; x2,y2]
% line2: 直线2的两个端点坐标,[x3,y3; x4,y4]
% 计算直线参数
A1 = line1(1,2) - line1(2,2);
B1 = line1(2,1) - line1(1,1);
C1 = line1(1,1)*line1(2,2) - line1(2,1)*line1(1,2);
A2 = line2(1,2) - line2(2,2);
B2 = line2(2,1) - line2(1,1);
C2 = line2(1,1)*line2(2,2) - line2(2,1)*line2(1,2);
% 计算交点
D = A1*B2 - A2*B1;
if D ~= 0
x = (B1*C2 - B2*C1) / D;
y = (A2*C1 - A1*C2) / D;
intPoint = [x, y];
else
intPoint = [nan, nan];
end
end