別實(shí)戰(zhàn):傳統(tǒng)圖像處理實(shí)現(xiàn)顏色分割與分類)
簡介這是一份面向MATLAB初學(xué)者與圖像識(shí)別入門者的水果識(shí)別程序源碼包通過圖像處理與機(jī)器學(xué)習(xí)技術(shù)實(shí)現(xiàn)對(duì)不同種類水果的自動(dòng)分類可應(yīng)用于課程設(shè)計(jì)、學(xué)術(shù)研究及農(nóng)業(yè)自動(dòng)化、超市果蔬分類等場景。壓縮包共11個(gè)文件包含5張jpg水果樣本圖、4個(gè)m腳本文件、1個(gè)txt說明文檔和1個(gè)fig圖形文件整體約77KB其中m文件承載預(yù)處理、特征提取與分類識(shí)別的核心流程jpg提供青椒、西紅柿、香蕉、梨等測試樣本txt為運(yùn)行指引fig保存界面或結(jié)果圖形。程序覆蓋灰度化、二值化、邊緣檢測等預(yù)處理步驟并涉及顏色直方圖、LBP、HOG等特征提取方法以及SVM、決策樹等分類器訓(xùn)練與測試評(píng)估思路。目前已有2679人學(xué)習(xí)下載適合希望理解圖像識(shí)別基本流程、提升MATLAB編程技巧的讀者參考借鑒。1. 從一堆爛水果照片到能跑的識(shí)別程序這個(gè) MATLAB 項(xiàng)目到底能干什么實(shí)驗(yàn)室的桌子上堆著三十多張用手機(jī)拍的水果照片蘋果、香蕉、橘子混在一起光照忽明忽暗有的還帶著陰影。帶我的師兄丟過來一句“你試試用 MATLAB 把這些分出來別用深度學(xué)習(xí)就傳統(tǒng)圖像處理?!蔽耶?dāng)時(shí)的第一反應(yīng)是——這能行嗎后來翻到這個(gè) MATLAB 水果識(shí)別程序才發(fā)現(xiàn)它正好就是為這種場景寫的不依賴任何深度學(xué)習(xí)工具箱純靠顏色空間轉(zhuǎn)換、形態(tài)學(xué)操作和區(qū)域特征做分類把一張隨手拍的水果圖變成帶標(biāo)簽的識(shí)別結(jié)果。它適合誰如果你正在上數(shù)字圖像處理、模式識(shí)別這類課需要交一份能演示、能改參數(shù)的課程設(shè)計(jì)或者你手頭有工業(yè)分揀、農(nóng)產(chǎn)品初篩的小需求想先用輕量方案驗(yàn)證可行性這個(gè)項(xiàng)目就是很好的起點(diǎn)。整個(gè)程序圍繞 MATLAB 的 Image Processing Toolbox 展開核心文件不多但把“讀圖—預(yù)處理—分割—特征提取—分類—可視化”這條鏈路走得很完整拿來當(dāng)骨架改比從零寫要省太多事。2. 技術(shù)選型與整體架構(gòu)為什么用傳統(tǒng)圖像處理而不是深度學(xué)習(xí)2.1 選型邏輯輕量、可解釋、不挑機(jī)器這個(gè)項(xiàng)目最核心的決策就是放棄深度學(xué)習(xí)路線。原因很實(shí)際水果識(shí)別在顏色和形狀上的區(qū)分度本來就高蘋果偏紅、香蕉偏黃、橘子偏橙用 HSV 顏色空間做閾值分割就能拿到相當(dāng)干凈的區(qū)域沒必要上卷積網(wǎng)絡(luò)。傳統(tǒng)方法的另一個(gè)好處是可解釋——每一步處理完你都能 imshow 出來看哪一步翻車了一眼就知道不像神經(jīng)網(wǎng)絡(luò)那樣是個(gè)黑匣子。從依賴上看整個(gè)項(xiàng)目只要求 MATLAB 基礎(chǔ)環(huán)境加 Image Processing Toolbox不需要 Deep Learning Toolbox也不需要 GPU。這意味著在普通辦公電腦甚至老舊的實(shí)驗(yàn)室機(jī)器上都能跑起來對(duì)課程設(shè)計(jì)這種交付場景非常友好。常見做法是配合 App Designer 做一個(gè)簡單的 GUI把圖片加載、識(shí)別按鈕和結(jié)果顯示框拼在一起演示的時(shí)候點(diǎn)兩下就行。2.2 模塊劃分六個(gè)函數(shù)各管一段項(xiàng)目按處理流程拆成了幾個(gè)獨(dú)立函數(shù)主腳本負(fù)責(zé)串聯(lián)。典型的目錄結(jié)構(gòu)長這樣fruit_recognition/ ├── main.m % 主入口串聯(lián)整個(gè)流程 ├── load_image.m % 讀圖與尺寸歸一化 ├── preprocess.m % 濾波、增強(qiáng)、去噪 ├── segment_fruit.m % 顏色空間分割 形態(tài)學(xué) ├── extract_features.m % 提取顏色/形狀/紋理特征 ├── classify_fruit.m % 基于規(guī)則的分類器 ├── show_result.m % 結(jié)果可視化與標(biāo)注 └── dataset/ % 存放測試圖片 ├── apple_01.jpg ├── banana_01.jpg └── orange_01.jpg這種拆法的好處是每個(gè)環(huán)節(jié)可以單獨(dú)調(diào)試。比如分割效果不好你只需要改 segment_fruit.m不用動(dòng)分類邏輯想換分類器也只影響 classify_fruit.m 一個(gè)文件。我一般會(huì)在每個(gè)函數(shù)開頭加一段參數(shù)注釋寫清楚輸入輸出和可調(diào)閾值后面改起來不用來回翻。2.3 數(shù)據(jù)流一張圖從進(jìn)入到出結(jié)果主流程的數(shù)據(jù)流很清晰讀入 RGB 圖像后先統(tǒng)一縮放到固定尺寸常見是 256×256避免大圖拖慢處理接著做中值濾波去噪再轉(zhuǎn) HSV 空間在 HSV 上用閾值把水果區(qū)域和背景分開得到二值掩膜對(duì)掩膜做開閉運(yùn)算去掉小噪點(diǎn)和填補(bǔ)空洞然后用掩膜摳出原圖里的水果區(qū)域提取顏色直方圖、面積、圓度、長寬比等特征最后送進(jìn)規(guī)則分類器判斷類別把結(jié)果和置信度畫回原圖上。提示尺寸歸一化這一步別省。不同手機(jī)拍出來的分辨率差異很大不統(tǒng)一尺寸的話后面基于像素?cái)?shù)的面積特征會(huì)完全失去可比性。3. 核心模塊拆解分割、特征提取與分類的代碼實(shí)現(xiàn)3.1 圖像預(yù)處理濾波和顏色空間轉(zhuǎn)換預(yù)處理的目標(biāo)是把噪聲壓下去、把顏色信息整理成更容易分割的形式。代碼大致是這樣function img_hsv preprocess(img_rgb) % 統(tǒng)一尺寸兼顧速度和細(xì)節(jié) img_rgb imresize(img_rgb, [256, 256]); % 中值濾波去椒鹽噪聲窗口別開太大否則邊緣糊 img_filt medfilt2(img_rgb(:,:,1), [3 3]); img_filt(:,:,2) medfilt2(img_rgb(:,:,2), [3 3]); img_filt(:,:,3) medfilt2(img_rgb(:,:,3), [3 3]); % 轉(zhuǎn) HSVH 通道對(duì)光照變化更魯棒 img_hsv rgb2hsv(img_filt); end這里有幾個(gè)參數(shù)值得說。imresize 的目標(biāo)尺寸 256×256 是速度和精度的折中如果你要識(shí)別的水果在畫面里占比很小可以提到 512×512但處理時(shí)間會(huì)明顯上升。medfilt2 的窗口用 [3 3] 是常規(guī)選擇窗口越大去噪越狠但邊緣也越糊水果輪廓本來就不算復(fù)雜3×3 夠用。轉(zhuǎn) HSV 是關(guān)鍵一步RGB 三個(gè)通道都和亮度耦合光照一變閾值就失效而 HSV 的 H 通道基本只反映顏色種類對(duì)陰影和反光容忍度高得多。3.2 顏色分割與形態(tài)學(xué)處理分割是整個(gè)項(xiàng)目里最需要調(diào)參的環(huán)節(jié)。思路是在 H 通道上給每個(gè)水果設(shè)一個(gè)色相區(qū)間落在區(qū)間內(nèi)的判為水果function mask segment_fruit(img_hsv, fruit_type) H img_hsv(:,:,1); S img_hsv(:,:,2); V img_hsv(:,:,3); % 按水果類型設(shè)定色相范圍H 取值 0~1 switch fruit_type case apple % 紅色系H 在 0 附近或接近 1 mask (H 0.05 | H 0.95) (S 0.4) (V 0.2); case banana % 黃色系 mask (H 0.12 H 0.18) (S 0.4) (V 0.4); case orange % 橙色系 mask (H 0.05 H 0.12) (S 0.5) (V 0.4); end % 形態(tài)學(xué)開運(yùn)算去小噪點(diǎn)閉運(yùn)算填內(nèi)部空洞 se strel(disk, 5); mask imopen(mask, se); mask imclose(mask, se); % 只保留最大連通區(qū)域去掉背景干擾 mask bwareafilt(mask, 1); end色相閾值是這套方法的核心參數(shù)。蘋果的紅色在 H 上跨越 0 邊界所以要寫成H 0.05 | H 0.95這種雙區(qū)間香蕉和橘子的區(qū)間相對(duì)集中。S 和 V 的下限用來排除灰白背景和過暗區(qū)域。形態(tài)學(xué)那兩行的順序不能反——先開后閉開運(yùn)算先把零星噪點(diǎn)清掉閉運(yùn)算再把水果內(nèi)部因?yàn)榉垂庠斐傻目斩囱a(bǔ)上。bwareafilt 保留最大連通域是個(gè)實(shí)用技巧能自動(dòng)丟掉畫面邊緣的雜物。注意如果你的測試圖背景是純白或純黑這套閾值很好用但背景里有其他暖色物體時(shí)分割會(huì)誤判這時(shí)候要么換背景要么加一步基于位置或形狀的二次篩選。3.3 特征提取顏色、形狀、紋理三路并進(jìn)分割出掩膜后就可以在水果區(qū)域上算特征了。項(xiàng)目里主要用了三類function feats extract_features(img_rgb, mask) % 顏色特征H 通道均值反映主色調(diào) img_hsv rgb2hsv(img_rgb); feats.hue_mean mean(img_hsv(:,:,1), all, omitnan); % 形狀特征面積、圓度、長寬比 stats regionprops(mask, Area, Perimeter, ... BoundingBox, Centroid); feats.area stats.Area; feats.circularity 4 * pi * stats.Area / (stats.Perimeter^2); bbox stats.BoundingBox; feats.aspect_ratio bbox(3) / bbox(4); % 紋理特征灰度共生矩陣的對(duì)比度 gray rgb2gray(img_rgb); glcm graycomatrix(gray, NumLevels, 8); props graycoprops(glcm, Contrast); feats.contrast props.Contrast; end顏色特征用 H 均值最直接蘋果、香蕉、橘子的 H 均值天然分得開。形狀特征里圓度4πA/P2對(duì)蘋果和橘子這種接近圓形的物體取值高香蕉因?yàn)榧?xì)長會(huì)明顯偏低長寬比進(jìn)一步強(qiáng)化這個(gè)區(qū)分。紋理特征用灰度共生矩陣的對(duì)比度香蕉表面的紋理比蘋果光滑表皮要豐富一些可以作為輔助判據(jù)。regionprops 返回的 stats 是結(jié)構(gòu)體字段名要和調(diào)用時(shí)寫的一致寫錯(cuò)了 MATLAB 會(huì)直接報(bào)錯(cuò)這點(diǎn)新手容易踩。3.4 規(guī)則分類器用 if-else 也能分得準(zhǔn)分類部分沒有用任何機(jī)器學(xué)習(xí)工具箱就是基于閾值的規(guī)則判斷function [label, score] classify_fruit(feats) % 按優(yōu)先級(jí)依次判斷命中即返回 if feats.circularity 0.75 feats.hue_mean 0.1 label apple; score feats.circularity; elseif feats.aspect_ratio 1.8 label banana; score feats.aspect_ratio / 3; elseif feats.hue_mean 0.05 feats.hue_mean 0.15 label orange; score 1 - abs(feats.hue_mean - 0.1) * 10; else label unknown; score 0; end end規(guī)則順序有講究。圓度加色相先判蘋果因?yàn)樘O果的圓形特征最穩(wěn)定長寬比判香蕉細(xì)長形狀幾乎不會(huì)誤判剩下的橙色系歸橘子。score 是個(gè)粗略的置信度用特征值離閾值的距離估算方便在界面上顯示“像不像”。這套規(guī)則在背景干凈、光照均勻的圖片上準(zhǔn)確率不錯(cuò)但閾值是硬編碼的換一批數(shù)據(jù)可能就要重新調(diào)。4. 跑起來環(huán)境準(zhǔn)備、運(yùn)行步驟與參數(shù)調(diào)試4.1 環(huán)境要求和啟動(dòng)方式跑這個(gè)項(xiàng)目需要 MATLAB R2018b 或更高版本以及 Image Processing Toolbox。檢查工具箱是否安裝在命令行敲ver看列表里有沒有 Image Processing Toolbox 就行。沒有的話通過附加功能管理器裝一下。啟動(dòng)方式有兩種。命令行方式最直接% 在 MATLAB 命令行里執(zhí)行 cd(fruit_recognition); % 進(jìn)入項(xiàng)目目錄 img imread(dataset/apple_01.jpg); result main(img); % 主函數(shù)返回識(shí)別結(jié)果 disp(result.label);如果項(xiàng)目帶了 GUI直接運(yùn)行main.m或者對(duì)應(yīng)的 App 文件界面上會(huì)有“加載圖片”和“開始識(shí)別”兩個(gè)按鈕點(diǎn)完結(jié)果會(huì)標(biāo)注在原圖上。第一次跑建議先用 dataset 里自帶的圖確認(rèn)流程通了再換自己的照片。4.2 關(guān)鍵參數(shù)怎么調(diào)調(diào)參是這個(gè)項(xiàng)目繞不開的環(huán)節(jié)幾個(gè)最常動(dòng)的參數(shù)和它們的調(diào)整方向參數(shù)位置作用調(diào)大效果調(diào)小效果目標(biāo)尺寸preprocess.m歸一化分辨率細(xì)節(jié)多、速度慢速度快、細(xì)節(jié)少濾波窗口preprocess.m去噪強(qiáng)度更平滑、邊緣糊保邊緣、噪點(diǎn)多色相區(qū)間segment_fruit.m分割范圍納入更多像素分割更嚴(yán)格形態(tài)學(xué)半徑segment_fruit.m開閉運(yùn)算核去噪強(qiáng)、可能吞細(xì)節(jié)保留細(xì)節(jié)、噪點(diǎn)多圓度閾值classify_fruit.m蘋果判據(jù)更嚴(yán)格更寬松調(diào)參的順序建議從分割開始。先把 mask 用 imshow 顯示出來看水果區(qū)域是不是完整、背景有沒有殘留分割對(duì)了再去看分類結(jié)果。分割不對(duì)的情況下調(diào)分類閾值是白費(fèi)功夫。4.3 用自帶數(shù)據(jù)驗(yàn)證流程驗(yàn)證流程是否正常可以寫個(gè)小腳本批量跑一遍 dataset% 批量測試統(tǒng)計(jì)識(shí)別情況 files dir(dataset/*.jpg); for i 1:length(files) img imread(fullfile(dataset, files(i).name)); result main(img); fprintf(%s - %s (score: %.2f)\n, ... files(i).name, result.label, result.score); end跑完看輸出如果大部分圖片都能給出合理標(biāo)簽說明流程沒問題。有個(gè)別識(shí)別錯(cuò)的先別急著改代碼把那張圖的中間結(jié)果mask、特征值打出來看看是哪一步出的問題定位清楚再動(dòng)手。5. 避坑與常見問題那些讓我重跑好幾遍的坑5.1 分割結(jié)果全是噪點(diǎn)現(xiàn)象mask 顯示出來是一片散點(diǎn)完全看不出水果輪廓。原因通常是色相閾值設(shè)得太寬把背景里的雜色也納入了。解決方法是先把原圖轉(zhuǎn) HSV用impixelinfo在圖上點(diǎn)幾個(gè)水果像素和背景像素看它們的 H 值分別是多少據(jù)此收窄區(qū)間。另外 S 和 V 的下限也要檢查背景如果是淺色S 值低加個(gè) S 下限就能濾掉。5.2 蘋果被識(shí)別成橘子現(xiàn)象紅色蘋果的識(shí)別結(jié)果偶爾跳到 orange。原因是蘋果的 H 值在 0 附近而橘子的區(qū)間從 0.05 開始邊界處有重疊。解決辦法是在分類規(guī)則里把蘋果的判斷放在前面并且給蘋果的色相條件加上H 0.95這個(gè)分支確???0 邊界的紅色被蘋果先接住。規(guī)則分類器的順序本質(zhì)上就是優(yōu)先級(jí)把最不容易混淆的判據(jù)放前面。5.3 換一批圖片后準(zhǔn)確率暴跌現(xiàn)象自帶數(shù)據(jù)跑得好好的換成自己拍的照片就大面積識(shí)別錯(cuò)誤。這幾乎都是光照和背景差異導(dǎo)致的。傳統(tǒng)閾值方法對(duì)成像條件敏感這是它的固有短板。應(yīng)對(duì)辦法有兩個(gè)方向一是拍攝時(shí)盡量用純色背景、均勻光照從源頭減少變量二是在預(yù)處理里加一步白平衡或直方圖均衡把不同圖片的亮度和對(duì)比度拉到相近水平減小域偏移。5.4 處理大圖時(shí)卡到懷疑人生現(xiàn)象用幾千萬像素的手機(jī)原圖跑每一步都要等好幾秒。原因是所有操作都是逐像素的圖越大越慢。解決很簡單在 preprocess 第一步就 imresize 到 256×256后面所有計(jì)算都在小圖上做。如果確實(shí)需要高分辨率輸出可以只在小圖上算 mask 和特征最后把 mask 放大回原尺寸再疊加顯示。5.5 regionprops 報(bào)字段錯(cuò)誤現(xiàn)象調(diào)用 regionprops 后取 stats.Circularity 報(bào)錯(cuò)說字段不存在。原因是 regionprops 的屬性名要用它支持的字符串圓度對(duì)應(yīng)的屬性名是 Circularity 沒錯(cuò)但如果你在調(diào)用時(shí)沒把它寫進(jìn)參數(shù)列表返回的結(jié)構(gòu)體里就不會(huì)有這個(gè)字段。檢查regionprops(mask, Area, Perimeter)這行需要哪些屬性就顯式列哪些別指望它全返回。6. 二次開發(fā)從規(guī)則分類器升級(jí)到可訓(xùn)練的分類流程規(guī)則分類器跑通之后最自然的改造方向就是把它換成可訓(xùn)練的分類器讓閾值從數(shù)據(jù)里學(xué)出來而不是手調(diào)。這一步不需要?jiǎng)忧懊娴姆指詈吞卣魈崛≈惶鎿Q classify_fruit.m 就行接口保持不變——輸入特征結(jié)構(gòu)體輸出標(biāo)簽和分?jǐn)?shù)。具體做法是先用 extract_features 把 dataset 里所有圖片的特征批量算出來存成一個(gè)特征矩陣標(biāo)簽單獨(dú)存一列。然后用 MATLAB 自帶的分類器應(yīng)用或者 fitcknn、fitcsvm 這類函數(shù)訓(xùn)練一個(gè)模型% 批量提取特征構(gòu)建訓(xùn)練集 files dir(dataset/*.jpg); X []; Y {}; for i 1:length(files) img imread(fullfile(dataset, files(i).name)); mask segment_fruit(preprocess(img), apple); % 分割邏輯復(fù)用 feats extract_features(img, mask); X [X; feats.hue_mean, feats.circularity, ... feats.aspect_ratio, feats.contrast]; Y{end1} extract_label(files(i).name); % 從文件名解析標(biāo)簽 end % 訓(xùn)練 KNN 分類器并做交叉驗(yàn)證 mdl fitcknn(X, Y, NumNeighbors, 3); cvmdl crossval(mdl, KFold, 5); fprintf(交叉驗(yàn)證準(zhǔn)確率: %.2f%%\n, ... (1 - kfoldLoss(cvmdl)) * 100);這樣改的好處是分類邊界由數(shù)據(jù)決定換一批圖片時(shí)只要重新標(biāo)注、重新訓(xùn)練不用再手動(dòng)調(diào)那一堆 if-else 閾值。特征維度也可以擴(kuò)展比如把 H 通道的直方圖分箱后拼進(jìn)去或者加上 Hu 矩這類形狀不變量。驗(yàn)證方法上交叉驗(yàn)證的準(zhǔn)確率比單次劃分更可靠樣本少的時(shí)候尤其明顯。我自己的習(xí)慣是每次改完分類器都先把混淆矩陣打出來看哪個(gè)類別容易被誤判一目了然。有一次蘋果和橘子互相誤判得厲害查了半天發(fā)現(xiàn)是特征里 hue_mean 的區(qū)分度不夠加了一維 H 直方圖特征之后就分開了。從那以后我每次動(dòng)特征或分類器都強(qiáng)制走一遍混淆矩陣加交叉驗(yàn)證不再只看總準(zhǔn)確率那個(gè)數(shù)。希望這個(gè)項(xiàng)目能幫到你把傳統(tǒng)圖像處理這條鏈路真正跑通、改順。本文還有配套的精品資源點(diǎn)擊獲取