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