習(xí)實現(xiàn)缺陷水果分選:Matlab從驗證到產(chǎn)線的完整流程)
做果蔬加工的朋友應(yīng)該都有體會分選線上的“挑壞果”這道工序看著簡單其實是最難自動化的一環(huán)。不同品類的缺陷形態(tài)差異極大——有的是磕碰造成的褐變有的是霉變白斑有的是果皮疤痕——而且同一種缺陷在不同品種、不同成熟度上的表現(xiàn)可能完全不一樣。傳統(tǒng)機器視覺靠固定閾值去分換個光照、換個批次就失靈。這幾年我陸陸續(xù)續(xù)做了幾個用機器學(xué)習(xí)做缺陷水果分選的項目其中一個典型做法就是在Matlab里快速完成算法驗證再遷移到產(chǎn)線設(shè)備。這篇文章就把這套流程拆開講清楚從數(shù)據(jù)準(zhǔn)備、特征與模型選型到Matlab代碼主干、評估調(diào)優(yōu)再到真正上線會遇到的工程問題一次說透。想做缺陷水果分選、或者剛接觸農(nóng)產(chǎn)品視覺檢測開發(fā)的人都可以拿這套思路作為起點。1. 缺陷分選的任務(wù)本質(zhì)先定義問題再談算法很多人在做這類項目時容易一上來就抱著深度學(xué)習(xí)模型不放結(jié)果數(shù)據(jù)集只有幾百張圖模型訓(xùn)出來連自己都不信。我的經(jīng)驗是先把“篩什么、分幾類、目標(biāo)是什么”這三件事徹底理清楚再談具體用哪種算法。1.1 缺陷的類型與分級標(biāo)準(zhǔn)決定了算法的輸出設(shè)計水果缺陷在視覺上并不是同一個概念至少可以分成這樣幾類缺陷類型典型表現(xiàn)視覺特征磕碰傷撞擊后局部褐變區(qū)域邊緣模糊顏色由黃褐色向深褐色過渡腐爛/霉變表面軟爛、菌絲或霉斑顏色突變明顯常伴隨凹陷或絨毛感疤痕/果銹生長期表皮損傷顏色與正常果皮差異小紋理粗糙蟲眼/孔洞蟲蛀造成的凹陷或開口局部暗色邊緣銳利畸形形狀偏離正常果形輪廓不規(guī)則與其他缺陷不同屬于形狀問題這五類缺陷對應(yīng)的視覺特征差異很大意味著同一個模型的輸入設(shè)計也可能不同??呐鰝透癄€適合用顏色與紋理特征捕捉畸形更適合分析輪廓形狀而疤痕如果顏色接近正常果皮可能要靠局部紋理細(xì)節(jié)才能抓住。更重要的是分揀目標(biāo)通常不止“好果/壞果”二分類。產(chǎn)線上常見的需求是三級分選無缺陷、輕微缺陷可打折銷售、嚴(yán)重缺陷直接剔除。這個輸出設(shè)計直接決定了最后的分類層是二分類還是三分類也決定了評估指標(biāo)怎么定。1.2 為什么固定閾值和人工規(guī)則在壞果面前會被打穿早期做機器視覺分選最常見的方案是用顏色空間加閾值把RGB轉(zhuǎn)到HSV或者Lab然后對某個通道設(shè)一個范圍超出范圍就判為缺陷。這個思路在玩具數(shù)據(jù)上看著沒問題一上產(chǎn)線就崩原因是這樣幾個一是光照不均。同一個蘋果表面高光區(qū)域和陰影區(qū)域的像素值差異可能比好果和壞果的差異還大。閾值一旦固定高光處的好果會被誤殺。二是缺陷顏色與正常果皮顏色接近。比如蘋果的果銹和正常紅暈之間、梨的碰傷和自然黃褐色之間在顏色上幾乎沒有清晰的界線。三是缺陷可能是局部的。一個果子95%是好的只有一小塊爛點。如果只統(tǒng)計全局平均色這個小面積缺陷很容易被淹沒如果做像素級閾值又會被果柄、花萼這些本身顏色比較深的部分干擾。所以純規(guī)則的方案只能在品種單一、光照控制極好、缺陷形態(tài)非常標(biāo)準(zhǔn)的條件下工作。一旦換成自然種植的水果規(guī)則就開始失效。這個場景下機器學(xué)習(xí)能自動從樣本中學(xué)習(xí)“缺陷到底長什么樣”尤其是小的局部異常比人工寫規(guī)則靠譜得多。1.3 機器學(xué)習(xí)介入的三個切入點根據(jù)項目需求和數(shù)據(jù)集條件大概有三條路線可以選整圖分類輸入一張果圖輸出是“好果/輕微缺陷/嚴(yán)重缺陷”。適合傳送帶上單果逐個拍照的場景實現(xiàn)最簡單。區(qū)域檢測/分割在圖上定位缺陷區(qū)域輸出缺陷位置的邊界框或像素掩膜。適合需要知道“壞在哪、有多大”的場景比如自動化削皮或精準(zhǔn)切割。質(zhì)量評分回歸輸出一個連續(xù)的質(zhì)量分?jǐn)?shù)。適合做出口分級比如按0到100分打分再映射到不同等級。這三個切入點不是互斥的。我接觸過的一個分選項目是先做整圖分類三分類把嚴(yán)重缺陷的果直接淘汰剩下“輕微缺陷”的果再用區(qū)域檢測找到缺陷位置好讓后端工人快速確認(rèn)能不能降價售賣。這樣既控制了初期實現(xiàn)難度又滿足業(yè)務(wù)需要。2. 樣本與標(biāo)注缺陷數(shù)據(jù)比模型更值得投入說句實在話這類項目能不能成七成取決于數(shù)據(jù)三成才取決于模型。但現(xiàn)實中大家最不重視的就是數(shù)據(jù)采集。很多項目組拿了一個公開果蔬數(shù)據(jù)集就開始跑代碼模型在公開數(shù)據(jù)上準(zhǔn)確率很高到了自己產(chǎn)線上連一半都不到。原因很簡單產(chǎn)線上的光照、相機角度、品種和公開數(shù)據(jù)集完全不一樣。2.1 采集環(huán)境的幾個關(guān)鍵設(shè)置采集圖像的目標(biāo)不是“拍得好看”而是“拍得穩(wěn)定”。同一個類別里光照引起的差異越少模型越好學(xué)。我常用的設(shè)置是這樣的背景選擇深色啞光板純黑或深藍(lán)。這樣后續(xù)做背景分割時HSV的飽和度通道能非常干凈地把水果和背景分開。光源用漫射光LED面板最好加遮光罩避免水果表面出現(xiàn)鏡面反射。高光的區(qū)域在圖像上是白色一片如果出現(xiàn)在訓(xùn)練集太多模型容易把這部分誤學(xué)成特征。相機高度和角度固定水果從傳送帶上滾過時盡量讓果萼/果柄朝向側(cè)面。角度一多變同一個果子的外觀差異會非常大增加了不必要的分類難度。分辨率不用太高500萬像素其實已經(jīng)夠用。更關(guān)鍵的是每顆果在畫面里的占比要基本一致不要讓小果和大果的成像尺寸差很多。采集數(shù)量上我建議至少覆蓋三個維度品種、成熟度、產(chǎn)地批次。每個維度下的缺陷形態(tài)都會有差異如果只采一批果模型上線遇到新批次大概率翻車。2.2 標(biāo)注粒度整圖、框選還是像素級這一步?jīng)Q定了標(biāo)注成本也決定了后面能做什么。標(biāo)注方式成本能支持的算法適用場景整圖級低圖像分類大樣本快速摸底矩形框中目標(biāo)檢測需要定位缺陷位置像素級掩膜高分割模型需要精確計算缺陷面積如果項目目標(biāo)是先做分類分選我強烈建議初期先不要做像素級標(biāo)注。用文件夾結(jié)構(gòu)組織數(shù)據(jù)一個類別一個文件夾Matlab的imageDatastore直接讀取半天就能建好一個可訓(xùn)練的數(shù)據(jù)集。后期如果需要上線區(qū)域檢測再挑一批樣本做框選或掩膜標(biāo)注。要特別提醒的是標(biāo)注標(biāo)準(zhǔn)的統(tǒng)一。比如“輕微碰傷”和“嚴(yán)重碰傷”誰說了算如果三個標(biāo)注員標(biāo)準(zhǔn)不一樣模型學(xué)到的東西就會混亂。我一般會先寫一版標(biāo)注規(guī)范配幾組典型的參考圖然后讓標(biāo)注人員先標(biāo)50張我來檢查一遍把分歧點全部統(tǒng)一后再擴大標(biāo)注規(guī)模。2.3 增強與類別平衡把少量缺陷樣本用出十倍效果缺陷水果的數(shù)據(jù)集天然不平衡好果數(shù)量遠(yuǎn)多于爛果。這很正常因為真實產(chǎn)線上合格率可能就是90%以上。問題是如果直接用原始比例訓(xùn)練分類器模型會把所有樣本都判成好果整體準(zhǔn)確率依然很高但爛果一個都沒攔住。處理思路有兩個方向。第一個是過采樣對缺陷類做圖像增強讓模型看到更多變換。第二個是代價敏感在訓(xùn)練時給缺陷類的誤判更高的懲罰權(quán)重。Matlab里做增強非常方便augmentedImageDatastore集成了旋轉(zhuǎn)、平移、縮放、反射這些操作。我自己會額外加這三樣亮度擾動±20%模擬不同曝光條件。高斯噪聲模擬傳感器噪聲。隨機局部遮擋模擬果梗、葉片遮擋的情況。有一點要注意不要過度增強。有一個常見的失敗案例把圖像旋轉(zhuǎn)90度后蘋果看起來還是蘋果但把長條形的香蕉旋轉(zhuǎn)成豎直外觀形態(tài)就失真了反而引入了錯誤樣本。增強操作必須符合這個水果品類在真實分選線上可能出現(xiàn)的姿態(tài)。3. 模型選型小樣本SVM與大樣本CNN的邊界模型選型沒有絕對的最佳只有合不合適。對于這種單品種水果的缺陷分類任務(wù)我通常按數(shù)據(jù)集規(guī)模畫一條線初始樣本總數(shù)在幾千張以內(nèi)走特征工程加SVM的路線數(shù)據(jù)量達(dá)到數(shù)萬張或者缺陷形態(tài)特別復(fù)雜再用卷積神經(jīng)網(wǎng)絡(luò)。3.1 特征工程加SVM千級樣本下的穩(wěn)妥路線傳統(tǒng)機器學(xué)習(xí)的核心是把圖像變成一組有判別力的數(shù)值特征。對水果缺陷分類我常用下面這組特征組合顏色特征在HSV空間分別計算均值、標(biāo)準(zhǔn)差必要時加入顏色直方圖。HSV比RGB更適合描述光照差異能把“同一顆果在不同亮度下”的差異盡量縮小。紋理特征用灰度共生矩陣GLCM提取對比度、能量、同質(zhì)性。磕碰傷和腐爛區(qū)域在紋理上往往是粗糙的和光滑果皮差異明顯。形狀特征面積、周長、圓形度、主軸長度比。對畸形果的識別特別有效。這組特征計算量不大維度也不高十幾維到幾十維完全不需要GPU。配合RBF核的SVM在幾百到幾千樣本量下泛化能力強于很多復(fù)雜模型而且訓(xùn)練只需幾秒鐘。為什么這個組合在中小樣本下比CNN靠譜是因為手動特征已經(jīng)先把人類經(jīng)驗注入進去了。比如我們明確知道顏色和紋理對缺陷判別很重要就主動提取了這兩類信息。而CNN要從零開始學(xué)這兩個概念需要大量樣本才能學(xué)到數(shù)據(jù)一少就過擬合。3.2 遷移學(xué)習(xí)與CNN什么時候值得切換當(dāng)條件變成這樣幾個我建議切換到CNN缺陷類型多且外觀變化大手動特征開始不夠用。樣本量已經(jīng)過萬或者能拿到一個同品類相近的預(yù)訓(xùn)練模型做遷移學(xué)習(xí)。產(chǎn)線光照環(huán)境無法完全控制要求模型對光照變化有更強的魯棒性。CNN方案在Matlab里通常不是從零開始訓(xùn)練而是用預(yù)訓(xùn)練網(wǎng)絡(luò)做遷移學(xué)習(xí)。把網(wǎng)絡(luò)的最后幾層換掉用果蔬數(shù)據(jù)微調(diào)前面的特征提取層。這樣做的好處是前面卷積層學(xué)到的邊緣、紋理、顏色漸變等基礎(chǔ)視覺特征可以復(fù)用大大降低了對數(shù)據(jù)量的要求。這里有一種常見的誤解以為遷移學(xué)習(xí)就不用準(zhǔn)備太多數(shù)據(jù)了。實際上遷移學(xué)習(xí)解決的是“特征提取能力”的起點問題但“缺陷長什么樣”這件事依然要靠你的數(shù)據(jù)教給它。如果自己的缺陷樣本太少遷移學(xué)習(xí)也一樣過擬合。3.3 為什么用Matlab做這個項目最順手我在這類項目里用過Python也用過Matlab現(xiàn)在可以坦然地說Matlab不是萬能的但在算法驗證階段確實是最順手的。第一圖像處理工具箱的函數(shù)覆蓋非常完整從讀取圖像、色彩空間轉(zhuǎn)換到形態(tài)學(xué)處理、GLCM特征提取全部是現(xiàn)成函數(shù)不需要東拼西湊裝包。第二從傳統(tǒng)機器學(xué)習(xí)到深度學(xué)習(xí)兩個工具箱能無縫銜接。你可以在同一個腳本里先用SVM做基線再用trainNetwork訓(xùn)練CNN評估部分的代碼幾乎不用改。第三調(diào)試體驗好。矩陣和圖像的變量可以在工作區(qū)直接可視化縮放查看缺陷區(qū)域非常直觀。Python在這一點上差不少往往要寫額外代碼把中間結(jié)果保存成圖片才能看到。第四部署路徑比較清晰。模型訓(xùn)練完之后可以用GPU Coder生成C/CUDA代碼也可以直接調(diào)用導(dǎo)出的模型在嵌入式設(shè)備上跑推理。雖然Python生態(tài)的部署工具多但Matlab這條鏈路在工業(yè)場景里其實很成熟。4. 核心代碼實現(xiàn)從預(yù)處理到分類的主干拆解下面這段我盡量說細(xì)一點把每一步的核心邏輯和為什么這么寫講清楚。完整的工程代碼會多一些但核心骨架就是這幾段。4.1 圖像讀取與背景分割第一步是把圖像里的水果和背景干凈地分開。用深色背景的話HSV空間里背景的飽和度通常很低直接用飽和度閾值就能摳出前景。% 讀取圖像 I imread(apple_sample.jpg); % 轉(zhuǎn)換到HSV空間 hsv rgb2hsv(I); % 提取飽和度通道低飽和度的背景置為0 mask hsv(:,:,2) 0.15; % 形態(tài)學(xué)閉運算填充小空洞 mask imclose(mask, strel(disk, 12)); % 填充內(nèi)部孔洞 mask imfill(mask, holes); % 利用掩膜摳出前景 J bsxfun(times, uint8(I), uint8(mask));這里有幾個容易踩的坑。飽和度閾值不能拍腦袋定要拿產(chǎn)線上實際拍的照片統(tǒng)計一下背景和果皮的飽和度分布再定。有的水果表面本身飽和度就不高比如部分黃桃品種閾值設(shè)高了會把果皮部分也當(dāng)背景摳掉。閉運算的結(jié)構(gòu)元素大小也要根據(jù)圖像分辨率調(diào)整圖像大就適當(dāng)加大半徑否則填補不了缺陷區(qū)域造成的空洞。4.2 特征提取函數(shù)設(shè)計特征提取的核心是把一張圖像壓縮成一串有判別力的數(shù)字。下面這個函數(shù)我經(jīng)常用包含了顏色矩和GLCM紋理特征。function feat extractFruitFeatures(I) I im2double(I); hsv rgb2hsv(I); % 顏色矩每個通道的均值和標(biāo)準(zhǔn)差 feat []; for ch 1:3 C hsv(:,:,ch); feat [feat, mean(C(:)), std(C(:))]; end % 灰度共生矩陣紋理特征 gray rgb2gray(I); glcm graycomatrix(gray, ... Offset, [0 1; -1 1; -1 0; -1 -1], ... Sym, true, ... NumLevels, 64); stats graycoprops(glcm, {Contrast, Energy, Homogeneity}); feat [feat, mean(stats.Contrast), mean(stats.Energy), mean(stats.Homogeneity)]; end這段代碼的邏輯是先用HSV空間提取顏色信息因為HSV比RGB在光照變化下更穩(wěn)定再對灰度圖計算四個方向上的GLCM把對比度、能量、同質(zhì)性取平均消除方向敏感性。如果后面發(fā)現(xiàn)特征不夠用可以繼續(xù)加入顏色直方圖、形狀因子等特征維度的擴展不影響整體流程。4.3 SVM訓(xùn)練與評估有了特征提取函數(shù)接下來就是把整個數(shù)據(jù)集的特征矩陣算出來然后訓(xùn)練SVM。% 使用imageDatastore管理數(shù)據(jù) imds imageDatastore(dataset/, ... IncludeSubfolders, true, ... LabelSource, foldernames); % 劃分訓(xùn)練集和測試集stratify保證類別比例一致 [imdsTrain, imdsTest] splitEachLabel(imds, 0.7, randomized); % 提取訓(xùn)練集特征 numTrain numel(imdsTrain.Files); trainFeatures zeros(numTrain, 9); for i 1:numTrain I readimage(imdsTrain, i); trainFeatures(i, :) extractFruitFeatures(I); end trainLabels imdsTrain.Labels; % 訓(xùn)練多分類SVMRBF核特征標(biāo)準(zhǔn)化 model fitcecoc(trainFeatures, trainLabels, ... Learners, templateSVM(KernelFunction, rbf, KernelScale, auto), ... Standardize, true); % 提取測試集特征并預(yù)測 numTest numel(imdsTest.Files); testFeatures zeros(numTest, 9); for i 1:numTest I readimage(imdsTest, i); testFeatures(i, :) extractFruitFeatures(I); end predictions predict(model, testFeatures); % 混淆矩陣 cm confusionchart(imdsTest.Labels, predictions);這段代碼里有三個細(xì)節(jié)值得展開。一是splitEachLabel默認(rèn)支持按類別比例分層劃分這比隨機切分更靠譜防止某一類全被分到測試集里。二是fitcecoc默認(rèn)采用一對一多類策略對三分類來說內(nèi)部會有三組二分類器效果穩(wěn)定。三是特征標(biāo)準(zhǔn)化一定要打開因為顏色矩的量級和GLCM的量級差別很大不標(biāo)準(zhǔn)化的話SVM會過分依賴數(shù)值大的特征。4.4 CNN快速實現(xiàn)的一個最小方案如果你的數(shù)據(jù)量上來了想切換到CNNMatlab里的代碼也很直白。% 定義網(wǎng)絡(luò)結(jié)構(gòu) layers [ imageInputLayer([100 100 3]) convolution2dLayer(3, 16, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) convolution2dLayer(3, 32, Padding, same) batchNormalizationLayer reluLayer maxPooling2dLayer(2, Stride, 2) fullyConnectedLayer(3) softmaxLayer classificationLayer]; % 數(shù)據(jù)增強 imageAugmenter imageDataAugmenter(... RandRotation, [-15 15], ... RandXTranslation, [-10 10], ... RandYTranslation, [-10 10], ... RandXScale, [0.9 1.1], ... RandYScale, [0.9 1.1], ... RandBrightness, [-0.15 0.15]); augmentedImds augmentedImageDatastore([100 100], imdsTrain, ... DataAugmentation, imageAugmenter); % 訓(xùn)練參數(shù)與訓(xùn)練 options trainingOptions(adam, ... InitialLearnRate, 1e-3, ... MaxEpochs, 30, ... MiniBatchSize, 32, ... Plots, training-progress); net trainNetwork(augmentedImds, layers, options);這里網(wǎng)絡(luò)的卷積層參數(shù)我故意設(shè)得很小是因為缺陷分類任務(wù)不需要像ImageNet那樣上千類的大網(wǎng)絡(luò)小網(wǎng)絡(luò)在數(shù)據(jù)有限時反而泛化更好、訓(xùn)練更快。輸入圖像統(tǒng)一縮放到100x100足以看清局部缺陷。注意這里我用的augmentedImageDatastore只對訓(xùn)練集做增強測試集不要增強否則評估結(jié)果會失真。4.5 跑通流程后的三條經(jīng)驗第一先跑傳統(tǒng)特征加SVM做基線。哪怕你篤定最后用CNN也務(wù)必先訓(xùn)練一個SVM基線。它能幫你快速判斷數(shù)據(jù)集是否靠譜如果SVM的準(zhǔn)確率都達(dá)到90%以上說明問題本身不難CNN沒必要上如果SVM只有70%可能數(shù)據(jù)量或特征有問題先解決這些問題再上CNN才有效。第二隨時可視化中間結(jié)果。我習(xí)慣在每個處理函數(shù)里保留幾張中間圖像的輸出用于確認(rèn)背景分割有沒有把果肉切掉、特征提取是否合理。這種“看一眼中間結(jié)果”的習(xí)慣能省下大量后期調(diào)參的時間。第三把特征提取函數(shù)設(shè)計成獨立模塊。后續(xù)無論是換網(wǎng)絡(luò)還是加特征都不用改主循環(huán)代碼。我見過很多項目把所有邏輯寫在一個腳本里改一個參數(shù)要跑半天這是工程習(xí)慣問題。5. 評估指標(biāo)與閾值調(diào)整別只看準(zhǔn)確率很多初學(xué)者拿到測試集準(zhǔn)確率90%就歡天喜地這個心態(tài)在分選項目里是要吃虧的。真實產(chǎn)線上準(zhǔn)確率是最容易欺騙人的指標(biāo)。5.1 混淆矩陣在不同代價下的讀法先看一個典型的混淆矩陣場景。假設(shè)總共1000顆果900顆好果、100顆嚴(yán)重缺陷。分類器把所有果全部判為好果準(zhǔn)確率90%。但這個分類器在產(chǎn)線上毫無價值因為100顆爛果全部流入市場。更合理的評估是分別看這三組數(shù)字指標(biāo)計算方式反映的問題精確率查準(zhǔn)率判為缺陷的果中有多少真的是缺陷誤檢率高低影響好果浪費比例召回率查全率真實缺陷中有多少被攔住漏檢率高低影響壞果流出比例F1分?jǐn)?shù)兩者的調(diào)和平均綜合平衡在分選場景里漏檢和誤檢的成本是不對稱的。漏檢意味著壞果流向市場可能引發(fā)投訴、品牌受損單次成本可能很高。誤檢意味著好果被丟棄單次成本是這顆果的售價看起來低但量大了累計成本也可觀。所以實際項目里我會先和業(yè)務(wù)方確認(rèn)他們更在意哪一類錯誤。如果更在意爛果不能流出那就優(yōu)先拉高召回率哪怕犧牲一些精確率。這在算法上的實現(xiàn)方法就是調(diào)整分類閾值。5.2 分類閾值調(diào)優(yōu)的實際操作SVM或者CNN輸出的是每個類別的分類得分或概率。默認(rèn)情況下模型會把樣本判給得分最高的那個類。我們可以不這么做。具體操作是用fitcecoc的predict輸出里包含得分矩陣或者訓(xùn)練完CNN后用predict輸出各分類的概率。然后我們不再取最大值的索引而是自己設(shè)定一個閾值只有當(dāng)“嚴(yán)重缺陷”的概率超過某個值時才判為嚴(yán)重缺陷否則寧可判成好果讓人工復(fù)檢。% 獲取分類得分而不是硬標(biāo)簽 [~, scores] predict(model, testFeatures); % 假設(shè)類別順序是好果1輕微缺陷2嚴(yán)重缺陷3 % 自定義閾值嚴(yán)重缺陷概率大于0.4才判為嚴(yán)重 severeIdx scores(:, 3) 0.4; finalLabels repmat(好果, size(severeIdx)); finalLabels(scores(:, 2) scores(:, 1) scores(:, 2) scores(:, 3)) 輕微缺陷; finalLabels(severeIdx) 嚴(yán)重缺陷;這個閾值怎么選定把測試集上所有樣本的分?jǐn)?shù)畫成直方圖或者畫出ROC曲線找到召回率與精確率的平衡點。通常我會畫出閾值從0.1到0.9變化時漏檢率和誤檢率的變化曲線讓業(yè)務(wù)方看著曲線來選閾值。因為閾值本質(zhì)上是商業(yè)決策不應(yīng)該由算法工程師一個人拍板。5.3 驗證集劃分避免同批次數(shù)據(jù)泄露這是最容易踩的一個坑比模型本身更值得警惕。如果訓(xùn)練集和測試集里的圖像是從同一批水果、同一光照條件下連續(xù)拍的它們之間高度相似測試集準(zhǔn)確率會虛高。一旦換到新批次的水果準(zhǔn)確率會斷崖式下跌。正確的做法是按“批次”或“采集時間”來劃分?jǐn)?shù)據(jù)。比如上個月采的果圖全部用于訓(xùn)練這個月新采的果圖全部用于測試。這樣測試結(jié)果才更接近線上真實表現(xiàn)。也可以用cvpartition做分層交叉驗證但每一折內(nèi)部的樣本仍然可能來自同一批次所以最穩(wěn)妥的還是時間維度上的前后切分。我在一個項目里就吃過這個虧。訓(xùn)練和測試來自同一批橘子準(zhǔn)確率96%自我感覺很好。結(jié)果換了一批不同產(chǎn)地的橘子準(zhǔn)確率直接掉到82%。后來把所有數(shù)據(jù)按采集日期重新劃分訓(xùn)練集與測試集準(zhǔn)確率就穩(wěn)定在90%左右。這個90%雖然比96%低但它才是真實水平。6. 從實驗室到分選線讓人頭疼的是工程問題模型在電腦上跑通只是第一步真正上線之后你才會發(fā)現(xiàn)算法本身的難度可能只占30%剩下70%都是工程問題。這里挑三個最典型的展開說。6.1 推理速度與硬件部署先算一筆時間賬。假設(shè)分選線傳送帶速度是1米/秒相機視野覆蓋20厘米那么一顆果子從進入視野到離開視野只有0.2秒。如果你的系統(tǒng)還要在判定后控制機械機構(gòu)把缺陷果推出去留給算法的時間還要再砍一半大概100毫秒。而且這100毫秒里包含圖像采集、傳輸、推理、輸出控制信號的整個鏈路。SVM方案在這種場景下優(yōu)勢明顯單張圖像特征提取加預(yù)測能在CPU上跑到幾十毫秒以內(nèi)。CNN方案就看硬件了在嵌入式設(shè)備上跑小型網(wǎng)絡(luò)也要幾十毫秒再加預(yù)處理時間就逼近極限。所以如果產(chǎn)線速度很快優(yōu)先選輕量網(wǎng)絡(luò)比如只保留兩三個卷積層的極小網(wǎng)絡(luò)或者用深度可分離卷積結(jié)構(gòu)推理速度能大幅提升。Matlab的部署鏈路是把訓(xùn)練好的模型導(dǎo)出然后用GPU Coder生成C代碼再交叉編譯到工控機或嵌入式板卡。我建議在項目一開始就確認(rèn)目標(biāo)硬件不要讓算法在開發(fā)機上跑得飛快到了產(chǎn)線硬件上才發(fā)現(xiàn)推理時間嚴(yán)重超標(biāo)。6.2 光源、背景、遮擋產(chǎn)線環(huán)境的隱形變量同一套訓(xùn)練集里的圖片如果是在實驗室環(huán)境拍的到了工廠環(huán)境很容易出現(xiàn)三個問題。一是環(huán)境光干擾。工廠車間的頂燈、窗戶自然光都會影響成像要裝遮光罩把相機和檢測區(qū)域圍起來保證檢測區(qū)域的光照只來自可控光源。二是果體表面反光。表面光亮的蘋果和打過蠟的水果會反射光源形成高光區(qū)塊被模型誤認(rèn)成白色霉斑。解決方法是改用漫射光源或者在預(yù)處理里把高光區(qū)域的像素做校正處理。三是果與果之間的遮擋。如果相機視野里同時出現(xiàn)兩顆果邊緣交錯背景分割就很容易出錯。這種情況要么調(diào)整物理結(jié)構(gòu)讓果子單顆通過要么改用檢測模型而不是分類模型。這些事不少是到了現(xiàn)場才發(fā)現(xiàn)的。有一次項目調(diào)試時傳送帶表面有一層淡淡的水漬導(dǎo)致背景分割時飽和度閾值把部分背景誤當(dāng)成前景間接影響了特征提取的質(zhì)量。調(diào)整后加了除水裝置才恢復(fù)正常。小結(jié)一下任何“環(huán)境變化”都可能影響視覺系統(tǒng)的表現(xiàn)在現(xiàn)場跑之前留出充足的聯(lián)調(diào)時間。6.3 模型上線后的維護閉環(huán)模型上線不是結(jié)束而是新一輪數(shù)據(jù)積累的開始。真實產(chǎn)線上被誤判的樣本是寶貴的訓(xùn)練資源。我建議在分選線后端加一個復(fù)核環(huán)節(jié)定期把誤檢和漏檢的圖像回收回來由質(zhì)檢人員打標(biāo)簽累積一段時間后補充進訓(xùn)練集做增量訓(xùn)練或遷移學(xué)習(xí)。具體做法是每兩周或者每月導(dǎo)出一批誤判樣本人工復(fù)核后并入原數(shù)據(jù)集重新訓(xùn)練模型并對比新舊模型在驗證集上的表現(xiàn)。如果新模型沒有提升就檢查是不是標(biāo)注出了分歧如果提升了再部署到產(chǎn)線。這個節(jié)奏不需要太頻繁但是必須堅持因為新季節(jié)、新品種的水果外觀變化非??煲粋€模型在另一個產(chǎn)季往往就不適用了。還有一個容易忽略的細(xì)節(jié)是做好版本管理。模型文件、訓(xùn)練代碼、數(shù)據(jù)集的版本要記錄清楚否則幾個月后回來看自己當(dāng)初的模型完全不知道當(dāng)時用的什么參數(shù)、什么數(shù)據(jù)訓(xùn)出來的。我在項目里會為每個版本建一個文件夾包含訓(xùn)練腳本、網(wǎng)絡(luò)結(jié)構(gòu)定義、訓(xùn)練時的關(guān)鍵指標(biāo)截圖以及模型文件的哈希值。做這類項目我最大的體會是算法本身不是瓶頸數(shù)據(jù)質(zhì)量、評估方式和工程細(xì)節(jié)才是。你在Matlab里花一晚上寫出來的分類器可能比很多團隊花一個月搭出來的深度學(xué)習(xí)框架更實用。先把數(shù)據(jù)準(zhǔn)備扎實把評估指標(biāo)和業(yè)務(wù)目標(biāo)對應(yīng)起來再用最小的模型跑通整個流程最后再考慮上復(fù)雜方案這條路是最穩(wěn)的。