l 设计任务:
ipa05.jpg是一幅电气柜上的电表图像,试采用图像处理与分析技术,设计适当的
算法和程序,找出电流表所在的区域,提取其指针位置,计算指针与表盘下沿
的夹角,进而判断当前电表的读数(提示:电流表的读数范围为 0~400A)。请
按统一要求写出算法原理、设计流程,并完成程序测试与分析等内容。
l 算法设计:
解题思路:处理图片使之只剩下三根电流表的指针,通过霍夫变换定位指针所对应的直线,通过直线与过0刻度的直线之间的角度算出此时电表的读数。
设计方案说明:
读入图片,转换成灰度图,然后使用阈值分割,去除一部分不重要信息。之后设置去除大连通域函数与去除小连通域函数的阈值,得到三根指针。然后通过腐蚀操作,得到最终可进行霍夫变换的图像。对于经过霍夫变换后得到的三条直线,使用“两端点x坐标和最大”的特点确定第一排左数第二个表对应的直线,使用“两端点y坐标和最大”的特点确定第二排左数第一个表对应的直线,剩余的直线对应第一排左数第二个表,如此确定直线对应的表。将直线化为单位向量,与水平方向的单位向量求夹角,并由“0~400A 对应 0~90度”的原则确定电表读数。
关键算法的设计原理:
转换成灰度图:降低信息维数,减少处理难度
阈值分割:表针为黑色,其灰度强度低,将灰度图小于某强度的像素点提取出来,可以去除很多不重要信息。
去除大/小连通域:由于三根指针的面积差不多,则可按面积的规则筛选出只有三根指针作为连通域的图像。
腐蚀操作:使指针变薄,以实现霍夫变换精准识别。
两个单位向量求夹角:高等数学。
算法步骤:
读取图像→转化为灰度图→阈值分割→去掉大连通域→去掉小连通域→腐蚀→霍夫变换→识别直线所在表→计算直线单位向量与基准向量求角度→计算电表读数并输出结果
l 程序设计:
算法名称:阈值分割、去除大连通域、开运算、腐蚀、霍夫变换
工具函数:
阈值分割:G<110,其中G为原图像经灰度转换得到的灰度图。
去除大连通域:removeLargeArea()函数,为bwareaopen()函数修改而来。作用是去掉像素面积为某个阈值以上的大连通域。
开运算:bwareaopen()。
腐蚀:strel()函数和imerode()函数。
霍夫变换:hough、houghpeaks、houghlines等。
设计分析:完成任务。
l 测试分析:
图1
图1从左到右,为原图→灰度图→阈值分割后图→去掉大连通域后图→去掉小连通域后图(即开运算)→腐蚀图像后图
其中阈值分割阈值取110。去掉大连通域的阈值取160,去掉小连通域阈值取120。腐蚀使用方形结构元素,宽度为2。
图2
图2的霍夫变换检测到了三根指针所对应的直线,符合要求。
图3
图3为电表读数结果,基本符合要求。
代码
removeLargeArea.m
function bw2 = removeLargeArea(varargin)
matlab.images.internal.errorIfgpuArray(varargin{:});
[bw,p,conn] = parse_inputs(varargin{:});
CC = bwconncomp(bw,conn);
area = cellfun(@numel, CC.PixelIdxList);
idxToKeep = CC.PixelIdxList(area <= p);
idxToKeep = vertcat(idxToKeep{:});
bw2 = false(size(bw));
bw2(idxToKeep) = true;
%%%
%%% parse_inputs
%%%
function [bw,p,conn] = parse_inputs(varargin)
narginchk(2,3)
bw = varargin{1};
validateattributes(bw,{'numeric', 'logical'},{'real', 'nonsparse'},mfilename,'BW',1);
if ~islogical(bw)
bw = bw ~= 0;
end
p = varargin{2};
validateattributes(p,{'double'},{'scalar' 'integer' 'nonnegative'},...
mfilename,'P',2);
if (nargin >= 3)
conn = varargin{3};
else
conn = conndef(ndims(bw),'maximal');
end
iptcheckconn(conn,mfilename,'CONN',3)
主程序
clc,clear
%% 预处理
I = imread('ipa05.jpg'); % 读入图片
G = rgb2gray(I); % 转化灰度
BW1 = G<110; % 阈值提取
%% 保留像素面积(120,160)间的连通域
BW2 = removeLargeArea(BW1,160);
BW3 = bwareaopen(BW2,120);
%% 腐蚀使区域厚度减小
SE = strel('square',2);
BW4 = imerode(BW3,SE);
%% 霍夫变换
[H,theta,rho] = hough(BW4);
peaks = houghpeaks(H,3);
lines = houghlines(BW4,theta,rho,peaks);
%% 用直线的相对位置识别对应的表
% 两端点x坐标和最大,则是第一排第二个表
% 两端点y坐标和最大,则是第二排第一个表
% 剩下的表是第一排第一个表
lines_info = zeros(3,4);
for i = 1:3
lines_info(i,1) = lines(i).point1(1)+lines(i).point2(1);% x坐标和
lines_info(i,2) = lines(i).point1(2)+lines(i).point2(2);% y坐标和
tmp=lines(i).point1 - lines(i).point2;
lines_info(i,3:4) = tmp/norm(tmp); % 单位向量
end
lines_vector = zeros(3,2);% 储存排序后的向量
a = sortrows(lines_info,'descend');
lines_vector(2,:) = a(1,3:4);
a = sortrows(lines_info,2,'descend');
lines_vector(3,:) = a(1,3:4);
lines_vector(1,:) = lines_info(lines_info(:,3) ~= ...
lines_vector(2) & lines_info(:,3)~=lines_vector(3),3:4);
%% 使用每条线的单位向量,并与基准单位向量比较,计算夹角
theta = [];
base = [1,0]; % 基准单位向量
for i = 1:3
theta = [theta acos(abs(dot(base,lines_vector(i,:)))...
/(norm(base)*norm(lines_vector(i,:))))*180/pi];
end
%% 由计算得到的夹角,输出表的读数
% 0~400A 对应 0~90度,则
disp("第一排左数第一个表读数"+num2str(theta(1)/90*400)+"A");
disp("第一排左数第二个表读数"+num2str(theta(2)/90*400)+"A");
disp("第二排左数第一个表读数"+num2str(theta(3)/90*400)+"A");
%% 测试
figure(1)
subplot(231),imshow(I);title("原图");
subplot(232),imshow(G);title("灰度图");
subplot(233),imshow(BW1);title("阈值分割后图");
subplot(234),imshow(BW2);title("去掉大连通域");
subplot(235),imshow(BW3);title("去掉小连通域");
subplot(236),imshow(BW4);title("腐蚀图像减少区域厚度");
figure(2), imshow(BW4), hold on
max_len = 0;
for k = 1:length(lines)
xy = [lines(k).point1; lines(k).point2];
plot(xy(:,1),xy(:,2),'LineWidth',2,'Color','green');
% Plot beginnings and ends of lines
plot(xy(1,1),xy(1,2),'x','LineWidth',2,'Color','yellow');
plot(xy(2,1),xy(2,2),'x','LineWidth',2,'Color','red');
% Determine the endpoints of the longest line segment
len_temp = norm(lines(k).point1 - lines(k).point2);
if ( len_temp > max_len)
max_len = len_temp;
xy_long = xy;
end
title("霍夫变换",'FontWeight','bold');
end