胞檢測(cè)數(shù)據(jù)集與YOLO訓(xùn)練全流程:從標(biāo)注格式到模型部署)
簡(jiǎn)介面向醫(yī)學(xué)影像目標(biāo)檢測(cè)場(chǎng)景的YOLO紅白細(xì)胞血小板檢測(cè)數(shù)據(jù)集及配套教學(xué)資源適合正在學(xué)習(xí)YOLO系列算法、需要真實(shí)血細(xì)胞檢測(cè)數(shù)據(jù)訓(xùn)練模型的開(kāi)發(fā)者和高校課程學(xué)員。數(shù)據(jù)來(lái)自真實(shí)場(chǎng)景圖片質(zhì)量高已使用LabelImg完成精準(zhǔn)標(biāo)注并分別提供VOC(xml)、COCO(json)、YOLO(txt)三種格式的標(biāo)簽可直接接入YOLOv5、YOLOv8等常見(jiàn)框架訓(xùn)練。包內(nèi)共兩千個(gè)文件以一千個(gè)xml標(biāo)簽、九百九十個(gè)txt標(biāo)簽為主另有少量Python腳本、YAML配置和6個(gè)HTML說(shuō)明文檔整體約29.34MB便于下載使用。資源還附帶Windows與Linux雙平臺(tái)的環(huán)境搭建和訓(xùn)練教程以及三套數(shù)據(jù)集劃分腳本可靈活生成訓(xùn)練集、驗(yàn)證集和測(cè)試集提升模型迭代效率。目前已有一百九十一人學(xué)習(xí)對(duì)于入門醫(yī)學(xué)圖像處理、快速搭建YOLO檢測(cè)流程的學(xué)習(xí)者來(lái)說(shuō)是一份兼具數(shù)據(jù)、腳本和指導(dǎo)的實(shí)用資源。1. 血細(xì)胞檢測(cè)數(shù)據(jù)集怎么選1000 張圖到底夠不夠訓(xùn)練 YOLO做血液細(xì)胞檢測(cè)的落地項(xiàng)目時(shí)最卡人的往往不是模型結(jié)構(gòu)而是數(shù)據(jù)本身。你要找的是那種標(biāo)注出血紅細(xì)胞、白細(xì)胞、血小板的顯微圖像并且標(biāo)簽得能直接喂給 YOLO 訓(xùn)練。市面上公開(kāi)的醫(yī)學(xué)影像數(shù)據(jù)集不少但真正針對(duì)血液細(xì)胞目標(biāo)檢測(cè)、又帶 VOC/COCO/YOLO 三種標(biāo)簽的成套資源很稀缺所以像“YOLO紅白細(xì)胞血小板檢測(cè)數(shù)據(jù)集(含1000張圖片)對(duì)應(yīng)voc、coco和yolo三種格式標(biāo)簽劃分腳本訓(xùn)練教程.rar”這種壓縮包幾乎是專門為工程落地準(zhǔn)備的。1000 張圖聽(tīng)起來(lái)不多但關(guān)鍵在于這批圖是否聚焦在顯微鏡視野下的典型細(xì)胞形態(tài)以及標(biāo)簽是否經(jīng)過(guò)嚴(yán)格的人工校對(duì)。如果分類正確、邊界框緊貼細(xì)胞膜那這 1000 張就足以支撐一個(gè)初版模型完成從訓(xùn)練到部署的全流程驗(yàn)證。真正做過(guò)血細(xì)胞識(shí)別的人都知道這個(gè)任務(wù)的核心瓶頸在于白細(xì)胞和血小板在數(shù)量上遠(yuǎn)少于紅細(xì)胞類別嚴(yán)重不均衡加上染色方案不同會(huì)導(dǎo)致色調(diào)偏移所以數(shù)據(jù)集里如果沒(méi)特意保留這些難例模型訓(xùn)練時(shí)很容易偷懶把一切圓形的深紫色東西都當(dāng)成白細(xì)胞。下面就從解包開(kāi)始把這一套數(shù)據(jù)從清洗、校驗(yàn)到訓(xùn)練、驗(yàn)證的完整鏈路捋一遍。2. 解包 .rar 后的第一件事VOC/COCO/YOLO 三種標(biāo)簽的來(lái)龍去脈與校驗(yàn)?zāi)玫綁嚎s包后別急著開(kāi)訓(xùn)先花十分鐘把目錄結(jié)構(gòu)和標(biāo)簽格式摸清楚。一個(gè)規(guī)范的數(shù)據(jù)集壓縮包解壓后通常應(yīng)該有images、AnnotationsVOC 的 xml 文件、labelsYOLO 的 txt 文件以及對(duì)應(yīng)的 COCO 的 json 文件。如果壓縮包附帶表格或 readme 文件里面一般會(huì)寫(xiě)明類別數(shù)量、圖片尺寸和標(biāo)注規(guī)范。常見(jiàn)做法是先用一條 bash 命令把根目錄捋一遍看看每個(gè)文件夾里文件數(shù)是否對(duì)得上。# 進(jìn)入解壓后的數(shù)據(jù)集根目錄統(tǒng)計(jì)各文件夾下的文件數(shù)量 cd ./blood_cell_dataset echo Images 總數(shù): $(find images -type f | wc -l) echo VOC xml 總數(shù): $(find Annotations -type f | wc -l) echo YOLO txt 總數(shù): $(find labels -type f | wc -l) # 檢查有沒(méi)有空標(biāo)注文件空文件會(huì)導(dǎo)致訓(xùn)練時(shí) loss 為 nan 或直接崩 find labels -name *.txt -empty -print這段代碼的作用是快速核對(duì)三個(gè)目錄的文件數(shù)是否一致。正常情況下圖片數(shù)、xml 數(shù)和 txt 數(shù)應(yīng)該完全相等因?yàn)槊繌垐D都對(duì)應(yīng)一份 VOC 標(biāo)注和一份 YOLO 標(biāo)注。-empty參數(shù)用來(lái)找出那些沒(méi)有目標(biāo)的 txt 文件這在血細(xì)胞數(shù)據(jù)里很常見(jiàn)因?yàn)橛行┮曇跋驴赡艽_實(shí)沒(méi)有血小板或白細(xì)胞。如果發(fā)現(xiàn)空標(biāo)注文件你需要決定是保留讓 YOLO 學(xué)習(xí)“這張圖沒(méi)有目標(biāo)”還是直接刪掉對(duì)應(yīng)圖片我一般建議直接刪除不然訓(xùn)練時(shí)背景占比過(guò)大會(huì)干擾收斂。VOC 和 COCO 格式的區(qū)別核心在于坐標(biāo)系的表達(dá)方式。VOC 的 xml 文件里每個(gè)目標(biāo)用bndbox標(biāo)簽存左上角和右下角的坐標(biāo)即絕對(duì)值COCO 的 json 文件里框的格式是中心點(diǎn)坐標(biāo)加寬高也是絕對(duì)值而 YOLO 的 txt 文件里五個(gè)數(shù)字依次是class_id、x_center、y_center、width、height全部除以圖片寬高做了歸一化。這個(gè)區(qū)別決定了轉(zhuǎn)換腳本里的計(jì)算邏輯必須是x_center (xmin xmax) / 2 / img_width一旦分母用錯(cuò)模型訓(xùn)練出來(lái)的框位置會(huì)整體偏移。拿到壓縮包里的三種格式后建議寫(xiě)一個(gè)腳本做交叉驗(yàn)證即隨機(jī)抽 10 張圖把 YOLO 格式的框畫(huà)回原圖上確認(rèn)框是否貼合細(xì)胞邊緣。這一步不能省因?yàn)闃?biāo)注時(shí)的錯(cuò)位、坐標(biāo)超界會(huì)導(dǎo)致后續(xù)訓(xùn)練時(shí) loss 異常。# 交叉驗(yàn)證腳本將 YOLO 格式標(biāo)注畫(huà)回圖片 import cv2 import numpy as np # 類別映射0-red blood cell, 1-white blood cell, 2-platelet class_names [RBC, WBC, PLT] colors [(0, 0, 255), (255, 0, 0), (0, 255, 255)] # 讀取圖片和對(duì)應(yīng)的標(biāo)注文件 img cv2.imread(images/00001.jpg) h, w img.shape[:2] with open(labels/00001.txt, r) as f: lines f.readlines() # YOLO 格式是歸一化坐標(biāo)畫(huà)框前必須乘以圖片寬高恢復(fù)像素坐標(biāo) for line in lines: parts line.strip().split() cls int(parts[0]) x_c, y_c, box_w, box_h map(float, parts[1:]) x_min int((x_c - box_w / 2) * w) y_min int((y_c - box_h / 2) * h) x_max int((x_c box_w / 2) * w) y_max int((y_c box_h / 2) * h) cv2.rectangle(img, (x_min, y_min), (x_max, y_max), colors[cls], 2) cv2.putText(img, class_names[cls], (x_min, y_min - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, colors[cls], 1) cv2.imwrite(verify_00001.jpg, img)這段腳本里最關(guān)鍵的是類型轉(zhuǎn)換map(float, parts[1:])確保坐標(biāo)是浮點(diǎn)數(shù)避免整數(shù)除法導(dǎo)致中心點(diǎn)偏移。cv2.rectangle的坐標(biāo)參數(shù)必須是整數(shù)所以在乘完寬高后要強(qiáng)制int()。畫(huà)完框之后你還需要把這 10 張圖逐張過(guò)目重點(diǎn)看三個(gè)地方一是有沒(méi)有框偏到細(xì)胞核外二是血小板體積很小框是否過(guò)大或過(guò)小三是同一張圖里紅白細(xì)胞坐標(biāo)有沒(méi)有串位。這個(gè)驗(yàn)證過(guò)程大概花 10 分鐘但能省下后續(xù)訓(xùn)練時(shí)反復(fù)排錯(cuò)的時(shí)間。COCO 格式的驗(yàn)證方式則是把 json 文件解析出來(lái)后做同樣的事json 里如果有segmentation字段且為空不影響目標(biāo)檢測(cè)訓(xùn)練但如果bbox字段的寬度或高度是負(fù)數(shù)訓(xùn)練時(shí)就會(huì)報(bào)尺寸錯(cuò)誤。血細(xì)胞數(shù)據(jù)集的標(biāo)簽還有一個(gè)特殊風(fēng)險(xiǎn)就是類別順序不統(tǒng)一。有些壓縮包把類別定義成[WBC, RBC, PLT]有些是[red_blood_cell, white_blood_cell, platelet]YOLO 訓(xùn)練時(shí)類別索引完全由data.yaml里的names列表順序決定如果 txt 文件里的索引是 0 代表紅細(xì)胞而data.yaml里 0 卻寫(xiě)成了白細(xì)胞模型就會(huì)把所有紅細(xì)胞學(xué)成白細(xì)胞。診斷這個(gè)問(wèn)題的快準(zhǔn)狠方法是數(shù)一下訓(xùn)練集里各類別的標(biāo)注框數(shù)量正常情況下紅細(xì)胞應(yīng)該是白細(xì)胞的幾十倍如果出現(xiàn)數(shù)量和預(yù)想不符基本就是類別索引錯(cuò)位。3. 用劃分腳本重建訓(xùn)練集目錄結(jié)構(gòu)、隨機(jī)種子與樣本均衡拿到數(shù)據(jù)后首要任務(wù)是把訓(xùn)練集、驗(yàn)證集和測(cè)試集劃分好。壓縮包里如果自帶劃分腳本那最好不過(guò)但腳本往往只生成了默認(rèn)劃分你可能需要根據(jù)自己的顯存大小和訓(xùn)練目標(biāo)重新劃分。一個(gè)血細(xì)胞檢測(cè)項(xiàng)目的標(biāo)配劃分比例是 7:2:1也就是 700 張訓(xùn)練、200 張驗(yàn)證、100 張測(cè)試。很多開(kāi)源劃分腳本只隨機(jī)打亂圖片列表沒(méi)有考慮類別均衡這在血細(xì)胞這種極不均衡的數(shù)據(jù)集上是有問(wèn)題的。假設(shè)某張圖里只有紅細(xì)胞沒(méi)有血小板全部被分到訓(xùn)練集那驗(yàn)證集里血小板的表現(xiàn)就會(huì)虛低。我常用的做法是在劃分腳本里加一個(gè)維度即統(tǒng)計(jì)每張圖片包含的類別數(shù)量盡量把包含三種細(xì)胞類型的圖片均勻分配到三個(gè)子集里。import random import numpy as np from pathlib import Path # 固定隨機(jī)種子保證每次劃分結(jié)果可復(fù)現(xiàn) random.seed(42) np.random.seed(42) # 讀取所有圖片名 img_paths list(Path(images).glob(*.jpg)) n len(img_paths) shuffled img_paths.copy() random.shuffle(shuffled) # 按 7:2:1 劃分 train_imgs shuffled[:int(n * 0.7)] val_imgs shuffled[int(n * 0.7):int(n * 0.9)] test_imgs shuffled[int(n * 0.9):] # 輔助函數(shù)將圖片列表寫(xiě)入 txt供 YOLO 訓(xùn)練時(shí)讀取 def write_txt(path_list, out_file): with open(out_file, w) as f: for p in path_list: f.write(str(p.resolve()) \n) write_txt(train_imgs, train.txt) write_txt(val_imgs, val.txt) write_txt(test_imgs, test.txt) print(fTrain: {len(train_imgs)}, Val: {len(val_imgs)}, Test: {len(test_imgs)})代碼里的random.seed(42)非常重要它讓每次運(yùn)行腳本生成的劃分完全一致。有些人覺(jué)得隨機(jī)種子是玄學(xué)但當(dāng)你調(diào)整超參數(shù)再次訓(xùn)練時(shí)如果劃分不一致就分不清性能提升到底來(lái)自模型改動(dòng)還是數(shù)據(jù)變化。Path.resolve()會(huì)把相對(duì)路徑轉(zhuǎn)成絕對(duì)路徑這樣從任何工作目錄啟動(dòng) YOLO 訓(xùn)練都不會(huì)報(bào)找不到文件。不過(guò)要注意這份 txt 文件里的路徑指向的是源images目錄如果你做數(shù)據(jù)增強(qiáng)或數(shù)據(jù)清洗時(shí)把圖片復(fù)制到了新目錄必須同步更新這些 txt 文件否則訓(xùn)練時(shí)會(huì)讀到舊的路徑。劃分完數(shù)據(jù)后還需要確認(rèn)data.yaml里的配置。Ultralytics YOLOv8 需要用戶指定train、val和test三個(gè)路徑以及nc和names兩個(gè)字段。這里最常見(jiàn)的錯(cuò)誤是按 VOC 的類別名來(lái)填寫(xiě)names比如寫(xiě)成[rbc, wbc, plt]但沒(méi)有注意 txt 文件里的類別索引。最穩(wěn)妥的方法是打開(kāi)任意一個(gè) YOLO 標(biāo)注文件看第一列的數(shù)字再看壓縮包里有沒(méi)附帶classes.txt或obj.names文件以那個(gè)為準(zhǔn)填寫(xiě)names列表。# data.yaml 文件內(nèi)容 path: ./blood_cell_dataset train: train.txt val: val.txt test: test.txt nc: 3 names: [RBC, WBC, platelet]這里的path字段是根目錄train和val可以寫(xiě)絕對(duì)路徑也可以寫(xiě)相對(duì)路徑但如果填的是 txt 文件名就必須保證 txt 文件存在于path指定的這個(gè)目錄下。我之前犯過(guò)錯(cuò)把train直接寫(xiě)成/path/to/images/train而path又指向了上一級(jí)目錄結(jié)果 YOLO 把兩個(gè)路徑拼接后找不到任何圖片。原因在于path是相對(duì)于當(dāng)前工作目錄的偏移量而train是相對(duì)于path的偏移量?jī)烧呤钳B加關(guān)系。如果壓縮包自帶的劃分腳本已經(jīng)生成了train.txt你就在train字段寫(xiě)上train.txt就行如果想直接寫(xiě)文件夾路徑把train改成images/train也可以前提是目錄結(jié)構(gòu)必須匹配。4. 避坑指南VOC/COCO/YOLO 互轉(zhuǎn)時(shí)的坐標(biāo)、索引與類別順序做血細(xì)胞目標(biāo)檢測(cè)最隱蔽的坑不在于模型本身而在于格式轉(zhuǎn)換時(shí)對(duì)坐標(biāo)定義理解不透徹。VOC 格式里xmin和xmax定義的是像素絕對(duì)坐標(biāo)而 YOLO 格式里x_center和width是歸一化到 0 到 1 之間的小數(shù)。轉(zhuǎn)換的公式本身并不復(fù)雜x_center (xmin xmax) / 2 / img_width真正容易錯(cuò)的是圖片尺寸取反了。有些腳本讀 xml 時(shí)沒(méi)取寬高直接用[W, H]做分母但實(shí)際圖片是[H, W]結(jié)果所有框在橫向上正常、縱向上偏移。顯微鏡圖像的細(xì)胞分布通常比較均勻縱向偏移后框和細(xì)胞的錯(cuò)位一眼就能看出來(lái)就怕錯(cuò)位不嚴(yán)重模型學(xué)到的框始終偏上或偏下導(dǎo)致 mAP 不高但又不完全崩這種問(wèn)題最難定位。COCO 格式轉(zhuǎn) YOLO 時(shí)還有第二個(gè)大坑即類別 ID 的前后偏移。COCO 數(shù)據(jù)集的類別 ID 是從 1 開(kāi)始而 YOLO 是從 0 開(kāi)始。很多公共腳本轉(zhuǎn)換時(shí)忘了減 1導(dǎo)致原本的類別 2血小板被當(dāng)成了類別 3如果nc3越界的類別 ID 會(huì)在訓(xùn)練時(shí)被忽略輕則 mAP 下降重則直接提示標(biāo)簽索引超范圍。解決辦法是在轉(zhuǎn)換腳本里強(qiáng)制檢查最大類別索引確保不超過(guò)nc-1。同時(shí)血細(xì)胞標(biāo)注里如果存在同一個(gè)細(xì)胞被標(biāo)注了多個(gè)框的重復(fù)框問(wèn)題也會(huì)讓模型在 NMS 階段輸出亂跳的框最好在轉(zhuǎn)換前用腳本合并重復(fù)框。添加一個(gè)專門用于排查圖像、標(biāo)注不匹配的問(wèn)題通常的做法是在訓(xùn)練腳本跑起來(lái)前先用一個(gè)校驗(yàn)?zāi)_本做一致性檢查。比如檢查圖片文件名和標(biāo)注文件名是否一一對(duì)應(yīng)圖片是否損壞可讀。對(duì)血細(xì)胞這類專門的標(biāo)注數(shù)據(jù)我一般會(huì)額外確認(rèn)標(biāo)注框的寬高是否大于等于 2 個(gè)像素因?yàn)檠“蹇赡苤挥泻苄〉某叽缛绻麡?biāo)注時(shí)不小心給出 1 像素的框模型在前向傳播時(shí)難以從中提取到有效特征反而會(huì)對(duì)網(wǎng)絡(luò)造成負(fù)向影響。# 校驗(yàn)標(biāo)注框的合法性 from pathlib import Path import cv2 # 全局統(tǒng)計(jì)超界框數(shù)量 invalid [] for label_path in Path(labels).glob(*.txt): with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: invalid.append((label_path, 字段數(shù)錯(cuò)誤)) continue _, x_c, y_c, w, h parts if float(w) 0 or float(h) 0: invalid.append((label_path, 寬高小于等于0))血細(xì)胞數(shù)據(jù)集的典型問(wèn)題在于血小板數(shù)量很多但框很小轉(zhuǎn)換腳本如果做了int()取整很容易把小數(shù)坐標(biāo)截?cái)喑?0從而產(chǎn)生w0的非法框。訓(xùn)練腳本遇到這類框會(huì)報(bào)累積錯(cuò)誤可能在訓(xùn)練幾十個(gè) epoch 后突然爆出ValueError。上述腳本在訓(xùn)練之前跑一遍把所有非法框打印出來(lái)并統(tǒng)一過(guò)濾能夠避免訓(xùn)練中途中斷。在數(shù)據(jù)量只有 1000 張的情況下過(guò)濾掉少數(shù)異常標(biāo)注比直接放棄這些圖片更劃算。在 COCO 轉(zhuǎn) YOLO 的場(chǎng)景里另一個(gè)高頻踩坑點(diǎn)是annotations里的image_id和實(shí)際文件名對(duì)不上。比如 json 里的image_id是34但文件名是042.jpg如果你用str(image_id) .jpg去構(gòu)造路徑就會(huì)得到34.jpg而找不到042.jpg。遇到這種問(wèn)題一定要先讀取images列表的file_name字段用它作為文件名而不是想當(dāng)然用image_id。這類結(jié)構(gòu)性錯(cuò)誤會(huì)讓訓(xùn)練時(shí)大量圖片無(wú)法加載報(bào)錯(cuò)信息又只顯示FileNotFoundError排查起來(lái)極其惱人。5. 跑通 YOLOv8 訓(xùn)練從配置 data.yaml 到監(jiān)控 loss 曲線當(dāng)圖片、標(biāo)簽和劃分腳本全部校驗(yàn)完畢訓(xùn)練就是一個(gè)成熟流程了。我通常直接用 Ultralytics YOLOv8 的 Python API因?yàn)槊钚心J诫m然方便但參數(shù)太多時(shí)不易存檔而 Python 腳本可以把配置固化下來(lái)?yè)Q機(jī)器后重新跑一次即可。血細(xì)胞檢測(cè)最好用yolov8n.pt或yolov8s.pt作為預(yù)訓(xùn)練權(quán)重因?yàn)?1000 張圖的數(shù)據(jù)量不夠從隨機(jī)初始化開(kāi)始訓(xùn)練遷移學(xué)習(xí)可以極大降低收斂難度。如果壓縮包里有訓(xùn)練教程大概率也是基于 YOLOv5 或 YOLOv8 寫(xiě)的一行命令核心無(wú)非是指定data.yaml、epochs、imgsz和batch四個(gè)參數(shù)。from ultralytics import YOLO # 載入預(yù)訓(xùn)練權(quán)重只保留 backbone 和 neck 的特征提取能力 model YOLO(yolov8n.pt) # 訓(xùn)練入口epochs 設(shè)置 150batch 根據(jù)顯存調(diào)整 results model.train( datadata.yaml, # 上一步校驗(yàn)過(guò)的數(shù)據(jù)配置文件 epochs150, # 血細(xì)胞數(shù)據(jù)集較小150 輪足夠 imgsz640, # 顯微鏡原圖通常較大這里統(tǒng)一縮放到 640 batch16, # batch 大小根據(jù) GPU 顯存調(diào)整T4 上 16 安全 patience20, # 連續(xù) 20 輪驗(yàn)證集指標(biāo)不提升就提前停止 projectblood_cell, nameyolov8n_exp1, seed42, # 固定種子便于復(fù)現(xiàn) pretrainedTrue, # 使用 COCO 預(yù)訓(xùn)練權(quán)重遷移 )上述命令里最重要的參數(shù)是pretrainedTrue。有些人會(huì)把它設(shè)置成False理由是血細(xì)胞和 COCO 里的類別差異太大預(yù)訓(xùn)練權(quán)重幫不上忙。實(shí)際訓(xùn)練經(jīng)驗(yàn)告訴我哪怕 COCO 里只有幾種類別與血細(xì)胞形態(tài)接近骨干網(wǎng)絡(luò)對(duì)紋理、邊緣、顏色的底層特征提取能力依然是通用的而且預(yù)訓(xùn)練模型收斂更快在 150 個(gè) epoch 內(nèi)更容易達(dá)到可用精度。epochs150看起來(lái)很長(zhǎng)但配合patience20的早停機(jī)制通常到 60 到 80 個(gè) epoch 就會(huì)停止。如果你的 GPU 顯存只有 8Gbatch16跑 640 分辨率可能會(huì)爆顯存此時(shí)有兩種方案把batch降到 8或者把imgsz降到 512。顯微鏡下血小板非常小512 分辨率會(huì)丟失細(xì)節(jié)所以我寧可降 batch 也不降分辨率。訓(xùn)練過(guò)程中要盯住的不是打印出的box_loss而是驗(yàn)證集上的mAP50-95這個(gè)指標(biāo)綜合反映了框的定位精度和分類精度。血細(xì)胞檢測(cè)里紅細(xì)胞通常 mAP 很高因?yàn)闃颖径?、形態(tài)固定血小板 mAP 偏低因?yàn)槟繕?biāo)太小。如果訓(xùn)練過(guò)程中看到mAP50高但mAP50-95低說(shuō)明框的大致位置是對(duì)的但邊界不夠精確需要更多 epoch 來(lái)微調(diào)。如果兩者都低就要回頭檢查標(biāo)簽轉(zhuǎn)換腳本是不是有坐標(biāo)偏移。訓(xùn)練結(jié)束后模型會(huì)生成best.pt和last.pt兩個(gè)權(quán)重文件。best.pt是驗(yàn)證集指標(biāo)最優(yōu)的模型last.pt是最后一個(gè) epoch 的結(jié)果。部署時(shí)必須選best.pt這是基本共識(shí)但很多人忽略的是要在測(cè)試集上額外驗(yàn)證一次。因?yàn)轵?yàn)證集在訓(xùn)練時(shí)已經(jīng)被用來(lái)做早停判斷指標(biāo)有一定程度的“泄露”只有測(cè)試集是完全沒(méi)見(jiàn)過(guò)的新數(shù)據(jù)在血細(xì)胞這類已知數(shù)據(jù)分布固定的場(chǎng)景里通常測(cè)試集 mAP 會(huì)比驗(yàn)證集低 1 到 2 個(gè)點(diǎn)如果低得過(guò)多就要考慮是否存在過(guò)擬合。訓(xùn)練完成后我習(xí)慣用 TensorBoard 或直接查看runs/detect/name/results.csv來(lái)確認(rèn)收斂狀態(tài)。results.csv里記錄了每一輪的train/box_loss、val/box_loss、val/mAP50-95等指標(biāo)。核心觀察點(diǎn)是val/box_loss是否在訓(xùn)練后半段出現(xiàn)回彈。如果回彈說(shuō)明學(xué)習(xí)率沒(méi)有配合好一種解法是在訓(xùn)練代碼里加入cos_lrTrue參數(shù)讓學(xué)習(xí)率按余旋曲線衰減通常能解決后期震蕩的問(wèn)題。Ultralytics 默認(rèn)已經(jīng)使用lr00.01加上自動(dòng)脫落但如果換到醫(yī)學(xué)小數(shù)據(jù)集上我一般會(huì)把lr0降到0.005避免前期步長(zhǎng)過(guò)大把預(yù)訓(xùn)練特征沖壞。對(duì)于訓(xùn)練教程部分如果你是從零開(kāi)始入門建議先在val子集上做一次單張推理測(cè)試確認(rèn)輸入輸出的形狀。拿一張測(cè)試圖片替換掉默認(rèn)的bus.jpg運(yùn)行model.predict(sourceimages/test01.jpg, saveTrue)生成的標(biāo)注圖能直觀看到框和置信度。這一步與訓(xùn)練無(wú)關(guān)僅用于驗(yàn)證模型文件是否損壞。對(duì)于血細(xì)胞這種目標(biāo)密集的場(chǎng)景預(yù)測(cè)時(shí)還需要調(diào)conf參數(shù)默認(rèn)的0.25置信度閾值對(duì)血小板來(lái)說(shuō)容易漏檢最終部署時(shí)建議下調(diào)到0.1左右。6. 驗(yàn)證與部署用混淆矩陣和裁剪策略突破血小板漏檢模型訓(xùn)練完只是中點(diǎn)真正決定項(xiàng)目能否交付的是在驗(yàn)證集上的細(xì)粒度分析。血細(xì)胞檢測(cè)數(shù)據(jù)集里紅細(xì)胞在大部分視野中占絕對(duì)主體白細(xì)胞和血小板往往僅占少數(shù)如果用全局mAP來(lái)評(píng)價(jià)模型只需要把紅細(xì)胞學(xué)好就能拿到不錯(cuò)的分?jǐn)?shù)卻掩蓋了血小板全部漏檢的風(fēng)險(xiǎn)。因此我訓(xùn)練后必做的一步是導(dǎo)出混淆矩陣并按類別分開(kāi)看召回率。# 加載訓(xùn)練得到的權(quán)重重新在測(cè)試集上做驗(yàn)證 from ultralytics import YOLO model YOLO(runs/detect/yolov8n_exp1/weights/best.pt) metrics model.val(splittest, conf0.1, iou0.5) # 打印每個(gè)類別的召回率 print(metrics.box.mp, metrics.box.mr) # 平均精確率和召回率 print(metrics.box.ap_class_index) # 類別索引 print(metrics.box.p) # 每個(gè)類的精確率 print(metrics.box.r) # 每個(gè)類的召回率metrics.box.r返回的數(shù)組順序與data.yaml中names的順序一致這正是我們常說(shuō)的“驗(yàn)證指標(biāo)是跟著類別定義走的”。如果打印結(jié)果顯示血小板的召回率在 0.5 以下那就說(shuō)明模型對(duì)小板框的擬合能力不夠不要急著改模型結(jié)構(gòu)先嘗試把輸入分辨率從640提升到1024。顯微鏡圖片本來(lái)就是高分辨率圖像縮放至 640 對(duì)紅細(xì)胞無(wú)礙但血小板也會(huì)被同步縮小甚至小于特征圖上一個(gè)網(wǎng)格的尺寸。很多血細(xì)胞識(shí)別比賽的參賽方案都使用imgsz1024配合batch8目的就是保住小目標(biāo)的空間尺寸。如果調(diào)大分辨率后顯存不足另一條經(jīng)過(guò)驗(yàn)證的路線是切圖訓(xùn)練也就是把原圖按網(wǎng)格切成若干 640×640 的小塊再對(duì)每個(gè)小塊單獨(dú)做標(biāo)注和訓(xùn)練。壓縮包里如果帶了劃分腳本大概率沒(méi)有包含切圖邏輯因?yàn)榍袌D會(huì)改變所有標(biāo)注坐標(biāo)需要把框的絕對(duì)坐標(biāo)轉(zhuǎn)換成相對(duì)于小圖的坐標(biāo)。這里有兩個(gè)關(guān)鍵做法一是切圖時(shí)重疊率至少設(shè) 10%防止細(xì)胞正好位于切圖邊界導(dǎo)致被截?cái)喽沁^(guò)濾掉完全落在區(qū)塊外部的框同時(shí)保留跨越多個(gè)區(qū)塊的框讓模型能學(xué)到細(xì)胞的完整形態(tài)。# 切圖邏輯將原圖切為 640x640 的小塊并同步轉(zhuǎn)換標(biāo)簽坐標(biāo) import cv2 from pathlib import Path def crop_image_and_labels(img_path, label_path, crop_size640, overlap64): img cv2.imread(str(img_path)) h, w img.shape[:2] step crop_size - overlap # 計(jì)算橫向縱向切圖次數(shù) x_steps (w - crop_size) // step 1 y_steps (h - crop_size) // step 1 with open(label_path) as f: lines [line.strip() for line in f if line.strip()] # 遍歷每個(gè)切圖塊 for i in range(x_steps): for j in range(y_steps): x1 i * step y1 j * step x2 x1 crop_size y2 y1 crop_size crop img[y1:y2, x1:x2] new_label_lines [] # 把 YOLO 坐標(biāo)轉(zhuǎn)為像素坐標(biāo)再判斷是否在塊內(nèi) for line in lines: parts line.split() cls int(parts[0]) cx, cy, cw, ch map(float, parts[1:]) cx_px cx * w cy_px cy * h cw_px cw * w ch_px ch * h # 保留中心點(diǎn)落在切圖塊內(nèi)的框 if x1 cx_px x2 and y1 cy_px y2: new_cx (cx_px - x1) / crop_size new_cy (cy_px - y1) / crop_size new_cw cw_px / crop_size new_ch ch_px / crop_size new_label_lines.append(f{cls} {new_cx:.4f} {new_cy:.4f} {new_cw:.4f} {new_ch:.6f}\n) if new_label_lines: out_img fcrops/{Path(img_path).stem}_{i}_{j}.jpg out_label fcrops/{Path(img_path).stem}_{i}_{j}.txt cv2.imwrite(out_img, crop) with open(out_label, w) as f: f.writelines(new_label_lines)切圖腳本中center point落在塊內(nèi)就保留這個(gè)規(guī)則簡(jiǎn)單但不失有效。對(duì)于橫跨兩個(gè)塊的細(xì)胞模型會(huì)在每個(gè)小塊里學(xué)到它的殘缺部分預(yù)測(cè)時(shí)如果兩個(gè)塊都被判定有目標(biāo)NMS 會(huì)把其中一個(gè)抑制掉實(shí)際部署時(shí)再做一次全圖拼接即可。你也可以選擇讓框完全落在塊內(nèi)才保留但那樣會(huì)丟失邊界細(xì)胞的上下文信息對(duì)血小板檢測(cè)來(lái)說(shuō)很不劃算。經(jīng)驗(yàn)之談是先把不切圖的模型跑通然后單獨(dú)做一組切圖訓(xùn)練對(duì)比驗(yàn)證集結(jié)果。血細(xì)胞數(shù)據(jù)集的公開(kāi)方案里切圖加高分辨率幾乎總是能帶來(lái) 3 到 5 個(gè)點(diǎn)的可觀提升代價(jià)是訓(xùn)練時(shí)間成倍增加。如果項(xiàng)目時(shí)間緊迫也可以只對(duì)驗(yàn)證集做切圖推理即把驗(yàn)證圖片切小塊分別預(yù)測(cè)再在小圖層面做 NMS最后映射回原圖坐標(biāo)。Ultralytics 的model.predict支持直接傳入一個(gè)文件夾你只需要把切好的驗(yàn)證圖片放進(jìn)去它會(huì)自動(dòng)完成推理。部署端使用 TensorRT 或者 ONNX 加速時(shí)必須固定輸入分辨率。因?yàn)榍袌D方案訓(xùn)練用 640推理也必須用 640不能動(dòng)態(tài)輸入否則你得到的 mAP 數(shù)據(jù)會(huì)在部署時(shí)全部失真。我在一次項(xiàng)目里訓(xùn)練時(shí)用imgsz1024導(dǎo)出 ONNX 時(shí)把imgsz參數(shù)漏了默認(rèn)導(dǎo)出成了 640結(jié)果部署后板框漂得厲害反思原因就是推理分辨率小于訓(xùn)練分辨率小目標(biāo)的特征在降采樣過(guò)程中提前消失了。所以導(dǎo)出模型時(shí)一定要顯式指定同樣的imgsz最好在同一條命令里寫(xiě)入導(dǎo)出腳本保存參數(shù)。希望這篇內(nèi)容能幫你在血細(xì)胞檢測(cè)的落地上少走幾個(gè)來(lái)回。本文還有配套的精品資源點(diǎn)擊獲取