畢設實戰(zhàn):從環(huán)境配置到界面聯(lián)調(diào))
簡介這份資源面向計算機、人工智能、自動化等專業(yè)的在校學生與教師提供一套基于YOLOv8的果園成熟果實自動計數(shù)完整方案可用于畢業(yè)設計、課程設計或大作業(yè)。壓縮包共8個文件約15.91MB包含3個Python腳本、3個模型權(quán)重文件與2個說明文本分別對應可視化界面、模型訓練與視頻檢測等核心環(huán)節(jié)并附有完整數(shù)據(jù)集與部署教程簡單配置即可運行。項目可輸出核心指標曲線圖、混淆矩陣、F1分數(shù)曲線、精確率-召回率曲線、驗證集預測結(jié)果及標簽分布圖便于答辯展示與結(jié)果分析。目前已有30人學習關(guān)注。讀者可直接獲得經(jīng)過測試的源碼、預訓練權(quán)重與可視化頁面既能快速復現(xiàn)果實計數(shù)效果也能在此基礎上修改擴展功能適合作為畢設保底方案或入門目標檢測的實踐素材。1. 果園果實自動計數(shù)這套 YOLOv8 畢設資源到底能不能直接跑果園里數(shù)果子這件事人工干一天下來眼睛發(fā)花數(shù)錯率還高。這套資源的核心就是用 YOLOv8 做成熟果實的目標檢測再把檢測框轉(zhuǎn)成計數(shù)結(jié)果配了一個可視化界面數(shù)據(jù)集和部署教程都打包在里面。拿到手之后不需要自己從零標注、從零搭界面解壓完按教程走一遍就能跑起來。適合兩類人一類是畢設或課程設計時間緊、需要完整可演示系統(tǒng)的學生另一類是想快速驗證 YOLOv8 在農(nóng)業(yè)計數(shù)場景下效果的從業(yè)者。資源里包含源碼、可視化界面、完整數(shù)據(jù)集和部署教程技術(shù)棧是 Python YOLOv8 圖形界面框架整體走的是“檢測即計數(shù)”的路線不涉及復雜的多目標跟蹤。下面按實際拆包和復現(xiàn)的順序把環(huán)境配置、數(shù)據(jù)組織、訓練調(diào)參、界面聯(lián)調(diào)和常見翻車點逐個講清楚。2. 環(huán)境配置與依賴安裝從零到能跑通第一張推理圖2.1 為什么優(yōu)先選 CPU 版先跑通再換 GPU很多人拿到 YOLOv8 項目第一反應是裝 CUDA、配 cuDNN結(jié)果卡在驅(qū)動版本上耗掉一整天。我的建議是先用 CPU 版本把推理和界面跑通確認代碼邏輯沒問題再切 GPU 訓練。原因很直接CPU 版安裝簡單不會因為顯卡驅(qū)動和 PyTorch 版本不匹配導致 import 就報錯。等推理通了再裝 GPU 版 PyTorch 做訓練這時候即使出問題你也能確定是環(huán)境問題而不是代碼問題。常見做法是建一個獨立虛擬環(huán)境避免和系統(tǒng) Python 混在一起。Ubuntu 20.04 和 Windows 10/11 都適用下面以 conda 為例。# 創(chuàng)建虛擬環(huán)境Python 版本建議 3.9 或 3.10 conda create -n yolov8_fruit python3.10 -y conda activate yolov8_fruit # 安裝 CPU 版 PyTorch先跑通推理用 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 安裝 ultralytics 和界面依賴 pip install ultralytics opencv-python pillow pyqt5這里torch和torchvision走的是 CPU 源裝完大概幾百 MB。ultralytics是 YOLOv8 的官方庫版本建議鎖在 8.0.x 到 8.1.x 之間太新的版本有時會改 API 導致項目里的推理腳本報參數(shù)錯誤。pyqt5是可視化界面的依賴如果項目用的是 tkinter 就換成tkinterPython 自帶不用額外裝。裝完之后跑一句yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg驗證環(huán)境能輸出檢測結(jié)果圖就說明基礎環(huán)境沒問題。2.2 GPU 訓練環(huán)境的切換與版本對齊CPU 推理通了之后如果要訓練自己的數(shù)據(jù)集GPU 是繞不開的。這時候把 CPU 版 PyTorch 卸掉換成對應 CUDA 版本的 GPU 版。關(guān)鍵點是先確認顯卡驅(qū)動支持的 CUDA 最高版本再選 PyTorch 對應的 CUDA 版本不要反過來。# 查看顯卡驅(qū)動和 CUDA 支持版本 nvidia-smi # 卸載 CPU 版安裝 GPU 版以 CUDA 11.8 為例 pip uninstall torch torchvision -y pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 驗證 GPU 是否可用 python -c import torch; print(torch.cuda.is_available())nvidia-smi右上角顯示的 CUDA Version 是驅(qū)動支持的最高版本不是當前安裝的版本。比如顯示 12.2你可以裝 CUDA 11.8 的 PyTorch也可以裝 12.1 的但不能裝 12.3 的。torch.cuda.is_available()返回 True 才算成功。如果返回 False先檢查驅(qū)動版本再檢查 PyTorch 和 CUDA 是否匹配最后看是不是裝成了 CPU 版。這一步?jīng)]有后悔藥版本對不上就是跑不了 GPU。注意如果項目里 requirements.txt 鎖定了 torch 版本優(yōu)先按它來不要自己升級。版本沖突是 YOLOv8 項目里最常見的翻車原因之一。3. 數(shù)據(jù)集組織與標注格式轉(zhuǎn)換讓 YOLOv8 認得你的果園圖片3.1 目錄結(jié)構(gòu)與 data.yaml 的對應關(guān)系YOLOv8 對數(shù)據(jù)集的目錄結(jié)構(gòu)有固定要求不是隨便放幾個文件夾就能訓練。標準結(jié)構(gòu)是 images 和 labels 分開各自下面再分 train、val、test。資源里如果已經(jīng)帶了完整數(shù)據(jù)集先檢查目錄是不是這個結(jié)構(gòu)不是的話按下面調(diào)整。fruit_dataset/ ├── images/ │ ├── train/ # 訓練集圖片 │ ├── val/ # 驗證集圖片 │ └── test/ # 測試集圖片可選 ├── labels/ │ ├── train/ # 訓練集標注 txt │ ├── val/ # 驗證集標注 txt │ └── test/ # 測試集標注 txt └── data.yaml # 數(shù)據(jù)集配置文件data.yaml是訓練時的入口配置內(nèi)容必須和實際路徑對應。常見寫法如下path: ./fruit_dataset # 數(shù)據(jù)集根目錄 train: images/train # 訓練集相對路徑 val: images/val # 驗證集相對路徑 test: images/test # 測試集相對路徑?jīng)]有就刪掉這行 nc: 1 # 類別數(shù)果園成熟果實通常只檢測一類 names: [ripe_fruit] # 類別名稱順序要和標注里的 class_id 對應nc是類別數(shù)量如果只檢測成熟果實就是 1。names列表的順序很重要標注文件里的 class_id 是 0 就對應列表第一個名字。如果數(shù)據(jù)集里還有未成熟果實或其他類別nc和names都要相應增加。路徑可以用相對路徑也可以用絕對路徑相對路徑是相對于你執(zhí)行訓練命令時所在的目錄不是相對于 data.yaml 文件本身這一點容易搞混。3.2 標注格式轉(zhuǎn)換Labelme 轉(zhuǎn) YOLO 的腳本與邊界處理如果數(shù)據(jù)集里的標注是 Labelme 生成的 JSON 格式需要轉(zhuǎn)成 YOLO 的 txt 格式。轉(zhuǎn)換的核心是把多邊形或矩形坐標歸一化到 0 到 1 之間格式是class_id x_center y_center width height。下面是一個可直接用的轉(zhuǎn)換腳本。import json import os from pathlib import Path def labelme_to_yolo(json_dir, output_dir, class_map): json_dir: Labelme JSON 文件目錄 output_dir: 輸出 YOLO txt 的目錄 class_map: 類別名到 id 的映射如 {ripe_fruit: 0} json_dir Path(json_dir) output_dir Path(output_dir) output_dir.mkdir(parentsTrue, exist_okTrue) for json_file in json_dir.glob(*.json): with open(json_file, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue # 跳過未定義類別避免訓練時報錯 class_id class_map[label] points shape[points] # 取多邊形或矩形的外接框 xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 歸一化并計算中心點和寬高 x_center (x_min x_max) / 2.0 / img_w y_center (y_min y_max) / 2.0 / img_h width (x_max - x_min) / img_w height (y_max - y_min) / img_h # 邊界裁剪防止坐標超出 0-1 范圍 x_center min(max(x_center, 0.0), 1.0) y_center min(max(y_center, 0.0), 1.0) width min(max(width, 0.0), 1.0) height min(max(height, 0.0), 1.0) lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 寫入同名 txt txt_path output_dir / (json_file.stem .txt) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) if __name__ __main__: labelme_to_yolo( json_dir./raw_annotations, output_dir./fruit_dataset/labels/train, class_map{ripe_fruit: 0} )這個腳本做了三件事讀取 Labelme JSON、把多邊形轉(zhuǎn)成外接矩形并歸一化、寫入 YOLO 格式 txt。class_map參數(shù)控制類別映射數(shù)據(jù)集里有什么類別就加什么沒在 map 里的標注會被跳過避免訓練時出現(xiàn)未知 class_id 導致報錯。歸一化后的坐標做了 0 到 1 的裁剪防止標注時點超出圖片邊界導致訓練異常。轉(zhuǎn)換完記得抽查幾個 txt 文件確認坐標數(shù)值在合理范圍內(nèi)不要出現(xiàn)大于 1 或負數(shù)的情況。注意圖片和標注文件名必須一一對應除了擴展名不同。如果圖片是img_001.jpg標注必須是img_001.txt放在 labels 對應目錄下。名字對不上訓練時會被當成無標注圖片跳過。4. 模型訓練與參數(shù)調(diào)優(yōu)從默認配置到果園場景適配4.1 訓練命令與關(guān)鍵參數(shù)含義YOLOv8 的訓練入口很簡潔一條命令就能啟動。但默認參數(shù)不一定適合果園果實這種密集小目標場景需要根據(jù)實際情況調(diào)整。# 基礎訓練命令 yolo detect train \ data./fruit_dataset/data.yaml \ modelyolov8n.pt \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ project./runs/train \ namefruit_expmodel參數(shù)決定用哪個預訓練權(quán)重。yolov8n.pt是最小的 nano 版本速度快但精度低如果果實目標小且密集建議換成yolov8s.pt或yolov8m.pt。epochs是訓練輪數(shù)100 輪起步數(shù)據(jù)集小的話可以加到 200。imgsz是輸入圖片尺寸640 是默認值果實占畫面比例小的話可以提到 1280但顯存占用會明顯增加。batch是批大小顯存不夠就往下調(diào)從 16 降到 8 再到 4。lr0是初始學習率默認 0.01數(shù)據(jù)集小可以降到 0.001 防止過擬合。patience是早停輪數(shù)20 輪內(nèi)驗證指標沒提升就自動停省時間。訓練過程中會在runs/train/fruit_exp下生成權(quán)重文件、損失曲線和驗證結(jié)果。重點關(guān)注results.csv里的metrics/mAP50和metrics/mAP50-95前者是 IoU 0.5 時的平均精度后者是 0.5 到 0.95 的平均值。果園計數(shù)場景下 mAP50 到 0.85 以上基本可用低于 0.7 就要檢查數(shù)據(jù)集標注質(zhì)量或增加數(shù)據(jù)量。4.2 小目標密集場景的參數(shù)調(diào)整策略果園果實往往在畫面里占比較小而且成熟果實可能成簇出現(xiàn)默認的 640 輸入尺寸和 anchor 配置不一定最優(yōu)。幾個實際有效的調(diào)整方向參數(shù)默認值果園場景建議原因imgsz640960 或 1280提高小目標分辨率modelyolov8nyolov8s 或 yolov8m更大模型對小目標更敏感batch168 或 4大輸入尺寸下顯存不夠lr00.010.005 或 0.001小數(shù)據(jù)集防過擬合mosaic1.00.5 或關(guān)閉果實密集時 mosaic 可能引入干擾close_mosaic1020 或 30最后階段關(guān)閉增強穩(wěn)定收斂mosaic是 YOLOv8 默認開啟的數(shù)據(jù)增強把四張圖拼成一張訓練。果實計數(shù)場景下如果圖片本身果實就很密集mosaic 可能讓模型學到錯誤的上下文關(guān)系適當降低比例或最后幾十輪關(guān)掉。close_mosaic控制最后多少輪關(guān)閉 mosaic設大一點讓模型在真實分布上多收斂幾輪。訓練完之后用驗證集跑一遍評估命令是yolo detect val model./runs/train/fruit_exp/weights/best.pt data./fruit_dataset/data.yaml??摧敵龅幕煜仃嚭透黝悇e AP如果只有一類重點看漏檢和誤檢的比例。漏檢多說明模型太保守可以降低置信度閾值誤檢多說明模型把背景當成了果實需要增加負樣本或提高置信度閾值。5. 可視化界面聯(lián)調(diào)與計數(shù)邏輯讓檢測框變成數(shù)字5.1 界面調(diào)用推理的代碼結(jié)構(gòu)與參數(shù)傳遞可視化界面通常是一個獨立的 Python 腳本通過按鈕觸發(fā)圖片選擇、推理和結(jié)果顯示。核心邏輯是加載訓練好的權(quán)重對選中圖片做推理把檢測框畫在圖上同時統(tǒng)計框的數(shù)量作為計數(shù)結(jié)果。下面是一個典型的界面推理函數(shù)。from ultralytics import YOLO import cv2 class FruitCounter: def __init__(self, model_path, conf_threshold0.5): self.model YOLO(model_path) self.conf_threshold conf_threshold def detect_and_count(self, image_path): # 執(zhí)行推理conf 控制置信度閾值 results self.model(image_path, confself.conf_threshold) # 獲取檢測框數(shù)量 boxes results[0].boxes count len(boxes) if boxes is not None else 0 # 繪制結(jié)果圖 annotated results[0].plot() return annotated, count # 界面里調(diào)用 counter FruitCounter(./runs/train/fruit_exp/weights/best.pt, conf_threshold0.5) img, num counter.detect_and_count(./test_images/orchard_01.jpg) cv2.imwrite(./output/result_01.jpg, img) print(f檢測到成熟果實數(shù)量{num})conf_threshold是置信度閾值默認 0.5。這個參數(shù)直接決定計數(shù)結(jié)果調(diào)高會減少誤檢但可能漏檢調(diào)低會檢出更多但可能把背景算進去。果園場景下建議先在驗證集上試幾個值看哪個閾值下計數(shù)誤差最小。results[0].boxes是檢測框?qū)ο髄en()就是數(shù)量。results[0].plot()返回畫好框的圖片可以直接顯示在界面上或保存。界面部分如果是 PyQt5把detect_and_count綁定到按鈕的點擊事件上圖片顯示用 QLabel 的 setPixmap。如果是 tkinter用 Canvas 或 Label 顯示。關(guān)鍵是把推理放在子線程里不要在主線程跑否則界面會卡死。這一點在演示的時候特別明顯老師或評委看到界面無響應會直接扣分。5.2 計數(shù)邏輯的邊界處理與結(jié)果校驗檢測框數(shù)量不等于果實數(shù)量這是最容易翻車的地方。兩個常見問題一是同一個果實被檢測出多個框二是相鄰果實被合并成一個框。前者需要加非極大值抑制NMSYOLOv8 默認已經(jīng)做了但如果 IoU 閾值設得太高重疊框不會被抑制。后者需要看訓練數(shù)據(jù)里果實間距如果標注時就把兩個果實標成一個框模型學到的就是合并檢測。# 調(diào)整 NMS 的 IoU 閾值 results self.model(image_path, conf0.5, iou0.45)iou參數(shù)控制 NMS 的閾值默認 0.7。調(diào)低到 0.45 會抑制更多重疊框適合果實密集且相互遮擋的場景。但調(diào)太低也可能把相鄰果實的一個框誤刪需要根據(jù)實際圖片試。另一個校驗手段是設置面積過濾太小的框可能是噪聲太大的框可能是誤檢。# 按面積過濾異???boxes results[0].boxes valid_count 0 for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() area (x2 - x1) * (y2 - y1) if 100 area 50000: # 根據(jù)實際圖片分辨率調(diào)整 valid_count 1面積閾值要根據(jù)圖片分辨率和果實實際像素大小來定??梢韵扰軒讖垐D打印出所有框的面積分布再定上下限。這一步?jīng)]有固定公式靠實際數(shù)據(jù)調(diào)。注意界面顯示計數(shù)結(jié)果時建議同時顯示置信度分布或檢測框可視化方便演示時解釋為什么是這個數(shù)字。只給一個數(shù)字評委問起來不好回答。6. 避坑與常見問題排查那些讓我重跑訓練的血淚教訓6.1 訓練 loss 不下降或震蕩嚴重現(xiàn)象訓練開始后 box_loss 和 cls_loss 一直在高位震蕩mAP 不漲。原因通常是學習率太大或數(shù)據(jù)標注有問題。先檢查標注文件里有沒有坐標超出 0 到 1 范圍的再檢查類別 id 是否從 0 開始連續(xù)。如果標注沒問題把lr0降到 0.001 再跑。還有一種情況是預訓練權(quán)重和模型結(jié)構(gòu)不匹配比如用yolov8n.pt加載到y(tǒng)olov8m的配置上這時候會報錯或靜默失敗確認 model 參數(shù)和權(quán)重文件對應。6.2 推理時檢測框數(shù)量明顯偏多或偏少現(xiàn)象同一張圖界面顯示的計數(shù)和肉眼數(shù)的差很多。原因可能是置信度閾值不合適或者 NMS 的 IoU 閾值需要調(diào)整。先固定一張測試圖把conf從 0.1 到 0.9 逐個試看計數(shù)變化曲線選一個穩(wěn)定區(qū)間。如果框大量重疊把iou從 0.7 降到 0.45 再試。另外檢查輸入圖片的尺寸如果推理時 resize 到 640 但原圖是 4000 像素寬小果實會縮到幾個像素檢測不到很正常。6.3 界面啟動報缺少 DLL 或模塊找不到現(xiàn)象在 Windows 上雙擊運行界面腳本報ImportError或缺少Q(mào)t platform plugin。原因是 PyQt5 的環(huán)境變量沒配好或者虛擬環(huán)境沒激活。解決方法是確保在激活的虛擬環(huán)境里運行不要直接雙擊 py 文件。如果還報 Qt 插件錯誤設置環(huán)境變量QT_QPA_PLATFORM_PLUGIN_PATH指向 PyQt5 的 plugins 目錄或者重裝 PyQt5pip install --force-reinstall pyqt5。6.4 訓練完的權(quán)重在界面里加載失敗現(xiàn)象訓練時一切正常界面加載best.pt時報錯或檢測結(jié)果全空。原因是界面腳本里的模型路徑寫錯了或者用了last.pt而不是best.pt。last.pt是最后一輪的權(quán)重可能過擬合best.pt是驗證集上最好的權(quán)重優(yōu)先用這個。另外確認界面腳本和訓練用的 ultralytics 版本一致版本不同可能導致權(quán)重加載兼容性問題。6.5 數(shù)據(jù)集劃分不合理導致驗證指標虛高現(xiàn)象mAP 很高但實際用的時候效果差。原因是訓練集和驗證集里有重復圖片或相似度極高的圖片模型相當于在“背答案”。劃分數(shù)據(jù)集時確保同一棵樹的圖片不要同時出現(xiàn)在訓練集和驗證集里最好按拍攝時間或地點劃分。如果數(shù)據(jù)集本身不大用 8:1:1 的比例驗證集至少留幾十張有代表性的圖。7. 進階技巧用置信度分布和面積過濾把計數(shù)誤差壓到最低訓練和界面都跑通之后真正決定這套系統(tǒng)好不好用的是計數(shù)誤差。我自己的習慣是做一個簡單的后處理校準先在一批測試圖上跑推理把每個檢測框的置信度和面積都導出來然后畫散點圖看分布。通常會發(fā)現(xiàn)置信度在 0.3 到 0.5 之間有一批模棱兩可的框這些就是誤差的主要來源。具體做法是分兩步過濾。第一步按置信度卡一個下限比如 0.4低于這個值的框直接丟掉。第二步按面積卡上下限太小的框面積小于圖片總面積的 0.1%大概率是噪聲太大的框面積大于 5%大概率是誤檢。兩個條件同時滿足才計入最終數(shù)量。def calibrated_count(results, img_w, img_h, conf_min0.4, area_min_ratio0.001, area_max_ratio0.05): boxes results[0].boxes if boxes is None: return 0 img_area img_w * img_h count 0 for box in boxes: conf box.conf[0].item() x1, y1, x2, y2 box.xyxy[0].tolist() area (x2 - x1) * (y2 - y1) if conf conf_min and area_min_ratio * img_area area area_max_ratio * img_area: count 1 return count這個函數(shù)比直接len(boxes)多了一層校準參數(shù)需要根據(jù)實際圖片調(diào)。conf_min從 0.4 開始試area_min_ratio和area_max_ratio根據(jù)果實占畫面比例定。調(diào)好之后同一批測試圖的計數(shù)誤差通常能從 ±20% 壓到 ±5% 以內(nèi)。還有一個技巧是保存每次推理的原始結(jié)果包括框坐標、置信度和面積存成 CSV。這樣后期如果發(fā)現(xiàn)某張圖計數(shù)不對可以回溯是哪個框被誤算或漏算了不用重新跑推理。我一般會在界面里加一個“導出詳細結(jié)果”的按鈕演示的時候如果評委問某個數(shù)字怎么來的直接打開 CSV 指給他看比口頭解釋有說服力。從那以后我每次做完檢測類項目都會強制走一遍“置信度分布 面積過濾 結(jié)果導出”這三步不管界面看起來多正常。希望幫到你。本文還有配套的精品資源點擊獲取