前言
实验中经常需要测量一个物体的角度变化,如果开发一个图像处理的方法可以大大节省我们的时间,这次我们使用霍夫变换等进行物体夹角的预测。
任务
图像如下图所示
我们需要检测的夹角示意图为:
因此整个任务可以分解成两个子任务:
- 检测红边的直线
- 检测绿边的直线
之后通过两条直线的k就可以算出夹角,由于实验中红边的位置几乎固定,根据绿边k的符号就可以判断夹角是锐角还是钝角。
检测红边
很明显,红边所在的位置即为交界处,可以使用边缘检测算子,但是实际的环境很难排除噪声,因此我们这里使用基于颜色的聚类方式首先将各个部分分割出来,代码为:
img = imread('test.jpg');
%% kmeans
%通过聚类及边界提取首先获取垂直线的坐标
numOfClass = 5;
mask = kmeansS(double(img),numOfClass);
代码中KmeanS会放在下面,这里对MATLAB内置的kmeans函数进行了一个简单的封装,得到的结果为:
这条边界现在看起来十分清晰,但是由于这种基于颜色的聚类并未考虑到空间信息,其他的图像中会存在一个大区域的一种标签内含一个小区域的现象,因此我们可以直接使用填充空洞对结果进行一个小小的改进:
%填充空洞
for kkk= 1:numOfClass
maskt = mask==kkk;
maskt = imfill(maskt,'holes');
mask(maskt) = kkk;
end
改进之后可以直接使用log算子提取边界,这里不要使用canny算子,因为它会使直线变得弯曲:
mask_edge = imfilter(mask,fspecial( 'log'));
mask_edge = mask_edge>0;
下面,我们可以直接把左下角的部分直接摘出来使用霍夫变换检测直线:
mask_edge_ld = false(size(mask_edge));
[M,N] = size(mask_edge);
%提取左下角的部分拟合直线
mask_edge_ld(end-floor(M/3):end,end-floor(N/3):end) = mask_edge(end-floor(M/3):end,end-floor(N/3):end);
[H,T,R] = hough(mask_edge_ld,'RhoResolution',0.5,'Theta',-90:0.5:89);
P = houghpeaks(H,5,'threshold',ceil(0.3*max(H(:))));
lines = houghlines(mask_edge_ld,T,R,P,'FillGap',5,'MinLength',15);
max_len = 0;
for k = 1:length(lines)
xy = [lines(k).point1; lines(k).point2];
% Determine the endpoints of the longest line segment
len = norm(lines(k).point1 - lines(k).point2);
if ( len > max_len)
max_len = len;
xy_long = xy;
end
end
上面这一节代码中我们把最长的直线选择了出来,绘制出来的直线示意图为:
虽然只有那么一小截,但这个也足够了,接着我们就开始处理弯曲的物体了,右下部分不需要了,我们裁出来即可:
%直接选取最长的直线作为参考,此节处理完毕,下面开展另外一条边
if xy_long(1,1) == xy_long(2,1)%垂直线
xy_points = zeros(min([xy_long(1,2),xy_long(2,2)]),2);
xy_points(1:end,2) = min([xy_long(1,2),xy_long(2,2)]):-1:1;
xy_points(1:end,1) = xy_long(1,1);
else%非垂直线
%在这里有个技巧,需要将x,y的坐标轴转换一下
funct = polyfit([xy_long(1,2),xy_long(2,2)],[xy_long(1,1),xy_long(2,1)],1);
xy_points = zeros(min([xy_long(1,2),xy_long(2,2)]),2);
xy_points(1:end,2) = min([xy_long(1,2),xy_long(2,2)]):-1:1;
xy_points(1:end,1) = polyval(funct, xy_points(1:end,2));
end
%从直线的顶点开始一直往上搜寻,根据边缘数量判断中心点
xy_points = floor(xy_points);
if xy_long(1,2)<xy_long(2,2)
newM = xy_long(1,2);
newN = xy_long(1,1);
else
newM = xy_long(2,2);
newN = xy_long(2,1);
end
new_img= img(1:newM,1:newN,:);
这一节包括两部分,第一部分是计算红边在图像中的所有位置,代码中自变量设置成了纵坐标,因变量设置成了横纵标,相当于进行了一下变换。截取后的图像为:
显然干净了很多,也没有其余部分影响,下面我们使用图像中的色调对图像进行二值化把这个弯曲的物体先分割出来:
hsv = rgb2hsv(new_img);
second_mask = imbinarize(hsv(:,:,1));
second_mask_edge = imfilter(second_mask,fspecial('sobel'));
CC = bwconncomp(second_mask_edge);
stats = regionprops(CC,'MajorAxisLength');
mls = cat(1,stats.MajorAxisLength);
for k = 1:CC.NumObjects
if mls(k) <8
second_mask_edge(CC.PixelIdxList{k}) = 0;
end
end
分割出来之后使用sobel算子提取边界,之后根据二值化组分中的主轴长度排除存在的噪声点,之后从图像的右侧到左侧选取8个点拟合直线:
%选取8个点
pointst= zeros(8,2);
count = 1;
for k = size(second_mask,2):-1:1
if sum(second_mask_edge(:,k)) <10 && sum(second_mask_edge(:,k)) >1
ind = find(second_mask_edge(:,k));
pointst(count,1) = k;
pointst(count,2) = ind(1);
count = count +1;
if count>8
break;
end
end
end
直线拟合之后使用https://zhidao.baidu.com/question/1731029675143733347.html的公式即可计算夹角:
funct2 = polyfit(pointst(:,2),pointst(:,1),1);
if numel(unique(xy_points(:,1))) == 1
angle =atan(abs(funct2(1)))*180/pi;
else
angle =atan(abs((funct(1)-funct2(1))./(1+funct(1).*funct2(1))))*180/pi;
end
if funct2(1)>0
angle = 180- angle;
end
刚开始计算出来的夹角为锐角,根据实际情况即可重新调整。
有疑问欢迎评论交流~