別系統(tǒng)實(shí)戰(zhàn):訓(xùn)練、界面與登錄注冊(cè)全解析)
這幾年做視覺檢測(cè)項(xiàng)目YOLO 系列基本成了目標(biāo)檢測(cè)任務(wù)的首選從 V3 一路用過來每個(gè)版本都解決了一部分實(shí)際問題。最近在用 YOLOv11 做一個(gè)蘋果成熟度識(shí)別檢測(cè)系統(tǒng)整個(gè)項(xiàng)目包含模型訓(xùn)練、UI 界面、登錄注冊(cè)模塊最后打包成一套可直接運(yùn)行的 Python 項(xiàng)目。這篇就圍繞“YOLOv11 蘋果成熟度識(shí)別 UI 界面 登錄注冊(cè)”這條主線把我在實(shí)際開發(fā)中踩過的坑、選型時(shí)的考慮、參數(shù)配置的經(jīng)驗(yàn)一次性整理出來供正在做同類農(nóng)業(yè)視覺項(xiàng)目的朋友參考。這套系統(tǒng)能做的事情很明確通過攝像頭或圖片輸入自動(dòng)識(shí)別畫面中的蘋果果實(shí)并判斷其處于未成熟、成熟中、已成熟三個(gè)狀態(tài)中的哪一種然后在界面中實(shí)時(shí)顯示檢測(cè)結(jié)果和置信度。適合的場(chǎng)景包括果園巡檢、采摘機(jī)器人視覺模塊、農(nóng)產(chǎn)品分揀實(shí)驗(yàn)、高校畢業(yè)設(shè)計(jì)、以及剛?cè)腴T深度學(xué)習(xí)目標(biāo)檢測(cè)的開發(fā)者用來做完整項(xiàng)目練手。1. 項(xiàng)目設(shè)計(jì)與技術(shù)選型思路1.1 先搞清楚成熟度識(shí)別的本質(zhì)很多人一上來就想著怎么把成熟度分類做好但忽略了一個(gè)關(guān)鍵前提蘋果在畫面中不是單獨(dú)存在的往往一棵樹上多個(gè)果實(shí)互相遮擋光線條件復(fù)雜背景有枝葉干擾。所以成熟度識(shí)別本質(zhì)上是一個(gè)“先定位、再分類”的級(jí)聯(lián)問題而不是單純的圖像分類問題。我最初也嘗試過直接拿分類網(wǎng)絡(luò) ResNet 處理整張圖片把圖片分成“生”“半熟”“熟”三類但實(shí)際效果非常差。因?yàn)楫嬅胬锟赡苡腥齻€(gè)蘋果一個(gè)熟了、兩個(gè)還是青的整圖分類只能給出一個(gè)結(jié)果根本沒法用。換成目標(biāo)檢測(cè)就不一樣了檢測(cè)框可以把每個(gè)蘋果單獨(dú)框出來然后在框內(nèi)做成熟度判斷這個(gè)思路才是解決真實(shí)場(chǎng)景問題的正確路徑。YOLOv11 在這里扮演的角色就是“定位 分類”二合一模型一個(gè)網(wǎng)絡(luò)同時(shí)輸出目標(biāo)框和類別概率省去了兩階段方法中區(qū)域提議的繁瑣流程推理速度也更快。對(duì)農(nóng)業(yè)場(chǎng)景來說速度優(yōu)勢(shì)很重要尤其是在移動(dòng)端或嵌入式設(shè)備上做實(shí)時(shí)檢測(cè)時(shí)單幀處理時(shí)間直接決定了系統(tǒng)實(shí)用性。1.2 為什么選擇 YOLOv11 而不是其他版本YOLOv11 是當(dāng)前 YOLO 系列中較新的版本相比之前的版本在特征提取網(wǎng)絡(luò)、注意力機(jī)制、頸部特征融合上都有更新。具體到蘋果成熟度識(shí)別這個(gè)任務(wù)我的選型理由主要有三條第一小目標(biāo)檢測(cè)能力有所增強(qiáng)。蘋果在果園場(chǎng)景中經(jīng)常處于中遠(yuǎn)距離果實(shí)占畫面的比例并不大。YOLOv11 在特征金字塔部分做了優(yōu)化對(duì)中等尺寸和小尺寸目標(biāo)的召回率比前代更穩(wěn)。第二訓(xùn)練收斂速度更快。同樣的數(shù)據(jù)集、同樣的訓(xùn)練輪數(shù)YOLOv11 在前幾十輪 loss 下降趨勢(shì)更明顯這對(duì)沒有動(dòng)輒上千張標(biāo)注圖的小項(xiàng)目來說很友好。第三部署生態(tài)成熟。模型可以導(dǎo)出 ONNX、TorchScript、TensorRT 等多種格式后續(xù)無論接 Python 程序、C 服務(wù)還是嵌入式設(shè)備都有對(duì)應(yīng)的轉(zhuǎn)換方案。當(dāng)然選 YOLOv11 也不代表它所有場(chǎng)景都碾壓舊版。如果你的數(shù)據(jù)集極小比如只有一兩百張圖V8 甚至 V5 可能也能達(dá)到差不多的效果。YOLOv11 的優(yōu)勢(shì)要在大幾百張、上千張圖的數(shù)據(jù)規(guī)模下才體現(xiàn)得比較充分這一點(diǎn)做項(xiàng)目前要有心理預(yù)期。1.3 整體系統(tǒng)架構(gòu)從整個(gè)項(xiàng)目來看這套系統(tǒng)分成三層底層是模型層訓(xùn)練好的 YOLOv11 權(quán)重負(fù)責(zé)檢測(cè)蘋果框和成熟度類別是系統(tǒng)的核心引擎。中間是服務(wù)層用 Python 封裝模型的加載、推理、結(jié)果解析提供穩(wěn)定的調(diào)用接口給上層。頂層是界面層包括登錄注冊(cè)窗口和主檢測(cè)窗口用戶通過界面完成圖片選擇、實(shí)時(shí)檢測(cè)、結(jié)果展示等操作。選 PySide6 做界面是因?yàn)樗?OpenCV、Pillow 配合方便圖像數(shù)據(jù)從 numpy 數(shù)組到 QPixmap 顯示幾乎無損耗轉(zhuǎn)換。界面部分還加了登錄注冊(cè)功能用戶數(shù)據(jù)用 SQLite 存儲(chǔ)密碼做哈希處理不存明文保證基礎(chǔ)安全性。2. 環(huán)境搭建與依賴準(zhǔn)備2.1 Python 版本與 CUDA 版本匹配這個(gè)項(xiàng)目踩過的第一個(gè)坑就是環(huán)境版本匹配。YOLOv11 依賴 PyTorchPyTorch 和 CUDA 版本必須嚴(yán)格對(duì)應(yīng)不然torch.cuda.is_available()返回值永遠(yuǎn)是 False模型被迫用 CPU 跑訓(xùn)練速度慢到你懷疑人生。我最終使用的穩(wěn)定組合是 Python 3.10 CUDA 11.8 PyTorch 2.1 對(duì)應(yīng)版本。如果你用的是 NVIDIA 30 系或 40 系顯卡這個(gè)組合基本通吃。如果你的顯卡比較老比如 10 系建議把 CUDA 降到 11.7 或直接用 CPU 版本先跑通邏輯再升級(jí)環(huán)境。驗(yàn)證 CUDA 是否可用在 Python 里跑兩行代碼就夠import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0))第一次跑這串代碼時(shí)如果cuda.is_available()返回 False不要急著去重裝 CUDA先檢查你是不是裝了 CPU 版的 PyTorch。很多人的問題出在 pip 安裝時(shí)默認(rèn)拉了 CPU 包換用官方命令指定 CUDA 版本重裝一次就好了。2.2 依賴庫安裝清單除了 PyTorch這個(gè)項(xiàng)目還需要以下核心依賴依賴庫的版本不需要追求最新穩(wěn)定兼容才最省心。特別是 opencv-python 和 numpy 的版本兼容問題numpy 版本過高可能報(bào)錯(cuò)所以建議固定一個(gè)大版本范圍內(nèi)使用。安裝的時(shí)候強(qiáng)烈建議用虛擬環(huán)境不管是 venv 還是 conda 都行別把依賴裝到系統(tǒng) Python 里不然后面做其他項(xiàng)目時(shí)依賴沖突會(huì)很痛苦。pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow pyside6這里單獨(dú)解釋一下 ultralytics 這個(gè)包。它自帶 YOLOv11 的模型定義、訓(xùn)練入口、驗(yàn)證入口、導(dǎo)出工具不需要自己手寫網(wǎng)絡(luò)結(jié)構(gòu)。訓(xùn)練時(shí)只要準(zhǔn)備好數(shù)據(jù)集和配置文件敲一條命令行就能啟動(dòng)訓(xùn)練。不過要提醒一點(diǎn)不要因?yàn)榉庋b方便就完全不看底層代碼后面調(diào)試疑難問題時(shí)還是需要打開源碼定位。3. 數(shù)據(jù)集構(gòu)建與預(yù)處理3.1 數(shù)據(jù)采集與拍攝規(guī)范數(shù)據(jù)是整套系統(tǒng)的地基數(shù)據(jù)質(zhì)量直接決定模型上限。我在這個(gè)項(xiàng)目中用了兩個(gè)公開來源和一個(gè)自采小數(shù)據(jù)集混合訓(xùn)練總計(jì)約 2800 張圖片。如果你是自己采集數(shù)據(jù)拍到手機(jī)里之后不要急著標(biāo)注先把圖片過一遍篩模糊的刪掉、光線嚴(yán)重過曝的刪掉、完全沒有蘋果的刪掉最后留下來的圖應(yīng)該滿足“蘋果主體清晰、果實(shí)和背景可區(qū)分、各類別數(shù)量盡量均衡”三個(gè)條件。拍攝角度上盡量模擬真實(shí)檢測(cè)場(chǎng)景。如果你最終是在果園用攝像頭平視或略俯視拍攝那么訓(xùn)練數(shù)據(jù)就不要全是特寫擺拍。遠(yuǎn)距離、中距離、近距離的圖都要有尤其是中遠(yuǎn)距離的圖因?yàn)槟P驮谶h(yuǎn)處能不能穩(wěn)定檢測(cè)出來直接取決于這類樣本的數(shù)量。采集數(shù)據(jù)時(shí)另一個(gè)常用技巧是視頻抽幀。對(duì)著果園拍一段幾十秒的視頻用 OpenCV 隔幾幀抽一張很快就能攢出幾百張?jiān)紙D然后再人工篩選。這樣采集的圖片天然包含連綿動(dòng)作中的視角變化比一棵樹拍幾張靜態(tài)圖效果好。3.2 標(biāo)注工具與格式轉(zhuǎn)換數(shù)據(jù)標(biāo)注我用的常見標(biāo)注工具就是以 YOLO 格式為默認(rèn)輸出的那類工具。標(biāo)注時(shí)只需要畫框不需要精細(xì)化到像素級(jí)分割因?yàn)槟繕?biāo)檢測(cè)任務(wù)要的就是矩形邊界框。標(biāo)注類別有三種immature未成熟果實(shí)以青綠色為主、maturing半成熟底色開始轉(zhuǎn)黃或帶紅暈、mature已成熟紅色或深紅色為主。顏色不是唯一的判斷標(biāo)準(zhǔn)不同品種蘋果成熟時(shí)顏色差異很大所以標(biāo)注的時(shí)候還要結(jié)合果實(shí)硬度、大小作為輔助參考但視覺上能明確作為標(biāo)簽依據(jù)的仍然是顏色特征。YOLO 格式的標(biāo)注文件是純文本每行五個(gè)數(shù)字類別序號(hào)、歸一化中心點(diǎn) x、歸一化中心點(diǎn) y、歸一化寬度 w、歸一化高度 h。如果你的標(biāo)注工具直接導(dǎo)出成 XMLVOC 格式或 JSONCOCO 格式需要寫一個(gè)腳本做格式轉(zhuǎn)換。轉(zhuǎn)換的核心就是把像素坐標(biāo)除以圖片寬高換算成 0 到 1 的相對(duì)坐標(biāo)。import os import xml.etree.ElementTree as ET from PIL import Image def voc2yolo(xml_path, img_path, out_path, class_map): img_w, img_h Image.open(img_path).size tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.find(name).text box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) cls_id class_map[name] dw, dh 1.0 / img_w, 1.0 / img_h x_center (x1 x2) / 2.0 * dw y_center (y1 y2) / 2.0 * dh w (x2 - x1) * dw h (y2 - y1) * dh lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines))3.3 數(shù)據(jù)劃分與增強(qiáng)數(shù)據(jù)劃分嚴(yán)格按照 train/val/test 三類比例按 8:1:1 切分。這里有一個(gè)很關(guān)鍵的細(xì)節(jié)同一棵樹上拍出來的圖片或者說同一批連續(xù)抽幀的圖片不要同時(shí)被分進(jìn)訓(xùn)練集和驗(yàn)證集否則模型在驗(yàn)證集上的表現(xiàn)會(huì)虛高等你部署到新場(chǎng)景時(shí)才發(fā)現(xiàn)真實(shí)精度遠(yuǎn)沒有這么好看。我采取的做法是按拍攝視頻段分組一個(gè)視頻段的全部幀只進(jìn)其中一個(gè)集合從源頭上切斷數(shù)據(jù)泄漏。這個(gè)細(xì)節(jié)對(duì)農(nóng)業(yè)項(xiàng)目特別重要因?yàn)楣麡涞谋尘啊⑻鞖狻⒔嵌韧叨认嗨迫绻蛔龇纸M切割模型很容易“記住”背景而不是“學(xué)會(huì)”認(rèn)蘋果。數(shù)據(jù)增強(qiáng)方面ultralytics 框架自帶了一系列增強(qiáng)策略包括馬賽克增強(qiáng)、隨機(jī)透視、色彩擾動(dòng)、平移縮放等。訓(xùn)練時(shí)默認(rèn)增強(qiáng)已經(jīng)打開。但我額外加了對(duì) HSV 色彩擾動(dòng)的強(qiáng)化把 hue 偏移量調(diào)大了一些因?yàn)樘O果成熟度識(shí)別嚴(yán)重依賴顏色特征讓模型適應(yīng)不同光照條件下的色偏比增加圖片數(shù)量更見效。3.4 類別不均衡的處理真實(shí)果園場(chǎng)景下成熟果實(shí)的數(shù)量往往少于未成熟果實(shí)尤其是采摘季前期。如果你的原始數(shù)據(jù)同樣是這個(gè)分布模型訓(xùn)練時(shí)會(huì)產(chǎn)生明顯的類別偏好——對(duì)樣本量大的類別檢測(cè)更準(zhǔn)對(duì)樣本量小的類別容易漏檢。處理方式我嘗試過三種第一種是過采樣把成熟果實(shí)的圖片復(fù)制幾份放進(jìn)訓(xùn)練集第二種是給損失函數(shù)里的少數(shù)類加權(quán)重ultralytics 的配置文件中可以直接修改類別權(quán)重第三種是合成數(shù)據(jù)把成熟果實(shí)的樣本通過裁剪、縮放、旋轉(zhuǎn)疊加到不同背景上。實(shí)際效果里過采樣最直接雖然簡(jiǎn)單粗暴但有效尤其在類別比例不超過 1:3 的情況下。4. 模型訓(xùn)練與調(diào)優(yōu)4.1 初始化配置與預(yù)訓(xùn)練權(quán)重YOLOv11 訓(xùn)練之前需要準(zhǔn)備兩個(gè)文件數(shù)據(jù)集配置 YAML 和模型配置 YAML。數(shù)據(jù)集配置寫在項(xiàng)目目錄下的 data.yaml 里指定訓(xùn)練集路徑、驗(yàn)證集路徑、類別數(shù)和類別名模型配置則決定網(wǎng)絡(luò)深度和寬度。這個(gè)項(xiàng)目里我直接用 ultralytics 提供的 yolo11.yaml 模型結(jié)構(gòu)文件沒有改網(wǎng)絡(luò)拓?fù)?。預(yù)訓(xùn)練權(quán)重用的官方 COCO 權(quán)重通過參數(shù)指定加載這樣骨干網(wǎng)絡(luò)部分已經(jīng)有了很強(qiáng)的通用特征提取能力訓(xùn)練時(shí)只需要在它的基礎(chǔ)上微調(diào)收斂速度和最終精度都會(huì)好很多。注意數(shù)據(jù)集 YAML 里的類別順序必須和標(biāo)注文件里的類別序號(hào)完全對(duì)應(yīng)。比如 YAML 中寫著 names: 0-immature, 1-maturing, 2-mature那標(biāo)注文件中類別 0 必須是 immature。這個(gè)對(duì)應(yīng)關(guān)系錯(cuò)位是新手最容易犯的錯(cuò)誤表面上看模型也能訓(xùn)練但推理結(jié)果會(huì)全部亂掉類別名對(duì)不上檢測(cè)內(nèi)容。4.2 訓(xùn)練超參數(shù)設(shè)置與計(jì)算過程訓(xùn)練時(shí)我用的核心參數(shù)如下img設(shè)為 640雖然 YOLOv11 支持更高分辨率輸入但 640 是速度和精度比較平衡的點(diǎn)。如果蘋果在畫面中普遍偏小可以提到 960 以獲得更好的小目標(biāo)效果但訓(xùn)練時(shí)間和顯存占用會(huì)明顯上升需要權(quán)衡。batch設(shè)為 16這個(gè)值受顯存限制。一塊 12GB 顯存的顯卡batch 16 加 img 640 基本是上限8GB 顯存建議降到 8 或 4。batch 太小會(huì)讓梯度估計(jì)不穩(wěn)訓(xùn)練震蕩明顯可以配合調(diào)低學(xué)習(xí)率解決。epochs設(shè) 200。我在訓(xùn)練到第 130 輪左右時(shí)驗(yàn)證集 mAP 已經(jīng)趨于穩(wěn)定后面幾十輪提升很小但我的訓(xùn)練集包含不同光照條件多跑一些輪次能讓模型在難樣本上更穩(wěn)健。optimizer用 AdamW初始學(xué)習(xí)率為 0.001。如果是小數(shù)據(jù)集SGD 也能訓(xùn)出不錯(cuò)的結(jié)果但 AdamW 的收斂過程更平滑對(duì)小白用戶更友好不需要頻繁調(diào)整學(xué)習(xí)率策略。4.3 評(píng)估指標(biāo)解讀模型訓(xùn)練結(jié)束后驗(yàn)證集上會(huì)輸出一組指標(biāo)最值得關(guān)注的是 mAP50 和 mAP50-95。mAP50 表示 IoU 閾值在 0.5 時(shí)各類別平均精度mAP50-95 表示從 0.5 到 0.95 平均后的更嚴(yán)格指標(biāo)。我的實(shí)際數(shù)值是mAP50 在 0.913 左右mAP50-95 在 0.687 左右。這個(gè)結(jié)果對(duì)三分類的蘋果成熟度任務(wù)是夠用的推理單幀時(shí)間在 GTX 3060 上約 18ms。如果你自己的模型 mAP50 達(dá)到 0.85 以上實(shí)際使用中已經(jīng)能有不錯(cuò)的表現(xiàn)如果低于 0.8優(yōu)先去查數(shù)據(jù)質(zhì)量和標(biāo)注是否正確不要急著調(diào)網(wǎng)絡(luò)結(jié)構(gòu)。另外要區(qū)分兩類錯(cuò)誤漏檢和誤檢。漏檢是蘋果沒被框出來誤檢是框里不是蘋果或類別判斷錯(cuò)誤。果園場(chǎng)景里漏檢比誤檢更致命因?yàn)槁z意味著這個(gè)果實(shí)永遠(yuǎn)不會(huì)被系統(tǒng)看到直接損失產(chǎn)量。如果漏檢集中在遠(yuǎn)距離小目標(biāo)優(yōu)先補(bǔ)小目標(biāo)樣本如果誤檢集中在枝葉背景框優(yōu)先做數(shù)據(jù)清洗和負(fù)樣本補(bǔ)充。4.4 訓(xùn)練不收斂的排查經(jīng)驗(yàn)訓(xùn)練過程中 loss 不降反升或者直接 NaN這類問題我遇到過幾次最常見的原因有三個(gè)第一個(gè)是學(xué)習(xí)率過大。epochs 不多時(shí) loss 曲線劇烈震蕩很大概率是學(xué)習(xí)率太高把它從 0.01 降到 0.001 就能解決。第二個(gè)是標(biāo)注格式或類別順序錯(cuò)誤尤其是類別順序錯(cuò)位模型學(xué)到的東西互相矛盾loss 也會(huì)異常。第三個(gè)是顯存溢出后自動(dòng)恢復(fù)導(dǎo)致的隱性問題訓(xùn)練日志里會(huì)有 WARNING不要忽略它直接把 batch 調(diào)小。還有一個(gè)容易忽略的地方是多卡訓(xùn)練時(shí)的批次分配。如果用了 GPU 數(shù)量參數(shù)默認(rèn) batch 會(huì)被平均分配到每張卡上但很多人的數(shù)據(jù)集規(guī)模并不需要多卡單卡反而更穩(wěn)定沒必要為了用多卡而多卡。5. 模型導(dǎo)出與推理封裝5.1 導(dǎo)出格式的選型訓(xùn)練完權(quán)重后不能直接把.pt文件丟給 UI 程序用。.pt格式加載慢、依賴 PyTorch 環(huán)境、部署時(shí)不方便所以我會(huì)導(dǎo)出成 ONNX 格式再由 UI 程序加載 ONNX 模型做推理或者直接用 ONNX Runtime 在 UI 進(jìn)程中跑。導(dǎo)出 ONNX 的命令很簡(jiǎn)單yolo export modelruns/train/exp/weights/best.pt formatonnx imgsz640導(dǎo)出后建議順手做一次精度對(duì)比驗(yàn)證確認(rèn) ONNX 模型和原 PyTorch 模型的輸出差異在可接受范圍內(nèi)。因?yàn)槟承┳远x算子在高版本 PyTorch 轉(zhuǎn) ONNX 時(shí)可能會(huì)被替換成等效操作極少數(shù)情況下數(shù)值精度有細(xì)微變化。如果差距大需要檢查導(dǎo)出時(shí)是否帶了simplify參數(shù)或者嘗試固定動(dòng)態(tài)軸。5.2 推理腳本的封裝思路我在項(xiàng)目里寫了一個(gè)推理封裝類Classifier 類完成模型加載、預(yù)處理、推理、后處理四件事。輸入是圖片路徑或 numpy 數(shù)組輸出是檢測(cè)框列表、類別編號(hào)、置信度列表。UI 層只需要調(diào)用這個(gè)類的方法不需要關(guān)心模型內(nèi)部邏輯。核心代碼如下import cv2 import numpy as np from ultralytics import YOLO class AppleDetector: def __init__(self, weights_path, conf_thres0.4, iou_thres0.45): self.model YOLO(weights_path) self.conf_thres conf_thres self.iou_thres iou_thres self.class_names [immature, maturing, mature] def detect(self, image): results self.model.predict( sourceimage, confself.conf_thres, iouself.iou_thres, verboseFalse )[0] boxes results.boxes.xyxy.cpu().numpy() confs results.boxes.conf.cpu().numpy() cls_ids results.boxes.cls.cpu().numpy().astype(int) return boxes, confs, cls_ids置信度閾值conf_thres的設(shè)置對(duì)結(jié)果影響很大。調(diào)低了檢測(cè)框變多漏檢變少但誤檢變多調(diào)高了結(jié)果更干凈漏檢會(huì)上升。對(duì)蘋果成熟度任務(wù)我默認(rèn)設(shè) 0.4在界面里留一個(gè)滑塊讓用戶可以動(dòng)態(tài)調(diào)整實(shí)際體驗(yàn)下來這個(gè)設(shè)計(jì)比固定閾值更實(shí)用。5.3 模型大小與推理速度的權(quán)衡如果你打算把系統(tǒng)部署到?jīng)]有獨(dú)立顯卡的電腦上模型推理就會(huì)變成純 CPU 計(jì)算速度會(huì)大幅下降。YOLOv11 官方有幾個(gè)不同規(guī)模的版本從 n 到 x參數(shù)量和速度差異很大。我之前測(cè)試過用 x 版本在純 CPU 上推理單幀耗時(shí)超過 800ms完全沒法做實(shí)時(shí)視頻檢測(cè)。實(shí)際部署時(shí)我最終選擇了在性能和精度之間比較均衡的一個(gè)版本。如果你用集成顯卡或者性能較好的 CPU可以嘗試中型版本如果是低功耗設(shè)備建議直接選輕量版本。這個(gè)取舍直接影響用戶體驗(yàn)因?yàn)?UI 界面上視頻流檢測(cè)如果每秒只有兩幀流暢度會(huì)很差。提示做實(shí)時(shí)視頻檢測(cè)時(shí)OpenCV 讀取視頻幀默認(rèn)分辨率可能很高比如 1080P直接把原分辨率丟給模型推理會(huì)非常耗時(shí)。正確做法是先把幀縮放到 640x640 或 960x960再做歸一化和通道轉(zhuǎn)換??s放后的精度損失通常小于速度提升帶來的收益。6. UI界面與登錄注冊(cè)模塊設(shè)計(jì)6.1 界面技術(shù)選型的理由UI 部分整個(gè)項(xiàng)目除了“能用”還得“好看”和“好操作”。我用 PySide6 來做圖形界面替換掉以前項(xiàng)目里用的 Tkinter主要原因有兩點(diǎn)第一PySide6 的組件現(xiàn)代化程度更高QSS 樣式表可以做相對(duì)精細(xì)的界面美化白底卡片式布局、圓角邊框、按鈕懸浮變色這些用 Tkinter 實(shí)現(xiàn)起來極其痛苦。第二PySide6 的信號(hào)槽機(jī)制和線程配合更好。檢測(cè)模型推理是耗時(shí)操作放在 UI 主線程里畫面直接卡死凍結(jié)必須放到工作線程中跑通過信號(hào)把結(jié)果傳回主線程刷新界面。PySide6 在這套機(jī)制上是原生支持的寫起來清晰穩(wěn)定。6.2 登錄注冊(cè)邏輯的實(shí)際落地登錄注冊(cè)模塊在演示系統(tǒng)里經(jīng)常被當(dāng)成擺設(shè)但既然標(biāo)題里明確寫了這個(gè)需求我還是把它做到了可實(shí)際使用的狀態(tài)。用戶數(shù)據(jù)存儲(chǔ)用的是 SQLite不用額外部署數(shù)據(jù)庫服務(wù)一個(gè)文件搞定所有數(shù)據(jù)。密碼不是明文存儲(chǔ)而是經(jīng)過哈希處理后再入庫。校驗(yàn)時(shí)把用戶輸入的密碼加同樣的鹽再做哈希和庫里存的哈希值比對(duì)。這個(gè)方案雖然對(duì)安全要求極高的場(chǎng)景來說還不夠但放在本地面向個(gè)人用戶的系統(tǒng)里已經(jīng)足夠了。注冊(cè)時(shí)還需要做三個(gè)校驗(yàn)用戶名不能為空、兩次輸入的密碼必須一致、用戶名不能重復(fù)。代碼實(shí)現(xiàn)上很簡(jiǎn)單但很容易漏掉“用戶名重復(fù)”這個(gè)判斷導(dǎo)致數(shù)據(jù)庫出現(xiàn)多條相同用戶名記錄登錄時(shí)只能查出第一條邏輯就會(huì)變得很難看。登錄窗口和主窗口的切換邏輯用的是窗口關(guān)閉與新建。登錄成功后銷毀登錄窗口對(duì)象創(chuàng)建主窗口對(duì)象并展示。同時(shí)維護(hù)了一個(gè)全局會(huì)話狀態(tài)變量記錄當(dāng)前登錄用戶名這樣在主界面的標(biāo)題欄可以顯示“當(dāng)前用戶xxxx”體驗(yàn)會(huì)正式很多。6.3 主界面布局與功能設(shè)計(jì)主檢測(cè)界面布局分為三塊左側(cè)是操作區(qū)放置打開圖片、開始檢測(cè)、保存結(jié)果、切換模型等按鈕中間是圖片顯示區(qū)原始圖片和處理后的標(biāo)注圖片都在這里展示右側(cè)是信息區(qū)顯示每一幀檢測(cè)到的蘋果數(shù)量、各類別數(shù)量、平均置信度以及每類檢測(cè)框的詳細(xì)信息列表。圖片顯示區(qū)有這樣幾個(gè)細(xì)節(jié)首先顯示圖像前要把 BGR 轉(zhuǎn)成 RGB不然 OpenCV 讀進(jìn)來的圖片畫到 PySide6 界面里顏色會(huì)偏藍(lán)這是 BGR 和 RGB 通道順序?qū)е碌挠龅浇缑骖伾粚?duì)的先檢查這里。其次檢測(cè)結(jié)果畫框時(shí)不能只畫一個(gè)矩形還需要在框的左上角繪制類別標(biāo)簽和置信度置信度要在框外形成一個(gè)對(duì)比明顯的底色塊這些細(xì)節(jié)讓結(jié)果的視覺效果清晰很多。右側(cè)信息區(qū)我做得比較豐富檢測(cè)框詳細(xì)信息以表格形式展示每一行包括序號(hào)、類別、置信度、中心坐標(biāo)、寬高。這樣不僅能看到圖片上的檢測(cè)結(jié)果還能精確定位每一個(gè)目標(biāo)的數(shù)值信息后續(xù)做數(shù)據(jù)統(tǒng)計(jì)或?qū)С鰣?bào)告時(shí)很方便。6.4 保存結(jié)果與文件導(dǎo)出結(jié)果保存功能這塊我最初只做了“保存標(biāo)注圖片”后來看到實(shí)際操作需求才補(bǔ)上了“保存檢測(cè)報(bào)告”。檢測(cè)報(bào)告是一個(gè) CSV 文件包含文件名、各類別數(shù)量、各檢測(cè)框坐標(biāo)和置信度。對(duì)于果園巡檢測(cè)量果實(shí)成熟度分布這類應(yīng)用CSV 比圖片更實(shí)用可以直接導(dǎo)入 Excel 做統(tǒng)計(jì)。這里分享一個(gè)踩過的坑保存圖片時(shí)用cv2.imwrite寫中文路徑會(huì)報(bào)錯(cuò)OpenCV 對(duì)非 ASCII 路徑支持不好后來統(tǒng)一改成cv2.imencode加文件寫入的方式解決。def save_image_with_cn_path(image_bgr, path): ext . path.rsplit(., 1)[-1] result, encoded cv2.imencode(ext, image_bgr) if result: with open(path, wb) as f: f.write(encoded.tobytes())6.5 視頻流檢測(cè)與多線程實(shí)現(xiàn)實(shí)時(shí)視頻檢測(cè)是另外一個(gè)復(fù)雜點(diǎn)。如果直接用攝像頭采集幀、主線程推理、主線程顯示界面刷新率會(huì)非常低。我采用的是三個(gè)線程協(xié)作采集線程不斷從攝像頭或視頻文件讀取幀放入隊(duì)列推理線程從隊(duì)列取幀、運(yùn)行 YOLO 模型、生成標(biāo)注結(jié)果主線程通過定時(shí)器定期從結(jié)果隊(duì)列取出最新一幀刷新顯示。這樣做的好處是每一幀的采集、推理、顯示各環(huán)節(jié)解耦即使模型推理速度低于攝像頭幀率界面展示的也是最近一次成功推理的結(jié)果不會(huì)因?yàn)樘幚聿贿^來而卡死。多線程會(huì)遇到一個(gè)坑PySide6 的界面組件不能在非主線程里直接操作。哪怕只是更新一個(gè) QLabel 的文字也要通過信號(hào)槽發(fā)到主線程執(zhí)行否則輕則警告重則崩潰。7. 常見問題排查與避坑實(shí)錄7.1 訓(xùn)練階段問題速查訓(xùn)練這塊我把自己的排查經(jīng)驗(yàn)整理成了表格遇到問題直接對(duì)號(hào)入座最快?,F(xiàn)象可能原因解決方案Loss 不下降學(xué)習(xí)率過大或過小調(diào)整到 0.001 附近先用小學(xué)習(xí)率跑 10 輪試探驗(yàn)證集指標(biāo)虛高數(shù)據(jù)泄漏按視頻段/批次分組切分訓(xùn)練驗(yàn)證集小目標(biāo)漏檢嚴(yán)重輸入分辨率過低img 從 640 提高到 960補(bǔ)充小目標(biāo)樣本訓(xùn)練過程報(bào) NaN數(shù)據(jù)存在缺失標(biāo)注用腳本掃描標(biāo)簽文件中是否存在越界坐標(biāo)顯存溢出batch 過大降低 batch或者同步降低圖片輸入尺寸推理類別錯(cuò)亂類別順序不一致檢查 data.yaml 和標(biāo)注工具中類別順序7.2 界面運(yùn)行常見問題界面運(yùn)行階段我遇到最多的是模型加載慢和首次推理卡頓。.pt文件加載需要一定時(shí)間所以在主窗口初始化時(shí)我就把模型加載完成不要等到點(diǎn)擊“開始檢測(cè)”才加載否則用戶會(huì)以為程序卡死了。啟動(dòng)時(shí)在狀態(tài)欄顯示“模型加載中...”加載完成后更新狀態(tài)這個(gè)小細(xì)節(jié)很提升使用體驗(yàn)。另外一個(gè)高頻問題是圖片尺寸過大導(dǎo)致界面顯示卡頓。高清照片打開后如果不做縮放直接塞給界面組件縮放計(jì)算本身就很有壓力。我統(tǒng)一把傳入顯示區(qū)域的圖片先縮放至適合寬度再交給組件展示保留原圖用于檢測(cè)。這樣做之后界面操作流暢度提升明顯。7.3 部署換機(jī)時(shí)的注意事項(xiàng)如果你要把整個(gè)項(xiàng)目從開發(fā)機(jī)拷貝到另一臺(tái)電腦運(yùn)行最容易出問題的是環(huán)境依賴和路徑硬編碼。先說路徑項(xiàng)目代碼里不要寫絕對(duì)路徑不管訓(xùn)練還是推理都用相對(duì)路徑或者通過配置文件讀取路徑。我第一次在自己的項(xiàng)目里寫死了好幾個(gè)數(shù)據(jù)路徑拷到新環(huán)境后全部失效最后花了一上午才把路徑全部換成基于項(xiàng)目根目錄的動(dòng)態(tài)拼接。依賴環(huán)境方面最穩(wěn)妥的方式是在項(xiàng)目目錄里附一個(gè) requirement.txt把依賴庫和版本號(hào)鎖死。新機(jī)器上創(chuàng)建虛擬環(huán)境后一鍵安裝避免因?yàn)榘姹静灰恢鲁霈F(xiàn)隱秘的運(yùn)行時(shí)錯(cuò)誤。8. 項(xiàng)目擴(kuò)展與實(shí)踐建議這套系統(tǒng)目前已經(jīng)覆蓋了從模型到界面的完整流程但它仍然有非常大的擴(kuò)展空間。我個(gè)人在實(shí)操中有幾點(diǎn)體會(huì)特別深第一成熟度分類的粒度可以更細(xì)。當(dāng)前是三分類如果你對(duì)接的是果汁加工、鮮食分揀等具體產(chǎn)業(yè)需求可能需要五分類甚至按色度值連續(xù)回歸這本質(zhì)上就是把分類頭換成回歸頭的問題YOLOv11 不需要改主干只改輸出層即可。第二數(shù)據(jù)持續(xù)迭代比模型調(diào)參更重要。我實(shí)際感受最深的是文件夾里不斷補(bǔ)充新數(shù)據(jù)帶來的提升遠(yuǎn)比反復(fù)調(diào)confidence閾值和iou閾值有效。每換一個(gè)果園、每換一個(gè)品種就往數(shù)據(jù)集里加一批圖模型越用越準(zhǔn)這是做農(nóng)業(yè)視覺項(xiàng)目的長期規(guī)律。第三模型部署時(shí)輕量化改進(jìn)有很大空間。如果后續(xù)要上 Jetson 這類嵌入式設(shè)備可以考慮 TensorRT 加速推理速度還能再上一個(gè)臺(tái)階。有一段時(shí)間我在筆記本上做實(shí)驗(yàn)用 TensorRT 優(yōu)化后單幀從 18ms 降到 9ms 左右這個(gè)對(duì)實(shí)時(shí)性要求高的場(chǎng)景非常關(guān)鍵。最后提一個(gè)關(guān)于項(xiàng)目管理的小建議訓(xùn)練產(chǎn)生的各種權(quán)重文件、每次實(shí)驗(yàn)的指標(biāo)記錄、每個(gè)版本的數(shù)據(jù)集配置都要做好命名管理。不要像我早期那樣出現(xiàn)“final_final”這種命名的模型文件一定要把日期、精度、數(shù)據(jù)集版本寫在文件名里不然過兩周你根本分不清哪個(gè)是最終的。這些小習(xí)慣看起來很瑣碎但在真實(shí)項(xiàng)目中能幫你省下大量時(shí)間。