設(shè)計(jì):子程序化圖像處理全流程解析)
如果你正在準(zhǔn)備圖像處理相關(guān)的課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)大概率繞不開車牌識別這個經(jīng)典題目。我在學(xué)生時代也做過一回當(dāng)時導(dǎo)師的要求很樸素用 MATLAB 寫一套能跑通的車牌識別程序而且代碼要分模塊寫成子程序不能一個 main 腳本從頭寫到尾。這個要求當(dāng)時覺得麻煩后來回頭看恰恰是子程序化讓整套系統(tǒng)的調(diào)試和維護(hù)輕松了一個量級。這篇文章不打算給你貼一份完整的畢業(yè)設(shè)計(jì)論文而是實(shí)實(shí)在在拆開來講整體流程怎么搭、每個子程序內(nèi)部做了什么、參數(shù)為什么這么設(shè)、跑真實(shí)圖片時最容易在哪個環(huán)節(jié)翻車。內(nèi)容基于我自己做過的一個基于 MATLAB 的車牌識別含子程序項(xiàng)目來展開所有代碼邏輯都可以直接復(fù)用也歡迎你在此基礎(chǔ)上改成自己的版本。1. 這個題目的含金量與適用邊界1.1 為什么大家都在做MATLAB車牌識別從課程設(shè)計(jì)到電賽培訓(xùn)車牌識別幾乎成了圖像處理入門的標(biāo)準(zhǔn)考題。原因有三個。第一它的技術(shù)棧非常經(jīng)典圖像預(yù)處理、邊緣檢測、形態(tài)學(xué)操作、連通域分析、投影法、字符匹配識別幾乎把數(shù)字圖像處理課程里最重要的知識全串起來了。做完這個項(xiàng)目你對圖像處理到底怎么解決實(shí)際問題會有一個完整的認(rèn)知閉環(huán)。第二MATLAB 生態(tài)對入門者極其友好。不用像 C 那樣手動管理內(nèi)存不用配置 OpenCV 環(huán)境imread讀圖、imshow顯示、rgb2gray轉(zhuǎn)灰度這些函數(shù)的命名本身就是英文短語讀代碼就像讀需求文檔。第三車牌本身是一個結(jié)構(gòu)化很強(qiáng)的目標(biāo)尺寸比例固定、顏色固定國內(nèi)藍(lán)底白字、字符排列固定。相比人臉檢測、行人檢測這種需要大量數(shù)據(jù)訓(xùn)練的場景車牌識別用傳統(tǒng)圖像處理手段就能達(dá)到一個可用的準(zhǔn)確率這讓它特別適合在有限課時內(nèi)做完整交付。1.2 這套系統(tǒng)能做什么、不能做什么先說清楚適用范圍免得你被網(wǎng)上那些夸大宣傳帶偏?;?MATLAB 的傳統(tǒng)車牌識別方案適用于以下場景清晰或者中等光照條件下的標(biāo)準(zhǔn)藍(lán)底白字車牌照片、車輛正對或近似正對相機(jī)、沒有嚴(yán)重的傾斜和遮擋。它不太擅長的事情也很明顯極端光照下的夜間圖片、車速很快時產(chǎn)生的運(yùn)動模糊、車牌傾斜超過 15 度、新能源綠色車牌的適配需要額外改顏色判斷邏輯。這些不是 MATLAB 本身不行而是傳統(tǒng)圖像處理算法的天然邊界。想突破這些瓶頸得引入深度學(xué)習(xí)檢測模型那就是另一個量級的項(xiàng)目了。我做的這套系統(tǒng)目標(biāo)定在標(biāo)準(zhǔn)圖片識別率 90% 以上單張圖片處理時間在 1 秒以內(nèi)普通筆記本上。這個指標(biāo)對課程設(shè)計(jì)和畢設(shè)來說已經(jīng)足夠往上優(yōu)化是加分項(xiàng)不是必選項(xiàng)。2. 系統(tǒng)框架設(shè)計(jì)子程序怎么切分最合理2.1 從管線思維拆解功能模塊拿到題目先別急著寫代碼把整個識別過程看成一個流水線原材料是一張圖片最終產(chǎn)品是一個車牌字符串。中間經(jīng)過的每一道工序都應(yīng)該對應(yīng)一個獨(dú)立的子程序。我的切分方式是五段式管線車牌定位子程序輸入原始圖片輸出車牌區(qū)域的裁剪圖。預(yù)處理子程序輸入車牌裁剪圖輸出適合做字符分割的二值圖。字符分割子程序輸入二值車牌圖輸出按順序排列的單個字符圖像。字符識別子程序輸入單個字符圖像輸出對應(yīng)的字符標(biāo)簽漢字、字母、數(shù)字。主控程序負(fù)責(zé)按順序調(diào)用以上子程序匯總識別結(jié)果顯示中間過程和最終輸出。這套劃分的邏輯是每一級都有明確的輸入輸出互不依賴。定位子程序不關(guān)心字符長什么樣分割子程序不關(guān)心字符是什么含義識別子程序拿到一張干凈的字符圖只管匹配。這樣你調(diào)試任何一個環(huán)節(jié)都能單獨(dú)喂數(shù)據(jù)進(jìn)去測試不需要每次都跑完整條鏈。2.2 為什么用函數(shù)文件而不是腳本堆疊很多新手喜歡把代碼全寫在一個.m腳本里用注釋分行。這樣做項(xiàng)目前期很爽后期就是災(zāi)難某個環(huán)節(jié)的效果不理想你要在一大段代碼里翻找對應(yīng)位置改完還得擔(dān)心影響其他部分。子程序化的核心價值是隔離復(fù)雜度和可復(fù)用性。我把每個環(huán)節(jié)寫成獨(dú)立的函數(shù)文件放到同一個項(xiàng)目文件夾下% 項(xiàng)目文件夾結(jié)構(gòu) plate_recognition/ ├── main.m % 主控程序 ├── func_locatePlate.m % 車牌定位子程序 ├── func_preprocess.m % 預(yù)處理子程序 ├── func_segmentChars.m % 字符分割子程序 ├── func_recognizeChar.m % 字符識別子程序 ├── templates/ % 模板庫文件夾 │ ├── char_hanzi/ % 漢字模板 │ ├── char_letter/ % 字母模板 │ └── char_digit/ % 數(shù)字模板 └── test_images/ % 測試圖片文件夾每個函數(shù)文件的第一行注釋寫明功能、輸入輸出說明和調(diào)用示例。這個習(xí)慣后來幫我省了很多時間——隔了兩周再看代碼不需要回憶當(dāng)時的設(shè)計(jì)意圖看注釋就能直接上手。MATLAB 的函數(shù)文件還有一個好處它的工作區(qū)是獨(dú)立隔離的函數(shù)內(nèi)部產(chǎn)生的中間變量不會污染主程序的命名空間。如果你在腳本里調(diào)試i、figure、ans這些變量很容易被意外覆蓋用函數(shù)文件就從機(jī)制上避免了這個問題。3. 車牌定位子程序從背景中把車牌摳出來3.1 預(yù)處理與邊緣檢測的組合拳車牌定位是整個系統(tǒng)里最玄學(xué)的一環(huán)因?yàn)檎掌尘扒ё內(nèi)f化有車身同色系的藍(lán)車、有復(fù)雜的街景、有樹蔭的光斑。我在調(diào)試早期階段定位成功率只有六成左右后來不斷加規(guī)則才提到九成以上。定位子程序的第一步是預(yù)處理核心目標(biāo)只有一個增強(qiáng)車牌區(qū)域的特征壓制非車牌區(qū)域的特征。function plateImg func_locatePlate(imgPath) % 功能車牌定位子程序 % 輸入imgPath 圖片路徑 % 輸出plateImg 車牌子圖 img imread(imgPath); gray rgb2gray(img); % 邊緣檢測車牌字符區(qū)域有密集的紋理邊緣響應(yīng)強(qiáng)烈 edges edge(gray, sobel, 0.12); % 形態(tài)學(xué)閉運(yùn)算把相鄰字符的邊緣連接成一個整體區(qū)域 se strel(rectangle, [15, 25]); closed imclose(edges, se); % 形態(tài)學(xué)開運(yùn)算去除細(xì)小噪聲連通域 se2 strel(rectangle, [5, 5]); opened imopen(closed, se2); end這里有兩個關(guān)鍵參數(shù)值得展開說明。Sobel 邊緣檢測的閾值0.12是通過多次嘗試得到的閾值太高字符邊緣斷裂嚴(yán)重閉運(yùn)算都連不回來閾值太低背景紋理的干擾大量涌入后面形態(tài)學(xué)處理會非常痛苦。閉運(yùn)算的結(jié)構(gòu)元素[15, 25]也不是隨便定的——車牌大約有 7 個字符字符之間的間隔讓邊緣呈斷裂條帶狀橫向閉運(yùn)算要把這些條帶連成一塊完整的矩形區(qū)域結(jié)構(gòu)元素的寬度必須能跨越字符間距。3.2 連通域篩選用候選區(qū)思維代替精確定位做完形態(tài)學(xué)后圖像里會出現(xiàn)若干塊連通域。這時不要急于認(rèn)定哪一塊是車牌而是把所有候選區(qū)域都找出來用車牌的先驗(yàn)知識逐條篩除。這個策略比一次定位準(zhǔn)確要穩(wěn)健得多因?yàn)榫退愕谝惠喓Y不掉干擾區(qū)域第二輪、第三輪還能繼續(xù)排除。% 連通域分析 labeled bwlabel(opened, 8); stats regionprops(labeled, BoundingBox, Area); % 車牌先驗(yàn)知識寬高比約3.14:1面積占比在一定范圍內(nèi) 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是面積下界用來排除那些形態(tài)像車牌但面積太小的非目標(biāo)區(qū)域比如遠(yuǎn)處的小標(biāo)牌、車身上的小色塊。篩完區(qū)域后如果有多個候選區(qū)我采用的策略是把所有候選區(qū)都裁剪出來挨個送進(jìn)后續(xù)流程最后在識別階段根據(jù)字符匹配的得分判定哪個是真正的車牌。這種做法有點(diǎn)暴力但能大幅提高定位召回率。3.3 用顏色特征做二次確認(rèn)如果只靠邊緣和形態(tài)學(xué)藍(lán)色車身、藍(lán)色廣告牌的干擾會非常頑固因?yàn)樗鼈兒蛙嚺频倪吘壖y理結(jié)構(gòu)接近又恰好是近似矩形。我的解決方案是增加一個顏色校驗(yàn)環(huán)節(jié)。在 HSV 色彩空間里標(biāo)準(zhǔn)藍(lán)底車牌的藍(lán)色區(qū)域有明顯的色調(diào)特征比 RGB 空間更穩(wěn)定不易受光照亮度變化影響hsvImg rgb2hsv(img); % 車牌藍(lán)色區(qū)域H通道約在0.55到0.75之間對應(yīng)藍(lán)青色S飽和度較高 blueMask (hsvImg(:,:,1) 0.55) (hsvImg(:,:,1) 0.75) ... (hsvImg(:,:,2) 0.35) (hsvImg(:,:,3) 0.15); blueRatio sum(blueMask(:)) / (sum(blueMask(:)) eps); % 計(jì)算候選區(qū)域內(nèi)藍(lán)色像素占比低于閾值則排除 if blueRatio 0.1 % 候選區(qū)不是藍(lán)色車牌排除 end在實(shí)際調(diào)試中我還發(fā)現(xiàn)一個反直覺的現(xiàn)象飽和度閾值不能定太高。強(qiáng)光直射下車牌藍(lán)色會發(fā)白飽和度下降厲害陰天或夜間飽和度又會偏低。0.35這個值是一個折中犧牲一點(diǎn)區(qū)分力換取對光照的魯棒性。另外HSV 的 H 通道在藍(lán)色區(qū)域附近本身有跳變?nèi)绻褂?OpenCV 風(fēng)格的范圍可能還要處理環(huán)形色相的問題但在 MATLAB 的rgb2hsv中是0~1標(biāo)準(zhǔn)歸一化直接比較即可。4. 字符分割子程序投影法的完整落地4.1 水平投影先找上下邊界拿到車牌子圖后字符分割子程序要做的事情是把一整塊字符條帶變成一排單個字符圖像。第一步是水平投影也就是對二值化后的圖像按行統(tǒng)計(jì)白色像素數(shù)量。車牌區(qū)域的上下邊緣處白色像素數(shù)量會急劇變化沒有字符的區(qū)域接近 0有字符的區(qū)域會形成明顯的峰值帶。實(shí)際操作中我不建議直接對整幅車牌子圖做二值化后再投影因?yàn)檐嚺频你T釘、車牌邊框會產(chǎn)生干擾。正確做法是先把外邊框剔除在原圖中找到最小外接矩形然后向內(nèi)縮進(jìn)幾個像素去掉白色邊框的影響。function charCells func_segmentChars(plateImg) % 功能字符分割子程序 % 輸入plateImg 車牌子圖RGB % 輸出charCells 單字符圖像 cell 數(shù)組 gray rgb2gray(plateImg); % 大津法自適應(yīng)二值化處理光照不均效果好于固定閾值 bw imbinarize(gray, adaptive, ForegroundPolarity, bright, ... Sensitivity, 0.4); % 去除面積過小的雜點(diǎn)鉚釘、灰塵 bw bwareaopen(bw, 30); % 水平投影 rowProj sum(bw, 2); % 找到第一個和最后一個白色像素數(shù)量超過閾值的行號 threshold max(rowProj) * 0.1; rows find(rowProj threshold); top rows(1); bottom rows(end); bw bw(top:bottom, :); end大津法的自適應(yīng)版本imbinarize(..., adaptive, ...)在這里很關(guān)鍵。固定閾值im2bw在光照均勻的合成圖片上表現(xiàn)不錯但真實(shí)拍攝的圖片常常半邊亮半邊暗固定閾值會直接把暗部的字符吃掉。自適應(yīng)閾值根據(jù)每個像素鄰域的亮度計(jì)算局部閾值對光照變化的免疫力強(qiáng)很多。4.2 垂直投影逐列掃描拆分字符水平投影確定上下邊界后進(jìn)入垂直投影階段。垂直投影的原理類似按列統(tǒng)計(jì)白色像素數(shù)量字符列會有明顯的白色像素波峰字符間隙是波谷。中國車牌標(biāo)準(zhǔn)有 7 個字符中間有一個分隔圓點(diǎn)小圓點(diǎn)用于分隔省份簡稱和后面的編碼部分。圓點(diǎn)面積小、像素少如果不好好處理會被當(dāng)成一個字符切出來導(dǎo)致后續(xù)識別徹底錯亂。% 垂直投影 colProj sum(bw, 1); threshold max(colProj) * 0.1; colIdx find(colProj threshold); % 找出連續(xù)段一段字符區(qū)域 if isempty(colIdx) charCells {}; return; end % 將連續(xù)的列索引分成一個個段落 regions []; startIdx colIdx(1); prevIdx colIdx(1); for i 2:length(colIdx) if colIdx(i) - prevIdx 3 % 出現(xiàn)間隙分割段落 regions [regions; startIdx, prevIdx]; startIdx colIdx(i); end prevIdx colIdx(i); end regions [regions; startIdx, prevIdx];這里colIdx(i) - prevIdx 3的意思是如果在列方向上存在連續(xù)超過 3 個像素列的空白就認(rèn)為當(dāng)前字符段落結(jié)束。這個 3 像素的容差是為了防止字符內(nèi)部細(xì)小的斷裂被誤判成字符間隙——比如漢字滬中間有幾筆像素較淡可能產(chǎn)生局部投影低谷但不會連續(xù) 3 列都?xì)w零。切出來的段落數(shù)量如果大于 8多半是噪聲段落沒有濾干凈如果小于 6可能是兩個字符粘連了需要進(jìn)一步處理。實(shí)測中段落寬度也是一個重要的過濾指標(biāo)正常字符寬度應(yīng)該在車牌子圖寬度的 0.08~0.18 倍之間小圓點(diǎn)寬度大概只有字符的 0.2 倍可以通過寬度和面積直接剔除。4.3 字符歸一化與尺寸統(tǒng)一分割出的字符圖像尺寸不一直接送進(jìn)識別子程序會讓模板匹配失效。原因是corr2等相關(guān)性計(jì)算要求兩幅圖像大小一致尺寸不同就無法計(jì)算標(biāo)準(zhǔn)相關(guān)系數(shù)。歸一化的做法是統(tǒng)一縮放到 42×24 像素高×寬。這個尺寸是我對比過的經(jīng)驗(yàn)值太小會丟失筆畫細(xì)節(jié)造成滬和中這種結(jié)構(gòu)復(fù)雜漢字的分辨困難太大會讓模板文件占用內(nèi)存增加而且放大后邊緣鋸齒更明顯。% 字符歸一化 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]); % 二值類型轉(zhuǎn)成 double方便后續(xù)相關(guān)性計(jì)算 charCells{i} double(charImg); end這里我要提示一個細(xì)節(jié)直接imresize拉伸會改變字符的長寬比。標(biāo)準(zhǔn)車牌字符本身就接近長方體拉伸一點(diǎn)影響不大但如果你追求高識別率可以在縮放前先計(jì)算字符連通域的實(shí)際寬高比做成正方形模板或者在歸一化時保持比例做四周補(bǔ)零。前者更簡單后者更嚴(yán)謹(jǐn)我前期用前者后期加了補(bǔ)零邏輯。5. 字符識別子程序模板匹配的正確打開方式5.1 模板庫的建立方式字符識別子程序的思路是模板匹配把待識別字符和模板庫里的所有模板逐一比較相似度最高的那個就是識別結(jié)果。模板庫的建立是整個項(xiàng)目中最花力氣但最不顯眼的部分。你需要準(zhǔn)備 7 個字符位對應(yīng)的模板集合第一位是漢字矩陣全國省級行政區(qū)簡稱比如 京、津、冀、晉、蒙、遼、吉、黑、滬、蘇、浙、皖、閩、贛、魯、豫、鄂、湘、粵、桂、瓊、渝、川、貴、云、藏、陜、甘、青、寧、新。第二位是英文字母除去 I 和 O避免與數(shù)字 1 和 0 混淆。第三到七位是字母和數(shù)字的混合。模板圖像的來源有三種直接從標(biāo)準(zhǔn)車牌圖片上截取、用標(biāo)準(zhǔn)字體渲染生成、從自己的測試圖片里截取再人工標(biāo)注。我的經(jīng)驗(yàn)是三種都做最后混合成模板庫。純標(biāo)準(zhǔn)字體渲染的模板太干凈和真實(shí)圖像的字符有明顯風(fēng)格差異匹配得分反而偏低完全從真實(shí)圖片截取又容易引入各種噪聲?;旌夏0鍘斓娜哂嘈阅茱@著提高魯棒性。每個模板保存成一個.mat文件或者一個結(jié)構(gòu)體數(shù)組元素包含兩個字段歸一化后的二值圖像矩陣img和對應(yīng)的字符標(biāo)簽label。建議把所有模板加載到一個結(jié)構(gòu)體數(shù)組里識別時循環(huán)遍歷。5.2 匹配算法相關(guān)度計(jì)算的實(shí)戰(zhàn)細(xì)節(jié)模板匹配的核心代碼邏輯如下function charLabel func_recognizeChar(charImg, templateSet) % 功能字符識別子程序 % 輸入charImg 歸一化后的字符圖像double類型 % templateSet 模板庫結(jié)構(gòu)體數(shù)組每個元素含 img 和 label 字段 % 輸出charLabel 識別出的字符標(biāo)簽 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計(jì)算的是兩個矩陣的二維相關(guān)系數(shù)取值范圍在 [-1, 1] 之間。1 表示完全正相關(guān)也就是說兩幅圖像的灰度分布模式完全一致接近 0 表示不相關(guān)。在實(shí)驗(yàn)過程中我發(fā)現(xiàn)一個規(guī)律正確匹配的相關(guān)系數(shù)通常在 0.6~0.9 之間錯誤匹配的相關(guān)系數(shù)往往在 0.2~0.5 之間界限比較清晰。如果某位字符的最高匹配分低于 0.5很大概率是分割環(huán)節(jié)出了問題建議把該字符圖像保存下來檢查而不是強(qiáng)行給出一個識別結(jié)果。另外針對漢字識別形近字是主要的錯誤來源。比如渝和豫、 鄂和郭后者不在標(biāo)準(zhǔn)字庫中、皖和贛在某些模糊圖片下容易混淆。改進(jìn)方法主要有兩種一是引入筆畫密度特征作為輔助判斷二是對易混淆字對做專門的局部區(qū)域比對比如比較漢字的右下角部分有沒有特定的鉤筆結(jié)構(gòu)。5.3 多級投票與置信度閾值只做一次模板匹配就給出結(jié)果雖然簡單但抗噪能力弱。我的做法是多級投票不止用corr2一種相似度度量而是同時計(jì)算歐氏距離倒數(shù)、歸一化互相關(guān)、結(jié)構(gòu)相似度指數(shù)SSIM把三個指標(biāo)排序后投票票數(shù)最高的字符作為最終結(jié)果。% 三種相似度指標(biāo)綜合打分 score1 corr2(charImg, tpl); % 相關(guān)度 score2 1 / (sum((charImg(:) - tpl(:)).^2) 1e-6); % 歐氏距離相似度 score3 ssim(charImg, tpl); % 結(jié)構(gòu)相似度 totalScore 0.5 * score1 0.3 * score2_normalized 0.2 * score3;這里score2是原始距離的倒數(shù)需要先歸一化到和corr2相近的量級否則會把其他兩個指標(biāo)完全淹沒。我一般先找出所有模板中最小和最大的score2做一個 min-max 歸一化到 [0, 1]再加權(quán)合并。實(shí)測中這種加權(quán)打分比單一corr2在模糊圖片上能提升大約 5 個百分點(diǎn)。當(dāng)然如果模板庫比較小單一corr2也夠用加權(quán)屬于進(jìn)階優(yōu)化。6. 主程序與 GUI把子程序串成完整系統(tǒng)6.1 主程序的調(diào)用順序與錯誤處理子程序各自調(diào)試通過后主程序的工作就是把它們按順序串起來。這個過程看似簡單但有幾個容易忽略的問題。第一個問題中間過程的錯誤處理。真實(shí)圖片千奇百怪定位子程序可能返回空矩陣沒有找到候選區(qū)域分割子程序可能返回少于 7 個字符。如果主程序不做判空處理直接往識別子程序傳空數(shù)據(jù)MATLAB 會報一堆晦澀的錯誤信息而且是在你不知道的角落。我的做法是在每兩個環(huán)節(jié)之間加檢查plateImg func_locatePlate(imgPath); if isempty(plateImg) disp(未定位到車牌區(qū)域); result ; return; end charCells func_segmentChars(plateImg); if length(charCells) 7 disp(字符分割異常建議檢查預(yù)處理參數(shù)); % 這里可以保存中間過程供人工診斷 imwrite(plateImg, debug_plate.png); result ; return; end第二個問題結(jié)果的可視化。主程序中用subplot把原圖、定位結(jié)果、分割結(jié)果、識別結(jié)果按塊展示出來方便直觀驗(yàn)證每一步是否正常。課程設(shè)計(jì)答辯的時候評委老師最喜歡看這類中間過程展示它比單純輸出一個字符串更有說服力。6.2 GUI 界面的功能規(guī)劃如果你想把項(xiàng)目做成一個演示系統(tǒng)還可以套一個 GUI 界面。我用 MATLAB 的 App Designer 做了一個簡單的界面布局是一個瀏覽圖片按鈕、一個坐標(biāo)軸區(qū)域顯示原圖、一個坐標(biāo)軸區(qū)域顯示識別過程、一個文本框輸出結(jié)果。GUI 回調(diào)函數(shù)的核心就是調(diào)用主程序邏輯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); % 調(diào)用主識別函數(shù) result runPlateRecognition(fullPath); app.ResultEditField.Value result; end注意GUI 回調(diào)里不要寫完整的識別邏輯而是單獨(dú)封裝一個runPlateRecognition.m函數(shù)GUI 只負(fù)責(zé)調(diào)用它。這樣做的好處是命令行模式和 GUI 模式共用同一套核心代碼不會出現(xiàn)GUI 能跑但命令行報錯的復(fù)制粘貼版本問題。很多同學(xué)喜歡在回調(diào)函數(shù)里直接堆代碼做完界面之后想加批量測試功能發(fā)現(xiàn)自己都改不動了。6.3 批量測試怎么科學(xué)地統(tǒng)計(jì)識別率單個圖片跑通不算完課程設(shè)計(jì)里老師一句你的系統(tǒng)對多少圖有效就把人問住了。一定要做批量測試。批量測試的思路準(zhǔn)備一個測試集文件夾里面放 20~30 張不同場景的車牌圖片每張圖片的文件名里人工標(biāo)注好正確的車牌號然后跑一個批處理腳本逐個圖片調(diào)用識別函數(shù)把識別結(jié)果和真值比對統(tǒng)計(jì)字符級準(zhǔn)確率和整牌準(zhǔn)確率。我實(shí)際測試 20 張圖片的結(jié)果大致如下測試項(xiàng)目結(jié)果車牌定位成功率19/20字符分割完整率18/20單字符識別準(zhǔn)確率約 92%整牌完全正確率約 75%定位失敗的那張圖是強(qiáng)逆光環(huán)境車牌嚴(yán)重過暗邊緣檢測和顏色特征雙雙失效。字符分割失敗的兩張一張是因傾斜導(dǎo)致字符粘連一張是鉚釘噪聲沒有濾干凈。單字符識別錯誤主要集中在漢字部分尤其是筆畫復(fù)雜的漢字在低分辨率下容易混淆。這個測試結(jié)果的價值在于它告訴你優(yōu)化方向應(yīng)該往哪打。整牌正確率只有 75%但拆開看問題大頭在定位和分割不在識別。所以后續(xù)優(yōu)化的優(yōu)先級是先修傾斜校正再提升分割魯棒性最后才是優(yōu)化識別算法。盲目去調(diào)識別子程序的參數(shù)屬于南轅北轍。7. 我實(shí)測過的常見問題和調(diào)參記錄7.1 定位子程序的三個翻車現(xiàn)場翻車現(xiàn)場一藍(lán)色車身的強(qiáng)干擾。有一張藍(lán)色轎車的圖片車身占了畫面大半部分邊緣檢測后車身輪廓和車牌區(qū)域連成了一片大連通域形態(tài)學(xué)操作后候選區(qū)框住了大半個車頭。我的解決辦法是把面積上限加進(jìn)去候選區(qū)域面積不能超過圖像總面積的 30%。這個限制符合物理常識——車牌再大也不可能占到半張照片。翻車現(xiàn)場二路燈/樹蔭的亮斑。邊緣檢測對亮度突變極其敏感。樹蔭投射在車前擋風(fēng)玻璃上形成明暗分界邊緣檢測會畫出一堆亂七八糟的邊界線這些線組成的連通域有時候形態(tài)比車牌還像矩形。我最后靠的是顏色特征把關(guān)候選區(qū)里藍(lán)色像素占比低于 10% 的一律排除。這個規(guī)則非常管用因?yàn)闊o論邊緣怎么亂真正的車牌藍(lán)是穩(wěn)定的。翻車現(xiàn)場三車牌傾斜。車輛和相機(jī)不平行時車牌在圖像中是平行四邊形或梯形邊緣檢測和形態(tài)學(xué)處理雖然能圈出大致區(qū)域但裁剪出來的是含背景的斜塊。這個問題我在定位階段沒有徹底解決而是放在分割前做了一步簡單的傾斜校正用最小外接矩形regionprops(Orientation)拿到傾斜角然后imrotate反向旋轉(zhuǎn)。旋轉(zhuǎn)之后字符水平了投影法就好用了。7.2 字符分割遇到粘連和斷裂粘連是分割里最常見的問題。滬A·5B628這種號碼里如果 5 和 B 靠得近垂直投影時兩個字符的投影波峰會疊在一起無法形成谷底。處理粘連的一個實(shí)用技巧是預(yù)先知道車牌有 7 個字符如果垂直投影只切出 6 段而且其中一段特別寬就把該段按寬度比例從中間拆成兩段。% 如果段落數(shù)量不足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附近尋找投影的最低點(diǎn)作為分割點(diǎn) subProj colProj(regions(idx,1):regions(idx,2)); [~, localMin] min(subProj); splitPoint regions(idx,1) localMin - 1; % 拆分成兩個新區(qū)域 end end斷裂和粘連相反字符內(nèi)部筆畫淡、反光強(qiáng)導(dǎo)致字符被切成兩段垂直投影多出一段。這種情況一般可以通過寬度過濾解決——斷裂段寬度遠(yuǎn)小于正常字符寬度直接合并到相鄰段落。我的經(jīng)驗(yàn)是在調(diào)試分割子程序時把每一張測試圖的垂直投影曲線畫出來看而不是盯著二值圖憑感覺猜。投影曲線的波峰波谷非常直觀哪里該切哪里不該切一目了然。寫代碼的時候順手加一個plot(colProj)的調(diào)試開關(guān)能省下大量時間。7.3 識別率還能怎么往上提如果你做完基礎(chǔ)版還想往上優(yōu)化我按性價比從高到低排序第一擴(kuò)充模板庫。每增加一個場景的模板識別率就穩(wěn)定爬升一點(diǎn)。尤其是漢字模板多收集不同字體、不同清晰度的樣本比調(diào)整算法參數(shù)有效得多。第二加入二級分類器。先用模板匹配得到候選的前三名然后針對這三個候選字符再計(jì)算額外的區(qū)分特征。比如B和8的差異在于左半部分是直線還是弧線0和O標(biāo)準(zhǔn)車牌沒有 O但模板庫里可能有干擾的差異在于高寬比。這類規(guī)則寫起來簡單收效明顯。第三換神經(jīng)網(wǎng)絡(luò)。如果項(xiàng)目周期允許可以用 MATLAB 的 Deep Learning Toolbox 訓(xùn)練一個簡單的 CNN 做字符識別替代模板匹配。CNN 的泛化能力比模板匹配強(qiáng)但需要訓(xùn)練數(shù)據(jù)和 GPU 資源對課程設(shè)計(jì)來說有點(diǎn)殺雞用牛刀。第四引入多幀視頻識別。如果項(xiàng)目場景是停車場出入口連續(xù)抓拍多幀每幀獨(dú)立識別后投票能顯著降低偶然性錯誤。這個思路在工程上很常見但在課程設(shè)計(jì)中做到的人不多。我個人的體會是這類圖像處理項(xiàng)目最忌諱一上來就追求完美方案。把基礎(chǔ)流程跑通、把每一環(huán)節(jié)的中間結(jié)果可視化、把失敗案例積累下來逐條分析進(jìn)步速度遠(yuǎn)比盯著論文看要快。車牌識別項(xiàng)目雖然老套但它覆蓋的知識點(diǎn)和工程細(xì)節(jié)對訓(xùn)練圖像處理思維非常有效。哪怕你以后不做車牌方向這套拆解問題、分模塊實(shí)現(xiàn)、逐級調(diào)優(yōu)的方法論也值得帶走。