現(xiàn)超市商品識(shí)別:從選型到落地的完整方案)
簡(jiǎn)介目標(biāo)檢測(cè)是計(jì)算機(jī)視覺(jué)的核心任務(wù)旨在從圖像中定位并分類多個(gè)物體。YOLO作為單階段檢測(cè)算法的代表一次前向傳播即可完成定位與分類憑借實(shí)時(shí)性和對(duì)小目標(biāo)的良好支持在工業(yè)場(chǎng)景中得到廣泛應(yīng)用。結(jié)合Python生態(tài)中的PyQt5與OpenCV能夠快速搭建具備圖形界面的桌面視覺(jué)應(yīng)用支持圖片、攝像頭和RTSP視頻流等多種輸入源?;谶@一技術(shù)組合可以實(shí)現(xiàn)超市商品識(shí)別、自助結(jié)算等實(shí)用系統(tǒng)解決傳統(tǒng)圖像處理在光照變化和相似外觀區(qū)分上的痛點(diǎn)。本文從技術(shù)選型、環(huán)境配置到核心代碼實(shí)現(xiàn)完整梳理了一套可落地的工程方案為開發(fā)者提供從模型訓(xùn)練到界面交互的實(shí)戰(zhàn)參考。 做超市商品識(shí)別這個(gè)項(xiàng)目說(shuō)實(shí)話最開始我是被朋友拉去救場(chǎng)的。他們想做一個(gè)自助結(jié)算的原型機(jī)找的幾家外包報(bào)價(jià)離譜而且交付的模型在真實(shí)貨架上一測(cè)就露餡小瓶飲料和洗發(fā)水經(jīng)常分不清。后來(lái)這個(gè)項(xiàng)目落到我手上我用YOLOPyQt5重新搭了一套圖片、攝像頭、RTSP視頻流都能跑界面也做成了帶操作按鈕的桌面程序客戶看了演示直接拍板。這篇文章就把整套方案的選型思路、核心代碼、踩坑記錄都攤開講給想快速落地同類項(xiàng)目的朋友一個(gè)參考。文章適合有Python基礎(chǔ)、想了解YOLO實(shí)際工程落地或正在做桌面端視覺(jué)應(yīng)用的開發(fā)者新手也能跟著環(huán)境配置部分一步步跑起來(lái)。1. 項(xiàng)目整體設(shè)計(jì)與技術(shù)選型思路1.1 為什么超市商品識(shí)別選擇了YOLO方案超市商品識(shí)別這個(gè)場(chǎng)景第一眼看過(guò)去好像不難但真正做起來(lái)全是坑。貨架上的商品密集擺放同類商品不同口味的外包裝高度相似光照變化大還有反光、遮擋、變形的問(wèn)題。最典型的例子就是可樂(lè)和雪碧瓶身形狀一樣顏色一深一淺傳統(tǒng)圖像處理靠顏色直方圖去區(qū)分換個(gè)燈光環(huán)境就廢了。更別說(shuō)同一個(gè)商品換個(gè)角度、被其他商品擋住一半模板匹配類算法基本全部失效。YOLO屬于單階段目標(biāo)檢測(cè)算法一次前向傳播同時(shí)完成目標(biāo)定位和分類速度快特別適合超市這種需要實(shí)時(shí)反饋的場(chǎng)景。而且YOLO家族發(fā)展到現(xiàn)在對(duì)小目標(biāo)的檢測(cè)能力已經(jīng)有了很大提升密集排列的飲料瓶、牙膏盒都不在話下。對(duì)比一下傳統(tǒng)方案和YOLO方案的差異就很清楚了對(duì)比維度傳統(tǒng)圖像處理方案YOLO方案目標(biāo)定位需要手寫特征滑動(dòng)窗口復(fù)雜度高端到端回歸邊界框天然支持多目標(biāo)光照魯棒性對(duì)光照極其敏感需大量預(yù)處理CNN特征對(duì)光照變化有較強(qiáng)適應(yīng)力相似外觀區(qū)分紋理、顏色特征區(qū)分度不足可學(xué)習(xí)深層語(yǔ)義特征區(qū)分相似SKU實(shí)時(shí)性能多階段串行處理慢單階段GPU加速輕松跑實(shí)時(shí)新增品類每增加一類都要重新設(shè)計(jì)特征只需補(bǔ)充標(biāo)注數(shù)據(jù)重新訓(xùn)練即可人工設(shè)計(jì)特征的時(shí)代已經(jīng)過(guò)去了讓模型自己從數(shù)據(jù)里學(xué)特征才是正解。YOLO在準(zhǔn)確率和召回率之間能做到很好的平衡推理速度也夠用是商品識(shí)別這類落地項(xiàng)目最穩(wěn)妥的算法底座。1.2 技術(shù)棧選型的真實(shí)考量整套系統(tǒng)我選的是PythonYOLOPyQt5OpenCV的組合這個(gè)組合不是隨便湊的每一步都有明確考量。Python作為主語(yǔ)言是因?yàn)檎麄€(gè)深度學(xué)習(xí)生態(tài)它的支持最好。Ultralytics官方Y(jié)OLO包、PyTorch推理框架、OpenCV圖像處理庫(kù)全是Python優(yōu)先支持算法驗(yàn)證和工程落地的效率都很高。雖然Python在GUI性能和啟動(dòng)速度上不如C但對(duì)于商用原型機(jī)和中小型項(xiàng)目來(lái)說(shuō)開發(fā)效率的收益遠(yuǎn)大于這點(diǎn)性能損耗。YOLO模型選用Ultralytics YOLOv8。需要注意一點(diǎn)YOLOv8的Ultralytics版本使用的是AGPL-3.0協(xié)議如果是商用項(xiàng)目要么購(gòu)買企業(yè)授權(quán)要么就要認(rèn)真評(píng)估合規(guī)風(fēng)險(xiǎn)。這個(gè)我在后面專門說(shuō)。模型本身支持圖片、視頻流、攝像頭等多種輸入源一鍵推理接口封裝得很好對(duì)快速交付非常友好。PyQt5做桌面界面看中的是它的成熟穩(wěn)定和控件豐富程度。Qlabel顯示圖像、QPushButton綁定操作、QThread處理多線程這套組合在工業(yè)視覺(jué)項(xiàng)目里已經(jīng)被驗(yàn)證過(guò)無(wú)數(shù)次了。相比PySide6PyQt5的文檔和踩坑案例更多遇到問(wèn)題基本都能搜到解決方案。OpenCV負(fù)責(zé)視頻流的讀取和圖像預(yù)處理。VideoCapture配合多線程可以穩(wěn)定拉取USB攝像頭和RTSP網(wǎng)絡(luò)攝像頭的數(shù)據(jù)流轉(zhuǎn)成YOLO輸入格式也方便。整個(gè)鏈路從圖像采集到結(jié)果展示用這四個(gè)開源組件就能完整覆蓋。1.3 商用落地前必須想清楚的三件事技術(shù)方案能跑通只是第一步真正商用落地還得想清楚三件事。第一件是模型訓(xùn)練數(shù)據(jù)從哪來(lái)。超市商品SKU動(dòng)輒幾千個(gè)每個(gè)品類的有效標(biāo)注樣本至少需要一兩百?gòu)堖@還不算同品類的不同包裝、不同批次。實(shí)際項(xiàng)目中我通常先用公開數(shù)據(jù)集把模型預(yù)訓(xùn)練到能用的程度再用真實(shí)貨架照片做遷移學(xué)習(xí)。拍照的時(shí)候要注意覆蓋不同光照、不同角度、不同擺放姿態(tài)最好讓客戶提供多門店的實(shí)拍素材這樣模型的泛化能力才有保障。第二件是推理硬件成本。用一個(gè)RTX 3060級(jí)別顯卡跑YOLOv8s模型一張圖大約20到40毫秒客戶現(xiàn)場(chǎng)如果是普通辦公電腦沒(méi)有獨(dú)立顯卡CPU推理就要慢很多。所以項(xiàng)目開始前一定要確認(rèn)客戶現(xiàn)場(chǎng)的硬件條件如果沒(méi)有GPU要么選YOLOv8n這種輕量模型要么建議客戶加一塊顯卡這兩者的方案差距很大事先不確認(rèn)清楚后面會(huì)很難辦。第三件事是后續(xù)維護(hù)責(zé)任邊界。商品包裝更新?lián)Q代是常態(tài)模型部署上線之后新增SKU、舊SKU下架誰(shuí)來(lái)負(fù)責(zé)數(shù)據(jù)更新和模型重訓(xùn)都要在合同里寫清楚。不然客戶每周都有新品上架全指望你免費(fèi)迭代這個(gè)項(xiàng)目就變成一個(gè)填不滿的坑了。2. 環(huán)境準(zhǔn)備與基礎(chǔ)依賴配置2.1 從零搭建Python虛擬環(huán)境不管你是Windows還是Linux機(jī)器第一步都是創(chuàng)建一個(gè)獨(dú)立的Python環(huán)境千萬(wàn)別直接往系統(tǒng)Python里裝一大堆包。我之前吃過(guò)虧系統(tǒng)Python環(huán)境被裝亂了連pip都用不了最后只能重裝系統(tǒng)那種痛苦希望你不要體驗(yàn)。# 創(chuàng)建虛擬環(huán)境 python -m venv venv_supermarket # 激活虛擬環(huán)境 # Windows venv_supermarket\Scripts\activate # Linux/Mac source venv_supermarket/bin/activate虛擬環(huán)境激活后再安裝依賴包。下面是我的requirements.txt版本都經(jīng)過(guò)實(shí)測(cè)驗(yàn)證直接用不會(huì)出問(wèn)題。ultralytics8.0.222 PyQt55.15.9 opencv-python4.8.1.78 numpy1.24.4 Pillow10.0.0 torch2.0.1 torchvision0.15.2提示PyTorch的安裝建議去PyTorch官網(wǎng)用CUDA匹配的命令安裝不要直接pip install torch。GPU版本的torch對(duì)顯存利用率和推理速度的影響非常大CPU版本在視頻流場(chǎng)景下很容易成為性能瓶頸。2.2 PyQt5安裝中的兩個(gè)高頻坑PyQt5的安裝本身不復(fù)雜但有兩個(gè)坑我每次遇到都有人問(wèn)。第一個(gè)坑是pip安裝速度極慢或者直接超時(shí)。PyQt5的wheel包體積不小幾十兆到上百兆都有國(guó)內(nèi)網(wǎng)絡(luò)直連官方PyPI源經(jīng)常下載到一半就斷了。解決方法是使用國(guó)內(nèi)鏡像源速度能提升十倍以上。pip install PyQt5 -i https://pypi.tuna.tsinghua.edu.cn/simple第二個(gè)坑是界面中文亂碼。PyQt5的默認(rèn)字體對(duì)中文支持不好界面上按鈕、標(biāo)簽一遇到中文就顯示成方塊。解決辦法是在程序啟動(dòng)時(shí)統(tǒng)一設(shè)置字體我習(xí)慣用微軟雅黑Windows和Linux的兼容性都還不錯(cuò)。from PyQt5.QtGui import QFont app.setFont(QFont(Microsoft YaHei, 9))2.3 商品識(shí)別模型權(quán)重與數(shù)據(jù)標(biāo)注準(zhǔn)備項(xiàng)目里我用的預(yù)訓(xùn)練權(quán)重是yolov8s.pt和yolov8n.pt分別應(yīng)對(duì)有GPU和無(wú)GPU兩種現(xiàn)場(chǎng)條件。如果你做的是全新品類的識(shí)別千萬(wàn)不要直接拿COCO預(yù)訓(xùn)練權(quán)重去識(shí)別你的商品COCO的80個(gè)類別里沒(méi)有洗發(fā)水、薯片、飲料這些具體SKU直接用的話什么都檢測(cè)不出來(lái)。必須要用你自己標(biāo)注的商品數(shù)據(jù)重新訓(xùn)練。數(shù)據(jù)標(biāo)注工具我用的是labelImg老牌穩(wěn)定支持YOLO格式的txt標(biāo)注文件直接導(dǎo)出。每張圖片的標(biāo)注文件格式是class_index x_center y_center width height其中x_center、y_center、width、height都是歸一化到0-1區(qū)間的比例值不是像素坐標(biāo)。訓(xùn)練完成后生成best.pt權(quán)重文件這個(gè)就是后面部署推理用的核心模型文件。標(biāo)注的時(shí)候有個(gè)細(xì)節(jié)一個(gè)商品如果被遮擋超過(guò)一半我通常還是會(huì)把沒(méi)被遮擋的部分標(biāo)出來(lái)但會(huì)打上低置信度的標(biāo)簽。這樣模型能學(xué)到部分遮擋情況下的特征。完全無(wú)法辨認(rèn)的目標(biāo)就不標(biāo)避免給模型傳遞錯(cuò)誤信息。3. 核心功能模塊實(shí)現(xiàn)與關(guān)鍵代碼拆解3.1 圖片檢測(cè)模塊從單張圖片開始驗(yàn)證效果圖片檢測(cè)是整套系統(tǒng)的基礎(chǔ)模塊也是效果驗(yàn)證最快的方式。模型拿到一張圖片輸出檢測(cè)框、類別和置信度畫框顯示出來(lái)。這個(gè)模塊的實(shí)現(xiàn)邏輯很清晰。from ultralytics import YOLO import cv2 # 加載訓(xùn)練好的模型 model YOLO(weights/best.pt) # 執(zhí)行推理 results model.predict( sourcetest_images/shelf_01.jpg, conf0.35, # 置信度閾值 iou0.45, # NMS IoU閾值 imgsz640, # 輸入尺寸 devicecuda:0, # 使用GPU無(wú)GPU則填cpu ) # 輸出檢測(cè)結(jié)果 for r in results: boxes r.boxes for box in boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) x1, y1, x2, y2 [int(p) for p in box.xyxy[0]] label f{model.names[cls_id]} {conf:.2f} print(f檢測(cè)到: {label}坐標(biāo): ({x1}, {y1}, {x2}, {y2}))這里有一個(gè)非常重要的參數(shù)conf置信度閾值。閾值設(shè)高了漏檢多貨架上密集擺放的商品容易漏掉后排的閾值設(shè)低了誤檢多相似的包裝很容易被認(rèn)錯(cuò)。我在實(shí)際項(xiàng)目中一般先用0.25跑一遍看效果再根據(jù)檢測(cè)結(jié)果逐步上調(diào)到0.35到0.4之間找到一個(gè)漏檢和誤檢平衡的點(diǎn)。如果單獨(dú)跑predict沒(méi)有出現(xiàn)任何報(bào)錯(cuò)但結(jié)果圖里什么都沒(méi)有先別懷疑代碼直接用下面這段代碼把推理后的圖像保存出來(lái)看看確認(rèn)模型是否真的輸出了空結(jié)果以及畫框是否成功。annotated_frame results[0].plot() cv2.imwrite(output/shelf_result.jpg, annotated_frame)3.2 視頻流檢測(cè)模塊本地視頻、攝像頭、RTSP全支持視頻流檢測(cè)是這套系統(tǒng)的重頭戲。商品識(shí)別如果只能看靜態(tài)圖片實(shí)用性會(huì)大打折扣接入攝像頭或者RTSP視頻流才能真正用于超市的實(shí)時(shí)監(jiān)控和自助結(jié)算場(chǎng)景。視頻流處理的關(guān)鍵在于逐幀讀取和多線程并發(fā)不然界面會(huì)卡成PPT。OpenCV的VideoCapture可以統(tǒng)一處理本地視頻文件、USB攝像頭和RTSP網(wǎng)絡(luò)流只是source參數(shù)不同# 本地視頻 cap cv2.VideoCapture(test_videos/checkout.mp4) # USB攝像頭 cap cv2.VideoCapture(0) # RTSP網(wǎng)絡(luò)攝像頭流 cap cv2.VideoCapture(rtsp://admin:password192.168.1.100:554/stream1)RTSP接入的關(guān)鍵在于協(xié)議參數(shù)很多攝像頭默認(rèn)配置用OpenCV打開會(huì)黑屏或者斷流。我一般會(huì)加上ffmpeg后端參數(shù)并且關(guān)掉緩沖來(lái)降低延遲cap cv2.VideoCapture( rtsp://admin:password192.168.1.100:554/stream1, cv2.CAP_FFMPEG ) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 降低緩沖減少延遲幀率控制也需要關(guān)注。如果攝像頭是30幀但模型推理只有10幀的處理能力直接逐幀推理會(huì)導(dǎo)致積壓和延遲越來(lái)越大。標(biāo)準(zhǔn)做法是設(shè)置一個(gè)目標(biāo)FPS通過(guò)控制幀間隔來(lái)做丟幀處理。target_fps 15 frame_interval int(1000 / target_fps) last_time cv2.getTickCount() while True: ret, frame cap.read() if not ret: break current_time cv2.getTickCount() elapsed (current_time - last_time) / cv2.getTickFrequency() * 1000 if elapsed frame_interval: results model.predict(frame, conf0.35, iou0.45, imgsz640) annotated results[0].plot() # 顯示或推送到界面 last_time current_time3.3 PyQt5桌面界面設(shè)計(jì)布局與交互邏輯PyQt5的界面我采用左右分欄布局左側(cè)是實(shí)時(shí)畫面顯示區(qū)右側(cè)是結(jié)果信息區(qū)。操作按鈕放在底部包括打開圖片、打開攝像頭、打開視頻流、停止檢測(cè)、保存截圖這幾個(gè)核心功能。核心控件三件套是QLabel顯示畫面、QPushButton操作按鈕、QTextEdit日志信息。界面代碼如下class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(超市商品識(shí)別系統(tǒng)) self.setMinimumSize(1280, 800) # 中間畫面顯示 self.video_label QLabel(self) self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setMinimumSize(960, 600) self.video_label.setStyleSheet(background-color: #1e1e1e; color: #ffffff;) # 右側(cè)信息面板 self.info_text QTextEdit(self) self.info_text.setReadOnly(True) # 按鈕區(qū) self.btn_image QPushButton(打開圖片) self.btn_camera QPushButton(打開攝像頭) self.btn_stream QPushButton(打開視頻流) self.btn_stop QPushButton(停止檢測(cè)) self.btn_stop.setEnabled(False) # 用布局管理器排列 layout self._build_layout() # ...省略布局細(xì)節(jié)界面設(shè)計(jì)有個(gè)值得強(qiáng)調(diào)的思路信號(hào)與槽機(jī)制是PyQt5的核心。按鈕點(diǎn)擊后觸發(fā)信號(hào)槽函數(shù)里啟動(dòng)相應(yīng)的檢測(cè)任務(wù)。這里有個(gè)關(guān)鍵點(diǎn)所有耗時(shí)操作不能在主線程里執(zhí)行否則界面會(huì)卡死。檢測(cè)任務(wù)要放到QThread工作線程里通過(guò)信號(hào)把檢測(cè)結(jié)果傳回主線程更新界面。from PyQt5.QtCore import QThread, pyqtSignal class DetectionThread(QThread): frame_ready pyqtSignal(object) # 畫面更新信號(hào) result_ready pyqtSignal(dict) # 檢測(cè)結(jié)果信號(hào) def __init__(self): super().__init__() self.running False self.source None self.model YOLO(weights/best.pt) def run(self): cap cv2.VideoCapture(self.source) while self.running: ret, frame cap.read() if not ret: break results self.model.predict(frame, conf0.35, iou0.45) annotated results[0].plot() # 將OpenCV BGR圖像轉(zhuǎn)為RGB再轉(zhuǎn)成QImage用于界面顯示 rgb_image cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape qimage QImage(rgb_image.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(qimage)這個(gè)線程類是整個(gè)視頻檢測(cè)模塊的核心。main window拿到frame_ready信號(hào)后把QImage顯示到QLabel上界面就實(shí)時(shí)刷新了。結(jié)果信息每次檢測(cè)后統(tǒng)計(jì)各類商品數(shù)量通過(guò)result_ready信號(hào)傳給界面右側(cè)顯示。4. 線程架構(gòu)、推理性能與界面交互的最優(yōu)實(shí)踐4.1 多線程架構(gòu)別把主線程拖死PyQt5用戶界面運(yùn)行在GUI主線程里如果在主線程里直接調(diào)用模型推理視頻流一來(lái)界面就全卡住按鈕點(diǎn)了沒(méi)反應(yīng)最小化都費(fèi)勁。所有的耗時(shí)操作包括視頻讀取、模型推理、圖像處理都必須放到單獨(dú)的工作線程里。我推薦用QThread方案結(jié)構(gòu)清晰信號(hào)與槽天然適合跨線程通信。檢測(cè)線程拿到每一幀推理完通過(guò)信號(hào)把結(jié)果發(fā)回主線程主線程只負(fù)責(zé)顯示兩邊各干各的事互不阻塞。更復(fù)雜的場(chǎng)景還可以引入任務(wù)隊(duì)列。視頻讀取線程只負(fù)責(zé)讀幀往隊(duì)列里塞推理線程從隊(duì)列里取幀處理顯示線程展示結(jié)果。三個(gè)線程用Queue解耦好處是每一幀的耗時(shí)波動(dòng)不會(huì)影響其他環(huán)節(jié)。比如某一幀推理特別慢讀幀線程不會(huì)卡住隊(duì)列可以緩沖顯示線程也不會(huì)因?yàn)檫@一幀拖慢后續(xù)幀。4.2 推理參數(shù)工程化調(diào)優(yōu)不只是調(diào)conf和iou很多人只調(diào)conf和iou兩個(gè)參數(shù)就完事了實(shí)際上還有幾個(gè)參數(shù)對(duì)最終效果影響巨大。imgsz參數(shù)決定輸入模型的圖像尺寸。默認(rèn)640如果你貨架上的商品都很小可以試960小目標(biāo)的檢出率會(huì)明顯提升。但代價(jià)是推理時(shí)間翻倍這個(gè)要根據(jù)實(shí)際硬件來(lái)權(quán)衡。我用RTX 3060測(cè)試640尺寸約25毫秒/幀960尺寸約60毫秒/幀流暢度差距明顯。半精度推理是白撿的性能提升。GPU推理時(shí)把模型和輸入轉(zhuǎn)為fp16推理速度可以提升30%到50%精度損失在絕大多數(shù)場(chǎng)景下肉眼不可見。只需在predict時(shí)加一個(gè)參數(shù)results model.predict(frame, conf0.35, iou0.45, imgsz640, halfTrue)批處理vs單幀推理。如果做的是離線批量識(shí)別幾百?gòu)垐D片可以一次性傳入整個(gè)圖片列表模型自動(dòng)批處理吞吐量會(huì)高很多。但視頻流場(chǎng)景必須逐幀推理批次大小固定為1此時(shí)線程架構(gòu)比推理參數(shù)更影響整體流暢度。4.3 界面流暢度優(yōu)化QImage格式轉(zhuǎn)換的小技巧視頻流畫面從OpenCV到PyQt5展示中間必須經(jīng)過(guò)數(shù)據(jù)類型轉(zhuǎn)換。這個(gè)轉(zhuǎn)換如果做不好性能差距可以到好幾倍。OpenCV的圖像格式是BGRPyQt5的QImage是RGB。很多人用cv2.cvtColor一個(gè)個(gè)通道轉(zhuǎn)換速度很慢。我的做法是直接用QImage的Format_RGB888格式配合numpy數(shù)組的內(nèi)存布局一次性轉(zhuǎn)換到位避免逐像素操作。def convert_cv_to_qimage(cv_img): rgb_image cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w return QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888)還要注意QLabel顯示大圖時(shí)使用scaled縮放會(huì)消耗不少CPU。商品識(shí)別畫面通常是1920x1080的而界面顯示區(qū)域可能只有960x600直接setPixmap原始尺寸會(huì)讓界面刷新變慢。正確做法是做一次帶比例縮放的resizepixmap QPixmap.fromImage(qimage) scaled_pixmap pixmap.scaled( self.video_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.video_label.setPixmap(scaled_pixmap)5. 實(shí)際項(xiàng)目中的典型問(wèn)題與排查經(jīng)驗(yàn)5.1 線上環(huán)境常見問(wèn)題速查表做過(guò)的項(xiàng)目多了踩過(guò)的坑也多了。我把商品識(shí)別項(xiàng)目里最常遇到的幾個(gè)問(wèn)題整理成一張速查表遇到對(duì)應(yīng)癥狀可以直接按表排查。癥狀問(wèn)題原因解決方案啟動(dòng)后窗口假死模型加載放到了主線程模型初始化放到檢測(cè)線程的run方法里視頻畫面卡頓但CPU占用不高未加幀間間隔無(wú)限循環(huán)讀幀按目標(biāo)FPS控制幀處理間隔檢測(cè)框錯(cuò)位輸入圖像尺寸與顯示尺寸不一致圖像resize時(shí)記錄縮放比例坐標(biāo)按比例映射中文標(biāo)簽顯示亂碼PyQt5默認(rèn)字體不支持中文程序啟動(dòng)時(shí)統(tǒng)一設(shè)置QFont中文字體GPU顯存占用持續(xù)上漲推理結(jié)果未釋放或video capture緩存堆積每次循環(huán)結(jié)束時(shí)顯式del results控制隊(duì)列長(zhǎng)度RTSP斷流后無(wú)法自動(dòng)恢復(fù)沒(méi)有重連機(jī)制檢測(cè)線程捕獲異常后自動(dòng)延遲重連小商品總檢測(cè)不到imgsz太小小目標(biāo)特征丟失提升imgsz至960或裁剪感興趣區(qū)域后檢測(cè)5.2 一個(gè)最典型的排查案例有一次客戶反饋說(shuō)攝像頭接入后畫面斷斷續(xù)續(xù)檢測(cè)結(jié)果還經(jīng)常延遲好幾秒。我遠(yuǎn)程看了日志發(fā)現(xiàn)是RTSP拉流后每幀都做推理但攝像頭是25幀推理只有8幀的處理能力隊(duì)列里積壓了大量幀延遲越來(lái)越大。排查思路是先把推理性能和推流性能分開測(cè)。用一段本地視頻測(cè)試發(fā)現(xiàn)推理穩(wěn)定在8幀左右排除了模型本身的問(wèn)題。再單獨(dú)拉RTSP測(cè)試發(fā)現(xiàn)視頻讀取本身沒(méi)問(wèn)題。最后定位到問(wèn)題就是沒(méi)有做幀丟棄和隊(duì)列長(zhǎng)度控制。解決方案是加了一個(gè)有界隊(duì)列隊(duì)列滿時(shí)直接丟棄最舊的幀保證處理的一定是最新畫面。修改后延遲降到300毫秒以內(nèi)客戶現(xiàn)場(chǎng)體驗(yàn)完全可接受。暴露這個(gè)案例是想說(shuō)明視頻處理項(xiàng)目里80%的性能問(wèn)題都不是算法的問(wèn)題而是架構(gòu)設(shè)計(jì)的問(wèn)題。幀的生產(chǎn)速度和消費(fèi)速度不匹配就必須引入緩沖和丟幀策略這個(gè)思想在任何視頻AI項(xiàng)目里都通用。5.3 訓(xùn)練數(shù)據(jù)不充分的應(yīng)急方案有些項(xiàng)目時(shí)間很緊客戶給的圖片只有幾十張直接訓(xùn)練出來(lái)的模型泛化能力很差。這個(gè)階段我一般會(huì)做兩件事。第一是數(shù)據(jù)增強(qiáng)。用albumentations庫(kù)做隨機(jī)水平翻轉(zhuǎn)、亮度對(duì)比度擾動(dòng)、隨機(jī)裁剪縮放、加噪聲把幾十張圖片膨脹到兩三百?gòu)?。注意翻轉(zhuǎn)操作要謹(jǐn)慎商品上的文字翻轉(zhuǎn)后會(huì)變成反的這類樣本應(yīng)去掉或者特殊處理否則模型會(huì)學(xué)到錯(cuò)誤特征。第二是加載預(yù)訓(xùn)練權(quán)重做遷移學(xué)習(xí)。用COCO預(yù)訓(xùn)練模型作為初始權(quán)重凍結(jié)前幾層卷積特征提取層只訓(xùn)練后面的檢測(cè)頭。這樣即使數(shù)據(jù)量少也能利用到預(yù)訓(xùn)練模型學(xué)到的通用視覺(jué)特征。做法是在訓(xùn)練腳本里設(shè)置model YOLO(yolov8s.pt) # 加載預(yù)訓(xùn)練權(quán)重 # freeze層數(shù)可以根據(jù)數(shù)據(jù)量調(diào)整 results model.train( datasupermarket.yaml, epochs100, imgsz640, freeze10, # 凍結(jié)前10層 lr00.001, # 學(xué)習(xí)率調(diào)低防止破壞預(yù)訓(xùn)練特征 batch16, )數(shù)據(jù)量不足時(shí)訓(xùn)練輪數(shù)不能開太大否則會(huì)過(guò)擬合。我一般控制在100輪左右同時(shí)用早停策略驗(yàn)證集損失連續(xù)20輪不下降就自動(dòng)停止。6. 項(xiàng)目交付與持續(xù)迭代的實(shí)操細(xì)節(jié)6.1 客戶現(xiàn)場(chǎng)的模型更新流程模型訓(xùn)練完善后客戶現(xiàn)場(chǎng)要更新模型怎么辦很多項(xiàng)目死在交付后的維護(hù)環(huán)節(jié)因?yàn)槊看味荚诳蛻魴C(jī)器上手動(dòng)換文件、重啟程序既低效又容易出錯(cuò)。我的做法是把模型文件放到一個(gè)固定的models目錄程序啟動(dòng)時(shí)掃描目錄下所有.pt文件界面上加一個(gè)下拉框切換模型??蛻裟玫叫履P臀募胚M(jìn)目錄后在界面上一選程序自動(dòng)重新加載并生效完全不需要重啟。代碼邏輯很簡(jiǎn)單def reload_model(self, model_path): if hasattr(self, detection_thread) and self.detection_thread.isRunning(): self.detection_thread.stop() self.detection_thread.wait() self.detection_thread DetectionThread() self.detection_thread.set_model(model_path) self.detection_thread.start()這個(gè)流程極大降低了客戶的維護(hù)門檻后期新增SKU或優(yōu)化識(shí)別效果時(shí)客戶自己就能操作不需要每次叫你去現(xiàn)場(chǎng)。6.2 誤檢率和漏檢率如何平衡商品識(shí)別項(xiàng)目驗(yàn)收時(shí)客戶最關(guān)心的就是誤檢率和漏檢率。這兩個(gè)指標(biāo)此消彼長(zhǎng)。你把conf閾值調(diào)高漏檢減少但誤檢增加因?yàn)槟P筒淮_定的樣本會(huì)被當(dāng)作負(fù)樣本過(guò)濾掉調(diào)低conf閾值模型更容易把相似的誤檢為同一類但漏檢會(huì)減少。沒(méi)有一個(gè)參數(shù)是萬(wàn)能解。我的經(jīng)驗(yàn)是分場(chǎng)景定策略。自助結(jié)算場(chǎng)景寧可多問(wèn)一句也不放過(guò)任何商品適合用高召回率即把conf調(diào)低到0.2左右盤點(diǎn)機(jī)器人場(chǎng)景追求的是準(zhǔn)確統(tǒng)計(jì)庫(kù)存寧可漏檢也能事后補(bǔ)充適合用高精度conf調(diào)到0.5以上。實(shí)操中我會(huì)在程序里加一個(gè)精確度模式切換下拉框讓現(xiàn)場(chǎng)人員根據(jù)實(shí)際場(chǎng)景選擇模式不同模式自動(dòng)切換不同的conf和iou參數(shù)組合。這樣既保證了靈活性也讓客戶感受到系統(tǒng)的人性化設(shè)計(jì)。6.3 關(guān)于商用授權(quán)的一點(diǎn)提醒最后這個(gè)話題一定要提千萬(wàn)不要忽略。如果你用的是Ultralytics官方Y(jié)OLOv8它默認(rèn)是AGPL-3.0協(xié)議這個(gè)協(xié)議要求任何基于它的衍生作品都必須以相同協(xié)議開源。換句話說(shuō)如果你直接把它封裝成商業(yè)軟件賣給客戶又不開放自己軟件的源代碼嚴(yán)格來(lái)說(shuō)是存在合規(guī)風(fēng)險(xiǎn)的。我在商用項(xiàng)目中會(huì)提前做合規(guī)評(píng)估。方案一是購(gòu)買Ultralytics的企業(yè)授權(quán)價(jià)格按項(xiàng)目規(guī)模計(jì)算預(yù)算充足又追求省事的客戶可以選這個(gè)。方案二是自行實(shí)現(xiàn)或選用其他寬松協(xié)議的目標(biāo)檢測(cè)模型比如YOLOv5的某些開源版本用的是GPL協(xié)議情況也不同需要具體分析。方案三是在合同中明確告知客戶模型算法的授權(quán)限制把風(fēng)險(xiǎn)轉(zhuǎn)移給客戶決策。不要因?yàn)檫@個(gè)事情翻車技術(shù)再好授權(quán)問(wèn)題沒(méi)有理清項(xiàng)目交付后還可能面臨法律風(fēng)險(xiǎn)。我在項(xiàng)目交付前都會(huì)把這一項(xiàng)列成文檔和客戶核對(duì)確認(rèn)保護(hù)自己也保護(hù)客戶。這套系統(tǒng)的完整落地思路到這里就差不多講完了。從YOLO選型、PyQt5界面設(shè)計(jì)到線程架構(gòu)、參數(shù)調(diào)優(yōu)再到客戶現(xiàn)場(chǎng)交付的細(xì)節(jié)都是我在項(xiàng)目里一步步驗(yàn)證過(guò)的方法。最后再說(shuō)一個(gè)我個(gè)人的操作習(xí)慣每次上線之前我都會(huì)找一批客戶現(xiàn)場(chǎng)完全沒(méi)見過(guò)的照片跑一遍模型專門看那些置信度徘徊在0.3到0.4之間的樣本。這批樣本往往能暴露模型泛化能力的真實(shí)水平比看訓(xùn)練集指標(biāo)有效得多。多花半小時(shí)做這個(gè)驗(yàn)證能幫你在客戶現(xiàn)場(chǎng)少熬三個(gè)通宵。本文還有配套的精品資源點(diǎn)擊獲取