與可視化實戰(zhàn):從原理到工程實現(xiàn))
1. 項目緣起從“看見”到“數(shù)清”的剛需在計算機視覺的日常開發(fā)里目標檢測是基礎(chǔ)中的基礎(chǔ)。我們常常用YOLOv5這樣的模型在圖像或視頻里框出一個個目標比如生產(chǎn)線上的零件、果園里的蘋果、或者監(jiān)控畫面中的人。但很多時候光“框出來”還不夠業(yè)務方緊接著就會問“那到底有多少個”——這就是目標計數(shù)。更進一步他們希望這個數(shù)字能直接、清晰地顯示在結(jié)果圖上方便報告、存檔或者實時監(jiān)控。這個從“檢測”到“分類計數(shù)并可視化”的需求在工業(yè)質(zhì)檢、農(nóng)業(yè)估產(chǎn)、智慧安防等領(lǐng)域幾乎是標配。然而把YOLOv5的檢測結(jié)果直接拿來計數(shù)新手很容易踩進幾個坑里。最常見的就是重復計數(shù)同一個蘋果因為模型在不同幀或略有差異的推理中給出了兩個略有重疊的框就被數(shù)了兩次。另一個頭疼的問題是顯示雜亂原始的檢測結(jié)果圖所有識別框和類別標簽都堆在一起計數(shù)信息要么沒有要么擠在角落根本看不清。網(wǎng)上能找到的很多教程要么只講檢測要么計數(shù)邏輯寫得過于簡單沒考慮這些實際工程問題。所以今天我就結(jié)合自己多次在項目里折騰的經(jīng)驗來聊聊怎么用YOLOv5不僅實現(xiàn)高精度的目標檢測更能完成準確的目標分類計數(shù)并且把結(jié)果以清晰、美觀的方式疊加顯示在原始圖像上。我會重點拆解如何避免重復計數(shù)、如何設計計數(shù)信息的顯示布局以及如何將整個流程封裝成便于調(diào)用的模塊。你會發(fā)現(xiàn)要實現(xiàn)這個功能核心代碼可能也就百來行但里面的細節(jié)和思路才是真正決定項目成敗的關(guān)鍵。2. 環(huán)境搭建與YOLOv5基礎(chǔ)避開初學者的第一個坑工欲善其事必先利其器。第一步的環(huán)境準備很多人覺得簡單照著官方README做就行但恰恰這里埋伏著幾個導致后續(xù)各種詭異錯誤的“暗樁”。2.1 創(chuàng)建并激活獨立的Python環(huán)境強烈建議不要用系統(tǒng)全局的Python環(huán)境。使用conda或venv創(chuàng)建一個獨立環(huán)境能完美隔離不同項目間的依賴沖突。這里以conda為例# 創(chuàng)建一個名為yolo_count的新環(huán)境指定Python版本推薦3.8或3.9兼容性最好 conda create -n yolo_count python3.8 -y conda activate yolo_count2.2 克隆YOLOv5倉庫與安裝依賴YOLOv5的官方倉庫更新頻繁直接pip install yolov5并不是官方推薦的做法因為那樣安裝的是別人打包的庫可能缺少訓練、導出等腳本。正確做法是克隆整個倉庫# 克隆官方倉庫 git clone https://github.com/ultralytics/yolov5.git cd yolov5 # 安裝requirements.txt中列出的依賴 pip install -r requirements.txt注意requirements.txt里默認的torch和torchvision通常是CPU版本。如果你有NVIDIA GPU并需要CUDA加速務必在安裝完其他依賴后去 PyTorch官網(wǎng) 根據(jù)你的CUDA版本獲取對應的安裝命令。例如對于CUDA 11.3pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu1132.3 驗證安裝與初步測試安裝完成后不要急著寫自己的代碼。先用官方提供的預訓練模型和示例圖片跑一個最簡單的檢測驗證環(huán)境是否正常。import torch # 加載官方預訓練模型這里用中等大小的yolov5s model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) # 進行推理 img https://ultralytics.com/images/zidane.jpg # 使用YOLOv5官方示例圖片 results model(img) # 顯示結(jié)果 results.show() # 會彈出窗口顯示帶檢測框的圖片 results.print() # 在控制臺打印檢測到的目標信息如果這一步能成功彈出圖片并在控制臺看到類似“person 0.89...”的輸出恭喜你基礎(chǔ)環(huán)境沒問題了。這個results對象里就包含了我們后續(xù)實現(xiàn)計數(shù)和顯示所需的所有原始數(shù)據(jù)。3. 核心邏輯拆解從原始輸出到結(jié)構(gòu)化計數(shù)數(shù)據(jù)YOLOv5模型推理后返回的results對象是個寶藏但它的數(shù)據(jù)結(jié)構(gòu)需要理解。我們實現(xiàn)計數(shù)的第一步就是正確地從中提取并處理信息。3.1 理解Results對象的構(gòu)成執(zhí)行results.print()你可能會看到這樣的輸出image 1/1: 720x1280 2 persons, 1 tie, 1 handbag Speed: 10.2ms pre-process, 12.1ms inference, 1.2ms NMS per image at shape (1, 3, 384, 640)這只是摘要。詳細數(shù)據(jù)藏在results.pandas().xyxy[0]里如果你更喜歡用Pandas DataFrame或者直接訪問results.xyxy[0]返回一個Torch Tensor。我更傾向于直接使用Tensor因為速度更快也便于后續(xù)的數(shù)值計算。results.xyxy[0]的形狀通常是[N, 6]其中N是檢測到的目標數(shù)量6列分別代表x_min,y_min,x_max,y_max: 邊界框的左上角和右下角坐標。confidence: 檢測置信度范圍0-1。class: 目標類別索引整數(shù)對應模型訓練時data.yaml里names列表的順序。3.2 實現(xiàn)基于IOU的去重計數(shù)這是避免重復計數(shù)的關(guān)鍵。同一個物理目標由于模型不確定性或視頻相鄰幀間目標移動很小可能會產(chǎn)生多個高度重疊的檢測框。我們需要用非極大值抑制NMS的思想來進行去重。雖然YOLOv5在推理時已經(jīng)做過一次NMS但其閾值iou_thres參數(shù)默認0.45可能對于你的特定場景還不夠嚴格或者你需要對最終結(jié)果再做一次聚合。這里我們實現(xiàn)一個基于類內(nèi)IOU的合并與計數(shù)函數(shù)。IOUIntersection over Union交并比計算兩個框的重疊程度。import torch def count_objects(detections, iou_threshold0.5): 對檢測結(jié)果進行去重計數(shù)。 Args: detections (torch.Tensor): [N, 6] 格式為 [x1, y1, x2, y2, conf, cls] iou_threshold (float): IOU閾值高于此值則認為兩個框是同一目標。 Returns: dict: 鍵為類別索引int值為該類別的計數(shù)int。 torch.Tensor: 去重后的檢測框 [M, 6], M N。 if detections.shape[0] 0: return {}, detections # 按置信度降序排序優(yōu)先保留置信度高的框 detections detections[detections[:, 4].argsort(descendingTrue)] keep [] # 保留的框的索引 counted_detections [] # 用于存儲最終保留的框 # 獲取所有類別 unique_classes detections[:, 5].unique() count_dict {int(cls): 0 for cls in unique_classes} for cls in unique_classes: cls_mask (detections[:, 5] cls) cls_detections detections[cls_mask] while len(cls_detections) 0: # 取出當前置信度最高的框因為已排序第一個就是 current_box cls_detections[0] keep.append(torch.where((detections[:, 5] cls) (detections[:, 4] current_box[4]))[0][0].item()) counted_detections.append(current_box) count_dict[int(cls)] 1 # 計數(shù)1 if len(cls_detections) 1: break # 計算當前框與剩余同類框的IOU other_boxes cls_detections[1:] ious calculate_iou(current_box.unsqueeze(0), other_boxes) # 找出IOU過低的框即不同的目標保留下來進行下一輪循環(huán) low_iou_mask ious iou_threshold cls_detections other_boxes[low_iou_mask] counted_detections torch.stack(counted_detections) if counted_detections else torch.empty((0, 6)) return count_dict, counted_detections def calculate_iou(box1, box2): 計算兩組框之間的IOU。 box1: [1, 4] (x1, y1, x2, y2) box2: [N, 4] # 計算交集區(qū)域的坐標 inter_x1 torch.max(box1[:, 0], box2[:, 0]) inter_y1 torch.max(box1[:, 1], box2[:, 1]) inter_x2 torch.min(box1[:, 2], box2[:, 2]) inter_y2 torch.min(box1[:, 3], box2[:, 3]) # 計算交集面積 inter_area torch.clamp(inter_x2 - inter_x1, min0) * torch.clamp(inter_y2 - inter_y1, min0) # 計算各自面積 area_box1 (box1[:, 2] - box1[:, 0]) * (box1[:, 3] - box1[:, 1]) area_box2 (box2[:, 2] - box2[:, 0]) * (box2[:, 3] - box2[:, 1]) # 計算并集面積和IOU union_area area_box1 area_box2 - inter_area iou inter_area / (union_area 1e-6) # 加一個極小值防止除零 return iou.squeeze()這個count_objects函數(shù)做了幾件事1按類別分別處理2在同類框里根據(jù)IOU閾值判斷是否屬于同一物體3只保留每個“物體簇”中置信度最高的那個框作為代表并計數(shù)一次。通過調(diào)整iou_threshold你可以控制去重的嚴格程度。對于靜止圖片閾值可以設高一點如0.6對于視頻連續(xù)幀可能需要根據(jù)幀率適當調(diào)低。3.3 整合類別名稱映射計數(shù)字典的鍵是類別索引我們需要把它轉(zhuǎn)換成人類可讀的類別名。YOLOv5模型加載后其類別名存儲在model.names中這是一個列表。例如COCO預訓練模型的model.names[0]是“person”。def get_count_with_names(count_dict, model): 將計數(shù)字典中的類別索引轉(zhuǎn)換為類別名稱。 names model.names result {} for cls_idx, count in count_dict.items(): cls_name names[int(cls_idx)] result[cls_name] count return result現(xiàn)在我們已經(jīng)有了一個干凈、去重后的檢測框張量counted_detections以及一個按類別名稱統(tǒng)計的計數(shù)字典count_with_names。接下來就是如何把這些信息美觀地畫到圖上了。4. 結(jié)果可視化在圖像上清晰呈現(xiàn)計數(shù)信息可視化部分的目標是生成一張既包含所有檢測框又突出顯示統(tǒng)計結(jié)果的圖片。直接使用results.show()或results.render()得到的圖片計數(shù)信息并不明顯。我們需要自定義繪制邏輯。4.1 使用OpenCV進行高級繪制我們將使用OpenCV已在requirements.txt中安裝來繪制。主要步驟是1將YOLOv5的結(jié)果圖片RGB格式轉(zhuǎn)換為OpenCV格式BGR2繪制去重后的檢測框和標簽3在圖像的固定位置如左上角繪制一個半透明的信息板顯示各類別的計數(shù)。import cv2 import numpy as np def visualize_detections_with_count(original_img, detections, count_dict, model, conf_threshold0.25): 在圖像上繪制檢測框并疊加計數(shù)信息板。 Args: original_img (numpy.ndarray): 原始RGB圖像通常來自results.imgs[0]。 detections (torch.Tensor): 去重后的檢測框 [M, 6]。 count_dict (dict): 類別名稱到計數(shù)的映射。 model: YOLOv5模型對象用于獲取顏色和名稱。 conf_threshold: 置信度閾值低于此值的框不繪制。 Returns: numpy.ndarray: 繪制好的BGR圖像可供保存或顯示。 # 復制圖像避免修改原圖 img_draw original_img.copy() # 如果原圖是RGBYOLOv5輸出通常是的轉(zhuǎn)換為BGR供OpenCV使用 if img_draw.shape[2] 3: img_draw cv2.cvtColor(img_draw, cv2.COLOR_RGB2BGR) h, w img_draw.shape[:2] names model.names colors [[np.random.randint(0, 255) for _ in range(3)] for _ in names] # 1. 繪制檢測框和標簽 for *xyxy, conf, cls in detections: if conf conf_threshold: continue label f{names[int(cls)]} {conf:.2f} # 為每個類別分配固定顏色確保一致性 color colors[int(cls)] # 畫矩形框 cv2.rectangle(img_draw, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), color, 2) # 計算文本背景大小 (text_width, text_height), baseline cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2) # 畫文本背景 cv2.rectangle(img_draw, (int(xyxy[0]), int(xyxy[1]) - text_height - baseline - 5), (int(xyxy[0]) text_width, int(xyxy[1])), color, -1) # -1表示填充 # 寫文本 cv2.putText(img_draw, label, (int(xyxy[0]), int(xyxy[1]) - baseline - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2) # 2. 在左上角繪制計數(shù)信息板 # 信息板參數(shù) panel_x, panel_y 20, 20 panel_width, panel_height 220, 50 len(count_dict) * 30 # 創(chuàng)建一個半透明矩形區(qū)域 overlay img_draw.copy() cv2.rectangle(overlay, (panel_x, panel_y), (panel_x panel_width, panel_y panel_height), (50, 50, 50), -1) alpha 0.6 # 透明度 img_draw cv2.addWeighted(overlay, alpha, img_draw, 1 - alpha, 0) # 繪制信息板邊框和標題 cv2.rectangle(img_draw, (panel_x, panel_y), (panel_x panel_width, panel_y panel_height), (200, 200, 200), 2) cv2.putText(img_draw, Detection Counts:, (panel_x 10, panel_y 30), cv2.FONT_HERSHEY_DUPLEX, 0.7, (255, 255, 255), 2) # 繪制每一類別的計數(shù) y_offset panel_y 60 for idx, (cls_name, count) in enumerate(count_dict.items()): color colors[list(names.values()).index(cls_name)] if cls_name in names.values() else (255, 255, 255) count_text f{cls_name}: {count} cv2.putText(img_draw, count_text, (panel_x 20, y_offset), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) y_offset 30 # 3. 可選在圖像底部中央繪制總計數(shù) total_count sum(count_dict.values()) total_text fTotal Objects: {total_count} (total_width, total_height), _ cv2.getTextSize(total_text, cv2.FONT_HERSHEY_DUPLEX, 1.0, 3) cv2.putText(img_draw, total_text, (w // 2 - total_width // 2, h - 30), cv2.FONT_HERSHEY_DUPLEX, 1.0, (0, 200, 255), 3) return img_draw這個可視化函數(shù)做了三件事首先用不同顏色繪制每個檢測框及其類別、置信度標簽其次在左上角創(chuàng)建了一個半透明的信息面板清晰列出每個類別的具體數(shù)量最后在圖片底部中央醒目地顯示了目標總數(shù)。這樣的布局信息層次分明無論是用于屏幕顯示還是生成報告圖片都非常清晰。4.2 封裝成完整流程函數(shù)現(xiàn)在我們把前面的所有步驟整合成一個函數(shù)實現(xiàn)“輸入圖片路徑輸出帶計數(shù)的結(jié)果圖”的完整流程。def detect_count_and_visualize(model, img_path, iou_thres0.5, conf_thres0.25): 端到端的檢測、計數(shù)與可視化流程。 # 1. 推理 results model(img_path) # 獲取原始檢測張量 (xyxy格式) detections results.xyxy[0] # 2. 去重計數(shù) count_dict_idx, filtered_dets count_objects(detections, iou_thresholdiou_thres) # 3. 轉(zhuǎn)換類別索引為名稱 count_dict_name get_count_with_names(count_dict_idx, model) # 4. 可視化 # results.imgs[0] 是推理用的原始圖像經(jīng)過預處理和resize的 # 為了獲得原始尺寸的繪制我們通常直接用原始圖像或results.render()[0] # 這里使用results.render()返回的帶原始框的圖片列表第一張 rendered_imgs results.render() # 返回一個列表每個元素是一個帶框的RGB圖像 if rendered_imgs: orig_img_with_boxes rendered_imgs[0] else: # 如果render()沒返回則用原始推理圖像 orig_img_with_boxes results.imgs[0] final_img visualize_detections_with_count(orig_img_with_boxes, filtered_dets, count_dict_name, model, conf_thresholdconf_thres) # 5. 返回結(jié)果 return final_img, count_dict_name, filtered_dets # 使用示例 model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) result_img, counts, boxes detect_count_and_visualize(model, your_image.jpg) # 顯示圖片 cv2.imshow(Result, result_img) cv2.waitKey(0) cv2.destroyAllWindows() # 保存圖片 cv2.imwrite(result_with_count.jpg, result_img) print(Counts:, counts)5. 進階優(yōu)化與實戰(zhàn)踩坑點把基礎(chǔ)流程跑通只是第一步要讓這個功能在實際項目中穩(wěn)定可靠還需要考慮更多細節(jié)。5.1 處理視頻流與實時計數(shù)對于視頻文件或攝像頭實時流核心邏輯不變但需要處理幀與幀之間計數(shù)結(jié)果的平滑問題。直接對每一幀獨立計數(shù)會導致數(shù)字頻繁跳動。一個常見的優(yōu)化是使用滑動窗口或簡單濾波。from collections import deque import time class SmoothCounter: def __init__(self, window_size5): self.window_size window_size self.history deque(maxlenwindow_size) # 存儲最近N幀的計數(shù)字典 def update(self, current_count_dict): 更新歷史記錄并返回平滑后的計數(shù)取最近N幀的平均值或眾數(shù)。 self.history.append(current_count_dict) if len(self.history) self.window_size: return current_count_dict # 平滑策略取眾數(shù)出現(xiàn)次數(shù)最多的值 smoothed {} all_classes set() for d in self.history: all_classes.update(d.keys()) for cls in all_classes: counts [d.get(cls, 0) for d in self.history] # 取出現(xiàn)次數(shù)最多的值作為平滑結(jié)果 from collections import Counter smoothed[cls] Counter(counts).most_common(1)[0][0] return smoothed # 在視頻處理循環(huán)中使用 counter SmoothCounter(window_size10) cap cv2.VideoCapture(0) # 打開攝像頭 while True: ret, frame cap.read() if not ret: break # 將BGR幀轉(zhuǎn)換為RGBYOLOv5期望RGB輸入 rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 推理、計數(shù)使用前面定義的函數(shù)但傳入numpy數(shù)組 results model(rgb_frame) detections results.xyxy[0] count_dict_idx, filtered_dets count_objects(detections) count_dict_name get_count_with_names(count_dict_idx, model) # 平滑計數(shù) smoothed_counts counter.update(count_dict_name) # 使用平滑后的計數(shù)進行可視化 # ... (可視化代碼傳入smoothed_counts)5.2 針對特定場景的調(diào)參經(jīng)驗置信度閾值conf_thres默認0.25。在干凈背景下檢測大目標可以提高到0.5以上以減少誤檢在復雜、小目標場景下可能需要降低到0.1甚至0.05但同時要配合更嚴格的后續(xù)過濾如根據(jù)目標尺寸。NMS IOU閾值iou_thres默認0.45。這個值控制檢測階段框合并的激進程度。如果你發(fā)現(xiàn)同一個目標被拆分成多個小框比如一個行人被分成頭和身體兩個框可以適當提高這個值如0.6讓它們更容易合并。反之如果不同目標靠得太近被合并成一個框就降低這個值。計數(shù)去重IOU閾值這是我們自定義函數(shù)里的iou_threshold。它應該大于或等于推理時的NMS IOU閾值。通常設置在0.5-0.7之間。對于靜態(tài)圖片用0.6對于視頻可以結(jié)合目標跟蹤如使用ByteTrack、DeepSORT來跨幀關(guān)聯(lián)目標實現(xiàn)更準確的計數(shù)而不是每幀獨立去重。5.3 常見問題與排查“訓練map總是0”的啟示雖然本篇重點在推理和可視化但標題相關(guān)熱詞中提到了“yolov5訓練map總是0”。這提醒我們計數(shù)的前提是檢測模型要足夠準確。如果你的模型訓練時mAP平均精度為0計數(shù)自然無從談起。這里快速過一下可能的原因數(shù)據(jù)標注問題檢查標注文件YOLO格式的.txt文件內(nèi)容是否正確。坐標是否歸一化0-1之間類別索引是否從0開始且連續(xù)標注框是否完全包含了目標數(shù)據(jù)路徑配置data.yaml文件里的train和val路徑是否正確建議使用絕對路徑。類別不匹配data.yaml中的names列表是否與標注文件中的類別索引對應nc類別數(shù)設置是否正確學習率過高過高的初始學習率可能導致訓練發(fā)散。嘗試使用更小的學習率如--lr 0.001。模型復雜度與數(shù)據(jù)量不匹配數(shù)據(jù)量很少時使用過大的模型如yolov5x容易過擬合。從小模型yolov5n或yolov5s開始嘗試。檢查訓練輸出關(guān)注訓練日志中損失值box_loss, obj_loss, cls_loss的變化趨勢。它們應該隨著訓練輪數(shù)逐漸下降并趨于平穩(wěn)。如果損失值一開始就是NaN或者異常大基本可以斷定是數(shù)據(jù)或配置問題。5.4 部署與性能考量當需要將這套系統(tǒng)部署到邊緣設備如RK3568、RV1106等時不能直接使用Python腳本。你需要模型導出使用YOLOv5提供的export.py腳本將PyTorch模型導出為ONNX、TensorRT或OpenVINO等格式。python export.py --weights yolov5s.pt --include onnx --img 640 --batch 1推理引擎重寫在C或相應的邊緣計算框架中加載導出的模型并重新實現(xiàn)前處理圖像縮放、歸一化、推理、后處理解碼輸出、NMS以及我們上面討論的計數(shù)和顯示邏輯。后處理中的框解碼和NMS是性能關(guān)鍵點務必用高效的方式實現(xiàn)。顯示部分在嵌入式設備上可能沒有GUI??梢暬Y(jié)果可以保存為圖片或者通過RTSP流推送到網(wǎng)絡在遠程客戶端查看。6. 完整代碼示例與使用指南最后我將提供一個整合了所有功能的腳本并附上詳細的注釋和使用說明。你可以將此腳本保存為yolov5_count_display.py并根據(jù)你的需求修改。 YOLOv5目標分類計數(shù)與圖像顯示 - 完整實現(xiàn)腳本 作者一個愛折騰的開發(fā)者 功能加載YOLOv5模型對輸入圖像/視頻進行目標檢測、去重計數(shù)并將結(jié)果可視化保存。 import torch import cv2 import numpy as np from pathlib import Path import argparse def count_objects(detections, iou_threshold0.5): 基于IOU的去重計數(shù)函數(shù)。 # ... (函數(shù)體同上文此處省略以節(jié)省篇幅實際使用時請復制完整函數(shù)) pass def calculate_iou(box1, box2): 計算IOU。 # ... (函數(shù)體同上文) pass def get_count_with_names(count_dict, model): 將類別索引轉(zhuǎn)換為名稱。 # ... (函數(shù)體同上文) pass def visualize_detections_with_count(original_img, detections, count_dict, model, conf_threshold0.25): 可視化檢測結(jié)果與計數(shù)信息。 # ... (函數(shù)體同上文) pass def process_image(model, img_path, output_dir./output, iou_thres0.5, conf_thres0.25): 處理單張圖片。 Path(output_dir).mkdir(parentsTrue, exist_okTrue) results model(img_path) detections results.xyxy[0] count_dict_idx, filtered_dets count_objects(detections, iou_thresholdiou_thres) count_dict_name get_count_with_names(count_dict_idx, model) # 使用渲染后的圖像進行可視化 rendered_imgs results.render() if rendered_imgs: orig_img_with_boxes rendered_imgs[0] else: orig_img_with_boxes results.imgs[0] final_img visualize_detections_with_count(orig_img_with_boxes, filtered_dets, count_dict_name, model, conf_thresholdconf_thres) # 生成輸出文件名 img_name Path(img_path).stem output_path Path(output_dir) / f{img_name}_result.jpg cv2.imwrite(str(output_path), final_img) print(f結(jié)果已保存至: {output_path}) print(f檢測計數(shù): {count_dict_name}) return final_img, count_dict_name def process_video(model, video_path, output_dir./output, iou_thres0.5, conf_thres0.25, show_videoFalse): 處理視頻文件。 # ... (視頻處理邏輯結(jié)合SmoothCounter類) pass def main(): parser argparse.ArgumentParser(descriptionYOLOv5目標分類計數(shù)與顯示) parser.add_argument(--source, typestr, default./data/images, help輸入源可以是圖片路徑、圖片文件夾、視頻文件或0攝像頭) parser.add_argument(--weights, typestr, defaultyolov5s.pt, help模型權(quán)重路徑如 yolov5s.pt) parser.add_argument(--output, typestr, default./output, help結(jié)果輸出目錄) parser.add_argument(--iou-thres, typefloat, default0.5, help計數(shù)去重的IOU閾值) parser.add_argument(--conf-thres, typefloat, default0.25, help檢測置信度閾值) args parser.parse_args() # 加載模型 print(f加載模型: {args.weights}) model torch.hub.load(ultralytics/yolov5, custom, pathargs.weights, force_reloadFalse) model.conf args.conf_thres # 設置模型置信度閾值 model.iou 0.45 # 設置模型NMS IOU閾值 source_path Path(args.source) if source_path.is_file(): if source_path.suffix.lower() in [.jpg, .jpeg, .png, .bmp]: process_image(model, str(source_path), args.output, args.iou_thres, args.conf_thres) elif source_path.suffix.lower() in [.mp4, .avi, .mov]: process_video(model, str(source_path), args.output, args.iou_thres, args.conf_thres, show_videoTrue) elif source_path.is_dir(): for img_file in source_path.glob(*.[jp][pn]g): process_image(model, str(img_file), args.output, args.iou_thres, args.conf_thres) elif args.source 0: process_video(model, 0, args.output, args.iou_thres, args.conf_thres, show_videoTrue) else: print(錯誤不支持的輸入源。) if __name__ __main__: main()使用指南確保已安裝好環(huán)境見第2部分。將上述完整腳本保存。準備一張測試圖片例如test.jpg。在終端運行python yolov5_count_display.py --source ./test.jpg --weights yolov5s.pt --output ./results查看./results文件夾下的結(jié)果圖片和控制臺輸出的計數(shù)信息。通過這個完整的流程你應該能夠?qū)OLOv5變成一個強大的目標檢測與計數(shù)工具。記住核心在于理解數(shù)據(jù)流從原始輸出到結(jié)構(gòu)化計數(shù)和可視化設計清晰傳達信息。在實際項目中你可能還需要根據(jù)具體場景調(diào)整去重邏輯、優(yōu)化顯示樣式甚至集成到更大的系統(tǒng)流水線中。希望這些從實際項目中總結(jié)出的細節(jié)和代碼能幫你少走彎路。