時(shí)目標(biāo)檢測(cè):CUDA/PyTorch環(huán)境配置與攝像頭部署避坑)
簡(jiǎn)介這份教程面向計(jì)算機(jī)視覺(jué)初學(xué)者與Python開(kāi)發(fā)者圍繞YOLOv10實(shí)時(shí)目標(biāo)檢測(cè)系統(tǒng)的搭建串聯(lián)環(huán)境準(zhǔn)備、模型訓(xùn)練、評(píng)估與視頻流檢測(cè)等環(huán)節(jié)幫助讀者理解從數(shù)據(jù)標(biāo)注到應(yīng)用驗(yàn)證的完整流程。壓縮包內(nèi)共1個(gè)doc文件約28KB以目錄化文檔承載核心內(nèi)容便于按章節(jié)查閱和對(duì)照實(shí)踐。已有207人學(xué)習(xí)適合希望快速熟悉YOLOv10目標(biāo)檢測(cè)開(kāi)發(fā)路徑的讀者。文檔涵蓋硬件與軟件要求、PyTorch和OpenCV等庫(kù)的安裝配置、COCO或自定義數(shù)據(jù)集的準(zhǔn)備與標(biāo)注、訓(xùn)練參數(shù)設(shè)置、mAP等評(píng)估指標(biāo)及可視化方法并給出攝像頭視頻流讀取與檢測(cè)框繪制的示例思路可幫助讀者建立可復(fù)現(xiàn)的實(shí)驗(yàn)框架并在此基礎(chǔ)上擴(kuò)展更多檢測(cè)場(chǎng)景與優(yōu)化方向。1. 攝像頭能亮框卻畫(huà)不出來(lái)某開(kāi)發(fā)者把 yolov10 倉(cāng)庫(kù)克隆下來(lái)pip 一路裝完python train.py一敲屏幕上跳出 CUDA out of memory退回 CPU 推理一張 640×640 的圖要等三秒多攝像頭畫(huà)面卡成 PPT。這類現(xiàn)場(chǎng)我見(jiàn)過(guò)太多次。yolov10 實(shí)時(shí)目標(biāo)檢測(cè)這套環(huán)境難點(diǎn)從來(lái)不在裝沒(méi)裝上而在版本對(duì)沒(méi)對(duì)上——驅(qū)動(dòng)支持的 CUDA 檔位、PyTorch 輪子的 cu 標(biāo)簽、Python 小版本錯(cuò)一個(gè)就 GPU 認(rèn)不出來(lái)或者訓(xùn)練十個(gè) epoch 后崩在顯存上。這份開(kāi)發(fā)案例把環(huán)境準(zhǔn)備、訓(xùn)練、評(píng)估、實(shí)時(shí)檢測(cè)串成了一條線適合手里有一張 NVIDIA 顯卡、想從零跑通攝像頭畫(huà)面的開(kāi)發(fā)者。下面按我自己的裝機(jī)順序把命令、參數(shù)和翻車點(diǎn)攤開(kāi)寫(xiě)。2. 裝前先對(duì)表CUDA、Python 與 PyTorch 的三方版本鎖定裝深度學(xué)習(xí)環(huán)境最忌諱先裝再說(shuō)。我一般的順序是先看卡再看驅(qū)動(dòng)然后定 CUDA 檔位最后反推 Python 和 PyTorch中間任何一步跳過(guò)后面都要花兩三倍時(shí)間回滾。這一章不裝東西只把表對(duì)清楚。2.1 硬件門檻4 核 CPU 只是底線顯存才是分水嶺案例里列的四條硬件要求是CPU 至少 4 核、NVIDIA GPU推薦 RTX 系列、內(nèi)存至少 8GB、存儲(chǔ)至少 10GB。照著這份清單配機(jī)器不算錯(cuò)但它只回答了能不能啟動(dòng)沒(méi)回答能不能訓(xùn)練。真正卡人的是顯存和顯卡算力這兩件事。項(xiàng)目案例給出的要求我實(shí)際會(huì)留的余量卡點(diǎn)在哪CPU至少 4 核8 核以上數(shù)據(jù)加載的 dataloader 進(jìn)程吃 CPUGPUNVIDIARTX 系列推薦顯存 8GB 起640 輸入、batch 16 大約吃 6~7GB 顯存內(nèi)存至少 8GB16GB數(shù)據(jù)集緩存、多進(jìn)程讀圖、Matplotlib 畫(huà)圖存儲(chǔ)至少 10GB50GB數(shù)據(jù)集解壓 多份權(quán)重 訓(xùn)練日志顯卡算力這一條得單獨(dú)說(shuō)。PyTorch 新版輪子對(duì)老架構(gòu)的支持是在收緊的算力過(guò)低的卡裝上之后torch.cuda.is_available()能返回 True但一跑卷積就報(bào) no kernel image is available for execution on the device。筆記本上的低功耗獨(dú)顯跑推理勉強(qiáng)能用訓(xùn)練基本別指望。另外只有 CPU 的機(jī)器并不是完全不能跑只是實(shí)時(shí)檢測(cè)會(huì)退化成準(zhǔn)實(shí)時(shí)——640 輸入下單幀推理幾百毫秒到一秒都有可能攝像頭預(yù)覽會(huì)明顯滯后。跑之前先確認(rèn)卡被系統(tǒng)認(rèn)出來(lái)了# 查看顯卡型號(hào)、顯存、驅(qū)動(dòng)版本右上角那行 CUDA Version 后面會(huì)用到 nvidia-smi這條命令輸出的右上角有一個(gè)CUDA Version: xx.x很多人誤以為那是本機(jī)裝的 CUDA 版本其實(shí)它是當(dāng)前驅(qū)動(dòng)最高能支持的 runtime 版本。這個(gè)數(shù)字決定了你后面能選哪一檔 PyTorch 輪子記住它。2.2 Python 3.7 到 3.10為什么我一般不裝最新版案例寫(xiě)的是 Python 3.7 及以上。3.7 這個(gè)版本現(xiàn)在已經(jīng)停止維護(hù)很多科學(xué)計(jì)算包早就不發(fā) cp37 的輪子了pip 會(huì)退回到源碼編譯在 Windows 上十有八九編譯不過(guò)。3.11、3.12 這類較新的版本也會(huì)撞上同一個(gè)問(wèn)題——包的最新版未必提供了對(duì)應(yīng) ABI 的預(yù)編譯輪子。我一般鎖 3.9 或 3.10這兩個(gè)版本在 PyTorch、OpenCV、NumPy 這一圈里輪子最全出了問(wèn)題也最好搜到現(xiàn)成的解決方案。先看本機(jī)裝了什么# Windows 上用 python --versionLinux/macOS 上區(qū)分 python 和 python3 python --version python3 -V # 機(jī)器上同時(shí)裝了多個(gè)解釋器時(shí)確認(rèn)當(dāng)前 shell 指向哪一個(gè) which python # Windows: where python如果輸出是 3.11 以上別急著卸用 conda 單獨(dú)建一個(gè) 3.10 的環(huán)境就行系統(tǒng) Python 不要?jiǎng)印芏嘞到y(tǒng)工具依賴它刪了會(huì)連帶把包管理器搞壞這是我早年踩過(guò)的血淚教訓(xùn)。2.3 驅(qū)動(dòng)、CUDA Toolkit 與 PyTorch 自帶 cu 庫(kù)別混為一談新手最容易在這里繞圈看到教程說(shuō)要裝 CUDA就去官網(wǎng)下了幾個(gè) G 的 CUDA Toolkit裝完發(fā)現(xiàn) PyTorch 還是認(rèn)不到 GPU。原因在于這是三個(gè)不同的東西顯卡驅(qū)動(dòng)操作系統(tǒng)層面的nvidia-smi能出結(jié)果就說(shuō)明驅(qū)動(dòng)正常。CUDA Toolkit包含 nvcc 編譯器只有你要自己編譯 CUDA 算子、或者跑某些需要現(xiàn)場(chǎng)編譯的擴(kuò)展時(shí)才需要。PyTorch 輪子自帶的 CUDA runtime 和 cuDNNpip 裝cu118、cu121這類標(biāo)簽的包時(shí)運(yùn)行庫(kù)是打包在輪子里的跟系統(tǒng)有沒(méi)有裝 Toolkit 無(wú)關(guān)。所以常規(guī)路線是只裝驅(qū)動(dòng)不裝 Toolkit直接裝對(duì)應(yīng) cu 標(biāo)簽的 PyTorch。案例給的cu113屬于比較早的檔位現(xiàn)在這個(gè)檔位對(duì)應(yīng)的輪子版本已經(jīng)很老硬裝要么找不到包要么被降級(jí)到一個(gè)跟新版代碼不兼容的 torch。驅(qū)動(dòng)版本和可選輪子標(biāo)簽大致這樣對(duì)應(yīng)具體以 PyTorch 官方安裝矩陣為準(zhǔn)nvidia-smi右上角顯示我一般選的輪子標(biāo)簽11.8cu11812.1cu118 或 cu12112.4 及以上cu121 或更高檔位高版本驅(qū)動(dòng)向下兼容低檔位輪子反過(guò)來(lái)不行。所以驅(qū)動(dòng)新、輪子選低一檔是最省事的組合。還有一條容易忽略的Windows 上原生環(huán)境和 WSL2 是兩套驅(qū)動(dòng)棧WSL2 里能跑不代表原生 Windows 能跑反之亦然。團(tuán)隊(duì)協(xié)作時(shí)把這一條寫(xiě)進(jìn)環(huán)境說(shuō)明能省掉一半我這兒明明能跑的爭(zhēng)論。3. 按順序裝conda 環(huán)境、PyTorch 輪子與推理依賴對(duì)完表就可以動(dòng)手了。這一章的四步是有嚴(yán)格順序的尤其是第二步和第三步順序反了會(huì)出現(xiàn) pip 把已經(jīng)裝好的 torch 覆蓋成 CPU 版這種玄學(xué)問(wèn)題。3.1 建一個(gè)不污染系統(tǒng)的 conda 環(huán)境# 創(chuàng)建名為 yolov10 的環(huán)境顯式指定 Python 小版本 conda create -n yolov10 python3.10 -y # 激活環(huán)境 conda activate yolov10 # 確認(rèn) pip 和 python 指向同一個(gè)解釋器 python -c import sys; print(sys.executable) pip -V三條命令里最關(guān)鍵是最后那條sys.executable。它會(huì)打印當(dāng)前 python 的絕對(duì)路徑正常應(yīng)該落在.../envs/yolov10/python.exe或.../envs/yolov10/bin/python下面。如果打印出來(lái)的是系統(tǒng)路徑說(shuō)明 conda 沒(méi)激活成功后面 pip 裝的東西全都會(huì)進(jìn)系統(tǒng)環(huán)境等你發(fā)現(xiàn)問(wèn)題時(shí)已經(jīng)污染了一片。pip -V的輸出里也會(huì)帶一個(gè)路徑它必須和sys.executable的目錄一致。這兩個(gè)對(duì)不上就是pip 裝完 import 還是找不到的根源。激活環(huán)境后建議每次新開(kāi)終端都先跑一遍conda activate yolov10別嫌煩省下的排錯(cuò)時(shí)間遠(yuǎn)超這幾秒。3.2 選對(duì) PyTorch 安裝源--index-url和--extra-index-url差一個(gè)字結(jié)果差很多案例給的命令是pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113 pip install opencv-python matplotlib這條命令本身能跑通但有兩個(gè)問(wèn)題。第一cu113檔位現(xiàn)在對(duì)應(yīng)的 torch 版本很老第二--extra-index-url是額外源pip 會(huì)同時(shí)去 PyPI 找同名包而 PyPI 上的torch默認(rèn)是 CPU 版。兩個(gè)源里版本號(hào)相同時(shí)pip 不一定選哪個(gè)結(jié)果就是你以為裝了 GPU 版實(shí)際裝的是 CPU 版。按第 2 章確認(rèn)的檔位改成--index-url讓它只認(rèn)這一個(gè)源# 有 NVIDIA 顯卡按 nvidia-smi 顯示的檔位選這里以 cu121 為例 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 # 沒(méi)有獨(dú)顯、或者只想先跑通 CPU 推理 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu參數(shù)說(shuō)明--index-url是替換默認(rèn)源--extra-index-url是追加源。裝 PyTorch 這類有 CPU/GPU 同名包的情況我一律用前者。裝完立刻驗(yàn)證別等到跑訓(xùn)練時(shí)才發(fā)現(xiàn)# 期望輸出 True 和顯卡數(shù)量False 就回第 2 章查驅(qū)動(dòng) python -c import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.device_count()) # 打印當(dāng)前顯卡名確認(rèn)是獨(dú)顯不是核顯 python -c import torch; print(torch.cuda.get_device_name(0))如果版本號(hào)里帶cpu后綴說(shuō)明裝錯(cuò)了先pip uninstall torch torchvision torchaudio -y再按上面重裝。這個(gè)后綴是最直觀的判斷依據(jù)。3.3 補(bǔ)齊 OpenCV、Matplotlib 與標(biāo)注工具# 圖像讀寫(xiě)、畫(huà)框、攝像頭取流 pip install opencv-python # 訓(xùn)練曲線和評(píng)估結(jié)果可視化 pip install matplotlib # 數(shù)據(jù)加載與配置解析訓(xùn)練腳本會(huì)直接依賴 pip install numpy pyyaml tqdm pillow # 數(shù)據(jù)集標(biāo)注工具只在本地標(biāo)注時(shí)用得到 pip install labelImgopencv-python和opencv-contrib-python不要同時(shí)裝兩者提供同名模塊后裝的會(huì)覆蓋先裝的import 時(shí)表現(xiàn)得很隨機(jī)。需要 SIFT 這類擴(kuò)展算法才裝 contrib 版普通的讀幀畫(huà)框用基礎(chǔ)版足夠。另外如果這套環(huán)境裝在沒(méi)有圖形界面的服務(wù)器上把opencv-python換成opencv-python-headless。帶 GUI 的版本依賴一堆 X11 庫(kù)在容器里 import 會(huì)直接報(bào)libGL.so.1: cannot open shared object file換成 headless 版就沒(méi)事。代價(jià)是不能用cv2.imshow得改成寫(xiě)視頻文件或者推流。labelImg在部分系統(tǒng)上還需要額外的 Qt 依賴裝不上不影響主流程標(biāo)注可以換到有桌面的機(jī)器上做標(biāo)注結(jié)果是一堆 txt拷過(guò)來(lái)就能用。3.4 三條自檢命令把環(huán)境黑匣子打開(kāi)裝完先別急著拉代碼跑這三條# 1. torch 能不能看見(jiàn) GPUcuDNN 版本是否正常 python -c import torch; print(torch, torch.__version__); print(cuda, torch.cuda.is_available()); print(cudnn, torch.backends.cudnn.version()) # 2. OpenCV 能不能正常 import版本是多少 python -c import cv2; print(cv2, cv2.__version__) # 3. 做一次真實(shí)的小矩陣運(yùn)算確認(rèn) kernel 能跑起來(lái) python -c import torch; atorch.randn(1000,1000,devicecuda); print((aa).sum().item())第一條期望看到cuda True和一個(gè) cuDNN 版本號(hào)。第二條只要打印出版本就行報(bào) DLL 或 so 找不到就回 3.3 節(jié)看是不是裝成了帶 GUI 的版本。第三條最關(guān)鍵——它不只是檢查is_available()而是真的在 GPU 上跑了一次矩陣乘法前面提到的 no kernel image 那種情況只有到這一步才會(huì)暴露。三條都過(guò)了環(huán)境這關(guān)就算過(guò)了。后面再出問(wèn)題基本都能定位在代碼、數(shù)據(jù)或權(quán)限上范圍一下縮小很多。4. 拉代碼、放權(quán)重、改配置從倉(cāng)庫(kù)到攝像頭畫(huà)面的完整鏈路環(huán)境通了接下來(lái)是把倉(cāng)庫(kù)、權(quán)重、配置、腳本這條鏈路接上。這一章按先能推理再能訓(xùn)練的順序排反過(guò)來(lái)的話你會(huì)在還沒(méi)驗(yàn)證模型能不能加載的情況下花幾小時(shí)排查訓(xùn)練不收斂。4.1 克隆倉(cāng)庫(kù)后的目錄結(jié)構(gòu)與權(quán)重落位# 倉(cāng)庫(kù)地址用項(xiàng)目方給出的實(shí)際地址替換這里用占位符 git clone repo-url yolo-v10 cd yolo-v10 # 看目錄結(jié)構(gòu)確認(rèn)入口腳本和配置文件都在 ls -la一個(gè)典型的目錄長(zhǎng)這樣不同分支會(huì)有出入但核心幾項(xiàng)跑不掉yolo-v10/ ├── train.py # 訓(xùn)練入口 ├── evaluate.py # 評(píng)估入口 ├── configs/ # 模型與訓(xùn)練配置 │ └── yolov10.yaml ├── weights/ # 權(quán)重文件存放位置 │ └── yolov10.pt └── data/ # 數(shù)據(jù)集配置 └── data.yaml權(quán)重文件yolov10.pt從項(xiàng)目方發(fā)布的地址下載后放在倉(cāng)庫(kù)根目錄或weights/下都行關(guān)鍵是訓(xùn)練和推理命令里的--weights路徑要跟實(shí)際位置對(duì)上。相對(duì)路徑是相對(duì)當(dāng)前工作目錄算的不是相對(duì)腳本位置所以在yolo-v10/外面執(zhí)行python yolo-v10/train.py --weights yolov10.pt會(huì)找不到文件。這類報(bào)錯(cuò)信息往往是FileNotFoundError看著像權(quán)重沒(méi)下下來(lái)實(shí)際是路徑錯(cuò)了。下載完建議校驗(yàn)一下文件完整性權(quán)重文件不完整時(shí)加載會(huì)報(bào) unpickling 相關(guān)的錯(cuò)# 看文件大小是否合理幾百 MB 量級(jí) ls -lh weights/yolov10.pt # 有官方校驗(yàn)值時(shí)對(duì)一下 sha256sum weights/yolov10.pt4.2 訓(xùn)練參數(shù)與數(shù)據(jù)集 YAML每一項(xiàng)改動(dòng)都會(huì)在顯存和收斂上體現(xiàn)案例給的配置是這樣的# config.yaml batch_size: 16 learning_rate: 0.001 epochs: 50 img_size: 640四個(gè)參數(shù)逐項(xiàng)過(guò)一遍這些是我實(shí)際會(huì)調(diào)整的地方batch_size直接決定顯存占用幾乎線性相關(guān)。640 輸入、8GB 顯存下16 是能跑但不寬裕的值顯存緊張的機(jī)器降到 8 甚至 4配合梯度累積補(bǔ)回等效批量。這個(gè)值設(shè)大了第一個(gè) epoch 就 OOM設(shè)太小BN 層統(tǒng)計(jì)不準(zhǔn)mAP 會(huì)掉得莫名其妙。learning_rate取 0.001 偏保守。YOLO 系列在 SGD 優(yōu)化器下常用 0.01 起步配合 warmup0.001 更適合 Adam 系。案例沒(méi)指定優(yōu)化器我一般先按默認(rèn)跑看前幾個(gè) epoch 的 loss 曲線——loss 劇烈震蕩就往下調(diào)一個(gè)數(shù)量級(jí)loss 幾乎不動(dòng)就往上調(diào)。epochs設(shè) 50 在自建小數(shù)據(jù)集上很容易過(guò)擬合。我一般把值設(shè)大一點(diǎn)比如 200靠驗(yàn)證集 mAP 連續(xù)若干輪不漲就早停比拍腦袋定 50 靠譜。img_size必須是 32 的整數(shù)倍因?yàn)橹鞲删W(wǎng)絡(luò)的下采樣倍率決定了這個(gè)約束。填 600 會(huì)直接報(bào)錯(cuò)或者被靜默取整訓(xùn)練出來(lái)的模型輸入尺寸和你以為的不一致。640 是通用值小目標(biāo)多的話可以提到 1280但顯存占用漲得很快。數(shù)據(jù)集的data.yaml是另一個(gè)必改文件# data.yaml path: ../datasets/mydata # 數(shù)據(jù)集根目錄相對(duì)本文件或絕對(duì)路徑 train: images/train # 訓(xùn)練圖目錄相對(duì) path val: images/val # 驗(yàn)證圖目錄 nc: 3 # 類別數(shù) names: [person, car, bike] # 類別名順序即 class idnc和names必須和標(biāo)注文件里的 class id 嚴(yán)格對(duì)應(yīng)。標(biāo)注時(shí) person 是 0、car 是 1這里寫(xiě)成[car, person, bike]模型能訓(xùn)練、loss 也能降但預(yù)測(cè)出來(lái)的框全貼著錯(cuò)誤的標(biāo)簽——這種錯(cuò)誤在指標(biāo)上幾乎看不出來(lái)只有肉眼看檢測(cè)結(jié)果才會(huì)發(fā)現(xiàn)屬于最陰的一類。數(shù)據(jù)集目錄結(jié)構(gòu)也要對(duì)齊 YOLO 格式mydata/ ├── images/ │ ├── train/ xxx.jpg │ └── val/ yyy.jpg └── labels/ ├── train/ xxx.txt # 與圖片同名每行 類別 中心x 中心y 寬 高全部歸一化到 0~1 └── val/ yyy.txt標(biāo)完先抽查幾個(gè) txt確認(rèn)坐標(biāo)都在 0 到 1 之間。有工具導(dǎo)出的格式是絕對(duì)像素坐標(biāo)混進(jìn)去訓(xùn)練時(shí)不會(huì)報(bào)錯(cuò)只是 loss 收斂得特別慢、框到處都是。4.3 訓(xùn)練命令與日志從 loss 曲線判斷是不是白跑# 單卡訓(xùn)練--device 0 指定第一張卡 python train.py \ --data data.yaml \ --cfg yolov10.yaml \ --weights yolov10.pt \ --img-size 640 \ --batch 16 \ --epochs 50 \ --device 0參數(shù)含義--data指向數(shù)據(jù)集配置--cfg是模型結(jié)構(gòu)配置--weights是預(yù)訓(xùn)練權(quán)重從它開(kāi)始微調(diào)比從零訓(xùn)練收斂快得多--img-size與配置文件里的值保持一致--device填0表示第一張卡填cpu強(qiáng)制走 CPU排錯(cuò)時(shí)用得上。啟動(dòng)后頭幾十行輸出要重點(diǎn)看三件事模型參數(shù)量和 GFLOPs、數(shù)據(jù)集里訓(xùn)練集和驗(yàn)證集的圖片數(shù)量、顯存占用。圖片數(shù)量對(duì)不上多半是data.yaml里的相對(duì)路徑?jīng)]解析對(duì)訓(xùn)練會(huì)拿空數(shù)據(jù)集跑loss 一直是常數(shù)。顯存占用接近上限的話趁早把 batch 降下來(lái)不然跑到一半 OOM 更難受。訓(xùn)練日志和權(quán)重一般落在runs/目錄下每輪一個(gè)子目錄里面有權(quán)重、results.csv、混淆矩陣和 PR 曲線。results.csv是后面畫(huà)曲線的數(shù)據(jù)源別急著刪。4.4 實(shí)時(shí)檢測(cè)腳本從 VideoCapture 到畫(huà)框的每一行案例給的推理腳本是這樣的我加了注釋import cv2 import torch # 加載模型。torch.hub.load 要求倉(cāng)庫(kù)里有 hubconf.py # 沒(méi)有的話會(huì)報(bào)找不到 entrypoint改用倉(cāng)庫(kù)自己的推理入口或直接加載本地權(quán)重 model torch.hub.load(yourusername/yolo-v10, yolov10, pretrainedTrue) model.eval() # 切推理模式關(guān)掉 dropout、固定 BN 統(tǒng)計(jì)量 cap cv2.VideoCapture(0) # 0 是默認(rèn)攝像頭外接 USB 相機(jī)通常從 1、2 開(kāi)始 while True: ret, frame cap.read() if not ret: break # 讀幀失敗可能是設(shè)備被占用或已斷開(kāi) # 預(yù)處理縮放、歸一化、HWC 轉(zhuǎn) CHW、補(bǔ) batch 維 img cv2.resize(frame, (640, 640)) img img / 255.0 img img.transpose((2, 0, 1)) img torch.tensor(img).float().unsqueeze(0) # 推理 results model(img) # 逐框繪制坐標(biāo)、置信度、類別 for *box, conf, cls in results.xyxy[0]: label f{model.names[int(cls)]} {conf:.2f} cv2.rectangle(frame, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (255, 0, 0), 2) cv2.putText(frame, label, (int(box[0]), int(box[1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2) cv2.imshow(YOLOv10 Detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()幾個(gè)需要說(shuō)明的點(diǎn)。cap.read()返回的ret必須判斷攝像頭被其他程序占用、或者設(shè)備號(hào)填錯(cuò)時(shí)它返回 False不判斷就會(huì)在cv2.resize那里報(bào) NoneType 錯(cuò)誤看著像 resize 的問(wèn)題實(shí)際是取流失敗。img / 255.0之后再transpose和unsqueeze這個(gè)順序不能亂轉(zhuǎn)成 tensor 之后再除也能跑但會(huì)多一次顯存拷貝。推理前記得把張量搬到 GPU 上img img.cuda()否則模型在 GPU、輸入在 CPU會(huì)直接報(bào)設(shè)備不一致。results.xyxy[0]這種取法依賴具體返回結(jié)構(gòu)。不同版本的推理入口返回的可能是 tensor、可能是帶.boxes屬性的對(duì)象硬套一種寫(xiě)法換個(gè)權(quán)重就崩。我一般的做法是先在單張圖上跑一次print(type(results))和print(results)看清楚結(jié)構(gòu)再寫(xiě)解析代碼比照著教程猜快得多。5. 避坑排查五個(gè)裝完跑不起來(lái)的現(xiàn)場(chǎng)這一章全是真實(shí)現(xiàn)場(chǎng)。每條按現(xiàn)象、原因、解決寫(xiě)遇到對(duì)應(yīng)的直接對(duì)號(hào)入座。5.1 現(xiàn)象torch.cuda.is_available()返回 False原因按概率從高到低排——裝成了 CPU 版輪子版本號(hào)帶cpu顯卡驅(qū)動(dòng)太老支持的 CUDA 檔位低于輪子要求--extra-index-url導(dǎo)致 pip 從 PyPI 拿了 CPU 版在 WSL2 里裝的原生 Windows 版 Python兩套驅(qū)動(dòng)棧對(duì)不上。解決先看版本號(hào)后綴帶cpu就卸了按 3.2 節(jié)用--index-url重裝再看nvidia-smi是否正常輸出報(bào)NVIDIA-SMI has failed說(shuō)明驅(qū)動(dòng)本身有問(wèn)題先修驅(qū)動(dòng)驅(qū)動(dòng)正常但檔位太低就換低一檔的輪子標(biāo)簽。三步走完還是 False跑python -c import torch; print(torch.version.cuda)輸出為 None 就是 CPU 版無(wú)疑。5.2 現(xiàn)象訓(xùn)練第一個(gè) epoch 就CUDA out of memory原因batch_size 相對(duì)顯存設(shè)大了img_size提到了 1280 但沒(méi)同步降 batch同一張卡上還跑著別的進(jìn)程沒(méi)退干凈數(shù)據(jù)加載的 worker 數(shù)太多每個(gè)進(jìn)程都占一份顯存上下文。解決先把 batch 減半試能跑就把--batch 8寫(xiě)進(jìn)命令確認(rèn)沒(méi)有僵尸進(jìn)程nvidia-smi看顯存占用有殘留就kill -9掉對(duì)應(yīng) PID還是不夠就把img_size降到 512 或 416。另外有個(gè)容易忽略的點(diǎn)PyTorch 的緩存分配器不會(huì)立刻把顯存還給系統(tǒng)報(bào) OOM 時(shí)實(shí)際空閑顯存可能比nvidia-smi顯示的更多遇到明明還有顯存卻 OOM的情況在訓(xùn)練腳本開(kāi)頭加torch.cuda.empty_cache()試一次。5.3 現(xiàn)象cap.read()一直返回 False或者畫(huà)面卡住不動(dòng)原因設(shè)備號(hào)不對(duì)筆記本內(nèi)置攝像頭是 0外接 USB 相機(jī)往往是 1 或 2攝像頭被瀏覽器、會(huì)議軟件占用Linux 下當(dāng)前用戶沒(méi)有/dev/video*的讀權(quán)限imshow后沒(méi)有waitKey導(dǎo)致窗口不刷新。解決寫(xiě)個(gè)小循環(huán)遍歷設(shè)備號(hào)看哪個(gè)能讀出幀# 逐個(gè)試設(shè)備號(hào)能讀到幀就打印分辨率并退出 python -c import cv2 for i in range(5): cap cv2.VideoCapture(i) ok, frame cap.read() print(i, ok, frame.shape if ok else None) cap.release() 有輸出 True 的那個(gè)編號(hào)就是該用的。全是 False 就檢查是不是被其他程序占用Linux 下把用戶加進(jìn)video組再重新登錄。5.4 現(xiàn)象檢測(cè)框位置整體偏移或者標(biāo)簽全是錯(cuò)的類別原因預(yù)處理里做了 resize 但畫(huà)框時(shí)用的是原圖坐標(biāo)兩套坐標(biāo)系沒(méi)對(duì)齊data.yaml里的names順序和標(biāo)注時(shí)的 class id 不一致標(biāo)注文件里混進(jìn)了絕對(duì)像素坐標(biāo)沒(méi)歸一化。解決畫(huà)框前把檢測(cè)結(jié)果按原圖寬/640、原圖高/640縮放回原圖坐標(biāo)系names列表順序逐項(xiàng)核對(duì)標(biāo)注工具的類別配置抽查幾個(gè) label txt確認(rèn)所有數(shù)值都落在 0~1 之間超了就是沒(méi)歸一化用腳本批量除以圖片寬高修正。5.5 現(xiàn)象import cv2或import torch報(bào) DLL load failed原因Windows 上缺 Visual C 運(yùn)行庫(kù)裝的是帶 GUI 的 OpenCV 但系統(tǒng)沒(méi)有圖形環(huán)境conda 環(huán)境里混進(jìn)了系統(tǒng) site-packages 的同名包多個(gè) Python 版本共存pip 和 python 指向不同解釋器。解決先跑 3.1 節(jié)的sys.executable和pip -V對(duì)比路徑服務(wù)器或容器環(huán)境換成opencv-python-headlessWindows 裝一遍微軟的 VC 運(yùn)行庫(kù)還不行就pip list | grep -i opencvWindows 用findstr看是不是裝了多個(gè) OpenCV 包卸到只剩一個(gè)。6. 裝完怎么驗(yàn)證三條命令加一張對(duì)照表把系統(tǒng)跑通環(huán)境裝完不等于系統(tǒng)跑通。我習(xí)慣按能加載、能推理、能實(shí)時(shí)、指標(biāo)對(duì)得上四層往下驗(yàn)每層都有明確的通過(guò)標(biāo)準(zhǔn)任何一層不過(guò)就停在那兒查不往下走。第一層環(huán)境自檢就是 3.4 節(jié)那三條命令重點(diǎn)看 GPU 上真實(shí)跑一次矩陣乘法的結(jié)果。第二層單圖推理。拿一張確認(rèn)有目標(biāo)的圖跑一次前向看輸出的框數(shù)量和置信度是否合理# 單圖推理--conf 過(guò)濾低置信度框先看 0.25 以上的 python detect.py --weights weights/yolov10.pt --source test.jpg --img-size 640 --conf 0.25如果一張明顯有人的圖輸出零個(gè)框先別懷疑模型把--conf降到 0.05 再看——有框但置信度低說(shuō)明預(yù)處理或類別映射有問(wèn)題依然零框說(shuō)明權(quán)重沒(méi)加載成功。第三層實(shí)時(shí)鏈路。跑攝像頭并統(tǒng)計(jì)幀率只統(tǒng)計(jì)推理加繪制的耗時(shí)不含imshow的等待# 跑 100 幀輸出平均單幀耗時(shí)和等效 FPS import time, cv2, torch cap cv2.VideoCapture(0) times [] for i in range(100): ok, frame cap.read() if not ok: break t0 time.time() # 這里放你的預(yù)處理 model(frame) 畫(huà)框 times.append(time.time() - t0) cap.release() avg sum(times) / len(times) print(favg {avg*1000:.1f} ms, fps {1/avg:.1f})第四層指標(biāo)復(fù)核。用驗(yàn)證集跑一遍評(píng)估把 mAP 和訓(xùn)練日志最后一輪的數(shù)值對(duì)一下python evaluate.py --weights best.pt --data data.yaml驗(yàn)證項(xiàng)命令或動(dòng)作通過(guò)標(biāo)準(zhǔn)不通過(guò)先查GPU 可用torch.cuda.is_available()返回 True 且版本號(hào)無(wú)cpu3.2 節(jié)輪子源單圖推理detect.py --source test.jpg框數(shù)量與肉眼一致--conf閾值、權(quán)重路徑實(shí)時(shí)幀率100 幀計(jì)時(shí)腳本單幀耗時(shí)穩(wěn)定無(wú)逐幀遞增是否每幀都在建 tensor、有沒(méi)有torch.no_grad()評(píng)估指標(biāo)evaluate.py --weights best.ptmAP 與訓(xùn)練日志末輪接近data.yaml的 val 路徑幀率那一項(xiàng)有個(gè)隱蔽的坑推理時(shí)忘了套torch.no_grad()每幀都會(huì)建計(jì)算圖顯存一路漲前幾十幀看著正常后面越來(lái)越慢直到 OOM??吹胶臅r(shí)逐幀遞增先補(bǔ)這一句。從那以后我每次裝完這套環(huán)境不管多急都會(huì)把單圖推理和 100 幀計(jì)時(shí)這兩步強(qiáng)制走一遍——玄學(xué)問(wèn)題大多在這兩步就現(xiàn)原形了等接上業(yè)務(wù)代碼再發(fā)現(xiàn)排查成本要翻好幾倍。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取