尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

BP神经网络水表数值识别:MATLAB图像处理与字符分割实践

BP神经网络水表数值识别:MATLAB图像处理与字符分割实践 简介基于MATLAB的水表数值识别项目是一套面向图像识别初学者与工程人员的完整方案利用BP神经网络完成水表读数区域的定位与数字识别可作为课程设计或算法验证的参考。压缩包共16169个文件大小约21.66MB其中以16164张JPG水表样本图像为主配合两个M源码文件、一个MAT训练数据文件、一个DOCX说明文档及一张PNG效果图结构清晰便于对照学习。已有508人浏览学习适合需要快速上手MATLAB图像处理与神经网络训练的读者。资源中除可直接运行的识别程序外还附有详细说明文档和不同场景下的样本图像可帮助理解数据预处理、网络训练与结果验证的完整流程节省自行收集与调试的时间。1. 为什么选择BP神经网络做水表数值识别随手拍一张水表照片直接丢给OCR程序十有八九识别不出来。水表读数窗里除了数字还有滚轮边缘、红色指针表盘、反光点甚至表号钢印更麻烦的是数字排列在窄条窗口内宽度和间距受拍摄角度影响很大。真正的难点往往不在分类器而在“从照片中拿到干净的单个数字”这件事上。这类基于matlab的“水表数值识别”项目用BP神经网络而不是深度学习并不是落伍而是因为读数数字的字体、笔形相对固定背景干扰主要是光照和位置偏移浅层网络配合充分的样本预处理就能达到足够好的准确率。新手可以用它走通“图像处理—特征提取—分类器训练”的完整链路有经验的人也可以在此基础上替换新网络作为matlab图像识别任务的基准实验。2. 样本预处理与字符分割从表盘照片到归一化数字图像在训练BP网络之前必须先解决一个老问题水表照片里的数字不是一个“规规矩矩”的字符图像而是嵌在复杂表盘中的一个子区域。如果直接把整张照片缩成固定尺寸输入网络背景的干扰会淹没字符特征模型几乎学不到鲁棒信息。2.1 读入图像与灰度化去掉彩色冗余信息大多数水表照片是彩色JPG格式项目中给定的1.jpg、yue-11.jpg等文件都属于这类。彩色信息对数字识别没有直接帮助反而会增加计算量而且不同表盘的颜色差异会引入额外噪声。因此第一步统一转成灰度图再用中值滤波抑制传感器噪点。img imread(1.jpg); if size(img, 3) 3 gray rgb2gray(img); else gray img; end gray medfilt2(gray, [3 3]);这里rgb2gray按亮度加权公式将RGB三通道合并成单通道灰度图medfilt2用3×3邻域中值替代中心像素能有效去除零星亮点同时比均值滤波更好地保留数字边缘。处理水表这类带有金属反光的照片时中值滤波的窗口不宜选太大否则数字笔划会被“抹平”。对于反光严重的图像可以先用adapthisteq做限制对比度直方图均衡化再用大津法分割。2.2 二值化与形态学修正灰度图不能直接用于字符分割。水表数字区域是白底黑字但在实际照片中白色窗框可能偏灰红色小数指针也可能混入所以使用全局固定阈值往往不稳定。常见做法是采用自适应阈值让每个像素根据周围邻域亮度决定归属。bw imbinarize(gray, adaptive, ForegroundPolarity, bright, ... Sensitivity, 0.45); bw bwareaopen(bw, 60);这句代码得到的结果是亮度明显高于局部背景的像素为前景1白色读数窗和数字间的浅色区域会被保留而黑色数字则变成背景0。bwareaopen会删除面积小于60像素的连通区域表盘上的灰尘和刻纹噪点在这个尺度下被清除。需要特别说明的是Sensitivity参数控制判断局部亮度差异的敏感性——调大会把数字笔划也当成背景调小则可能留下大片浅色反光区域。在我的经验里水表照片通常取值0.4到0.55之间具体以白色窗格能被完整连通为基准。这一步之后bw中较大的白色连通块就是读数窗和浅色区域。如果白色窗格四周有黑框干扰可以用imclose把细小的断口连接成完整矩形。下表列出水表识别流程中常用的形态学操作。操作函数场景说明开运算imopen(bw, strel(rectangle,[2 2]))去除孤立亮点让字符边界更平滑闭运算imclose(bw, strel(rectangle,[3 3]))填补白色窗格上的暗色擦痕膨胀imdilate(bw, strel(line,3,0))连接被噪声断开的水平笔划腐蚀imerode(bw, strel(rectangle,[1 2]))分离粘连字符的竖向间距2.3 字符分割基于连通域与投影裁剪二值化后的bw是整个读数窗的区域需要从坐标范围上切分出单个数字。由于数字宽度大致均匀先通过连通域分析提取候选区域再用宽高比过滤掉指针和边框。cc bwconncomp(bw); stats regionprops(cc, BoundingBox, Area); boxes []; accepted []; for k 1:length(stats) bbox stats(k).BoundingBox; % [x y w h] area stats(k).Area; w bbox(3); h bbox(4); if area 100 w 4 h 12 h / w 1.3 h / w 3 boxes(end1, :) bbox; accepted(end1) k; end end boxes sortrows(boxes, 1); % 按x坐标从左到右排序这段代码的过滤逻辑基于水表数字的几何特征数字高度明显大于宽度常见宽高比在1.5到2.5之间而红色指针表盘宽高接近1边缘刻度线面积又太小。所以h/w 1.3能排除大部分非字符区域。排序后得到从左到右排列的字符包围盒。得到的boxes可能仍然包含小数位红色数字或者有轻微粘连的宽字符。再针对每一列做垂直投影检查字符间隙是否为零charImgs {}; for i 1:size(boxes, 1) x round(boxes(i, 1)); y round(boxes(i, 2)); w round(boxes(i, 3)); h round(boxes(i, 4)); crop bw(y:yh, x:xw); charImgs{end1} imresize(crop, [20 20]); % 输入层固定为20×20 endimresize归一化到20×20像素是为了符合神经网络输入层维度。这里要注意归一化不改变字符拓扑但会放大锯齿所以训练集和测试集都必须用同样尺寸和插值方式否则模型性能会明显下降。整套预处理链路跑通后每个字符都变成一个400维的0/1向量再送去训练BP神经网络。3. BP神经网络结构设计与训练集构造字符图像已经变成20×20的规则网格接下来要决定用什么分类器。很多人乍一看会想到CNN但对水表数字这组固定印刷字体BP神经网络在样本量小、硬件受限时反而更实用。3.1 为什么浅层BP足够从数字识别任务本身的复杂度分析水表读数数字与手写数字最大的不同在于所有字符来自同一块表盘的印刷字体笔画宽度一致、边缘锐利没有手写体那种随意变形。MNIST手写体用BP网络也能达到95%以上前提是样本量足够水表字符印刷工整类内差异很小一个单隐藏层的BP网络就足以描述这些模式。另外BP网络训练速度比CNN快一个量级MATLAB环境下不需要GPU普通CPU几秒钟就能迭代完成。如果换成LeNet先不说训练数据量不足会导致过拟合光是卷积层、池化层的超参数调整就会消耗大量时间。所以对课程设计和快速原型的场景BP是更稳妥的起点。3.2 网络结构选择输入层、隐藏层、输出层的确定输入层由字符归一化尺寸决定20×20像素展平成单个列向量所以输入节点数为400。输出层按0到9共10个类别设计但编码方式有两种直接输出10个节点目标向量为单峰独热编码或者输出4个节点用二进制编码表示数字。两种方式都能用但二进制编码的误差没有自然语义模型优化更困难。我一般选10节点独热编码。隐藏层节点数没有绝对公式常见经验做法是取输入层和输出层数量平均值的1到2倍即20到40之间。选择较小的25既可以保留足够的非线性映射能力又不容易在样本量只有几百时过拟合。net newff(minmax(P), [25, 10], {logsig, purelin}, trainlm); net.trainParam.epochs 500; net.trainParam.goal 1e-5; net.trainParam.lr 0.01; net.divideParam.trainRatio 0.7; net.divideParam.valRatio 0.15; net.divideParam.testRatio 0.15; net train(net, P, T);newff是MATLAB神经网络工具箱中搭建前馈网络的老牌接口trainlm指定Levenberg-Marquardt优化器适合几十到几千个样本的中小规模数据集。logsig作为隐藏层激活函数将输出压缩到0和1之间输出层用purelin线性激活避免多分类任务中出现输出饱和。divideParam会把数据随机划分为训练、验证、测试三部分验证集用于提前停止避免过拟合。3.3 训练集构造与数据增强策略训练集的质量直接影响识别精度。水表照片中的数字与标准字库字体有差异因此不能直接用打印字体训练。常见做法是先把读数窗里的数字一个个裁剪出来人工打标签保存到以数字命名的文件夹下再批量读取成向量。每类数字至少准备30到50个样本整体样本量不大但需要覆盖不同光照、倾斜和表盘磨损情况。如果样本不足可以对已有的字符图像做数据增强。MATLAB中常用以下操作aug imrotate(charImg, 5, bilinear, crop); aug imnoise(charImg, gaussian, 0.02); aug imtranslate(charImg, [1, 0], FillValues, 0);imrotate以5度小角度旋转模拟拍摄偏斜imnoise增加高斯噪声模拟暗光环境imtranslate平移模拟分割时的左右偏移。需要注意的是水表数字不会出现90度翻转或大幅缩放所以增强范围一定要克制旋转超过15度反而会让模型学到错误形态。增强后的样本要打乱顺序并保证每个数字的样本数量基本均衡避免模型倾向样本量大的类别。4. MATLAB实现从样本训练到水表读数识别主流程前面两章的准备工作最终要落到可运行的MATLAB脚本上。完整的程序分两段先训练并保存模型再读取新照片做预测。下面按实际调试顺序给出代码和参数调整方法。4.1 训练脚本生成样本向量并保存模型假设所有字符图片已经按charSets/0、charSets/1等文件夹归类训练脚本需要遍历这些目录将每张图缩放到20×20并构造成训练矩阵。labels 0:9; P []; T []; for i 1:length(labels) folder fullfile(charSets, num2str(labels(i))); files dir(fullfile(folder, *.jpg)); for j 1:length(files) img imread(fullfile(folder, files(j).name)); if size(img, 3) 3 img rgb2gray(img); end img imbinarize(img); vec double(img(:)) / 255; P [P; vec]; % 每个样本一行 target zeros(1, 10); target(labels(i) 1) 1; T [T; target]; end end P P; T T; net newff(minmax(P), [25, 10], {logsig, purelin}, trainlm); net.trainParam.showWindow true; net.trainParam.epochs 1000; net train(net, P, T); save(water_meter_net.mat, net);训练过程中MATLAB会弹出训练窗口实时显示均方误差和验证集表现。看到验证集误差不再下降甚至上升就可以判断当前迭代次数已经足够。训练完成后模型保存在water_meter_net.mat中后续识别脚本只需要加载这个文件不用重新训练。4.2 识别脚本自动定位读数窗并逐字识别识别新图片时第一步是找到读数窗。如果用固定坐标裁剪遇到不同分辨率手机会失配所以我优先用连通域找最大亮色区域。load(water_meter_net.mat, net); img imread(4.jpg); gray rgb2gray(img); bwWindow imbinarize(gray, adaptive, ForegroundPolarity, bright, ... Sensitivity, 0.45); stats regionprops(bwWindow, BoundingBox, Area, Extent); areaList [stats.Area]; [maxArea, idx] max(areaList); bbox stats(idx).BoundingBox; readout imcrop(gray, bbox); bwROI ~imbinarize(readout, adaptive, ForegroundPolarity, ... bright, Sensitivity, 0.5); bwROI bwareaopen(bwROI, 30);这里先得到白色窗格的大致外接矩形再从原图中裁剪出readout对readout重新二值化并取反让黑色数字变成前景1方便后续连通域分析。如果自动定位不准比如把红色指针区域当成读数窗比较稳健的兜底方案是直接手动裁剪readout imcrop(gray);。接下来对bwROI做垂直投影找到每一列是否有数字像素colProj sum(bwROI, 1); inChar false; startCols []; endCols []; for c 1:length(colProj) if colProj(c) 0 ~inChar startCols(end1) c; inChar true; elseif colProj(c) 0 inChar endCols(end1) c - 1; inChar false; end end每一组startCols到endCols就是一个候选字符列区间。计算每个区间的宽度过滤掉宽度过窄的小数点和过宽的粘连数字然后裁剪并识别。numStr ; for i 1:length(startCols) w endCols(i) - startCols(i) 1; if w 3 continue; end charImg imcrop(bwROI, [startCols(i), 1, w, size(bwROI,1)]); charImg imresize(charImg, [20 20]); inVec double(charImg(:)) / 255; y sim(net, inVec); [~, idxChar] max(y); numStr [numStr, char(0 idxChar - 1)]; %#okAGROW end result str2double(numStr); fprintf(识别结果: %d\n, result);sim是MATLAB神经网络工具箱中用于前向传播的函数输入一个400维列向量输出10维预测向量。max取最大值对应的下标映射到数字0到9。这里有个容易踩的坑训练时如果把字符展平成了行向量识别时也必须是同样的方向否则sim会报维度错误。4.3 参数调整与常见报错处理实际运行中大部分错误不在网络分类而在预处理环节。以下是我调试时经常遇到的几类情况整理成对照表。现象可能原因调整方向识别结果全是同一个数字二值化极性反了数字为背景检查imbinarize后再取反分割出的字符总连在一起数字间有反光桥接腐蚀核加宽到1×3后二值化报错“Matrix dimensions must agree”输入向量维度不是20×20确认imresize和charImg(:)顺序训练集误差下降但测试集差隐藏层节点过多或样本过少减小隐藏层节点数增加增强样本读数窗定位到表盘外大面积浅色区域干扰改用Extent过滤矩形区域Extent是包围盒面积与连通区实际面积的比值读数窗接近矩形Extent通常高于0.8而表盘外不规则的浅色反光区域该值偏低。所以在regionprops后加上[stats.Extent] 0.7的条件能有效减少误裁。5. 提升水表数字识别率的三个验证手段模型能跑通只是第一步想判断这套流程是不是真的可靠需要从数据和中间结果上找证据。5.1 用混淆矩阵找出易混字符对只看整体准确率会掩盖很多问题。接水表工况里3、8、6、9 这几个数字最容易被混淆。通过混淆矩阵可以精确定位是哪一对数字出了问题。[confMat, order] confusionmat(trueLabels, predLabels); disp(array2table(confMat, VariableNames, string(0:9), ... RowNames, string(0:9)));如果发现3和8大量交叉再去翻训练图像往往是3的顶部曲线因为分割误差被裁掉了一截导致形态接近8。此时不需要换网络只需要调整字符裁剪的左右边界给每个字符留一些余量。5.2 用垂直投影曲线监控字符分割质量分割错误是水表识别里最隐蔽的问题。数字粘连后投影曲线没有完全回落到零程序会把两个数字当成一个字符。我会在分割循环中顺手画一下垂直投影曲线figure; bar(1:size(bwROI, 2), colProj); xlabel(列坐标); ylabel(前景像素数);理想情况下字符之间有明显低谷。遇到粘连可以取局部极小值作为候选分隔点或者用形态学腐蚀把窄缝扩大后再分割。这个可视化手段在调试阶段非常有效我每次调整完阈值都会先看一遍曲线再跑识别。5.3 把中间变量落盘方便回头排查训练集坏样本训练集里偶尔会混入裁剪失败的图片比如只有半个数字或者带了一个指针尖。这种坏样本不会让训练报错但会悄悄拉低识别率。我会把归一化前后的字符统一保存成mat文件同时生成一个蒙太奇图像快速检查所有训练样本montage(charImgs, Size, [2 5]); save(debug_chars.mat, charImgs, bwROI, bbox);如果发现某个数字的样本整体偏移说明字符裁剪的参考点不一致需要回到regionprops那步修正包围盒对齐方式。训练数据干净、分割稳定、模型结构合理这三个条件都满足时水表数值识别在测试集上的准确率通常能稳定在92%以上剩下的差距再通过补充真实场景样本逐步收窄。本文还有配套的精品资源点击获取
返回列表