系統(tǒng):從數(shù)據(jù)標(biāo)注到部署全解析)
開(kāi)頭做水藻檢測(cè)這幾年我最大的感受是傳統(tǒng)的顯微鏡人工鏡檢已經(jīng)遠(yuǎn)遠(yuǎn)跟不上水質(zhì)監(jiān)測(cè)的實(shí)際節(jié)奏了。一個(gè)水樣從采樣、固定、沉淀到人工計(jì)數(shù)熟練的操作員也得花十幾分鐘遇到藻類爆發(fā)的季節(jié)樣品堆積如山等結(jié)果出來(lái)水華可能已經(jīng)進(jìn)入下一個(gè)階段了。也正是因?yàn)檫@個(gè)痛點(diǎn)我?guī)е鴪F(tuán)隊(duì)做了一個(gè)基于YOLOv8深度學(xué)習(xí)的水藻檢測(cè)系統(tǒng)把整個(gè)流程從人工鏡檢變成了拍照、上傳、出結(jié)果三步走。這篇文章就把這個(gè)項(xiàng)目從頭到尾拆開(kāi)講清楚包括YOLOv8的選型邏輯、YOLO數(shù)據(jù)集的構(gòu)建標(biāo)準(zhǔn)、UI界面的設(shè)計(jì)思路、模型訓(xùn)練和部署落地中踩過(guò)的坑希望能給正在做生物目標(biāo)檢測(cè)或水質(zhì)監(jiān)測(cè)自動(dòng)化的朋友一些參考。1. 水藻檢測(cè)到底在解決什么問(wèn)題為什么選擇YOLOv81.1 人工鏡檢的瓶頸遠(yuǎn)比想象中嚴(yán)重水藻檢測(cè)不是一個(gè)錦上添花的功能而是水質(zhì)預(yù)警里非常剛性的一環(huán)。水體中藻類的種類和密度直接反映富營(yíng)養(yǎng)化程度藍(lán)藻、綠藻、硅藻、甲藻等不同門類的藻類對(duì)應(yīng)的水體健康風(fēng)險(xiǎn)和治理手段完全不同。傳統(tǒng)做法是通過(guò)顯微鏡人工觀察玻片樣本識(shí)別藻種并計(jì)數(shù)最后換算成單位體積的藻細(xì)胞數(shù)量。這個(gè)流程的問(wèn)題在于三個(gè)方面。第一是慢。一個(gè)樣本從制樣到出報(bào)告通常需要10到20分鐘高頻監(jiān)測(cè)場(chǎng)景下根本吃不消。我見(jiàn)過(guò)某水質(zhì)監(jiān)測(cè)平臺(tái)在夏季高峰期每天要處理上百個(gè)水樣人工鏡檢團(tuán)隊(duì)三班倒還是積壓嚴(yán)重。第二是主觀性太強(qiáng)。不同的操作員對(duì)同一種藻的識(shí)別標(biāo)準(zhǔn)不一致特別是半透明、形態(tài)不規(guī)則的藻類誤差很大同一個(gè)樣本不同人計(jì)數(shù)可能差出30%以上。第三是難以形成連續(xù)的數(shù)據(jù)積累。人工記錄的數(shù)據(jù)多半是紙質(zhì)的或者散亂的表格很難做成結(jié)構(gòu)化的樣本庫(kù)去支撐后續(xù)趨勢(shì)分析。所以這個(gè)項(xiàng)目的核心目標(biāo)非常明確用深度學(xué)習(xí)目標(biāo)檢測(cè)替代人工識(shí)別的部分做到圖像進(jìn)來(lái)、檢測(cè)結(jié)果出來(lái)同時(shí)把結(jié)果統(tǒng)計(jì)成可導(dǎo)出的結(jié)構(gòu)化數(shù)據(jù)。這也就決定了模型輸出的核心必須是目標(biāo)框類別置信度這樣標(biāo)準(zhǔn)的信息結(jié)構(gòu)而不是單純的圖像分類結(jié)論。1.2 為什么是YOLOv8而不是傳統(tǒng)圖像處理或兩階段檢測(cè)器很多第一次做生物識(shí)別項(xiàng)目的人會(huì)問(wèn)水藻檢測(cè)能不能用OpenCV的閾值分割、邊緣檢測(cè)去做我的回答是能做但只能做非常有限的應(yīng)用。藻類在顯微鏡下的形態(tài)千變?nèi)f化絲狀藻、群體藻、單細(xì)胞藻的輪廓差異極大光照和背景質(zhì)感的變化會(huì)讓閾值參數(shù)瞬間失效。傳統(tǒng)圖像分割適合背景干凈、目標(biāo)形態(tài)固定的場(chǎng)景水藻顯然不屬于這一類。用固定規(guī)則去擬合高度多變的生物形態(tài)本質(zhì)上是不現(xiàn)實(shí)的。接著我們對(duì)比了兩類深度學(xué)習(xí)方案兩階段檢測(cè)器和單階段檢測(cè)器。兩階段檢測(cè)器比如Faster R-CNN這一類的識(shí)別精度確實(shí)高但推理速度普遍較慢在實(shí)時(shí)視頻流檢測(cè)場(chǎng)景下很難保證流暢性。而水藻檢測(cè)的最終形態(tài)一定是要支持實(shí)時(shí)或者準(zhǔn)實(shí)時(shí)檢測(cè)的——現(xiàn)場(chǎng)采集的視頻、流動(dòng)顯微鏡的實(shí)時(shí)畫(huà)面都需要模型能在幾十毫秒內(nèi)完成一幀推理。YOLOv8這一代版本有幾個(gè)設(shè)計(jì)對(duì)生物目標(biāo)檢測(cè)特別友好anchor-free的解耦檢測(cè)頭讓正負(fù)樣本的定義更簡(jiǎn)潔C2f結(jié)構(gòu)在保持輕量的同時(shí)增強(qiáng)了梯度流動(dòng)多尺度特征融合網(wǎng)絡(luò)對(duì)大小差異懸殊的藻類目標(biāo)有更好的召回能力。再加上Ultralytics生態(tài)里訓(xùn)練、導(dǎo)出、部署的工具鏈非常完善這讓我們能更快地把模型從實(shí)驗(yàn)臺(tái)搬到實(shí)際系統(tǒng)里。做一個(gè)直觀對(duì)比方案識(shí)別精度推理速度開(kāi)發(fā)成本適合場(chǎng)景閾值分割/邊緣檢測(cè)低高低背景固定的簡(jiǎn)單目標(biāo)傳統(tǒng)HOGSVM分類器中低中中形態(tài)單一的目標(biāo)兩階段檢測(cè)器高低高對(duì)速度不敏感的離線檢測(cè)YOLOv8高高較低實(shí)時(shí)檢測(cè)、工程化落地選定YOLOv8之后整個(gè)項(xiàng)目就被拆成了三條線數(shù)據(jù)集建設(shè)、模型訓(xùn)練、UI界面與部署。接下來(lái)一一展開(kāi)。2. 數(shù)據(jù)集建設(shè)比模型訓(xùn)練更花時(shí)間的部分2.1 數(shù)據(jù)采集渠道與拍攝規(guī)范模型效果的上限由數(shù)據(jù)決定這句話在水藻檢測(cè)項(xiàng)目中體現(xiàn)得淋漓盡致。最開(kāi)始我們拿現(xiàn)成的公開(kāi)數(shù)據(jù)集試跑發(fā)現(xiàn)泛化效果很差——公開(kāi)數(shù)據(jù)集里的水藻圖像大多是在理想光照、單一背景下拍攝的而實(shí)際水樣的背景里會(huì)有雜質(zhì)、碎屑、氣泡藻類的密度和聚集狀態(tài)也完全不同。所以dataset部分我的建議是公開(kāi)數(shù)據(jù)打底自采數(shù)據(jù)做主力。自采數(shù)據(jù)的方式不復(fù)雜但需要注意規(guī)范。我們需要一臺(tái)帶數(shù)字相機(jī)的生物顯微鏡放大倍數(shù)控制在100到400倍之間這個(gè)區(qū)間既能看清藻類細(xì)胞細(xì)節(jié)又能保證單張畫(huà)面里有足夠的樣本量。拍攝時(shí)盡量統(tǒng)一曝光參數(shù)背景保持潔凈避免玻片上的劃痕和氣泡干擾。不同季節(jié)、不同水域的水樣都采樣一些因?yàn)橥瑯右环N藻在不同營(yíng)養(yǎng)條件下形態(tài)會(huì)發(fā)生變化如果只在單一條件下采集模型到現(xiàn)場(chǎng)很容易臉盲。每一類水藻的目標(biāo)建議至少收集500到1000個(gè)實(shí)例框太少的話模型學(xué)不到類內(nèi)變化太多的話標(biāo)注成本又過(guò)大。類別的定義也要提前想清楚是按形態(tài)學(xué)門類分藍(lán)藻、綠藻、硅藻、裸藻等還是按具體的屬來(lái)分。這個(gè)決定了標(biāo)注的難度和模型最終的使用方式。我們最后定了4個(gè)類別對(duì)應(yīng)項(xiàng)目實(shí)際關(guān)注的幾個(gè)關(guān)鍵指標(biāo)類群。2.2 標(biāo)注規(guī)則怎么定才不返工標(biāo)注是水藻檢測(cè)項(xiàng)目中最容易被低估的工作量。水藻和常規(guī)目標(biāo)人、車、貓狗有本質(zhì)區(qū)別很多藻類是半透明的邊緣模糊有些是絲狀群體黏連在一起有些單個(gè)細(xì)胞只有幾十個(gè)像素大小。如果標(biāo)注規(guī)則不提前定清楚標(biāo)注員和模型都會(huì)被折騰死。我們實(shí)際操作中定下的規(guī)則有這樣幾條第一所有水藻目標(biāo)都按最小可識(shí)別單元進(jìn)行框選。比如絲狀藻如果一段絲狀體超過(guò)視野范圍就按一段連續(xù)可辨識(shí)的區(qū)域來(lái)框不強(qiáng)行把整條絲拉進(jìn)去第二對(duì)于極度密集的區(qū)域框與框之間允許重疊但重疊面積不超過(guò)30%給后續(xù)NMS留出空間第三半透明藻類以可見(jiàn)輪廓為準(zhǔn)如果輪廓實(shí)在無(wú)法判斷寧可不框也不要框錯(cuò)第四背景中疑似藻類但無(wú)法確定的微小顆粒統(tǒng)一歸為背景不強(qiáng)行標(biāo)注。這些規(guī)則看起來(lái)簡(jiǎn)單但在實(shí)際標(biāo)注過(guò)程中反復(fù)出現(xiàn)分歧做了一輪培訓(xùn)之后才穩(wěn)定下來(lái)。工具方面我們用的是開(kāi)源的圖像標(biāo)注工具LabelImg支持YOLO格式的txt直接導(dǎo)出。但后面發(fā)現(xiàn)純手動(dòng)標(biāo)注的效率瓶頸太明顯換成了帶有半自動(dòng)標(biāo)注功能的工具先用一個(gè)小模型預(yù)標(biāo)注人工只修正錯(cuò)框和補(bǔ)漏框效率提高了接近三倍。中途如果發(fā)現(xiàn)某張圖里標(biāo)注框的邊界和模型實(shí)際輸出明顯不一致優(yōu)先去查標(biāo)注不用急著改模型參數(shù)——這是很多項(xiàng)目反復(fù)調(diào)參卻原地踏步的原因。2.3 數(shù)據(jù)增強(qiáng)與格式轉(zhuǎn)換的細(xì)節(jié)標(biāo)注完成之后原始的標(biāo)注框數(shù)量通常是不夠的尤其密集樣本很少。我們做數(shù)據(jù)增強(qiáng)的核心策略是Copy-Paste和Mosaic增強(qiáng)的組合。Copy-Paste可以把單個(gè)藻類目標(biāo)的圖像塊復(fù)制到不同的背景上從而增加目標(biāo)數(shù)量和背景多樣性Mosaic增強(qiáng)把四張圖拼在一起訓(xùn)練讓模型在一個(gè)batch里看到更多樣的數(shù)據(jù)對(duì)密集場(chǎng)景的魯棒性提升非常明顯。另外水藻檢測(cè)對(duì)光照變化比較敏感我們?cè)谠鰪?qiáng)策略里特意加了隨機(jī)亮度、對(duì)比度擾動(dòng)模擬不同顯微鏡光源下的色溫變化。這里要提醒一個(gè)非常容易踩的坑如果數(shù)據(jù)是從VOC格式XML或COCO格式JSON轉(zhuǎn)成YOLO格式txt的話坐標(biāo)換算一定要小心。YOLO格式保存的是歸一化后的中心點(diǎn)坐標(biāo)和寬高XML保存的是左上角和右下角坐標(biāo)。換算公式很簡(jiǎn)單x_center (x_min x_max) / 2 / image_widthy_center (y_min y_max) / 2 / image_heightwidth (x_max - x_min) / image_widthheight (y_max - y_min) / image_height。一旦寬高搞反訓(xùn)練出來(lái)的模型會(huì)全部偏移而且這種錯(cuò)誤在Loss曲線上很難直接看出來(lái)表現(xiàn)形式是模型一直不收斂或者收斂后驗(yàn)證集mAP極低。數(shù)據(jù)劃分方面我們堅(jiān)持的原則是按來(lái)源分集。也就是說(shuō)同一個(gè)水樣拍攝出來(lái)的連續(xù)圖片全部放進(jìn)訓(xùn)練集或者全部放進(jìn)驗(yàn)證集不能隨機(jī)混著切。否則模型在驗(yàn)證集上表現(xiàn)極好到了新水樣上立刻拉胯——因?yàn)轵?yàn)證集里有和訓(xùn)練集幾乎一樣的背景等于作弊。真實(shí)的場(chǎng)景永遠(yuǎn)是新背景、新光照、新藻類聚集狀態(tài)。3. 模型訓(xùn)練鏈路從配置到評(píng)估的完整流程3.1 模型規(guī)格選型與關(guān)鍵超參數(shù)配置YOLOv8提供了n/s/m/l/x等不同規(guī)格的模型參數(shù)從小到大速度和精度呈反向關(guān)系。水藻檢測(cè)這類任務(wù)的分辨率要求和目標(biāo)大小直接關(guān)聯(lián)。顯微鏡畫(huà)面里的藻類單細(xì)胞往往只占幾十個(gè)像素如果選太小的模型特征提取能力不足很容易漏檢。我們的建議是先用最小的nano模型把整個(gè)訓(xùn)練和推理流程跑通確認(rèn)數(shù)據(jù)和代碼沒(méi)有問(wèn)題然后換成small或者medium做正式訓(xùn)練。最終線上部署用的是medium規(guī)格在邊緣設(shè)備上跑單幀推理大約需要30到50毫秒完全夠用。訓(xùn)練配置方面我們使用的數(shù)據(jù)配置文件和命令如下。# data.yaml train: data/images/train val: data/images/val nc: 4 names: [blue_green_algae, green_algae, diatom, euglena]yolo detect train datadata.yaml modelyolov8m.pt epochs200 imgsz960 batch16這里有三個(gè)超參數(shù)值得展開(kāi)說(shuō)一下。第一個(gè)是輸入尺寸imgsz。默認(rèn)是640但水藻檢測(cè)屬于典型的小目標(biāo)檢測(cè)把輸入尺寸提高到960后小目標(biāo)的檢出率提升非常明顯代價(jià)是訓(xùn)練速度變慢和顯存占用變大。有條件的話可以上1024甚至1280但要先確認(rèn)顯存夠用。第二個(gè)是epochs我們?cè)?00輪左右驗(yàn)證集mAP基本收斂繼續(xù)訓(xùn)練會(huì)對(duì)訓(xùn)練集越來(lái)越過(guò)擬合。第三個(gè)是batch size盡量開(kāi)大充分占用顯存如果顯存不足就把batch調(diào)小配合梯度累積來(lái)保證訓(xùn)練的穩(wěn)定性。優(yōu)化器直接選了AdamW學(xué)習(xí)率保持默認(rèn)沒(méi)有額外手工微調(diào)。3.2 Loss曲線的觀察方法不是越低越好訓(xùn)練過(guò)程中最需要盯的是三類Lossbox_loss邊界框回歸損失、cls_loss分類損失、dfl_loss分布焦點(diǎn)損失。很多剛接觸目標(biāo)檢測(cè)的人看到Loss曲線一路下降就很開(kāi)心但這里有個(gè)常見(jiàn)的誤區(qū)Loss只是訓(xùn)練指標(biāo)最終評(píng)判要看驗(yàn)證集上的mAP等指標(biāo)。如果訓(xùn)練集Loss持續(xù)下降而驗(yàn)證集mAP停滯甚至下降說(shuō)明模型開(kāi)始過(guò)擬合這個(gè)時(shí)候應(yīng)該提前停止訓(xùn)練而不是繼續(xù)跑下去。我在訓(xùn)練水藻模型時(shí)觀察到的一個(gè)現(xiàn)象是cls_loss下降得很快但box_loss下降得相對(duì)緩慢。原因是水藻目標(biāo)的邊緣模糊標(biāo)注框本身存在一定的主觀性模型很難把邊界框回歸得非常精準(zhǔn)。遇到這種情況不要急優(yōu)先保證分類和中心點(diǎn)定位的準(zhǔn)確性框稍微大一點(diǎn)或者小一點(diǎn)在實(shí)際計(jì)數(shù)場(chǎng)景中影響不大。訓(xùn)練過(guò)程中每10輪保存一次checkpoint最后用best.pt而不是last.pt做推理。3.3 模型評(píng)估m(xù)AP之外還要看什么訓(xùn)練完的模型不能只看mAP50和mAP50-95的數(shù)值。在生物檢測(cè)場(chǎng)景里除了整體指標(biāo)必須要逐類看Precision和Recall。不同藻類的形態(tài)差異很大可能出現(xiàn)整體mAP還不錯(cuò)、但某一類比如硅藻的Recall特別低的情況因?yàn)楣柙鍤っ娴募y理在低倍鏡下不明顯模型容易漏檢。通過(guò)打印per-class的PR曲線和混淆矩陣才能定位到真正的問(wèn)題出在哪一類目標(biāo)上。另外驗(yàn)證集圖像和實(shí)時(shí)視頻流的檢測(cè)難度不是一個(gè)量級(jí)。靜態(tài)圖像光照穩(wěn)定、沒(méi)有運(yùn)動(dòng)模糊而實(shí)時(shí)視頻流中藻類在流動(dòng)液中會(huì)旋轉(zhuǎn)和移動(dòng)模型性能會(huì)明顯下降。所以我在訓(xùn)練結(jié)束后習(xí)慣用一段真實(shí)場(chǎng)景錄制的視頻做二次驗(yàn)證統(tǒng)計(jì)連續(xù)幀的漏檢率這比驗(yàn)證集上的mAP更能反映線上表現(xiàn)。這個(gè)習(xí)慣幫我們提前發(fā)現(xiàn)了很多問(wèn)題比如某類藻在運(yùn)動(dòng)狀態(tài)下形態(tài)扭曲嚴(yán)重后續(xù)我們針對(duì)這類數(shù)據(jù)做了專項(xiàng)補(bǔ)充。4. UI界面設(shè)計(jì)讓檢測(cè)系統(tǒng)真正可用4.1 為什么選擇PyQt5做桌面端界面模型訓(xùn)練得再好如果沒(méi)有一個(gè)可操作的界面檢測(cè)系統(tǒng)就只能活在代碼里。我們最終選擇了PyQt5來(lái)搭建UI原因很直接Python生態(tài)無(wú)縫對(duì)接模型推理和OpenCV圖像處理信號(hào)槽機(jī)制做多線程交互非常順手打包成桌面程序后部署到監(jiān)控室或者實(shí)驗(yàn)室的工作站上也很方便。相比之下Web前端方案比如Flask網(wǎng)頁(yè)雖然界面美觀但部署需要額外的服務(wù)環(huán)境在離線內(nèi)網(wǎng)環(huán)境里反而麻煩。Tkinter雖然輕量但做復(fù)雜交互界面時(shí)樣式和性能都跟不上。我們的界面功能劃分得很清楚左側(cè)是控制區(qū)和參數(shù)區(qū)右側(cè)是圖像顯示區(qū)和結(jié)果列表區(qū)。頂部是模式切換單張圖片檢測(cè)、本地視頻檢測(cè)、攝像頭實(shí)時(shí)檢測(cè)三種模式。左側(cè)參數(shù)區(qū)可以調(diào)節(jié)置信度閾值和NMS IoU閾值這兩個(gè)參數(shù)在實(shí)際使用中經(jīng)常需要現(xiàn)場(chǎng)調(diào)整放到界面上可以避免每次改代碼。右側(cè)結(jié)果區(qū)實(shí)時(shí)顯示檢測(cè)框、類別名稱、置信度以及各類別的統(tǒng)計(jì)計(jì)數(shù)并支持一鍵導(dǎo)出CSV報(bào)告。4.2 多線程架構(gòu)不能讓界面卡死在模型推理上這是UI設(shè)計(jì)中最核心的一個(gè)坑。如果把模型推理直接寫(xiě)在主線程里每一幀檢測(cè)耗時(shí)幾十毫秒甚至上百毫秒界面會(huì)明顯卡頓點(diǎn)擊按鈕也半天沒(méi)反應(yīng)體驗(yàn)極差。正確做法是讓界面主線程只負(fù)責(zé)渲染和交互模型推理放在單獨(dú)的QThread工作線程里通過(guò)信號(hào)把檢測(cè)結(jié)果傳回主線程。這樣即使模型在后臺(tái)慢一點(diǎn)界面依然流暢。核心代碼結(jié)構(gòu)大致是這樣的import sys from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QMainWindow, QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget from ultralytics import YOLO class InferThread(QThread): frame_signal pyqtSignal(object, list) def __init__(self, model_path, source): super().__init__() self.model YOLO(model_path) self.source source self.running True def run(self): import cv2 cap cv2.VideoCapture(self.source) while self.running: ret, frame cap.read() if not ret: break results self.model(frame, conf0.35, iou0.5) boxes [] for r in results: for box in r.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 [int(v) for v in box.xyxy[0]] boxes.append((cls_id, conf, x1, y1, x2, y2)) self.frame_signal.emit(frame, boxes) cap.release() class MainWindow(QMainWindow): def __init__(self): super().__init__() self.btn QPushButton(選擇視頻, self) self.label QLabel(self) layout QVBoxLayout() layout.addWidget(self.btn) layout.addWidget(self.label) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.btn.clicked.connect(self.start_detect) self.thread None def start_detect(self): path, _ QFileDialog.getOpenFileName(self, 選擇視頻) if not path: return self.thread InferThread(best.pt, path) self.thread.frame_signal.connect(self.update_frame) self.thread.start() def update_frame(self, frame, boxes): import cv2 for cls_id, conf, x1, y1, x2, y2 in boxes: color (0, 255, 0) cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) cv2.putText(frame, f{cls_id} {conf:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.label.setPixmap(QPixmap.fromImage(qimg))這個(gè)示例只是最簡(jiǎn)結(jié)構(gòu)實(shí)際項(xiàng)目中還要加攝像頭熱插拔檢測(cè)、視頻暫停繼續(xù)開(kāi)關(guān)、檢測(cè)結(jié)果自動(dòng)累計(jì)統(tǒng)計(jì)、置信度閾值實(shí)時(shí)滑塊聯(lián)動(dòng)等。但核心思想就是一句話重活放子線程界面只畫(huà)結(jié)果。4.3 界面資源組織與啟動(dòng)封裝UI模塊的資源組織也很重要。我們把模型權(quán)重文件統(tǒng)一放在models目錄下類別名稱映射寫(xiě)成一個(gè)txt文件或直接寫(xiě)在配置文件里界面啟動(dòng)時(shí)加載。這樣后續(xù)模型迭代時(shí)只要替換權(quán)重文件界面代碼幾乎不用動(dòng)。啟動(dòng)入口做了封裝雙擊bat文件或者桌面快捷方式就能拉起程序不需要使用者手動(dòng)安裝Python依賴和模型路徑。實(shí)際部署到實(shí)驗(yàn)室后操作員只關(guān)心點(diǎn)哪個(gè)按鈕能出結(jié)果完全不碰代碼這才是工程化落地和Demo的區(qū)別。5. 部署優(yōu)化與推理加速5.1 ONNX導(dǎo)出與精度對(duì)齊驗(yàn)證當(dāng)模型在開(kāi)發(fā)機(jī)上驗(yàn)證效果穩(wěn)定后就可以進(jìn)入部署環(huán)節(jié)了。我們把PyTorch模型導(dǎo)出成ONNX格式這一步不僅是為了跨平臺(tái)部署更是為了在推理性能上做進(jìn)一步優(yōu)化。導(dǎo)出的命令很簡(jiǎn)單yolo export modelbest.pt formatonnx opset12 halfTrue導(dǎo)出完成后要做精度對(duì)齊驗(yàn)證用同一張測(cè)試圖分別跑PyTorch原模型和ONNX推理對(duì)比輸出的坐標(biāo)框和置信度。正常情況下兩者的差異應(yīng)該在極小的范圍內(nèi)如果出現(xiàn)明顯偏差優(yōu)先檢查opset版本和輸入輸出的名映射問(wèn)題。我們?cè)谧畛鯇?dǎo)出時(shí)就踩過(guò)這個(gè)坑opset版本過(guò)高導(dǎo)致某些算子在目標(biāo)機(jī)器上的推理庫(kù)不支持導(dǎo)出過(guò)程沒(méi)有報(bào)錯(cuò)但推理結(jié)果完全不對(duì)。5.2 推理加速的幾個(gè)實(shí)用技巧ONNX導(dǎo)出完成后推理端我們用的是ONNX Runtime相比原生的PyTorch推理速度提升了50%以上。在此基礎(chǔ)上還有幾招可以進(jìn)一步加速。第一是半精度推理在支持FP16的GPU或者AI加速芯片上把模型參數(shù)和中間張量都切成FP16推理速度幾乎翻倍而且對(duì)水藻檢測(cè)這類非極端精度要求的任務(wù)幾乎沒(méi)有指標(biāo)損失。第二是設(shè)置動(dòng)態(tài)輸入或固定輸入尺寸在部署時(shí)把輸入尺寸固定為訓(xùn)練時(shí)的960×960避免每次推理重新分配內(nèi)存。第三是對(duì)后處理進(jìn)行剪裁大幅降低類別數(shù)是關(guān)鍵但更重要的是把單幀檢測(cè)上限max_det從默認(rèn)的300調(diào)低到100左右水藻檢測(cè)一幀里極少超過(guò)100個(gè)目標(biāo)調(diào)低了反而能避免一些低置信度的雜亂框干擾計(jì)數(shù)。5.3 邊緣設(shè)備部署的取舍我們的系統(tǒng)最終計(jì)劃要部署到現(xiàn)場(chǎng)的水質(zhì)監(jiān)測(cè)站那里沒(méi)有高性能服務(wù)器只能用常見(jiàn)的嵌入式部署平臺(tái)。這種場(chǎng)景下UNet規(guī)模的模型都偏大需要把YOLOv8s或者YOLOv8n拿來(lái)做量化。INT8量化后模型體積能減少約75%推理速度能再上一個(gè)臺(tái)階但mAP會(huì)有小幅下降。我的建議是在量化前后用一段真實(shí)視頻跑一遍對(duì)比如果精度下降在可接受范圍內(nèi)比如mAP50從0.85降到0.8就可以接受INT8方案。另外現(xiàn)場(chǎng)部署還要考慮輸入源的適配工業(yè)相機(jī)和顯微鏡的SDK五花八門界面層最好把圖像采集也做一層抽象否則換一個(gè)相機(jī)型號(hào)可能就要改一遍代碼。6. 實(shí)測(cè)效果、常見(jiàn)問(wèn)題與排查思路6.1 真實(shí)場(chǎng)景中的誤檢漏檢案例模型上線后我們?cè)谡鎸?shí)水樣上做了一批驗(yàn)證發(fā)現(xiàn)的問(wèn)題比訓(xùn)練集上看到的要復(fù)雜得多。最典型的是氣泡誤檢水樣在制樣過(guò)程中會(huì)產(chǎn)生微小的氣泡在顯微鏡下呈現(xiàn)圓形、邊緣明亮形態(tài)上和一些微小的單細(xì)胞藻非常相似模型經(jīng)常把氣泡錯(cuò)認(rèn)成藻類。解決辦法不是一味調(diào)高置信度閾值那樣會(huì)同時(shí)砍掉真正的目標(biāo)而是要在數(shù)據(jù)層面補(bǔ)齊氣泡這類負(fù)樣本。我們專門采集了含氣泡的水樣圖像用背景類別或者直接不標(biāo)注讓模型在訓(xùn)練時(shí)見(jiàn)過(guò)足夠多的氣泡形態(tài)誤檢率顯著下降。另一個(gè)高發(fā)問(wèn)題是密集區(qū)域的漏檢和錯(cuò)檢。水華爆發(fā)期藻類聚集形成大團(tuán)塊單體邊界完全重疊人眼都很難分清個(gè)體。模型在團(tuán)塊區(qū)域的輸出往往是一大坨框NMS之后剩下幾個(gè)計(jì)數(shù)誤差很大。我們嘗試了兩種改進(jìn)一是把輸入尺寸進(jìn)一步加大到1280讓模型看到更多細(xì)節(jié)二是訓(xùn)練時(shí)針對(duì)性加入高密度的Copy-Paste增強(qiáng)樣本提高模型對(duì)重疊目標(biāo)的區(qū)分能力。效果有一定改善但要完全解決需要對(duì)標(biāo)注標(biāo)準(zhǔn)做更細(xì)致的定義比如在團(tuán)塊區(qū)域標(biāo)注群體作為一個(gè)獨(dú)立目標(biāo)而不是逐個(gè)個(gè)體。6.2 閾值參數(shù)現(xiàn)場(chǎng)調(diào)優(yōu)的思路部署后我們發(fā)現(xiàn)訓(xùn)練階段的效果很好不代表現(xiàn)場(chǎng)推理直接能用?,F(xiàn)場(chǎng)環(huán)境的光照、藻種結(jié)構(gòu)和水樣背景千差萬(wàn)別一個(gè)固定不變的置信度閾值無(wú)法適應(yīng)所有場(chǎng)景。所以界面才特別留了置信度和IoU閾值的實(shí)時(shí)調(diào)節(jié)控件。經(jīng)驗(yàn)是如果現(xiàn)場(chǎng)誤檢多把背景當(dāng)藻類優(yōu)先調(diào)高置信度閾值從0.25往0.4甚至0.5調(diào)如果漏檢多該檢測(cè)到的目標(biāo)沒(méi)有框出來(lái)優(yōu)先調(diào)低置信度閾值同時(shí)檢查輸入圖像有沒(méi)有運(yùn)動(dòng)模糊。IoU閾值則影響重疊框的合并強(qiáng)度密集場(chǎng)景下調(diào)低到0.4左右可以保留更多重疊目標(biāo)但可能帶來(lái)重復(fù)計(jì)數(shù)。這些參數(shù)沒(méi)有統(tǒng)一標(biāo)準(zhǔn)必須結(jié)合實(shí)際畫(huà)面效果微調(diào)。6.3 標(biāo)注一致性最容易被忽略的模型天花板最后想專門聊一下標(biāo)注一致性的問(wèn)題。水藻這類邊緣模糊的生物目標(biāo)標(biāo)注的主觀性對(duì)模型上限的影響非常巨大。如果A標(biāo)注員習(xí)慣把絲狀藻框得緊一些B標(biāo)注員習(xí)慣框得松一些模型學(xué)到的是一個(gè)忽緊忽松的邊界框分布效果自然拉胯。我們?cè)陧?xiàng)目中期做了一次標(biāo)注質(zhì)檢發(fā)現(xiàn)同一張圖的重復(fù)標(biāo)注IoU只有0.75左右這個(gè)數(shù)值在生物圖像任務(wù)里其實(shí)不算差但仍意味著模型學(xué)到的邊界本身就有噪聲。解決思路有兩個(gè)一是建立清晰的標(biāo)注SOP文檔配示例圖并在多人標(biāo)注時(shí)定期交叉抽檢二是用模型輔助標(biāo)注迭代先訓(xùn)練一個(gè)基線模型把所有待標(biāo)注圖片跑一遍預(yù)標(biāo)注人工只負(fù)責(zé)修正和確認(rèn)這樣能把標(biāo)注標(biāo)準(zhǔn)迅速拉齊。對(duì)于水藻這類重復(fù)性高、邊界模糊的目標(biāo)第二種方式明顯更省力也是我們最終采用的主要方式。6.4 一個(gè)可以繼續(xù)擴(kuò)展的方向項(xiàng)目做到這里基礎(chǔ)的水藻檢測(cè)流程已經(jīng)完整落地。如果后續(xù)要做得更深入一個(gè)自然的擴(kuò)展方向是從檢測(cè)哪些藻走向算有多少藻。借助目標(biāo)檢測(cè)輸出的框和類別計(jì)數(shù)結(jié)合采樣水樣的體積和顯微鏡視野面積可以估算單位體積內(nèi)的藻類密度進(jìn)一步做富營(yíng)養(yǎng)化趨勢(shì)曲線和水華預(yù)警。這種方法不需要額外的模型能力只需要在UI層的統(tǒng)計(jì)模塊里把幾何換算和單位換算做好。再往后還可以把檢測(cè)范圍從水藻擴(kuò)展到其他浮游生物輪蟲(chóng)、枝角類等形成一個(gè)通用的浮游生物圖像識(shí)別平臺(tái)。我自己在這個(gè)項(xiàng)目里最深刻的體會(huì)是一個(gè)目標(biāo)檢測(cè)系統(tǒng)真正的工程價(jià)值并不在于模型有多新、mAP有多高而在于數(shù)據(jù)是否真實(shí)、標(biāo)注是否統(tǒng)一、部署是否穩(wěn)定。最后分享一個(gè)小建議給準(zhǔn)備做類似生物目標(biāo)檢測(cè)的朋友動(dòng)手訓(xùn)練模型之前先花時(shí)間把標(biāo)注標(biāo)準(zhǔn)的細(xì)節(jié)定清楚把真實(shí)場(chǎng)景的樣本拍足。這一步做好了后面的路會(huì)順很多這一步偷懶了后面大概率要在調(diào)試中反復(fù)還債。