前面写的一篇描述了基于占空比的方法对简单图形的识别,今天继续对里面的代码进行一点小小的补充。主要是对图形的轮廓进行校验,仅仅利用占空比对于不是十分严格 的图形很可能造成误判,添加点校验的代码应该会好点。限于时间和能力,仅仅对圆形进行了简单的代码补充,其他的图形操作基本相同,只是后续填充进入相关代码即可。
1 读入图像并完成二值化;
2 区域标记和分割,并对分割出的局部图进行加边框;
3 提取局部图的轮廓;
4 调用判别函数进行图形形状的判别;
5 显示判断结果。
clc;
clear all;
t0 = clock;
%% 图像读入,灰度化,二值化,显示二值图
img=imread('F.jpg');
img = rgb2gray(img);
BW=~im2bw(img); % 背景用0表示,有效区域用1表示
figure;imshow(BW),title('二值显示');
%% 连通域切割,8连通域
% 图像连通域标记
[Local,image_num] = bwlabel(BW,8);
% RGB = label2rgb(Local);
% figure;imshow(RGB),title('rgb显示连通域');
% 连通域提取步骤:
Multi_pic = Multi_Local_f(Local,image_num);
MultiPic_cell = cell(1,image_num); % 保存局部图
MultiPic_cell_contour = cell(1,image_num); % 保存局部图的轮廓
for i=1:image_num
Local_shape = [];
Postion_range = EdgeCheck_f(Multi_pic,i);
% 提取的轮廓是经过扩充边界2像素的边框,要注意
Local_shape = Image_Cut_f(Local,Postion_range,i);
% 保存并显示局部图像
MultiPic_cell{i} = Local_shape;
figure,imshow(Local_shape);
% 跟踪搜索得轮廓链表,将各图的轮廓进行保存
lb = lb_get_f(Local_shape);
MultiPic_cell_contour{i} = lb;
end
%% 判断图像类型并显示各个图像
for i =1:image_num
% 占空比法判断图像的类型,参数:局部图和局部图轮廓链表
Class_name{i} = ratio_search_f(MultiPic_cell{i},MultiPic_cell_contour{i});
figure;imshow(MultiPic_cell{i}),title(['this is ', Class_name{i}]);
end
%% 耗时计算
Time_consumed = etime(clock,t0);
circle_check_f.m 函数代码:
function Sim_coe = circle_check_f( coutour_LB )
% 作用;对参数传递进来的轮廓进行圆效验,检测是否为圆形
% 原理:找到其最大最小圆,看看二者的半径差异有多大,小于2说明很圆
% coutour_LB:圆形轮廓的坐标链表
% Sim_coe:相似度系数
LB = coutour_LB;
% 取出最大和最小x,y坐标,计算对称中心
Lmax = max(LB(:,2));
Lmin = min(LB(:,2));
Hmax = max(LB(:,1));
Hmin = min(LB(:,1));
L0 = (Lmax + Lmin)/2;
H0 = (Hmax + Hmin)/2;
if (Lmax - Lmin) ~= (Hmax - Hmin)
disp('circle_check_f( coutour_LB )函数错误!图像宽高不同,不符合标准圆的特征!');
end
M = size(LB,1);
dist_stat = [];
for i =1:3:M
% 取轮廓上的各点到中心的距离并保存
x = LB(i,2);
y = LB(i,1);
dist = sqrt((x - L0)^2 + (y - H0)^2);
dist_stat =[dist_stat;dist];
end
% 返回最大和最小半径的差值,如果小于2说明很圆
if max(dist_stat)-min(dist_stat) <= 2
Sim_coe = 1;
else
Sim_coe = 0;
end
end
EdgeCheck_f.m 函数代码:
function Position_range = EdgeCheck_f(Multi_Image,num_Label)
% 作用:找出标记图像(Multi_Image中标号为num_Label的局部图的坐标范围
% Image_Labeled:标记图像
% num_Label:区域标记号
% [x1,y1,x2,y2]:标记区域的左上角和右下角坐标,x是列,y是行
X_MAX=0;X_MIN =0;Y_MAX=0;Y_MIN =0;
Image_Labeled = Multi_Image(:,:,num_Label);
% 计算指定标记的区域范围
H = size(Image_Labeled,1);
% 找出X_MIN和X_MAX
for j=1:H
% 从上到下,逐行寻找等于1的行
if max(Image_Labeled(j,:))==1
Y_MIN = j;
break;
end
end
for j=1:H
% 从下到上,逐行寻找等于1的行
if max(Image_Labeled(H+1-j,:))==1
Y_MAX = H+1-j;
break;
end
end
L = size(Image_Labeled,2);
% 找出Y_MIN和Y_MAX
for k=1:L
% 从左到右,逐行寻找等于1的列
if max(Image_Labeled(:,k))==1
X_MIN = k;
break;
end
end
for k=1:L
% 从右到左,逐行寻找等于1的列
if max(Image_Labeled(:,L+1-k))==1
X_MAX = L+1-k;
break;
end
end
Position_range =[X_MIN,Y_MIN,X_MAX,Y_MAX];
end
Image_Cut_f.m函数代码:
function Image = Image_Cut_f( Labeled_Pic,Position,label )
% 根据参数指定的范围将图像切割出来称为一个单独的小图片
% Labeled_Pic:标记的图像
% Position:要拷贝的区域
% label:标签号
% Image:返回一个切割好的局部图
% 截取指定的区域
TEMP = Labeled_Pic(Position(2):Position(4),Position(1):Position(3));
for i = 1:size(TEMP,1)
for j = 1:size(TEMP,2)
if TEMP(i,j) ~= label
TEMP(i,j) = 0;
end
end
end
% 上下左右各扩展2个像素宽度,便于后续的找轮廓,否则轮廓寻找可能会出错
% 当局部图的像素点在图像边沿时会影响腐蚀,所以需要扩大图像使得局部图不和边界相连
[M,N] = size(TEMP);
kong = zeros(M + 4,N + 4);
kong(3:M+2,3:N+2) = TEMP;
Image = kong;
end
lb_get_f.m函数代码:
function lb = lb_get_f( img )
% 作用:将内轮廓图形中的各个像素点取出并保存到链表LB
% 方法:从Xmin的点开始沿顺时针方向搜索,直到回到起点。
% local_pic :二值图像,周围有2个像素宽度的富裕,有效值为1,背景0;
% lb: 轮廓链表,多行两列;
% 状态:成功!
[m,n]=size(img);
imgn=zeros(m,n); %边界标记图像
LB = [];
count = 0;
flag = 0;
% 按行找出第一个轮廓点
for i=1:m
for j = 1:n
if img(i,j) == 1
IH = i;
IL = j;
flag = 1;
break;
end
end
if flag == 1
break;
end
end
flag = 0;
i = IH ;
j = IL ;
imgn(i,j) = 1; % 标记起始点
LB = [LB;i,j];
ed=[-1 -1;0 -1;1 -1;1 0;1 1;0 1;-1 1;-1 0]; % 从左上角像素,逆时针搜索
while (count < m*n)
for k=1:8 %逆时针8邻域搜索
tmpi=i+ed(k,1); %8邻域临时坐标
tmpj=j+ed(k,2);
% 只要k-1 是0 ,k是1,就保存这个点,并更新点
if k == 1
tmpi2=i+ed(8,1); %八邻域临时坐标
tmpj2=j+ed(8,2);
else
tmpi2=i+ed(k-1,1); %八邻域临时坐标
tmpj2=j+ed(k-1,2);
end
% 只要k-1 是0 ,k是1,就保存这个点,并更新点
if img(tmpi,tmpj)==1 && img(tmpi2,tmpj2)==0
i=tmpi; %更新内部搜寻坐标,继续搜索
j=tmpj;
imgn(i,j)=1; %边界标记图像该像素标记,普通边界为1
LB = [LB;i,j];
if i == IH && j == IL && count >1 % 边界转完一圈了,回到了终点
flag = 1;
end
break;
end
end
% 跳出死循环,测试用
count = count + 1;
if flag ==1
break;
end
end
figure,imshow(imgn),title('寻找的内边沿轮廓,函数lb_get()');
lb = LB;
end
Multi_Local_f.m函数代码:
function Multi_pic = Multi_Local_f( Labeled_Pic,Label_num)
% 将标记好的图差分成多张图,每张图仅有一个标签
% 采用list结构存储效率更高,可惜MATLAB中没有对应数据类型
[m,n] = size(Labeled_Pic);
TEMP = zeros(m,n,Label_num);
for i = 1:m
for j = 1:n
for k = 1:Label_num
if Labeled_Pic(i,j) == k
TEMP(i,j,k) = 1;
break;
end
end
end
end
Multi_pic = TEMP;
end
ratio_search_f.m函数代码:
function Iamge_class = ratio_search_f( input_image,contour )
% 作用:利用图像的占空比判断图像是什么类型
% input_image:输入的局部图,二值图,有效区域是标签(1,2,3...),背景是0
% Iamge_class:图像类型名称,例如:矩形
% contour: 局部图对应的轮廓图
% 思路:
% 将三角形,矩形,五角形,六角形,圆形的占空比预先保存到一个元组中;
% 对输入图像计算占空比,然后和数组中的个元组素对比;
% 这只是个粗略的结果,根据得到了占空比进行邻近的几个排序,后续还需要进行轮廓验证
% 占空比模板库
Class_Module = {'triangle','rectangle','pentagon','hexagon','circle'};
Ratio_Module = [0.5,1,0.7,0.75,0.79];
% 图像大小
[m,n] = size(input_image);
% 计算占空比
index_vector = find(input_image ~= 0); % 统计有效点
%计算占空比,因为前面做局部图是给四周添加的2个像素宽度的背景,所以这里需要减去
RATIO = size(index_vector,1)/((m*n)-4*m - 4*(n-4));
% 占空比和模板中的占空比进行比较大小
TEMP = abs(Ratio_Module - RATIO);
ind = find(TEMP == min(TEMP));
% 图形轮廓校验,暂时这里只添加了圆形的检测
check_flag = circle_check_f(contour);
% 圆形轮廓检查
if ind == 5
if check_flag == 1
Iamge_class = Class_Module{ind};
disp('这个圆非常的圆!!!');
else
fprintf('这个图形可能是圆形,但是严格的圆形检查未通过!');
end
else
% 其他图形轮廓
Iamge_class = Class_Module{ind};
end
end
代码中的图形内边沿提取代码虽然针对矩形,圆形都没有问题,其他图形提取效果尚不明确。
后续需要做的工作就是将其他图形校验的函数添加到 ratio_search_f.m 函数文件尾部的校验部分即可。