简介:面向高校信息类、自动化类专业学生的Matlab车牌识别课程设计报告,配套源代码,可用于课程设计、毕业设计入门与图像处理练手。压缩包共1个docx文件,约1.37MB,主体为报告正文,源代码随文档给出,便于边读原理边对照实现。目前已有116人学习。报告按设计目的、设计原理、详细设计步骤、结果分析、总结体会和参考文献展开,覆盖车辆检测、图像采集与牌照识别流程,并具体讨论灰度化、背景开运算、图像增强、二值化阈值、边缘提取、车牌定位、垂直投影字符分割,以及模板匹配和人工神经网络两类字符识别方法;同时涉及不同光照、牌照质量与车辆速度下的测试,以及多牌照、假牌照等挑战和识别率优化思路。适合需要理解车牌识别算法链路并完成Matlab课程设计实践的读者。
1. 从一份课程设计任务书说起:MATLAB 车牌识别要交付到什么程度
选题会上,A 同学拿到的是「车牌识别系统设计」,要求交一份 MATLAB 课程设计报告并附源代码,两周后验收,现场跑图、现场讲。多数人的第一反应是上深度学习,可答辩时老师往往只追问一句:这一步输出什么图,参数为什么取这个值。所以这套方案反着来——灰度跳变定位、形态学开闭运算、垂直投影分割、模板库匹配识别,全流程用脚本串起来,每一步都能落一张中间图,错了能定位到具体环节。适合要在两三周内跑通一套可演示系统、又必须讲清每个环节依据的人,代码不追求短,追求改一个参数就知道哪里会变。
2. 总体方案:三段链路与三个必须跑通的软件步骤
2.1 把任务书拆成三段链路,再塌缩成三个软件步骤
任务书里的完整系统由车辆检测、图像采集、牌照识别三部分组成:车辆检测部分判断车到了,触发采集单元抓下当前帧,识别单元再对这张图做处理,定位牌照、切出字符、识别并输出号码。这是一条硬件参与的实时链路,摄像机、补光、触发线圈都在里面。
课程设计现场没有这些设备,通常的做法是拿一张静态车辆图代替「采集到的当前帧」,把三段链路塌缩成软件上的三个连续步骤:牌照定位、字符分割、字符识别。塌缩不等于删减,因为定位的输入质量仍然由「采集」决定——图糊了、逆光了、车牌只露一半,后面全部白搭,这一点在写报告的需求分析里必须交代清楚,否则整篇会写成一个纯算法练习。
三个步骤的顺序不能调换,也不建议合并。定位粗一层,分割细一层,识别只对已经摆正的单个字符负责。把职责切开的好处是排错成本低:识别结果错,先看切出来的字符图对不对;字符图歪了缺边,回头看定位框是不是压住了边框。这种分层是整套方案最值钱的地方,后面所有参数调整都建立在它上面。
牌照颜色识别在报告里一般和号码识别互相配合、互相验证:先按颜色判断是不是蓝底白字牌照,缩小候选区范围,再走号码识别;号码识别置信度低时反查颜色。课程设计只做蓝底白字也能交,但报告里要把这条互相验证的思路写出来,答辩时是加分项。
2.2 主流程脚本与运行环境
工程别写成一坨。我一般按「一个主入口 + 三个函数 + 一个样本库目录」来组织,主入口只负责串联和打印结果,出问题直接看是哪一段返回的空值。
% run_lpr.m —— 车牌识别主流程,串起定位、分割、识别三段 clc; clear; close all; imgPath = fullfile('data', 'car_03.jpg'); % 原始车辆图像,换成自己的图 Scolor = imread(imgPath); if size(Scolor, 3) == 3 Sgray = rgb2gray(Scolor); % 彩色转灰度,后续全部在灰度域处理 else Sgray = Scolor; % 已经是灰度图就别再转一次 end [plateBw, plateGray, box] = locatePlate(Sgray); % 第一步:定位,返回车牌二值子图 chars = segmentChars(plateBw, plateGray); % 第二步:分割,返回 1x7 字符图 result = recognizeChars(chars, 'sam'); % 第三步:识别,样本库目录为 sam fprintf('定位框:[%d %d %d %d]\n', round(box)); fprintf('识别结果:%s\n', result);主入口里没有任何算法细节,locatePlate、segmentChars、recognizeChars三个函数各自独立可测。这样拆分之后,改定位参数不会碰到识别代码,调识别样本也不会影响定位结果,回退版本时能一段一段退。box用的是[x y width height]四元组,和regionprops的BoundingBox保持一致,省掉一次坐标换算,换算多一次就多一次错的机会。
工程文件的分工建议明确到表格里,写报告时直接抄这一节:
| 文件 | 作用 | 关键输出 |
|---|---|---|
run_lpr.m | 主入口,串联三段并打印 | 识别字符串 |
locatePlate.m | 灰度化、背景估计、二值化、边缘、形态学、连通域筛选 | 车牌二值子图、定位框 |
segmentChars.m | 投影分析、倾斜校正、按列切割、尺寸归一化 | 1×7 字符图像元胞 |
recognizeChars.m | 读样本库、逐张比对、取误差最小 | 字符类标字符串 |
sam/ | 样本库目录,文件名首字符即类标 | 22×14 的二值样本图 |
运行环境没有特殊要求,基础 MATLAB 加 Image Processing Toolbox 就够,strel、edge、bwlabel、regionprops、imrotate、imresize都在里面。原文里用到的imfeature是老版本函数,新版本已经并入regionprops,这一点必须在报告里说明,否则换台电脑跑不起来。曲线拟合部分原文用的fit(..., 'poly1')依赖 Curve Fitting Toolbox,用polyfit可以绕开,答辩现场少一个装工具箱的风险。
提示:所有路径用
fullfile拼,不要写成'F:\MATLAB\work\sam\image1.jpg'这种绝对路径,换机器必崩。
2.3 进度安排:把四周拆成四个可验收的节点
任务书给的进度是 19 周熟悉环境与基础、19 周选题与题目分析、20 周编程实现、20 周验收答辩。实际执行时这个粒度太粗,容易前松后紧。我一般会把它翻译成带自检标准的节点,每个节点都有能拿出来给人看的东西:
| 节点 | 主要工作 | 交付物 | 自检标准 |
|---|---|---|---|
| 第 1 段 | MATLAB 环境熟悉,跑通imread、imshow、rgb2gray、edge几个基础调用 | 一段能显示原图与灰度图的脚本 | 换三张图不报错 |
| 第 2 段 | 题目分析,确定定位、分割、识别三段的技术路线 | 一页流程图 + 参数初值 | 能口述每步输入输出 |
| 第 3 段 | 编程实现,逐个模块打通并保存中间图 | 四个函数 + 中间结果图集 | 单张图端到端出结果 |
| 第 4 段 | 用 10 张以上不同光照、不同角度的图做回归 | 成功率统计表 | 定位成功率与识别正确率分开统计 |
第 3 段最容易失控,因为定位一崩后面全崩,人会本能地去调识别参数。正确的顺序是先把定位稳定到 10 张图里至少 8 张框对,再往下走;定位不稳的情况下调识别,等于在流沙上盖楼。第 4 段的统计一定要分开算:定位成功率、分割正确率、单字符识别正确率,三个数分开看才知道该修哪一段,混成一个「准确率」等于自己把黑匣子关上。
3. 预处理到定位:五步把车牌从车身背景里抠出来
3.1 灰度化与形态学背景估计
自然环境下拍到的车辆图背景复杂、光照不均,直接二值化会把车身反光、地面阴影一起抬成前景。通常的做法是先估计一张「背景亮度图」,再用原图减掉它,把缓慢变化的光照压平,把突变的字符笔画留下。
彩色图先转灰度,这一步不只是省内存。三个通道在识别环节没有额外信息,反而会让阈值在三个通道上各调一遍,调参成本翻三倍。转灰度之后,像素动态范围压缩到 0 到 255,后续阈值的物理意义才清楚。
% step_preprocess.m —— 灰度化 + 形态学背景估计 + 差分增强 Scolor = imread('data/car_03.jpg'); Sgray = rgb2gray(Scolor); % 三通道按权值合成单通道 se = strel('disk', 13); % 半径 13 的圆盘,尺寸要大于字符笔画宽度 Bgray = imopen(Sgray, se); % 开运算抹掉字符,剩下的就是背景亮度 Egray = imsubtract(Sgray, Bgray); % 原图减背景,字符被抬亮,光照被压平 figure, imshow(Egray), title('增强后的灰度图');strel('disk', 13)里的 13 是这套方案里最需要解释的参数。开运算会抹掉比结构元素小的亮结构,半径取小了抹不掉字符,背景图里还残留笔画;取大了会把整块车牌当成背景一起抹掉,差分之后车牌区域变黑。经验值是取字符笔画宽度的 1.5 到 2 倍,普通蓝牌在常见分辨率下笔画大概 6 到 10 像素,13 落在合理区间。换分辨率就要重算这个数,这一点写进报告里比写一堆公式有用。
imsubtract和直接用减号的区别在于它会截断到 0,不会出现负值导致uint8回绕成 255 的亮斑。这个细节不注意,图上会冒出几块莫名其妙的白色区域,排查半天以为是定位算法的问题。
3.2 阈值选取与二值化
二值化的目标是把字符和背景彻底分开,同时保住字符的形状不缺口、不多出空洞。全局阈值在这个场景里其实够用,因为差分已经把光照压得比较平了。
fmax1 = double(max(Egray(:))); % 增强图的最大灰度 fmin1 = double(min(Egray(:))); % 增强图的最小灰度 level = (fmax1 - (fmax1 - fmin1) / 3) / 255; % 取动态范围上 1/3 处作为归一化阈值 bw = im2bw(Egray, level); % 灰度转二值,字符为 1 bw = double(bw); % 转 double,方便后面按列求和做投影阈值取的是动态范围上 1/3 处,不是中点。原因是差分之后车牌字符的灰度普遍高于背景,取偏高一点的位置能把字符留住、把残余噪声挡在外面。取中点会让车身反光混进来,取太高则笔画断裂,后面形态学闭合要花更大力气去补。im2bw接受的是 0 到 1 的归一化阈值,所以这里除以 255,很多同学忘掉这一步,结果阈值恒大于 1,二值图整片全白,排查起来很费时间。
二值化的输出转成double是为了后面sum投影的方便。logical类型也能求和,但和浮点运算混在一起时容易出现类型不匹配的报错,统一成double省事。
3.3 Canny 边缘与矩形结构元素的开闭运算
二值图里字符笔画是断的,尤其汉字部分,笔画之间有间隙。直接找连通域会把一个字拆成三四个小块,所以要先做边缘提取再连接。
grd = edge(bw, 'canny'); % Canny 提取笔画边缘 bg1 = imclose(grd, strel('rectangle', [5, 19])); % 闭运算:横向连接断开的笔画 bg3 = imopen(bg1, strel('rectangle', [5, 19])); % 开运算:去掉细小噪点 bg2 = imopen(bg3, strel('rectangle', [19, 1])); % 纵向长条开运算:断开车牌与车身粘连 figure, imshow(bg1), title('闭运算 [5,19]'); figure, imshow(bg2), title('开运算 [19,1]');结构元素用矩形而不是圆盘,是因为车牌是横长方形,字符沿水平方向排列。[5, 19]表示高 5 宽 19,横向拉长,闭运算能把同一行上相邻的笔画连成一整块,纵向只连 5 个像素,不会把上下两行(比如上下两排字符的双层牌)糊在一起。
[19, 1]是纵向长条,开运算会抹掉宽度小于 1 个像素、高度小于 19 个像素的竖直细条。车牌和保险杠、车灯之间常有细窄的连接,这一步就是专门断它的。参数别乱改,改大了车牌本身被啃掉一块,改小了粘连断不开,定位框会一路延伸到车头。
这三步的顺序有讲究:先闭后开,闭是为了连,开是为了断。反过来先开,笔画还没连上就被开运算当成噪点清掉了。
3.4 连通域特征筛选与车牌裁剪
到这里图上还剩好几个候选块:车牌、车标、散热格栅、部分车灯。需要用形状特征把它们分开。
[L, num] = bwlabel(bg2, 8); % 8 邻域标记连通域 stats = regionprops(L, 'BoundingBox', 'Area'); % 新版用 regionprops,旧版对应 imfeature ratio = zeros(1, num); for k = 1:num bb = stats(k).BoundingBox; % [x y width height] ratio(k) = bb(3) / bb(4); % 候选区宽高比 end cand = find(ratio > 2.2 & ratio < 3.8 & [stats.Area] > 800); % 宽高比 + 面积双条件 [~, idx] = max([stats(cand).Area]); % 面积最大的合格候选作为车牌 box = stats(cand(idx)).BoundingBox; plateBw = imcrop(bg2, box); % 裁出车牌二值子图 plateGray= imcrop(Sgray, box); % 同步裁灰度子图,分割时要用标准蓝牌尺寸 440×140,宽高比约 3.14,实际拍摄有透视变形,所以取 2.2 到 3.8 这个区间而不是精确值。面积下限 800 是为了过滤车标和小灯,这个数跟图像分辨率强相关,报告里应该给出你所用图片的分辨率,并说明这个阈值怎么定的,别直接抄。
两个条件缺一不可。只用宽高比,格栅条也可能命中;只用面积,车身大块区域会被选中。两个条件同时成立再取面积最大者,鲁棒性才够。
imcrop要裁两次,二值图和灰度图必须用同一个box。分割阶段要在灰度图上找字符边界、在二值图上做投影,两者尺寸不一致会直接导致下标越界。
4. 校正、分割与识别:从倾斜车牌到七位字符输出
4.1 水平投影峰谷分析定上下边框
裁出来的车牌子图通常还带着上下边框和铆钉。投影分析的做法是把二值图按行求和,字符行求和值高,边框行求和值低,峰谷一对比就能找到字符区的上下界。
histrow = sum(plateBw, 2); % 按行求和,得到水平投影 thr = mean(histrow) * 0.5; % 均值折半作为参考阈值 mark = histrow > thr; % 高于阈值的行视为字符行 d = diff([0 mark' 0]); rowTop = find(d == 1, 1, 'first'); % 第一个上升点:字符区上界 rowBot = find(d == -1, 1, 'last') - 1; % 最后一个下降点:字符区下界 plateBw2 = plateBw(rowTop:rowBot, :); % 去上下边框后的二值子图 plateGr2 = plateGray(rowTop:rowBot, :); charH = rowBot - rowTop + 1; % 字符高度,后面归一化要用阈值用均值的 0.5 倍是经验值。取值偏高,字符顶部笔画细的地方被判成背景,字符被削头;取值偏低,边框残留进来,字符高度算大,归一化后字符变扁。改这个参数时盯着两个数:charH是否落在合理区间(常见分辨率下 20 到 40 像素),以及去掉边框后第一行和最后一行是否还有大片连通白块。
这一步失败最典型的症状是字符被上下削掉一块,识别时「0」和「8」、「B」和「8」互相串。看到这类混淆,先怀疑rowTop/rowBot,别急着换识别算法。
4.2 线性拟合求倾斜角并旋转
车牌倾斜时投影峰谷会变糊,字符中心距算不准,切割位置整体偏移。倾斜校正的做法是取车牌上边框的一组前景点做直线拟合,斜率转角度,再反向旋转。
% step_rotate.m —— 用上边框点集线性拟合唱出倾斜角 [rowIdx, colIdx] = find(plateBw2 == 1); % 所有前景点坐标 topRow = min(rowIdx); sel = find(rowIdx <= topRow + 3); % 最上面几行作为上边框点集 p = polyfit(colIdx(sel), rowIdx(sel), 1); % 一次拟合 y = p(1)*x + p(2) angle = atan(p(1)) * 180 / pi; % 斜率转角度(度) plateRot = imrotate(plateBw2, angle, 'bilinear', 'crop'); % 反向旋转校正 grayRot = imrotate(plateGr2, angle, 'bilinear', 'crop');polyfit的第三个参数 1 表示一次多项式,等价于原文里的'poly1',好处是不依赖 Curve Fitting Toolbox。atan得到弧度,乘180/pi转成度,imrotate接受的就是度,这一步单位搞错会转出几百度,图直接转没了。
imrotate的第三个参数'bilinear'是双线性插值,比最近邻平滑,字符边缘不容易出现锯齿;第四个参数'crop'保持输出尺寸和输入一致,不加这个参数图会变大,后面按下标切割全部错位。
角度绝对值超过 15 度就要警惕。真实的车牌倾斜一般不超过 10 度,算出二十几度基本说明拟合点被字符笔画污染了,这时应该只取最上面一两行、并限制横向范围在车牌中部,别把左右两端也纳进来。
4.3 垂直投影切字符与 22×14 归一化
校正完之后按列求和,字符列投影值高,间隙列投影值低,据此切出七个字符。
% step_segment.m —— 垂直投影切字符 histcol = sum(plateRot, 1); % 按列求和,得到垂直投影 thr = mean(histcol) * 0.6; % 均值折半偏上作为间隙阈值 above = histcol > thr; % 高于阈值的列标记为字符列 d = diff([0 above 0]); segS = find(d == 1); % 每段字符的起始列 segE = find(d == -1) - 1; % 每段字符的结束列 segW = segE - segS + 1; keep = segW > 5 & segW < 40; % 过滤噪点和整块粘连 segS = segS(keep); segE = segE(keep); if numel(segS) > 7 segS = segS(1:7); segE = segE(1:7); % 蓝牌固定七位,多出来的截断 end chars = cell(1, numel(segS)); for k = 1:numel(segS) seg = plateRot(:, segS(k):segE(k)); seg = imresize(seg, [22 14]); % 统一到 22 行 14 列,与样本库同尺寸 chars{k} = seg; imwrite(seg, fullfile('sam', sprintf('tmp_%02d.jpg', k))); % 落盘便于肉眼核对 end宽度过滤的两个阈值 5 和 40 是按经验给的:小于 5 的段基本是铆钉或噪声,大于 40 的是两个字符粘在一起。粘字是这套方法最常见的失败模式,原因通常是二值化阈值偏低导致间隙被填满,回头调level比在这里硬切更有效。
归一化到 22×14 不是为了好看,是为了让待识别字符和样本库里的模板尺寸完全一致,后面做逐像素比对才有意义。宽高比 14:22 大致对应真实字符的窄高形状,缩放时不要用'crop',直接imresize拉伸即可,变形在模板匹配里是可接受的。
落盘那一行别省。切割对不对,看一眼tmp_01.jpg到tmp_07.jpg就知道,比在内存里翻变量快十倍。
4.4 模板库匹配与神经网络备选路线
识别环节,原文给了两条路:模板匹配和人工神经网络。课程设计用前者更稳,因为可解释、可调试、不需要训练时间;后者适合作为报告里的对比方案写一段。
模板匹配的流程是:把待识别字符和样本库中每张模板逐像素相减,误差最小的那张图的类标就是结果。
% step_recognize.m —— 与样本库逐张比对,误差最小者胜出 function label = recognizeChars(chars, libDir) files = dir(fullfile(libDir, '*.jpg')); % 样本文件名形如 'A_01.jpg' label = blanks(numel(chars)); for i = 1:numel(chars) q = im2double(imresize(chars{i}, [22 14])); % 统一尺寸并转浮点 best = inf; bestName = '?'; for j = 1:numel(files) t = im2double(imread(fullfile(libDir, files(j).name))); if ~isequal(size(t), size(q)), continue; end % 尺寸不符直接跳过 err = sum(abs(q(:) - t(:))); % 逐像素绝对差之和 if err < best best = err; bestName = files(j).name; end end label(i) = bestName(1); % 文件名首字符即类标 end end误差用绝对差之和而不是欧氏距离,计算量小,对二值图的效果差别不大,写报告时两种都算一遍做对比也行。bestName(1)直接取文件名首字符当类标,这是命名约定带来的便利,样本库按A_01.jpg、B_02.jpg这样命名,读代码的人一眼能懂。
样本库是这套方案的命门。原文那张失败样例里,G 被认成 B、0 被认成 8、K 被认成 A,问题几乎都出在样本不足或样本质量差上——同一个字符只有一两张模板,字形稍有差异就匹配到别的类。我一般要求每个字符至少 5 张模板,覆盖不同粗细和轻微倾斜。
样本库的字段建议在报告里列清楚:
| 字段 | 说明 | 取值示例 |
|---|---|---|
| 文件名 | 首字符为类标,后接序号 | A_03.jpg |
| 尺寸 | 与分割输出一致 | 22×14 |
| 类型 | 二值图,白底黑字或黑底白字统一 | 二值 |
| 类标集 | 蓝牌需要的字符集合 | 31 个省份汉字 + 24 个字母 + 10 个数字 |
| 每类样本数 | 至少 5 张 | 5~10 |
神经网络路线在报告里这么写就够:一种做法是先对待识别字符提取特征,用特征训练分类器;另一种是把字符图直接送进网络,由网络自己学特征。难点不在网络结构,在样本量——几十张图训练出来的网络在测试集上不一定打得过模板匹配,这一点老实在报告里承认,比硬吹效果好。
5. 避坑与排查:五个最容易在验收现场翻车的地方
5.1 定位阶段的三个翻车点
现象一:定位框套在车标或散热格栅上。原因通常是宽高比区间开得太宽,或者面积下限设得太小,让格栅条这种横向条纹块混进了候选集。解决办法是把宽高比收窄到 2.5 到 3.5,同时把「面积最大」改成「面积与宽高比加权打分最高」,权重按你的测试集调。改完之后一定要拿原来失败的图回归一遍,别修好一张坏掉三张。
现象二:定位框一直延伸到车头或保险杠。这是形态学参数没配对导致的粘连。纵向开运算strel('rectangle', [19, 1])的高度不够,车牌和车身之间的细连接断不开;或者横向闭运算宽度过大,车牌和旁边的格栅连成一片。排查方法是依次显示grd、bg1、bg3、bg2四张中间图,看哪一步开始粘连,就在那一步收参数。
现象三:换了张图直接报错「索引超出矩阵维度」。八成是候选集为空——cand是空数组,cand(idx)必然越界。根因可能是这张图里根本没有蓝牌,或者二值化阈值过高把车牌整体判成背景。加一行判空再往下走,比让程序崩在半路强:
if isempty(cand) warning('未找到符合特征的候选区域,检查阈值或换图'); plateBw = []; plateGray = []; box = [0 0 0 0]; return; end5.2 分割与识别阶段的坑
现象四:切出八个字符,或者把边框、铆钉当成字符。原因在垂直投影的阈值上,mean(histcol) * 0.6这个系数在不同图上表现不一样:图偏暗时均值低,阈值跟着低,边框列也被判成字符列。解决办法是先做左右边框剥离——把投影最左和最右各若干列直接裁掉,再走分段的逻辑;同时把宽度过滤的下限从 5 提到 7 左右。还有一个隐蔽原因是倾斜校正没做好,字符中心距算出来整体偏移,七个字符被切成八段。
现象五:G 认成 B、0 认成 8、K 认成 A。这类形近字混淆在模板匹配里几乎是必然的,根因有三个:样本库对应类别的模板太少或字形单一;字符分割时上下被削,0 和 8 的闭合区域差别被抹掉;二值化后字符笔画粘连。排查顺序是固定的——先看tmp_0x.jpg有没有缺边,再看样本库里 B 和 8 各有几张,最后才动距离度量。很多同学一上来就把绝对差换成相关系数,结果只是把错误从一类挪到另一类。
5.3 工程与版本兼容的坑
最典型的一个坑是路径写死。原文示例里出现了F:\MATLAB\work\sam\image1.jpg这种绝对路径,换台电脑、换个盘符,整个识别环节直接失效。统一改成fullfile('sam', sprintf('image%d.jpg', k)),所有资源跟着工程目录走,压缩包发出去别人也能直接跑。
第二个是函数版本差异。imfeature在较新版本里已经被regionprops取代,直接照抄老代码会报未定义函数;im2bw也建议换成imbinarize,虽然前者目前还能用,但会给出警告信息,答辩投影时跳一堆黄色警告不好看。写报告时在环境说明里把版本和替代关系写清楚,这本身就是工程素养的体现。
第三个是中间图没有保存。全程只在最后显示一张识别结果,出错了根本不知道错在哪一步。我一般强制在locatePlate和segmentChars里各存四到六张中间图到debug/目录,跑完一轮翻一遍图,比盯着代码猜快得多。
6. 验收与答辩:用中间结果图讲清每一步为什么这么做
验收现场的时间通常只有五到八分钟,老师不会让你从头讲代码,但一定会问「为什么」。有效的做法是准备一页中间结果图序列:原图、灰度图、增强图、二值图、边缘图、闭运算图、开运算图、定位框、去边框后的车牌、七个字符小图、最终识别串。每张图下面标一行参数,被问到就指图回答。
答辩自检表可以按这个结构准备:
| 检查项 | 合格标准 | 常见扣分点 |
|---|---|---|
| 现场跑通 | 换一张新图能在 10 秒内出结果 | 只对固定那张图有效 |
| 参数可解释 | 每个关键参数能说出取值依据 | 「试出来的」 |
| 失败可定位 | 能指出失败发生在定位/分割/识别哪一段 | 只会说「识别率不高」 |
| 边界说清 | 说明只做蓝底白字,其他颜色需调整 | 声称全部支持 |
| 版本兼容 | 说明regionprops与imfeature的替代关系 | 换机器报错 |
汇报顺序建议倒过来讲:先给三张测试图的最终结果,让人看到系统能跑;再回头讲链路,讲定位为什么用灰度跳变而不是颜色分割,讲形态学结构元素为什么是[5, 19]和[19, 1],讲字符为什么归一化到 22×14。倒序讲的好处是老师带着「结果是对的」这个前提听过程,追问会更聚焦在方法选择上,而不是纠缠系统能不能用。
失败案例别藏。原文里那张把 G 认成 B、0 认成 8 的样例,反而是最好的答辩素材——你能说清是样本库太薄、是分割削了字符顶部、还是二值化阈值偏低,就说明你真的理解这套链路,而不是跑通了一个黑匣子。诚实交代边界条件,比硬撑着说准确率百分之九十九可信得多。
从那以后我每次带课程设计,都强制要求先跑一遍十张图的回归,把定位成功率、分割正确率、单字符正确率分开记录下来,再谈优化。没有这三个数,任何调参都是在赌。希望帮到你。
本文还有配套的精品资源,点击获取