目標檢測實踐)
這篇數據集項目有點冷門但非常實在。福壽螺作為入侵物種這幾年在南方稻田、河道、濕地公園里泛濫得非常厲害人工清理成本高、效率低用目標檢測做自動識別其實是個很剛需的農業(yè)植保和生態(tài)監(jiān)測場景。所以看到這個福壽螺檢測數據集VOCYOLO格式545張2類別的項目我第一反應是做這件事的人應該是真的下過田、踩過泥、被卵塊惡心過的那種開發(fā)者不然不會把數據集做得這么具體。先說結論這份數據集定位很清晰545張圖像、2個類別、同時提供VOC和YOLO兩種標準格式覆蓋了從標注到訓練的最短路徑。它解決的核心問題是兩個一是讓做農業(yè)AI、生態(tài)治理的人少走收集和標注的彎路二是讓YOLO系列模型的訓練可以直接開箱使用不需要再做格式轉換。適合做目標檢測入門練手、農田害蟲檢測、邊緣設備部署驗證以及生態(tài)監(jiān)測類項目的同學參考。我這段時間把這個數據集從頭到尾捋了一遍從數據構成、標注質量、格式轉換邏輯到實際喂給YOLOv5訓練的效果踩了一些坑也總結了一些經驗。下面把整個過程完整拆出來希望對正準備搞農業(yè)檢測方向的人有幫助。1. 兩個檢測類別背后的真實需求1.1 福壽螺為什么值得做目標檢測福壽螺這個物種外來入侵的繁殖能力非常強一年可以產卵好幾次每次幾百上千顆。它啃食水稻秧苗、蓮藕、茭白這些水生作物對農田的危害很大。關鍵是它的卵是粉紅色、鮮紅色的一團一團附著在水線以上的莖稈、墻壁、石頭上識別度高但分布散、數量大人工作業(yè)很難全面排查。傳統(tǒng)的人工清理方式是拿鏟子刮卵塊、撿成螺然后集中銷毀。但問題很明顯河道岸線幾十公里稻田面積幾百上千畝靠人走一遍根本走不徹底漏檢率很高。無人機巡檢加上目標檢測模型自動識別然后根據坐標定點清理是目前各地植保部門在探索的路線。這里就體現出數據集的價值了。做農業(yè)AI的都知道模型算法本身大家都能跑通真正卡脖子的往往是數據。福壽螺不算常見公開數據集里的目標類別COCO、VOC官方數據集里根本沒有這個類想用預訓練模型做遷移學習也只能在別的類別基礎上湊合效果自然不理想。所以專門收集、標注、整理一份福壽螺檢測數據集是整個應用鏈路的底座工程。1.2 為什么2個類別就夠用很多做檢測的人一上來喜歡分類特別細比如按成螺、幼螺、卵塊、空殼、寄生螺分一堆類。但實際項目里分類越細標注成本越高模型學習難度越大而且不同類之間如果外觀接近反而會把AP拉低。土木工程里有一句話叫夠用就好做數據集標注也一樣分類粒度取決于下游任務。這份數據集選了2個類別從農業(yè)防治的角度來說是經過考量的類別對象定義防治動作成螺外觀可見的福壽螺成體殼體螺旋特征明顯人工撿拾或藥物滅殺卵塊粉紅色/紅色的卵團附著在植物莖稈、岸壁等物體上刮除銷毀阻止繁殖兩個類別對應兩種完全不同的清理動作。成螺是移動目標分布相對稀疏要的是定位準確卵塊是靜態(tài)目標經常成片出現要的是數量估算和密集場景下的不漏檢。這兩個需求在模型設計上是有區(qū)別的所以分成兩個類而不是合并成一個福壽螺大類更合理。2. 545張圖像的數據集是怎樣從零攢出來的2.1 素材收集的渠道與篩選收集福壽螺圖像最理想的來源是自己拿相機拍但工作量太大尤其要覆蓋不同光照、不同背景、不同生長階段靠單個人很難在短時間內湊夠。實際整理這類數據集通常有三個來源田間實地拍攝用手機或運動相機在稻田、水渠、池塘邊拍攝這類圖像最接近真實部署場景是數據集的主力。植保部門公開宣傳材料很多地區(qū)做過福壽螺防治宣傳公開過大量高清實拍圖可以作為補充。網絡圖庫檢索搜福壽螺、Pomacea canaliculata、福壽螺卵塊等關鍵詞篩選版權允許使用的圖片素材。篩選時我個人的標準很嚴格圖像模糊的、目標占比過小的、嚴重過曝或欠曝的、主體被遮擋超過一半的全部排除。545張最終入選的圖像實際上是從上千張原始素材里篩出來的質量比數量重要得多這個原則在訓練階段會被反復驗證。2.2 圖像清洗與去重只篩清晰度不夠還要做去重。福壽螺卵塊外觀高度相似網絡素材里經常出現同一張圖被不同網站反復搬運的情況如果不做去重模型會把重復特征當成重要特征來學習導致過擬合驗證集指標虛高。我用的是感知哈希加人工抽查的方式先計算每張圖的dHash值再對相似度超過閾值的圖像做人工判斷保留質量更高、目標更清晰的一張。這個步驟很多人偷懶不做但實際訓練里出現過驗證集mAP非常高、換到真實場景立刻崩掉的案例根因就是數據泄露。2.3 標注規(guī)范與細節(jié)兜底標注是這份數據集最容易出問題也最需要細摳的部分。福壽螺檢測的標注有兩個容易吵起來的點卵塊邊界怎么劃一團卵塊附著在莖稈上形狀不規(guī)則有的延伸到水里有的被葉子擋住。VOC格式要求axis-aligned的矩形框所以標注時按卵塊的視覺主體區(qū)域來框把最亮、最密集的卵粒部分作為目標主體邊緣零散卵粒如果連不成片就不納入保持框的一致性。成螺與背景的區(qū)分福壽螺殼色偏黃褐色跟泥地、枯葉容易混淆尤其螺靜止不動的時候。標注時要求框緊密貼合螺的最大外輪廓不能為了省事只框殼中心區(qū)域。螺口朝下、只露出部分殼體的只要有清晰可見的殼體特征就標只露觸角或完全埋在泥里的不標。每個類別在標注工具里分別建立標簽項導出的XML文件里name字段嚴格區(qū)分大小寫這個細節(jié)如果處理不好后面轉YOLO格式時會全部亂掉。3. VOC與YOLO雙格式轉換過程比想象中更容易出問題3.1 兩種標注格式的底層差異這個話題看似基礎但真做數據集的人很多直接被格式問題困住。Pascal VOC格式是XML文件每個目標用一個object節(jié)點存類別名和bndbox坐標坐標是絕對值單位是像素。YOLO格式則是純txt文件每行一個目標格式是class_id x_center y_center width height坐標全部歸一化到0到1之間而且center、width、height都是相對圖像寬高的比例。兩者的核心區(qū)別是XML記錄的是圖像坐標系下的絕對位置txt記錄的是相對圖像大小的比例位置。YOLO之所以用歸一化坐標是因為它訓練時會把圖像縮放到固定尺寸歸一化坐標可以保證目標位置不隨縮放失真。3.2 一個規(guī)范的XML轉TXT腳本長什么樣很多人拿到VOC格式數據集的第一件事就是找轉換腳本但網上的腳本質量參差不齊有的忘了處理difficult屬性有的沒有對坐標做邊界裁剪。下面這個是我用Python寫的轉換邏輯經過實際數據集校驗可以直接參考import os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_file, class_names, img_width, img_height): tree ET.parse(xml_file) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue class_id class_names.index(name) bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 邊界裁剪防止越界坐標導致訓練報錯 xmin max(0, min(xmin, img_width - 1)) ymin max(0, min(ymin, img_height - 1)) xmax max(0, min(xmax, img_width - 1)) ymax max(0, min(ymax, img_height - 1)) if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2.0 / img_width y_center (ymin ymax) / 2.0 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height # 歸一化后理論范圍是[0,1]這里保留6位小數 lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return \n.join(lines)這段代碼有幾個細節(jié)值得說明。第一是img_width和img_height必須從真實的圖片尺寸讀取不能從XML的size節(jié)點里取因為有些標注工具的XMLsize字段跟實際圖不符。第二是邊界裁剪標注時手抖或者誤操作會產生坐標超出圖像邊界的框這類框不處理YOLO訓練時有些版本會直接報錯或者產生NaN梯度。第三是過濾掉寬高為0的非法框這類框在XML里看著正常實際計算后就是退化成一條線必須丟棄。3.3 轉換后的校驗比轉換本身更重要轉換腳本寫完跑完一遍很多人的習慣是直接開始訓練。我的習慣是先做一輪完整的校驗因為格式轉換是機械過程出錯的概率不高但對齊錯誤會靜默地腐蝕訓練效果。具體做三步校驗隨機抽取20張轉換后的txt文件把歸一化坐標反算回像素坐標畫到原圖上肉眼核對框的位置是否跟原標注一致。檢查每張圖的標注類別分布確認沒有出現成螺標成卵塊這類明顯的類別錯位。用腳本統(tǒng)計所有txt中的歸一化坐標是否都在0到1區(qū)間內有沒有出現負數或大于1的值。這套校驗流程我自己在數據集上跑過后續(xù)訓練中出現的box越界、class_id超范圍這類問題大部分都能在訓練前攔住。同時建議保留一份沒轉換的VOC格式作為原始存檔萬一格式有調整還能重新生成不用重新標一遍。4. 用這份數據集訓練YOLOv5的完整配置4.1 數據集目錄的規(guī)范組織方式拿到VOCYOLO雙格式的數據集如果要做YOLOv5訓練目錄結構是第一步。YOLOv5的datasets目錄推薦按下面這種方式組織datasets/ └── fuluoshou/ ├── images/ │ ├── train/ # 436張 │ └── val/ # 109張 ├── labels/ │ ├── train/ # 對應txt與images文件名一一對應 │ └── val/ └── data.yaml我實際測試時是按8:2比例劃分的訓練集和驗證集也就是436張訓練、109張驗證。如果對數據劃分沒有特殊要求這個比例是跑小數據集比較合理的起點。注意一個原則劃分的時候要用隨機種子固定sampling避免每次劃分結果不同導致復現不了實驗。4.2 data.yaml的配置細節(jié)這份數據集是2個類別data.yaml文件內容可以寫成train: datasets/fuluoshou/images/train val: datasets/fuluoshou/images/val nc: 2 names: [adult_snail, egg_mass]這里有兩個坑必須提醒。一是train和val的路徑是相對執(zhí)行train.py時的工作目錄寫的如果路徑寫錯不是報錯而是靜默地把圖片路徑列表讀到空訓練會一直用空的數據加載最終效果完全不可用。二是names列表的順序和下標必須和轉換腳本里class_names的順序完全一致不然就會出現模型輸出的類別名和實際框錯位看起來AP很高實際預測結果張冠李戴。4.3 訓練參數與實測效果我在一臺單卡GPU環(huán)境下跑的測試具體參數供參考參數設置值備注模型YOLOv5s小模型邊緣部署友好img_size640覆蓋面廣兼顧小目標batch_size16單卡顯存12GB夠用epochs100小數據集不需要太長輪次優(yōu)化器SGD比Adam在小數據上泛化更穩(wěn)數據增強mosaic1.0, hsv0.015等開啟默認增強小幅調低hsv跑完的結果成螺類別的mAP50在0.88左右卵塊類別在0.83左右整體mAP50大概0.86。這個結果在545張的小數據集上算是不錯的水平驗證了這個數據集的標注質量基本在線。但這里要潑一盆冷水mAP50看起來不錯主要是這個指標對框位置寬松IoU只要到0.5就算正確實際部署在田間密集場景里漏檢率還是會比指標高不少尤其是卵塊。5. 訓練過程中暴露出來的問題與修正思路5.1 密集卵塊的小目標漏檢我第一次訓練完把模型拿到真實農田環(huán)境中做測試發(fā)現最大的問題不是識別錯而是漏檢。一簇卵塊分得很散的時候模型經常只框住最大的那一團旁邊小團的直接被忽略。這個問題的根源不完全是模型容量不夠而是數據集里密集目標的標注框本身有遺漏——標注的人只標了顯眼的卵塊邊緣零散的小卵團沒標。模型學習的時候正確地學會了只關注大目標。解決這個問題要從數據端下手而不是盲目換大模型。我復盤后做的是把漏標的小卵團補標了約30個樣本同時用標注工具確認沒有一個樣本里存在未標的大卵團。這步做完之后重新訓練的漏檢情況明顯改善。這類問題在農業(yè)檢測里非常典型因為田間目標分布天然不均勻標注質量直接決定模型的天花板。5.2 成螺和卵塊重疊時的權重表達還有一個有意思的問題有些圖像里成螺就趴在卵塊堆旁邊兩個目標位置挨得非常近甚至部分重疊。這時候模型容易把兩個類別混淆輸出結果里同一個位置出現兩個不同類別的框。從數據結構的角度分析這種重疊目標在小數據集里往往樣本量很少模型沒有足夠的正樣本去學習一個位置存在兩個類別的分布。我自己處理的方法是單獨把這類重疊樣本挑出來對成螺邊界做微調讓成螺框和卵塊框的交疊面積盡量小同時在訓練時對這兩個類別的loss權重做微調給卵塊類別稍微高一點的權重因為卵塊是種植戶最關心的繁殖抑制點。不過說實話這兩類目標重疊導致的誤判在真實場景里影響不是致命的。植保人員看到框出來的是成螺還是卵塊清理動作本來就是一起做的。但從數據集打磨的角度這類重疊樣本的標注標準需要數據集提供方在文檔里明確寫清楚不然每個人用的時候理解不一樣訓練出來的模型行為就有差異。5.3 關于545張這個體量的冷思考最后想認真說說這個數據量。545張、2個類別對深度學習目標檢測來說屬于偏小的量級。如果有人問我夠不夠我的回答是做前期驗證、跑通算法流程、做Demo演示完全夠做真正的田間無人巡檢部署不夠至少還要擴充到1500到2000張并且要覆蓋更多天氣條件、光照條件、不同地區(qū)的水生植被背景。但這不是說這個數據集本身價值低。它最大的價值在于把最難的從0到1解決了——數據收集路徑、類別定義、標注規(guī)范、格式轉換、訓練基線全部跑通。后續(xù)擴數據時只需要按照同樣的標注規(guī)范去補圖就可以了不用再摸索流程。從做項目的角度我拿到這份數據后最大的體會是小數據集不可怕可怕的是小數據集還標注隨意。在545張的體量下模型對標注噪聲是非常敏感的一個邊界框偏了幾個像素在幾百張的樣本里都會被放大。所以如果有人要在這個數據集基礎上做微調或擴展我的建議是先把標注質量檢查做扎實再談模型結構升級。5.4 一些實操中的體會按我自己的使用體驗這份VOCYOLO雙格式的數據集在實際部署驗證階段幫了不小的忙。首先是YOLO格式能直接喂給YOLOv5/YOLOv8訓練省掉了轉換的額外工作量其次VOC格式保留了更完整的語義信息萬一后面要用SSD、Faster R-CNN這類框架做對比實驗也不需要再從零標注。雙格式并存的思路值得做數據集的開發(fā)者借鑒。另外提一個小建議使用這個數據集時盡量在訓練前單獨看看所有圖片和標注框的對應關系尤其是卵塊這類邊界不清晰的目標每個人的標注風格可能有差異。理解數據集的標注口徑比直接跑訓練重要得多——這也就是標題里寫的數據集三個字最容易被低估的部分。模型訓練可以重來無數次但一份標注準確的底數數據集才是這個項目里真正值錢的東西。