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