
如果你正在准备图像处理相关的课程设计或毕业设计大概率绕不开车牌识别这个经典题目。我在学生时代也做过一回当时导师的要求很朴素用 MATLAB 写一套能跑通的车牌识别程序而且代码要分模块写成子程序不能一个 main 脚本从头写到尾。这个要求当时觉得麻烦后来回头看恰恰是子程序化让整套系统的调试和维护轻松了一个量级。这篇文章不打算给你贴一份完整的毕业设计论文而是实实在在拆开来讲整体流程怎么搭、每个子程序内部做了什么、参数为什么这么设、跑真实图片时最容易在哪个环节翻车。内容基于我自己做过的一个基于 MATLAB 的车牌识别含子程序项目来展开所有代码逻辑都可以直接复用也欢迎你在此基础上改成自己的版本。1. 这个题目的含金量与适用边界1.1 为什么大家都在做MATLAB车牌识别从课程设计到电赛培训车牌识别几乎成了图像处理入门的标准考题。原因有三个。第一它的技术栈非常经典图像预处理、边缘检测、形态学操作、连通域分析、投影法、字符匹配识别几乎把数字图像处理课程里最重要的知识全串起来了。做完这个项目你对图像处理到底怎么解决实际问题会有一个完整的认知闭环。第二MATLAB 生态对入门者极其友好。不用像 C 那样手动管理内存不用配置 OpenCV 环境imread读图、imshow显示、rgb2gray转灰度这些函数的命名本身就是英文短语读代码就像读需求文档。第三车牌本身是一个结构化很强的目标尺寸比例固定、颜色固定国内蓝底白字、字符排列固定。相比人脸检测、行人检测这种需要大量数据训练的场景车牌识别用传统图像处理手段就能达到一个可用的准确率这让它特别适合在有限课时内做完整交付。1.2 这套系统能做什么、不能做什么先说清楚适用范围免得你被网上那些夸大宣传带偏。基于 MATLAB 的传统车牌识别方案适用于以下场景清晰或者中等光照条件下的标准蓝底白字车牌照片、车辆正对或近似正对相机、没有严重的倾斜和遮挡。它不太擅长的事情也很明显极端光照下的夜间图片、车速很快时产生的运动模糊、车牌倾斜超过 15 度、新能源绿色车牌的适配需要额外改颜色判断逻辑。这些不是 MATLAB 本身不行而是传统图像处理算法的天然边界。想突破这些瓶颈得引入深度学习检测模型那就是另一个量级的项目了。我做的这套系统目标定在标准图片识别率 90% 以上单张图片处理时间在 1 秒以内普通笔记本上。这个指标对课程设计和毕设来说已经足够往上优化是加分项不是必选项。2. 系统框架设计子程序怎么切分最合理2.1 从管线思维拆解功能模块拿到题目先别急着写代码把整个识别过程看成一个流水线原材料是一张图片最终产品是一个车牌字符串。中间经过的每一道工序都应该对应一个独立的子程序。我的切分方式是五段式管线车牌定位子程序输入原始图片输出车牌区域的裁剪图。预处理子程序输入车牌裁剪图输出适合做字符分割的二值图。字符分割子程序输入二值车牌图输出按顺序排列的单个字符图像。字符识别子程序输入单个字符图像输出对应的字符标签汉字、字母、数字。主控程序负责按顺序调用以上子程序汇总识别结果显示中间过程和最终输出。这套划分的逻辑是每一级都有明确的输入输出互不依赖。定位子程序不关心字符长什么样分割子程序不关心字符是什么含义识别子程序拿到一张干净的字符图只管匹配。这样你调试任何一个环节都能单独喂数据进去测试不需要每次都跑完整条链。2.2 为什么用函数文件而不是脚本堆叠很多新手喜欢把代码全写在一个.m脚本里用注释分行。这样做项目前期很爽后期就是灾难某个环节的效果不理想你要在一大段代码里翻找对应位置改完还得担心影响其他部分。子程序化的核心价值是隔离复杂度和可复用性。我把每个环节写成独立的函数文件放到同一个项目文件夹下% 项目文件夹结构 plate_recognition/ ├── main.m % 主控程序 ├── func_locatePlate.m % 车牌定位子程序 ├── func_preprocess.m % 预处理子程序 ├── func_segmentChars.m % 字符分割子程序 ├── func_recognizeChar.m % 字符识别子程序 ├── templates/ % 模板库文件夹 │ ├── char_hanzi/ % 汉字模板 │ ├── char_letter/ % 字母模板 │ └── char_digit/ % 数字模板 └── test_images/ % 测试图片文件夹每个函数文件的第一行注释写明功能、输入输出说明和调用示例。这个习惯后来帮我省了很多时间——隔了两周再看代码不需要回忆当时的设计意图看注释就能直接上手。MATLAB 的函数文件还有一个好处它的工作区是独立隔离的函数内部产生的中间变量不会污染主程序的命名空间。如果你在脚本里调试i、figure、ans这些变量很容易被意外覆盖用函数文件就从机制上避免了这个问题。3. 车牌定位子程序从背景中把车牌抠出来3.1 预处理与边缘检测的组合拳车牌定位是整个系统里最玄学的一环因为照片背景千变万化有车身同色系的蓝车、有复杂的街景、有树荫的光斑。我在调试早期阶段定位成功率只有六成左右后来不断加规则才提到九成以上。定位子程序的第一步是预处理核心目标只有一个增强车牌区域的特征压制非车牌区域的特征。function plateImg func_locatePlate(imgPath) % 功能车牌定位子程序 % 输入imgPath 图片路径 % 输出plateImg 车牌子图 img imread(imgPath); gray rgb2gray(img); % 边缘检测车牌字符区域有密集的纹理边缘响应强烈 edges edge(gray, sobel, 0.12); % 形态学闭运算把相邻字符的边缘连接成一个整体区域 se strel(rectangle, [15, 25]); closed imclose(edges, se); % 形态学开运算去除细小噪声连通域 se2 strel(rectangle, [5, 5]); opened imopen(closed, se2); end这里有两个关键参数值得展开说明。Sobel 边缘检测的阈值0.12是通过多次尝试得到的阈值太高字符边缘断裂严重闭运算都连不回来阈值太低背景纹理的干扰大量涌入后面形态学处理会非常痛苦。闭运算的结构元素[15, 25]也不是随便定的——车牌大约有 7 个字符字符之间的间隔让边缘呈断裂条带状横向闭运算要把这些条带连成一块完整的矩形区域结构元素的宽度必须能跨越字符间距。3.2 连通域筛选用候选区思维代替精确定位做完形态学后图像里会出现若干块连通域。这时不要急于认定哪一块是车牌而是把所有候选区域都找出来用车牌的先验知识逐条筛除。这个策略比一次定位准确要稳健得多因为就算第一轮筛不掉干扰区域第二轮、第三轮还能继续排除。% 连通域分析 labeled bwlabel(opened, 8); stats regionprops(labeled, BoundingBox, Area); % 车牌先验知识宽高比约3.14:1面积占比在一定范围内 minArea size(img, 1) * size(img, 2) * 0.001; candidates []; for k 1:length(stats) area stats(k).Area; bbox stats(k).BoundingBox; w bbox(3); h bbox(4); ratio w / h; if area minArea ratio 2.0 ratio 4.5 candidates [candidates; bbox]; end end判断逻辑里宽高比范围放宽到2.0 ~ 4.5而不是死等 3.14 附近。原因是图片拍摄角度、裁剪误差都会让车牌在图像中的宽高比偏移如果卡得太死可能把真正的车牌也筛掉了。minArea是面积下界用来排除那些形态像车牌但面积太小的非目标区域比如远处的小标牌、车身上的小色块。筛完区域后如果有多个候选区我采用的策略是把所有候选区都裁剪出来挨个送进后续流程最后在识别阶段根据字符匹配的得分判定哪个是真正的车牌。这种做法有点暴力但能大幅提高定位召回率。3.3 用颜色特征做二次确认如果只靠边缘和形态学蓝色车身、蓝色广告牌的干扰会非常顽固因为它们和车牌的边缘纹理结构接近又恰好是近似矩形。我的解决方案是增加一个颜色校验环节。在 HSV 色彩空间里标准蓝底车牌的蓝色区域有明显的色调特征比 RGB 空间更稳定不易受光照亮度变化影响hsvImg rgb2hsv(img); % 车牌蓝色区域H通道约在0.55到0.75之间对应蓝青色S饱和度较高 blueMask (hsvImg(:,:,1) 0.55) (hsvImg(:,:,1) 0.75) ... (hsvImg(:,:,2) 0.35) (hsvImg(:,:,3) 0.15); blueRatio sum(blueMask(:)) / (sum(blueMask(:)) eps); % 计算候选区域内蓝色像素占比低于阈值则排除 if blueRatio 0.1 % 候选区不是蓝色车牌排除 end在实际调试中我还发现一个反直觉的现象饱和度阈值不能定太高。强光直射下车牌蓝色会发白饱和度下降厉害阴天或夜间饱和度又会偏低。0.35这个值是一个折中牺牲一点区分力换取对光照的鲁棒性。另外HSV 的 H 通道在蓝色区域附近本身有跳变如果使用 OpenCV 风格的范围可能还要处理环形色相的问题但在 MATLAB 的rgb2hsv中是0~1标准归一化直接比较即可。4. 字符分割子程序投影法的完整落地4.1 水平投影先找上下边界拿到车牌子图后字符分割子程序要做的事情是把一整块字符条带变成一排单个字符图像。第一步是水平投影也就是对二值化后的图像按行统计白色像素数量。车牌区域的上下边缘处白色像素数量会急剧变化没有字符的区域接近 0有字符的区域会形成明显的峰值带。实际操作中我不建议直接对整幅车牌子图做二值化后再投影因为车牌的铆钉、车牌边框会产生干扰。正确做法是先把外边框剔除在原图中找到最小外接矩形然后向内缩进几个像素去掉白色边框的影响。function charCells func_segmentChars(plateImg) % 功能字符分割子程序 % 输入plateImg 车牌子图RGB % 输出charCells 单字符图像 cell 数组 gray rgb2gray(plateImg); % 大津法自适应二值化处理光照不均效果好于固定阈值 bw imbinarize(gray, adaptive, ForegroundPolarity, bright, ... Sensitivity, 0.4); % 去除面积过小的杂点铆钉、灰尘 bw bwareaopen(bw, 30); % 水平投影 rowProj sum(bw, 2); % 找到第一个和最后一个白色像素数量超过阈值的行号 threshold max(rowProj) * 0.1; rows find(rowProj threshold); top rows(1); bottom rows(end); bw bw(top:bottom, :); end大津法的自适应版本imbinarize(..., adaptive, ...)在这里很关键。固定阈值im2bw在光照均匀的合成图片上表现不错但真实拍摄的图片常常半边亮半边暗固定阈值会直接把暗部的字符吃掉。自适应阈值根据每个像素邻域的亮度计算局部阈值对光照变化的免疫力强很多。4.2 垂直投影逐列扫描拆分字符水平投影确定上下边界后进入垂直投影阶段。垂直投影的原理类似按列统计白色像素数量字符列会有明显的白色像素波峰字符间隙是波谷。中国车牌标准有 7 个字符中间有一个分隔圆点小圆点用于分隔省份简称和后面的编码部分。圆点面积小、像素少如果不好好处理会被当成一个字符切出来导致后续识别彻底错乱。% 垂直投影 colProj sum(bw, 1); threshold max(colProj) * 0.1; colIdx find(colProj threshold); % 找出连续段一段字符区域 if isempty(colIdx) charCells {}; return; end % 将连续的列索引分成一个个段落 regions []; startIdx colIdx(1); prevIdx colIdx(1); for i 2:length(colIdx) if colIdx(i) - prevIdx 3 % 出现间隙分割段落 regions [regions; startIdx, prevIdx]; startIdx colIdx(i); end prevIdx colIdx(i); end regions [regions; startIdx, prevIdx];这里colIdx(i) - prevIdx 3的意思是如果在列方向上存在连续超过 3 个像素列的空白就认为当前字符段落结束。这个 3 像素的容差是为了防止字符内部细小的断裂被误判成字符间隙——比如汉字沪中间有几笔像素较淡可能产生局部投影低谷但不会连续 3 列都归零。切出来的段落数量如果大于 8多半是噪声段落没有滤干净如果小于 6可能是两个字符粘连了需要进一步处理。实测中段落宽度也是一个重要的过滤指标正常字符宽度应该在车牌子图宽度的 0.08~0.18 倍之间小圆点宽度大概只有字符的 0.2 倍可以通过宽度和面积直接剔除。4.3 字符归一化与尺寸统一分割出的字符图像尺寸不一直接送进识别子程序会让模板匹配失效。原因是corr2等相关性计算要求两幅图像大小一致尺寸不同就无法计算标准相关系数。归一化的做法是统一缩放到 42×24 像素高×宽。这个尺寸是我对比过的经验值太小会丢失笔画细节造成沪和中这种结构复杂汉字的分辨困难太大会让模板文件占用内存增加而且放大后边缘锯齿更明显。% 字符归一化 targetH 42; targetW 24; charCells cell(1, length(regions)); for i 1:size(regions, 1) colStart regions(i, 1); colEnd regions(i, 2); charImg bw(:, colStart:colEnd); % 保持长宽比的前提下缩放会更好但为简单直接拉伸 charImg imresize(charImg, [targetH, targetW]); % 二值类型转成 double方便后续相关性计算 charCells{i} double(charImg); end这里我要提示一个细节直接imresize拉伸会改变字符的长宽比。标准车牌字符本身就接近长方体拉伸一点影响不大但如果你追求高识别率可以在缩放前先计算字符连通域的实际宽高比做成正方形模板或者在归一化时保持比例做四周补零。前者更简单后者更严谨我前期用前者后期加了补零逻辑。5. 字符识别子程序模板匹配的正确打开方式5.1 模板库的建立方式字符识别子程序的思路是模板匹配把待识别字符和模板库里的所有模板逐一比较相似度最高的那个就是识别结果。模板库的建立是整个项目中最花力气但最不显眼的部分。你需要准备 7 个字符位对应的模板集合第一位是汉字矩阵全国省级行政区简称比如 京、津、冀、晋、蒙、辽、吉、黑、沪、苏、浙、皖、闽、赣、鲁、豫、鄂、湘、粤、桂、琼、渝、川、贵、云、藏、陕、甘、青、宁、新。第二位是英文字母除去 I 和 O避免与数字 1 和 0 混淆。第三到七位是字母和数字的混合。模板图像的来源有三种直接从标准车牌图片上截取、用标准字体渲染生成、从自己的测试图片里截取再人工标注。我的经验是三种都做最后混合成模板库。纯标准字体渲染的模板太干净和真实图像的字符有明显风格差异匹配得分反而偏低完全从真实图片截取又容易引入各种噪声。混合模板库的冗余性能显著提高鲁棒性。每个模板保存成一个.mat文件或者一个结构体数组元素包含两个字段归一化后的二值图像矩阵img和对应的字符标签label。建议把所有模板加载到一个结构体数组里识别时循环遍历。5.2 匹配算法相关度计算的实战细节模板匹配的核心代码逻辑如下function charLabel func_recognizeChar(charImg, templateSet) % 功能字符识别子程序 % 输入charImg 归一化后的字符图像double类型 % templateSet 模板库结构体数组每个元素含 img 和 label 字段 % 输出charLabel 识别出的字符标签 bestScore -1; charLabel ?; for i 1:length(templateSet) tpl templateSet(i).img; % 确保模板和待识别图尺寸一致 if size(tpl, 1) ~ size(charImg, 1) || size(tpl, 2) ~ size(charImg, 2) tpl imresize(tpl, [size(charImg, 1), size(charImg, 2)]); end score corr2(charImg, tpl); if score bestScore bestScore score; charLabel templateSet(i).label; end end end这里corr2计算的是两个矩阵的二维相关系数取值范围在 [-1, 1] 之间。1 表示完全正相关也就是说两幅图像的灰度分布模式完全一致接近 0 表示不相关。在实验过程中我发现一个规律正确匹配的相关系数通常在 0.6~0.9 之间错误匹配的相关系数往往在 0.2~0.5 之间界限比较清晰。如果某位字符的最高匹配分低于 0.5很大概率是分割环节出了问题建议把该字符图像保存下来检查而不是强行给出一个识别结果。另外针对汉字识别形近字是主要的错误来源。比如渝和豫、 鄂和郭后者不在标准字库中、皖和赣在某些模糊图片下容易混淆。改进方法主要有两种一是引入笔画密度特征作为辅助判断二是对易混淆字对做专门的局部区域比对比如比较汉字的右下角部分有没有特定的钩笔结构。5.3 多级投票与置信度阈值只做一次模板匹配就给出结果虽然简单但抗噪能力弱。我的做法是多级投票不止用corr2一种相似度度量而是同时计算欧氏距离倒数、归一化互相关、结构相似度指数SSIM把三个指标排序后投票票数最高的字符作为最终结果。% 三种相似度指标综合打分 score1 corr2(charImg, tpl); % 相关度 score2 1 / (sum((charImg(:) - tpl(:)).^2) 1e-6); % 欧氏距离相似度 score3 ssim(charImg, tpl); % 结构相似度 totalScore 0.5 * score1 0.3 * score2_normalized 0.2 * score3;这里score2是原始距离的倒数需要先归一化到和corr2相近的量级否则会把其他两个指标完全淹没。我一般先找出所有模板中最小和最大的score2做一个 min-max 归一化到 [0, 1]再加权合并。实测中这种加权打分比单一corr2在模糊图片上能提升大约 5 个百分点。当然如果模板库比较小单一corr2也够用加权属于进阶优化。6. 主程序与 GUI把子程序串成完整系统6.1 主程序的调用顺序与错误处理子程序各自调试通过后主程序的工作就是把它们按顺序串起来。这个过程看似简单但有几个容易忽略的问题。第一个问题中间过程的错误处理。真实图片千奇百怪定位子程序可能返回空矩阵没有找到候选区域分割子程序可能返回少于 7 个字符。如果主程序不做判空处理直接往识别子程序传空数据MATLAB 会报一堆晦涩的错误信息而且是在你不知道的角落。我的做法是在每两个环节之间加检查plateImg func_locatePlate(imgPath); if isempty(plateImg) disp(未定位到车牌区域); result ; return; end charCells func_segmentChars(plateImg); if length(charCells) 7 disp(字符分割异常建议检查预处理参数); % 这里可以保存中间过程供人工诊断 imwrite(plateImg, debug_plate.png); result ; return; end第二个问题结果的可视化。主程序中用subplot把原图、定位结果、分割结果、识别结果按块展示出来方便直观验证每一步是否正常。课程设计答辩的时候评委老师最喜欢看这类中间过程展示它比单纯输出一个字符串更有说服力。6.2 GUI 界面的功能规划如果你想把项目做成一个演示系统还可以套一个 GUI 界面。我用 MATLAB 的 App Designer 做了一个简单的界面布局是一个浏览图片按钮、一个坐标轴区域显示原图、一个坐标轴区域显示识别过程、一个文本框输出结果。GUI 回调函数的核心就是调用主程序逻辑function pushbutton_selectImagePushed(app, event) [file, path] uigetfile({*.jpg;*.png;*.bmp, 图片文件}); if isequal(file, 0) return; end fullPath fullfile(path, file); imshow(imread(fullPath), Parent, app.OriginalAxes); % 调用主识别函数 result runPlateRecognition(fullPath); app.ResultEditField.Value result; end注意GUI 回调里不要写完整的识别逻辑而是单独封装一个runPlateRecognition.m函数GUI 只负责调用它。这样做的好处是命令行模式和 GUI 模式共用同一套核心代码不会出现GUI 能跑但命令行报错的复制粘贴版本问题。很多同学喜欢在回调函数里直接堆代码做完界面之后想加批量测试功能发现自己都改不动了。6.3 批量测试怎么科学地统计识别率单个图片跑通不算完课程设计里老师一句你的系统对多少图有效就把人问住了。一定要做批量测试。批量测试的思路准备一个测试集文件夹里面放 20~30 张不同场景的车牌图片每张图片的文件名里人工标注好正确的车牌号然后跑一个批处理脚本逐个图片调用识别函数把识别结果和真值比对统计字符级准确率和整牌准确率。我实际测试 20 张图片的结果大致如下测试项目结果车牌定位成功率19/20字符分割完整率18/20单字符识别准确率约 92%整牌完全正确率约 75%定位失败的那张图是强逆光环境车牌严重过暗边缘检测和颜色特征双双失效。字符分割失败的两张一张是因倾斜导致字符粘连一张是铆钉噪声没有滤干净。单字符识别错误主要集中在汉字部分尤其是笔画复杂的汉字在低分辨率下容易混淆。这个测试结果的价值在于它告诉你优化方向应该往哪打。整牌正确率只有 75%但拆开看问题大头在定位和分割不在识别。所以后续优化的优先级是先修倾斜校正再提升分割鲁棒性最后才是优化识别算法。盲目去调识别子程序的参数属于南辕北辙。7. 我实测过的常见问题和调参记录7.1 定位子程序的三个翻车现场翻车现场一蓝色车身的强干扰。有一张蓝色轿车的图片车身占了画面大半部分边缘检测后车身轮廓和车牌区域连成了一片大连通域形态学操作后候选区框住了大半个车头。我的解决办法是把面积上限加进去候选区域面积不能超过图像总面积的 30%。这个限制符合物理常识——车牌再大也不可能占到半张照片。翻车现场二路灯/树荫的亮斑。边缘检测对亮度突变极其敏感。树荫投射在车前挡风玻璃上形成明暗分界边缘检测会画出一堆乱七八糟的边界线这些线组成的连通域有时候形态比车牌还像矩形。我最后靠的是颜色特征把关候选区里蓝色像素占比低于 10% 的一律排除。这个规则非常管用因为无论边缘怎么乱真正的车牌蓝是稳定的。翻车现场三车牌倾斜。车辆和相机不平行时车牌在图像中是平行四边形或梯形边缘检测和形态学处理虽然能圈出大致区域但裁剪出来的是含背景的斜块。这个问题我在定位阶段没有彻底解决而是放在分割前做了一步简单的倾斜校正用最小外接矩形regionprops(Orientation)拿到倾斜角然后imrotate反向旋转。旋转之后字符水平了投影法就好用了。7.2 字符分割遇到粘连和断裂粘连是分割里最常见的问题。沪A·5B628这种号码里如果 5 和 B 靠得近垂直投影时两个字符的投影波峰会叠在一起无法形成谷底。处理粘连的一个实用技巧是预先知道车牌有 7 个字符如果垂直投影只切出 6 段而且其中一段特别宽就把该段按宽度比例从中间拆成两段。% 如果段落数量不足7尝试对过宽段落做二次分割 if size(regions, 1) 7 widths regions(:,2) - regions(:,1); [maxW, idx] max(widths); if maxW 1.5 * median(widths) mid round((regions(idx,1) regions(idx,2)) / 2); % 在mid附近寻找投影的最低点作为分割点 subProj colProj(regions(idx,1):regions(idx,2)); [~, localMin] min(subProj); splitPoint regions(idx,1) localMin - 1; % 拆分成两个新区域 end end断裂和粘连相反字符内部笔画淡、反光强导致字符被切成两段垂直投影多出一段。这种情况一般可以通过宽度过滤解决——断裂段宽度远小于正常字符宽度直接合并到相邻段落。我的经验是在调试分割子程序时把每一张测试图的垂直投影曲线画出来看而不是盯着二值图凭感觉猜。投影曲线的波峰波谷非常直观哪里该切哪里不该切一目了然。写代码的时候顺手加一个plot(colProj)的调试开关能省下大量时间。7.3 识别率还能怎么往上提如果你做完基础版还想往上优化我按性价比从高到低排序第一扩充模板库。每增加一个场景的模板识别率就稳定爬升一点。尤其是汉字模板多收集不同字体、不同清晰度的样本比调整算法参数有效得多。第二加入二级分类器。先用模板匹配得到候选的前三名然后针对这三个候选字符再计算额外的区分特征。比如B和8的差异在于左半部分是直线还是弧线0和O标准车牌没有 O但模板库里可能有干扰的差异在于高宽比。这类规则写起来简单收效明显。第三换神经网络。如果项目周期允许可以用 MATLAB 的 Deep Learning Toolbox 训练一个简单的 CNN 做字符识别替代模板匹配。CNN 的泛化能力比模板匹配强但需要训练数据和 GPU 资源对课程设计来说有点杀鸡用牛刀。第四引入多帧视频识别。如果项目场景是停车场出入口连续抓拍多帧每帧独立识别后投票能显著降低偶然性错误。这个思路在工程上很常见但在课程设计中做到的人不多。我个人的体会是这类图像处理项目最忌讳一上来就追求完美方案。把基础流程跑通、把每一环节的中间结果可视化、把失败案例积累下来逐条分析进步速度远比盯着论文看要快。车牌识别项目虽然老套但它覆盖的知识点和工程细节对训练图像处理思维非常有效。哪怕你以后不做车牌方向这套拆解问题、分模块实现、逐级调优的方法论也值得带走。