練痤瘡數(shù)據(jù)集:927張圖從標(biāo)注解析到部署全指南)
簡(jiǎn)介痤瘡數(shù)據(jù)集是面向計(jì)算機(jī)視覺開發(fā)者與醫(yī)療AI研究者的目標(biāo)檢測(cè)資源基于YOLOv8框架構(gòu)建包含927張JPG圖像及對(duì)應(yīng)邊界框標(biāo)注可用于訓(xùn)練痤瘡識(shí)別模型輔助皮膚科醫(yī)生進(jìn)行病情診斷與遠(yuǎn)程醫(yī)療監(jiān)測(cè)免去人工標(biāo)注成本。壓縮包共1855個(gè)文件其中含927個(gè)jpg圖像、927個(gè)txt標(biāo)注文件與1個(gè)yaml配置文件壓縮包大小約為33.93MBtxt文件已按YOLO格式保存標(biāo)注坐標(biāo)yaml文件用于定義類別與路徑配置。目前已有329人瀏覽學(xué)習(xí)。數(shù)據(jù)集已按訓(xùn)練、驗(yàn)證、測(cè)試集標(biāo)準(zhǔn)劃分可直接導(dǎo)入YOLOv8訓(xùn)練流程驗(yàn)證集用于監(jiān)控過擬合測(cè)試集用于評(píng)估泛化能力。該數(shù)據(jù)適用于目標(biāo)檢測(cè)模型的訓(xùn)練、驗(yàn)證與評(píng)估支持?jǐn)?shù)據(jù)增強(qiáng)實(shí)驗(yàn)在醫(yī)學(xué)影像分析、智能診斷系統(tǒng)等方向上具有較高的實(shí)用價(jià)值與研究意義。1. 痤瘡數(shù)據(jù)集 JPG YOLOv8927 張標(biāo)注圖開箱能訓(xùn)嗎拿到這套痤瘡數(shù)據(jù)集時(shí)我第一反應(yīng)是927 張 JPG 圖像標(biāo)注已經(jīng)做成了 YOLOv8 格式是不是解壓完就能直接yolo detect train跑起來答案是「可以但別急著跑」。這套資源的價(jià)值在于它把圖像整理成了標(biāo)準(zhǔn) YOLOv8 訓(xùn)練目錄結(jié)構(gòu)病灶區(qū)域用邊界框方式標(biāo)好適合做皮膚影像目標(biāo)檢測(cè)的起步數(shù)據(jù)。如果你之前只用過 VOC 或 COCO 格式的通用數(shù)據(jù)集會(huì)發(fā)現(xiàn) YOLOv8 的 txt 標(biāo)簽有完全不同的組織邏輯——圖像和標(biāo)注文件靠同名綁定類別信息寫在data.yaml里任何一個(gè)文件名對(duì)不上訓(xùn)練時(shí)標(biāo)注就會(huì)被靜默丟掉。這篇文章按我實(shí)際拆包的順序把目錄結(jié)構(gòu)、標(biāo)簽解析、訓(xùn)練配置、翻車點(diǎn)、驗(yàn)證導(dǎo)出和增量訓(xùn)練一次講完。2. 先拆目錄YOLOv8 數(shù)據(jù)集結(jié)構(gòu)與標(biāo)簽逐行解析2.1 標(biāo)準(zhǔn)目錄結(jié)構(gòu)images 與 labels 的配對(duì)關(guān)系解壓這份資源后頂層目錄應(yīng)該是下面這種形狀這是 ultralytics 官方推薦的布局也是 YOLOv8 訓(xùn)練時(shí)data.yaml最低成本對(duì)接方式dataset/ ├── images/ │ ├── train/ │ │ ├── 0001.jpg │ │ ├── 0002.jpg │ │ └── ... │ └── val/ │ ├── 0501.jpg │ └── ... ├── labels/ │ ├── train/ │ │ ├── 0001.txt │ │ ├── 0002.txt │ │ └── ... │ └── val/ │ ├── 0501.txt │ └── ... └── data.yaml先記住一個(gè)關(guān)鍵規(guī)則images/train/0001.jpg的標(biāo)注必定在labels/train/0001.txt擴(kuò)展名不同、文件名 stem 必須完全一致。訓(xùn)練器加載時(shí)就是按這個(gè)匹配邏輯去找標(biāo)簽的如果同名 txt 不存在那張圖會(huì)被當(dāng)成「無目標(biāo)」樣本損失函數(shù)直接跳過它——結(jié)果往往是模型對(duì)這張圖什么都學(xué)不到你卻以為它訓(xùn)練了。這份資源標(biāo)稱 927 張圖常見的劃分習(xí)慣是 8:2 或 9:1即 742 張訓(xùn)練加 185 張驗(yàn)證或者 834 張訓(xùn)練加 93 張驗(yàn)證。拿到手先確認(rèn)train和val的實(shí)際數(shù)量跟 927 對(duì)不上也沒關(guān)系做一次重劃分就行。我見過不少人把全部圖塞進(jìn) trainval 目錄空著ultralytics 也能跑但幾乎無法判斷過擬合不建議這么干。2.2 標(biāo)簽 txt 每行含義類別 ID 加歸一化坐標(biāo)隨便打開一個(gè).txt文件內(nèi)容長這樣0 0.485156 0.533333 0.218750 0.288889 1 0.714844 0.468889 0.126563 0.200000每行五個(gè)數(shù)值從左到右分別是類別 ID、歸一化中心點(diǎn) x、歸一化中心點(diǎn) y、歸一化寬度 w、歸一化高度 h。這里的cx, cy, w, h全部除以了圖像自身寬高取值必須在 0 到 1 之間。如果哪個(gè)坐標(biāo)大于 1說明標(biāo)注工具導(dǎo)出時(shí)用了像素坐標(biāo)而沒有歸一化訓(xùn)練時(shí)框會(huì)歪到圖像外面去這是我見過最常見的 YOLO 格式翻車原因。想把歸一化坐標(biāo)轉(zhuǎn)回像素坐標(biāo)做可視化檢查可以寫個(gè)簡(jiǎn)單腳本from pathlib import Path label_path Path(labels/train/0001.txt) img_w, img_h 640, 640 # 用你實(shí)際圖像的寬高替換 for line in label_path.read_text().strip().splitlines(): parts line.split() cls int(parts[0]) cx, cy, w, h map(float, parts[1:]) x1 int((cx - w / 2) * img_w) y1 int((cy - h / 2) * img_h) x2 int((cx w / 2) * img_w) y2 int((cy h / 2) * img_h) print(fclass{cls}, bbox({x1}, {y1}, {x2}, {y2}))這里把中心點(diǎn)坐標(biāo)減去寬高一半得到左上角(x1, y1)加上寬高一半得到右下角(x2, y2)。注意 YOLOv8 的標(biāo)簽里存的是中心點(diǎn)和寬高不是左上角右下角很多從 VOC 轉(zhuǎn)過來的人會(huì)在這一步寫錯(cuò)導(dǎo)致畫出來的框整體偏移。2.3 類別分布與劃分核對(duì)先數(shù)一遍再動(dòng)手在寫data.yaml之前先用一行命令統(tǒng)計(jì)每個(gè)類別 ID 出現(xiàn)的次數(shù)確認(rèn)類的 ID 是連續(xù)的cat labels/train/*.txt | awk {print $1} | sort | uniq -c輸出大概是213 0 187 1 96 2這表示類別 0 出現(xiàn)了 213 次類別 1 出現(xiàn)了 187 次類別 2 出現(xiàn)了 96 次。如果最后一行awk輸出的 ID 不是從 0 開始的而是 1、2、3說明標(biāo)注工具導(dǎo)出時(shí)從 1 開始編號(hào)需要在前處理里做一次class_id - 1。如果 ID 之間有跳號(hào)比如 0、1、3也要先補(bǔ)齊或重映射否則模型輸出的類別維度會(huì)和data.yaml里的names對(duì)不上。我的習(xí)慣是統(tǒng)計(jì)完再找一個(gè) txt 畫一次框肉眼確認(rèn)框落在病灶上而不是整張圖亂飄。這一步花不了兩分鐘能省掉后面訓(xùn)練完才發(fā)現(xiàn)標(biāo)簽錯(cuò)的后悔藥。順便說一句如果發(fā)現(xiàn)某些 txt 是空文件優(yōu)先懷疑標(biāo)圖工具導(dǎo)出了「背景圖」——這類圖要么刪掉要么單獨(dú)放一個(gè)負(fù)樣本目錄處理。3. 跑通訓(xùn)練data.yaml、檢查腳本與關(guān)鍵參數(shù)3.1 data.yaml 的完整寫法與路徑陷阱YOLOv8 訓(xùn)練時(shí)唯一必須自己寫的配置文件是data.yaml。內(nèi)容不復(fù)雜但路徑寫錯(cuò)會(huì)導(dǎo)致訓(xùn)練器直接報(bào)錯(cuò)或靜默跳過數(shù)據(jù)。這份資源如果自帶 yaml先打開確認(rèn)如果沒有按下面這種寫法建一個(gè)path: /data/acne_dataset train: images/train val: images/val names: 0: blackhead 1: papule 2: pustulepath指向數(shù)據(jù)集根目錄train和val填相對(duì)于path的路徑。names列表的順序就是類別 ID 的順序names的第 0 項(xiàng)對(duì)應(yīng)標(biāo)簽里的 0第 1 項(xiàng)對(duì)應(yīng) 1以此類推。具體類別名稱以你解壓后的data.yaml里的定義為準(zhǔn)不要照抄我這里的示例。這里有兩個(gè)坑第一path不要寫相對(duì)路徑因?yàn)?ultralytics 在訓(xùn)練時(shí)會(huì)切換工作目錄相對(duì)路徑經(jīng)常找不到數(shù)據(jù)我習(xí)慣直接寫絕對(duì)路徑第二Windows 下路徑分隔符用反斜杠但 YAML 里反斜杠是轉(zhuǎn)義符穩(wěn)妥做法是把路徑里的\換成/Linux 服務(wù)器上則直接用正斜杠。如果path配的是數(shù)據(jù)集根目錄而根目錄本身帶中文或空格也盡量改成純英文路徑避免編碼問題。3.2 訓(xùn)練前最后一道檢查壞圖、空標(biāo)簽和擴(kuò)展名很多人解壓完數(shù)據(jù)集直接開訓(xùn)等到訓(xùn)練中途發(fā)現(xiàn) loss 不對(duì)才回頭查數(shù)據(jù)浪費(fèi)時(shí)間。我一般會(huì)先跑一段快速檢查腳本把壞圖和缺失標(biāo)簽一次篩出來import cv2 from pathlib import Path bad_imgs [] missing_labels [] for img_path in sorted(Path(images/train).glob(*.*)): if img_path.suffix.lower() not in (.jpg, .jpeg, .png): continue img cv2.imread(str(img_path)) if img is None: bad_imgs.append(str(img_path)) continue txt_path Path(labels/train) / (img_path.stem .txt) if not txt_path.exists(): missing_labels.append(str(txt_path)) continue lines txt_path.read_text().strip().splitlines() if not lines: # 空標(biāo)簽 missing_labels.append(str(txt_path)) print(壞圖/無法解碼:, len(bad_imgs), bad_imgs[:5]) print(缺標(biāo)簽或空標(biāo)簽:, len(missing_labels), missing_labels[:5])cv2.imread返回None說明文件頭損壞或不是完整 JPEG 編碼檢查標(biāo)簽的存在性和空文件是避免「有圖無標(biāo)注」的靜默樣本。Path.glob在這里匹配所有擴(kuò)展名再通過suffix.lower()做過濾是為了照顧*.JPG、*.jpeg這類大小寫不一致的情況。如果你的檢查結(jié)果顯示有幾百張圖找不到標(biāo)簽優(yōu)先懷疑是images里文件名帶_copy、(1)這類后綴或者是圖片被重命名過而標(biāo)簽沒同步。這種問題靠手動(dòng)改是不現(xiàn)實(shí)的直接用腳本做一次全量重命名更省事具體做法在下一章的 4.1 里給出。3.3 啟動(dòng)訓(xùn)練imgsz、batch、epochs 怎么定檢查沒問題后就可以跑訓(xùn)練了。以本資源為例命令如下yolo detect train \ dataacne.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ device0 \ projectacne_work \ namerun1modelyolov8n.pt表示加載 COCO 預(yù)訓(xùn)練權(quán)重作為起點(diǎn)對(duì) 927 張圖的小數(shù)據(jù)集來說用性能最輕的 n 模型起步比直接用 s 或 m 更不容易過擬合。imgsz640是訓(xùn)練時(shí)的輸入分辨率痤瘡病灶在圖像里通常占比較小640 不夠時(shí)可以試 1280但顯存占用會(huì)翻好幾倍。batch要根據(jù)顯存調(diào)6GB 的 GTX1660Ti 跑 n 模型batch 建議 8 到 1612GB 以上的卡可以開到 32 或更大。epochs100作為基準(zhǔn)值后面看 loss 曲線再?zèng)Q定加還是減。訓(xùn)練日志和權(quán)重會(huì)輸出到acne_work/run1其中weights/best.pt是驗(yàn)證集 mAP 最高的權(quán)重weights/last.pt是最后一輪權(quán)重。我一般只用best.pt因?yàn)樽詈笠惠喭呀?jīng)過擬合。訓(xùn)練過程中如果想中途停下看指標(biāo)直接按CtrlC中斷再單獨(dú)跑yolo detect val就能看當(dāng)前權(quán)重的表現(xiàn)。4. 避坑指南JPG 圖像接 YOLOv8 時(shí)的五個(gè)翻車現(xiàn)場(chǎng)4.1 圖像改名而標(biāo)簽沒同步靜默吞噬標(biāo)注現(xiàn)象訓(xùn)練時(shí)日志里出現(xiàn)WARNING提示image 1/742 ... missing或者訓(xùn)練結(jié)束后驗(yàn)證集 mAP 異常低但訓(xùn)練 loss 看起來正常。很多人這時(shí)候會(huì)去調(diào)參實(shí)際是數(shù)據(jù)問題。原因標(biāo)圖完成后為了讓文件名更規(guī)范手動(dòng)批量重命名了 JPG但labels下的 txt 還保留舊名字。YOLOv8 加載圖片后查找同名 txt 找不到就直接把這個(gè)樣本當(dāng)作無目標(biāo)圖片處理看起來「還能訓(xùn)」實(shí)際上大量樣本被靜默丟棄。解決把圖片和標(biāo)簽放在同一個(gè)循環(huán)里同步改名確保永遠(yuǎn)一起變import os from pathlib import Path for img in Path(images/train).glob(*.jpg): new_name facne_{img.name} new_img img.with_name(new_name) os.rename(img, new_img) txt Path(labels/train) / (img.stem .txt) if txt.exists(): new_txt txt.with_name(facne_{txt.name}) os.rename(txt, new_txt)這里用img.stem從圖片名推導(dǎo)標(biāo)簽名再同步修改兩個(gè)文件。.with_name()會(huì)替換文件對(duì)象的名字部分保留目錄路徑。改完之后再跑一次 3.2 的檢查腳本確認(rèn) missing 數(shù)量歸零。4.2 類別 ID 從 0 開始標(biāo)圖工具的 1-based 陷阱現(xiàn)象訓(xùn)練能正常跑loss 也下降但抽幾張驗(yàn)證圖看預(yù)測(cè)結(jié)果類別完全錯(cuò)亂比如本該是黑頭的框被識(shí)別成膿皰。原因不少標(biāo)注工具在界面上顯示的第一個(gè)類別是 1第二類是 2導(dǎo)出 YOLO 格式時(shí)直接把 UI 編號(hào)寫入 txt導(dǎo)致標(biāo)簽里的類別是 1-based。而 YOLOv8 要求類別嚴(yán)格從 0 開始錯(cuò)位后模型的類別語義和names列表整體平移了一位。解決先按 2.3 的方法統(tǒng)計(jì)類別 ID如果發(fā)現(xiàn)sort | uniq -c輸出從 1 開始就批量把第一列減 1。類別不多時(shí)可以直接用sedsed -i s/^1 /0 / labels/train/*.txt sed -i s/^2 /1 / labels/train/*.txt sed -i s/^3 /2 / labels/train/*.txt注意執(zhí)行順序要從大到小避免先把 2 改成 1導(dǎo)致下一步又把原來的 2 誤改。類目超過 4 個(gè)時(shí)別用一組 sed 硬湊用 Python 讀第一列統(tǒng)一減 1 更穩(wěn)。4.3 EXIF 旋轉(zhuǎn)信息為什么框總是整體偏移現(xiàn)象訓(xùn)練時(shí) loss 降得很快但驗(yàn)證集 mAP50 卡在 0.2 附近畫出來的框上下偏移或者左右翻轉(zhuǎn)而且對(duì)特定方向的圖像偏移程度不一樣。原因手機(jī)或數(shù)碼相機(jī)拍攝的 JPG 可能會(huì)寫入 EXIF Orientation 標(biāo)簽。OpenCV 的imread不會(huì)讀取 EXIF 旋轉(zhuǎn)信息圖像加載后方向不變但 ultralytics 訓(xùn)練預(yù)處理用的圖像幾何變換會(huì)先讀寬高如果原始圖像帶有 Orientation 標(biāo)簽讀取到的寬高和實(shí)際旋轉(zhuǎn)后的寬高是反的標(biāo)簽坐標(biāo)就會(huì)偏移。解決訓(xùn)練前用 PIL 過濾一遍所有圖像將 EXIF 旋轉(zhuǎn)落地到像素同時(shí)清掉 Orientation 元數(shù)據(jù)from PIL import Image, ImageOps from pathlib import Path for img_path in Path(images/train).glob(*.jpg): im Image.open(img_path) im ImageOps.exif_transpose(im) # 應(yīng)用 EXIF 旋轉(zhuǎn) im im.convert(RGB) # 去掉透明通道和多余模式 im.save(img_path, JPEG, quality95)ImageOps.exif_transpose根據(jù) EXIF 的 Orientation 值自動(dòng)旋轉(zhuǎn)圖像convert(RGB)會(huì)把 RGBA、灰度圖統(tǒng)一成三通道 JPEG避免訓(xùn)練時(shí)通道數(shù)不一致。處理完成后可以隨機(jī)抽幾張圖用第 2.2 節(jié)的腳本畫框?qū)Ρ瓤虻馁N合度會(huì)明顯改善。4.4 擴(kuò)展名大小寫.jpg 和 .JPG 在 Linux 下是兩回事現(xiàn)象數(shù)據(jù)集在 Windows 上一切正??降?Linux 服務(wù)器訓(xùn)練后報(bào)大量缺標(biāo)簽或找不到圖像報(bào)錯(cuò)集中在Assertion ... not found。原因Windows 文件系統(tǒng)不區(qū)分大小寫.JPG和.jpg是同一個(gè)文件Linux 嚴(yán)格區(qū)分而你標(biāo)簽文件里引用的圖像名是.jpg實(shí)際文件名是.JPG一旦程序按小寫去匹配就落空。解決把images下所有圖像擴(kuò)展名統(tǒng)一成小寫并同步修改文件名標(biāo)簽只跟 stem 走擴(kuò)展名不影響for f in images/train/*; do lower$(basename $f | tr A-Z a-z) mv $f images/train/$lower done同樣處理images/val。用tr A-Z a-z把文件名全部轉(zhuǎn)小寫連帶著把*.JPEG也統(tǒng)一成了*.jpeg。雖然 YOLOv8 能識(shí)別jpeg和png但為了后續(xù)設(shè)備端部署少踩坑建議統(tǒng)一轉(zhuǎn)成.jpg小寫擴(kuò)展名。4.5 空標(biāo)簽和純背景圖loss 震蕩的隱藏來源現(xiàn)象訓(xùn)練 loss 一開始就劇烈震蕩偶爾還有 NaN但數(shù)據(jù)量、batch、學(xué)習(xí)率看起來都正常。原因數(shù)據(jù)集里混入了空標(biāo)簽圖或者圖像本身是過曝/純色背景。空標(biāo)簽的 txt 文件存在但沒有任何行YOLOv8 在構(gòu)建損失時(shí)會(huì)得到一個(gè)空的目標(biāo)集合純背景圖如果標(biāo)注是「無目標(biāo)」一些數(shù)據(jù)加載器會(huì)把它當(dāng)成負(fù)樣本處理但如果目錄里同時(shí)存在標(biāo)簽文件內(nèi)容為空就會(huì)在匹配時(shí)產(chǎn)生奇怪行為。解決把空標(biāo)簽圖篩出來從訓(xùn)練集移除from pathlib import Path for txt in Path(labels/train).glob(*.txt): content txt.read_text().strip() if not content: img txt.with_suffix(.jpg) print(空標(biāo)簽:, txt, 對(duì)應(yīng)圖像:, img) # txt.unlink() # 確認(rèn)后再注釋掉執(zhí)行 # img.unlink()移除前建議先人工看一眼這些圖確認(rèn)是不是真的背景圖。如果你本來就把空標(biāo)簽當(dāng)成「負(fù)樣本」用正確做法是把它們整理到一個(gè)獨(dú)立目錄避免跟正樣本混在同一個(gè)train下混在一起會(huì)讓你后期調(diào)閾值的時(shí)候根本分不清模型是學(xué)不會(huì)還是數(shù)據(jù)太亂。5. 驗(yàn)證與導(dǎo)出從 mAP 指標(biāo)到 RK3588 部署鏈路5.1 驗(yàn)證集指標(biāo)怎么讀mAP50、mAP50-95 和混淆矩陣訓(xùn)練結(jié)束后用best.pt在驗(yàn)證集上跑一次完整驗(yàn)證yolo detect val \ modelacne_work/run1/weights/best.pt \ dataacne.yaml終端會(huì)輸出一串指標(biāo)重點(diǎn)看四列mAP50(B)、mAP50-95(B)、precision(B)、recall(B)。它們的含義如下指標(biāo)讀法mAP50IoU 閾值固定 0.5 時(shí)的平均精度均值小目標(biāo)數(shù)據(jù)最常用mAP50-95IoU 從 0.5 到 0.95 每隔 0.05 取平均要求框更精準(zhǔn)precision所有預(yù)測(cè)框中真正命中的比例高代表誤報(bào)少recall所有真框中被模型找出來的比例高代表漏檢少痤瘡病灶尺寸小邊界框通常不到圖像面積的 5%這種場(chǎng)景下mAP50的參考價(jià)值比mAP50-95更大因?yàn)樾∧繕?biāo)對(duì) IoU 閾值極其敏感。如果mAP50在 0.6 以上而mAP50-95只有 0.3 左右說明框大致位置對(duì)了但貼合度不夠優(yōu)先去調(diào)imgsz而不是換模型。驗(yàn)證完成后runs/val/exp目錄里會(huì)生成confusion_matrix.png這個(gè)圖能直接告訴你哪些類別互相混淆比如結(jié)節(jié)被識(shí)別成囊腫這類信息比單看數(shù)字更具體。想畫訓(xùn)練過程的 loss 曲線ultralytics 訓(xùn)練時(shí)已經(jīng)在acne_work/run1/results.csv里記錄了每個(gè) epoch 的train/box_loss、val/box_loss等字段直接用 pandas 讀出來畫即可import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(acne_work/run1/results.csv) plt.plot(df[epoch], df[train/box_loss], labeltrain_box) plt.plot(df[epoch], df[val/box_loss], labelval_box) plt.legend() plt.show()看曲線時(shí)注意如果 val loss 在某個(gè) epoch 后持續(xù)上升而 train loss 還在下降就是典型的過擬合信號(hào)回頭把epochs砍到那個(gè)拐點(diǎn)附近如果 val loss 從頭到尾都是平的先懷疑標(biāo)簽或數(shù)據(jù)劃分有問題別急著加訓(xùn)練輪數(shù)。5.2 導(dǎo)出 ONNXRK3588 上部署的樸素路徑很多人的目標(biāo)不是停在 PyTorch 里跑推理而是部署到邊緣設(shè)備。RK3588 這類平臺(tái)跑 YOLOv8 的標(biāo)準(zhǔn)鏈路是PyTorch 權(quán)重先轉(zhuǎn) ONNX再轉(zhuǎn) RKNN 格式加載到 NPU。第一步先用 ultralytics 自帶的導(dǎo)出功能yolo export modelacne_work/run1/weights/best.pt formatonnx opset12導(dǎo)出后的best.onnx可以直接用 ONNXRuntime 做純 CPU 推理驗(yàn)證確認(rèn)權(quán)重沒問題再往 RKNN 走import onnxruntime as ort import numpy as np sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name img np.zeros((1, 3, 640, 640), dtypenp.float32) # 模擬歸一化后的輸入 out sess.run(None, {input_name: img}) print(out[0].shape)輸出 shape 通常是(1, 4 num_classes, 8400)或(1, 8400, 4 num_classes)取決于 ONNX 導(dǎo)出版本。8400 是 YOLOv8 在不同尺度特征圖上的預(yù)測(cè)框總數(shù)后面接的后處理要按這個(gè)維度寫 NMS。到了 RK3588 這一步需要把 ONNX 轉(zhuǎn)成 RKNN 格式轉(zhuǎn)換工具鏈對(duì)算子支持有限前面導(dǎo)出時(shí)盡量用opset12而不是更高版本避免某些算子不被 NPU 驅(qū)動(dòng)識(shí)別。另外要注意導(dǎo)出時(shí)不要開dynamicTrueRKNN 轉(zhuǎn)換工具對(duì)動(dòng)態(tài) shape 支持很差固定成640x640最省事。這一步在 PC 上先拿 ONNX 跑通整張圖確認(rèn)輸出坐標(biāo)和 PyTorch 推理一致再上板子調(diào) NPU排查成本會(huì)低很多。6. 增量訓(xùn)練凍結(jié)主干微調(diào)與數(shù)據(jù)增強(qiáng)的取舍用 927 張圖訓(xùn)出初版權(quán)重后如果還想再拉一截 mAP我一般不會(huì)直接加 epoch而是做兩階段微調(diào)。第一階段凍結(jié) backbone只訓(xùn)練 head讓模型先適應(yīng)當(dāng)前數(shù)據(jù)的類別分布第二階段解凍全部參數(shù)小學(xué)習(xí)率微調(diào)。原因是病灶的紋理特征集中ImageNet 預(yù)訓(xùn)練模型在底層學(xué)到的邊緣、顏色特征已經(jīng)夠用硬訓(xùn)反而容易把主干帶偏。ultralytics 的freeze參數(shù)可以指定凍結(jié)前 N 層yolo detect train \ modelacne_work/run1/weights/best.pt \ dataacne.yaml \ epochs50 \ freeze10 \ imgsz640freeze10表示凍結(jié)前 10 層對(duì) YOLOv8n 來說基本覆蓋了 stem 和大部分主干階段。這一階段訓(xùn)練完后用新生成的best.pt再做一次不凍結(jié)的全參數(shù)微調(diào)學(xué)習(xí)率建議調(diào)低一個(gè)數(shù)量級(jí)常見做法是在命令里加lr00.001甚至lr00.0005避免在已經(jīng)收斂的基礎(chǔ)上把權(quán)重沖壞。數(shù)據(jù)增強(qiáng)這塊我的建議是優(yōu)先用 ultralytics 內(nèi)置的線上增強(qiáng)而不是自己去離線擴(kuò)圖。Mosaic、HSV 擾動(dòng)、隨機(jī)仿射這些增強(qiáng)默認(rèn)已經(jīng)開啟而且是在線實(shí)時(shí)作用在每張圖上不會(huì)產(chǎn)生額外文件離線擴(kuò)圖一旦忘了同步標(biāo)簽文件就會(huì)重新踩一遍第 4 章的坑。只有一種情況我才會(huì)做離線增強(qiáng)檢測(cè)目標(biāo)太小想通過切片把大圖裁成小圖再放大訓(xùn)練這種操作必須連同標(biāo)簽一起裁剪并重新歸一化工程量明顯更高。老實(shí)說我在項(xiàng)目里吃過一次虧把自己裁剪的新圖直接塞進(jìn)原數(shù)據(jù)目錄文件名前綴不一致labels里卻沒有對(duì)應(yīng) txt訓(xùn)練日志只報(bào)了幾條 WARNING當(dāng)時(shí)沒在意結(jié)果驗(yàn)證集 mAP 掉了一截才排查到問題。從那以后我每次加圖都強(qiáng)制走一遍同名同步腳本再只拿新圖跑 10 個(gè) epoch 做 sanity check確認(rèn)沒有 missing 標(biāo)簽才合并進(jìn)全量訓(xùn)練。這個(gè)習(xí)慣幫我避掉了后面很多次的「不知道為什么 loss 不動(dòng)」的玄學(xué)問題希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取