雾天车辆行人检测实战:YOLOv8权重训练与推理部署

发布时间:2026/10/1 6:02:05
雾天车辆行人检测实战:YOLOv8权重训练与推理部署 简介一套基于YOLOv8的雾天车辆行人检测系统面向智能交通与安防监控中雾霾天气下的目标识别场景适合计算机视觉学习者、算法工程师及需要快速搭建检测原型的开发者。系统包含在RTTS数据集上训练完成的best.pt与last.pt权重配套PyQt5图形界面支持图片、视频、摄像头三种输入方式检测结果可一键导出到本地目录方便后续离线分析与整理。整个压缩包共41个文件约20.72MB其中pt为模型权重py为GUI与推理计数逻辑yaml用于配置模型结构png/jpg包含训练样本及结果可视化图txt、md等文件提供运行说明与依赖清单整体结构清晰便于按需调用。目前已有1527人学习验证了其在雾天目标检测任务中的实用性。按readme说明安装依赖后可完成从环境配置、模型加载到推理演示的完整闭环包内附带的PR曲线、混淆矩阵等评估图表还能帮助了解模型在不同类别上的性能表现适合作为毕业设计或工程项目的参考基底。1. 雾天车辆行人检测为什么通用YOLOv8权重在雾天场景会翻车把在COCO上训练的YOLOv8通用权重直接接到雾天监控摄像头上晴天场景mAP能到0.85起雾天气直接掉到0.4左右——这不是模型退化是雾天图像分布和训练数据没对齐。雾让图像对比度下降、边缘和颜色信息衰减车辆和行人被“揉”进背景里通用模型学到的高层特征在这种输入下响应严重变弱。这套系统的思路其实不复杂用雾天增强后的数据训练专用权重配合推理代码和带图形界面的调用入口在图片、视频、摄像头三路输入下完成车辆行人检测并把结果导出为标注图和CSV记录。适合需要快速落地雾天交通监控demo的工程师、做毕业设计的学生以及想验证雾天检测方案可行性的研究人员。2. 雾天检测的选型关键去雾预处理还是雾天数据增强2.1 雾天图像为什么让检测框飘散射模型与特征衰减雾天成像可以用大气散射模型描述I(x) J(x)·t(x) A·(1 - t(x))。其中I是相机拍到的模糊图J是清晰场景A是大气光t是透射率t(x) exp(-β·d(x))β是雾浓度系数d是场景深度。这个公式解释了雾天检测的所有麻烦透射率t越小对比度压缩越狠目标的纹理和边缘被“抹平”更关键的是目标颜色会偏向大气光A的灰白色调模型在训练中学到的颜色特征完全失效。另一个容易被忽略的问题是深度分布。监控场景里车辆行人距离相机远近不一近处目标雾感轻、远处目标雾感重同一个画面里退化程度不均匀。YOLOv8的C2f特征提取网络在训练时见过的是均匀退化或者无退化图像遇到这种非均匀退化FPN各层特征融合时就会产生错位——浅层细节信息不足深层语义信息又和浅层对不上最终检测头输出的置信度普遍偏低。理解了这层物理过程选型就有了方向要么在推理前做去雾恢复把输入拉回训练分布要么在训练时加入雾天退化样本让模型自己适应。两条路都有自己的适用场景下面分别落地。2.2 去雾预处理暗通道先验的落地实现与适用边界暗通道先验DCP是最常用的去雾方案核心观察是无雾图像的局部区域里RGB三通道的最小值趋近于0——因为至少有一个通道在阴影或暗色物体上会“塌掉”。利用这个统计先验可以估计大气光A和透射率t再反推清晰图。完整的去雾函数可以这样实现import cv2 import numpy as np def dark_channel(image, patch_size15): 计算暗通道先取RGB三通道逐像素最小值再做最小值滤波 min_channel np.min(image, axis2) kernel cv2.getStructuringElement(cv2.MORPH_RECT, (patch_size, patch_size)) return cv2.erode(min_channel, kernel) def estimate_atmosphere(image, dark): 取暗通道最亮的0.1%像素对应原图位置的亮度均值作为大气光 flat_dark dark.ravel() num_pixels max(int(flat_dark.size * 0.001), 1) indices np.argpartition(flat_dark, -num_pixels)[-num_pixels:] atmosphere np.max(image.reshape(-1, 3)[indices], axis0) return atmosphere def dehaze_dcp(image, patch_size15, omega0.95): 暗通道先验去雾 img image.astype(np.float64) dark dark_channel(img, patch_size) A estimate_atmosphere(img, dark) # A是(3,)向量这里取均值参与透射率计算避免通道广播产生色偏 A_gray np.mean(A) # 透射率估计t(x) 1 - omega * dark / A t 1.0 - omega * dark / A_gray t np.clip(t, 0.1, 1.0) # 反演清晰图像t最小截断为0.1防止噪声放大 recovered (img - A) / t[..., None] A return np.clip(recovered, 0, 255).astype(np.uint8)# 使用示例 hazy cv2.imread(foggy_frame.jpg) clean dehaze_dcp(hazy, patch_size15, omega0.95) cv2.imwrite(dehazed.jpg, clean)这段代码里有两个常规实现容易翻车的点。第一A是三通道向量直接拿单通道dark除以三通道A会触发广播产生不可预期的色偏所以我在estimate_atmosphere之后取了A_gray均值参与计算。第二t的下限截断到0.1不截断的话远处浓雾区域的t趋近0除法会把噪声无限放大。patch_size控制暗通道的窗口大小窗口越大去雾越强但也会让去雾后出现块状伪影15这个值在监控画面里基本够用。DCP的实际边界问题也在这里天空区域本身亮度高、没有暗像素暗通道先验直接失效去雾后天空会变成灰黑色块。如果监控画面里天空占比大比如高速路口那种开阔场景DCP去雾后再做检测反而会引入新的误检。另一个问题是视频逐帧去雾会产生闪烁透射率在相邻帧间跳变检测框也跟着抖。2.3 合成雾数据增强把晴天数据集变成雾天数据集与其在推理链路里多塞一个去雾模块更省事且更符合检测模型胃口的方式是训练阶段直接把雾天退化做进样本分布。做法是在清晰图像上合成雾用随机深度图模拟场景深度再套大气散射模型。合成逻辑不复杂核心是深度图怎么生成——监控场景一般是中心近、边缘远或者存在若干亮度分层的雾带。import cv2 import numpy as np def generate_depth_map(shape, moderadial): h, w shape[:2] if mode radial: y, x np.mgrid[0:h, 0:w] cx, cy w / 2, h / 2 # 中心最近向四周线性变远归一化到0~1 depth np.sqrt((x - cx) ** 2 (y - cy) ** 2) depth depth / np.sqrt(cx * cx cy * cy) elif mode layered: # 随机分层雾带高斯模糊后模拟不均匀雾层 depth np.random.rand(h, w).astype(np.float32) depth cv2.GaussianBlur(depth, (0, 0), 30) depth (depth - depth.min()) / (depth.max() - depth.min()) return depth def add_synthetic_haze(image, depth, A220, beta1.2): 按大气散射模型合成雾图 t np.exp(-beta * depth) # 透射率随深度指数衰减 t np.clip(t, 0.1, 1.0) haze image * t[..., None] A * (1 - t[..., None]) return np.clip(haze, 0, 255).astype(np.uint8)# 批量合成雾天训练样本 image cv2.imread(clear_001.jpg) depth generate_depth_map(image.shape[:2], modelayered) foggy add_synthetic_haze(image, depth, A200, beta1.5) cv2.imwrite(foggy_001.jpg, foggy)beta是雾浓度系数1.0到1.5对应轻雾到中雾超过2.0就是浓雾目标几乎不可辨训练样本里浓雾比例不要超过20%。实际落地时我一般对训练集做三份扰动原图、beta1.0的轻雾、beta1.5的中雾每份各占三分之一这样模型既不会丢掉晴天特征也能覆盖雾天退化。如果直接拿全部样本做重雾增强模型会往“看见灰白色块就框”的方向过拟合这在实验里是真实踩过的。选型结论很明确轻雾和监控场景走合成雾增强训练省一次推理开销也不引入去雾伪影只有像高速公路团雾那种浓雾场景才需要先做一轮轻量去雾再进检测器。两条链路都建议跑一次对比实验用同一份验证集看mAP再决定而不是凭感觉拍板。3. 用YOLOv8训练雾天车辆行人检测模型数据集准备与训练参数3.1 数据集准备Labelme标注转YOLO格式既然要做车辆行人检测数据集的标注格式必须从Labelme的JSON转成YOLO的TXT。Labelme保存的是多边形顶点坐标而YOLO需要的是归一化的中心点坐标和宽高。这段转换脚本是固定的套路import json import os def labelme_to_yolo(json_path, output_dir, class_map): 将Labelme的JSON标注转为YOLO格式TXT class_map示例: {vehicle: 0, pedestrian: 1} with open(json_path, 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 # 多边形顶点取最小外接矩形 xs [p[0] for p in shape[points]] ys [p[1] for p in shape[points]] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 转归一化中心点宽高 cx (x_min x_max) / 2 / img_w cy (y_min y_max) / 2 / img_h w (x_max - x_min) / img_w h (y_max - y_min) / img_h lines.append(f{class_map[label]} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) txt_name os.path.basename(json_path).replace(.json, .txt) with open(os.path.join(output_dir, txt_name), w) as f: f.write(\n.join(lines))# 批量处理所有JSON for json_file in labelme_jsons/*.json; do python labelme_to_yolo.py $json_file yolo_labels --class vehicle pedestrian done坐标里有个隐蔽问题Labelme的顶点坐标是浮点数转YOLO格式时要保留至少6位小数否则宽高在归一化后会被舍入吃掉小目标的框会偏移好几个像素。另外如果标注时把车辆写成了“Vechicle”和“Vehicle”两种拼法class_map映射不上转换脚本会静默跳过——建议转换后立刻统计每个类别的TXT数量发现缺失再回头查JSON。数据集目录结构按YOLO的习惯组织训练和验证分开datasets/foggy_data/ images/ train/ # 合成雾真实雾图约2000~3000张 val/ labels/ train/ val/这里的val集建议单独保留一部分真实拍摄的雾天监控截图不要只用合成雾做验证否则模型的在真实场景的泛化能力完全没被检验到。3.2 训练命令与关键参数batch、lr、imgsz怎么调数据准备好后先写数据集配置文件再跑训练命令。配置文件是YAML格式指定路径和类别名# foggy_dataset.yaml path: datasets/foggy_data train: images/train val: images/val names: 0: vehicle 1: pedestrianyolo detect train \ datafoggy_dataset.yaml \ modelyolov8s.pt \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ device0 \ workers4 \ projectfoggy_train训练命令里的modelyolov8s.pt表示加载YOLOv8s的COCO预训练权重这是关键一步——从蒸馏的COCO权重开始比从零训练收敛快得多尤其在雾天这种小数据集上。yolov8s.pt在ultralytics首次执行时自动下载不需要手动去找。如果网络受限也可以先单独执行yolo detect download把权重拉下来再离线拷贝。参数含义和雾天场景下的调整建议参数默认值雾天场景建议说明imgsz640640或960雾天远距离小目标多时用960显存占用约翻倍batch168~16显存不够就减半梯度累积效果不如真batchlr00.010.005~0.01自制数据集建议从0.005起步防止前期发散epochs100150~200雾天数据量少拉长训练让收敛更充分lrf0.010.01最终学习率与初始学习率的比值默认即可workers84Windows下workers过大会报DataLoader异常两个特别值得说的参数。一个是lr0预训练权重已经学到了通用特征如果学习率偏大微调前期会把COCO上保留的特征冲掉发散了再调回来很费时间。另一个是imgsz雾天的难点恰恰是远处小目标如果显存允许16GB以上直接imgsz960训练检测效果会比640有一个肉眼可辨的提升代价是训练时间翻倍。3.3 训练完怎么挑权重损失曲线与验证指标训练结束后runs/detect/foggy_train/目录下会生成results.png包含box_loss、cls_loss、dfl_loss三条损失曲线以及验证集的precision、recall、mAP50、mAP50-95四条指标曲线。看曲线有个经验损失正常下降但mAP50一直上不去多半是标注类别不平衡vehicle样本远多于pedestrian模型把行人全当负样本压掉了。解决办法是给pedestrian类加权或者复制少数类样本做平衡而不是盲目加epochs。# 训练完后的目录结构 runs/detect/foggy_train/ weights/ best.pt # 按验证集mAP最优保存 last.pt # 最后一个epoch保存 results.png # 损失曲线和验证指标曲线部署时一定要复制best.pt而不是last.pt。best.pt是按验证集mAP保留的last.pt只是训练收尾时的状态两者在雾天场景下差距有时会有3~5个mAP点。读曲线时还要注意如果box_loss在最后30个epoch还在下降但mAP已经平了说明模型在过拟合训练集的标注噪声这时候直接取best.pt就行别贪最后的损失下降。验证可以用yolo detect val命令跑一遍输出每类的AP和混淆矩阵。雾天场景下重点看pedestrian这一类的recall——车辆是大目标任何模型都不容易漏行人小且雾天对比度低recall掉到0.6以下基本不能上线。4. 推理代码与GUI界面图片、视频、摄像头三路输入的实现4.1 推理代码的最小闭环拿到best.pt后推理的最小闭环是加载权重、对一帧图像做检测、画出标注结果。ultralytics的predict接口把预处理、推理、后处理都封装好了代码量很小import cv2 from ultralytics import YOLO # 加载训练好的雾天权重 model YOLO(foggy_train/weights/best.pt) def infer_image(image_path, save_pathNone, conf0.35, iou0.45): 图片推理返回标注图和results对象 img cv2.imread(image_path) if img is None: raise ValueError(f无法读取图片: {image_path}) results model.predict(img, confconf, iouiou, verboseFalse) annotated results[0].plot() # 在原图上绘制检测框和标签 if save_path: cv2.imwrite(save_path, annotated) return annotated, results[0]# 使用示例 annotated, result infer_image(foggy_test.jpg, save_pathoutput.jpg) print(result.boxes.cls) # 类别序号 print(result.boxes.conf) # 置信度 print(result.boxes.xyxy) # 左上右下坐标conf是置信度阈值雾天场景我习惯设0.35而不是默认的0.25因为雾天模型输出的置信度整体偏低阈值太低会导致大量误检框。iou是NMS的IoU阈值控制重叠框的合并力度0.45在密集车流场景下能减少框之间互相吞并的问题。在Ubuntu 20.04上CPU部署也很简单pip install ultralytics opencv-python一条命令就够CPU模式下推理一帧640图像大概200~400ms图片和视频离线处理完全能接受。4.2 GUI界面设计PyQt5三路输入与信号槽GUI用PyQt5做布局是左侧控制区、右侧显示区。三个输入按钮分别对应图片、视频、摄像头一个停止按钮控制中断一个导出按钮把当前结果写盘。界面卡死的根源是推理阻塞了UI线程所以推理必须丢到QThread里跑通过信号把标注好的帧回传到主线程刷新显示。import sys import cv2 from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, QFileDialog, QVBoxLayout, QHBoxLayout) from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtGui import QImage, QPixmap from ultralytics import YOLO class DetectThread(QThread): 推理线程负责读取输入源并执行检测发信号回主线程 frame_ready pyqtSignal(object) finished_all pyqtSignal() def __init__(self, model, source, conf0.35): super().__init__() self.model model self.source source # 图片路径 / 视频路径 / 摄像头索引0 self.conf conf self._running True def run(self): if isinstance(self.source, int): cap cv2.VideoCapture(self.source) # 摄像头 else: cap cv2.VideoCapture(self.source) # 视频文件 while self._running: ret, frame cap.read() if not ret: break results self.model.predict(frame, confself.conf, verboseFalse) annotated results[0].plot() self.frame_ready.emit(annotated) cap.release() self.finished_all.emit() def stop(self): self._running Falseclass MainWindow(QWidget): def __init__(self): super().__init__() self.model YOLO(foggy_train/weights/best.pt) self.thread None self.current_frame None self._init_ui() def _init_ui(self): self.setWindowTitle(雾天车辆行人检测系统) self.setGeometry(100, 100, 1000, 620) self.btn_image QPushButton(选择图片, self) self.btn_video QPushButton(选择视频, self) self.btn_camera QPushButton(开启摄像头, self) self.btn_stop QPushButton(停止, self) self.btn_export QPushButton(导出结果, self) self.label_display QLabel(请选择输入源, self) self.label_display.setAlignment(Qt.AlignCenter) layout QHBoxLayout() left QVBoxLayout() left.addWidget(self.btn_image) left.addWidget(self.btn_video) left.addWidget(self.btn_camera) left.addWidget(self.btn_stop) left.addWidget(self.btn_export) layout.addLayout(left) layout.addWidget(self.label_display, stretch1) self.setLayout(layout) self.btn_image.clicked.connect(self.select_image) self.btn_video.clicked.connect(self.select_video) self.btn_camera.clicked.connect(self.start_camera) self.btn_stop.clicked.connect(self.stop_all) self.btn_export.clicked.connect(self.export_result) def select_image(self): path, _ QFileDialog.getOpenFileName( self, 选择图片, , Images (*.jpg *.png *.bmp)) if path: results self.model.predict(path, conf0.35, verboseFalse) self.current_frame results[0].plot() self.show_frame(self.current_frame) def start_camera(self): if self.thread is not None: return self.thread DetectThread(self.model, 0) self.thread.frame_ready.connect(self.show_frame) self.thread.finished_all.connect(self.on_thread_done) self.thread.start() def stop_all(self): if self.thread: self.thread.stop() self.thread None def show_frame(self, frame): rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) # 按窗口宽度缩放显示保持比例 scaled qimg.scaled(self.label_display.width(), self.label_display.height(), Qt.KeepAspectRatio) self.label_display.setPixmap(QPixmap.fromImage(scaled))信号槽机制是这段代码的核心。DetectThread在子线程里跑推理每帧处理完把标注图通过frame_ready信号发出来主线程的show_frame槽负责把OpenCV的BGR图像转成QImage再显示。这里有个容易被忽视的点emit的frame对象在跨线程传递时并没有被拷贝PyQt的队列连接会把Python对象作为引用传递如果下一帧推理开始后原frame被复用或释放显示端可能看到花屏——稳妥做法是emit时传frame.copy()或者在emit前把标注图缩略到显示尺寸减少内存占用。4.3 摄像头实时推理采集线程与推理线程分离摄像头场景最大的问题是延迟。如果在一个循环里先读帧再推理推理时间会累积到采集间隔里画面越来越卡。常见做法是采集和推理分离一个线程只读摄像头帧放进队列另一个线程从队列取帧做推理。上面DetectThread的方案是串行的演示够用但如果要跑实时监控建议改成生产消费模型import queue import threading import cv2 from ultralytics import YOLO class CameraPipeline: def __init__(self, model_path, cam_index0, conf0.35): self.model YOLO(model_path) self.cap cv2.VideoCapture(cam_index) self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 关键减小缓冲堆积 self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) self.queue queue.Queue(maxsize2) self.stop_event threading.Event() def capture_loop(self): while not self.stop_event.is_set(): ret, frame self.cap.read() if not ret: break if self.queue.full(): try: self.queue.get_nowait() # 丢旧帧保证低延迟 except queue.Empty: pass self.queue.put(frame) def infer_loop(self): while not self.stop_event.is_set(): try: frame self.queue.get(timeout0.5) except queue.Empty: continue results self.model.predict(frame, conf0.35, verboseFalse) annotated results[0].plot() cv2.imshow(foggy detection, annotated) if cv2.waitKey(1) 0xFF ord(q): self.stop_event.set()queue.maxsize设置成2是刻意为之如果队列过长采集线程和推理线程的积压会导致延迟线性增长。新帧到达时队列已满就直接丢旧帧保证显示的永远是接近当前时刻的检测结果。cam_index0是第一个摄像头在笔记本上通常是内置摄像头外接USB摄像头有时是1或2需要逐个试。CAP_PROP_BUFFERSIZE设成1能减少摄像头驱动内部的帧积压这个参数在V4L2接口下效果明显在Windows的Media Foundation下可能不生效需要配合丢帧逻辑兜底。5. 雾天检测系统避坑指南5个常见的翻车场景5.1 去雾后检测效果反而变差现象测试时先跑DCP去雾再送进检测器mAP比不去雾还低了2~3个点轻雾场景尤其明显。原因DCP在天空区域失效产生伪影去雾后的图像引入了非自然的色块和边缘检测模型把这些伪影当成目标特征另外去雾本身改变了图像分布训练分布是“雾图→标注”推理时却输入“去雾图→标注”两段分布不一致。解决轻雾和中雾场景不要做去雾直接靠雾天增强训练的权重就地检测。只有浓雾场景才考虑去雾而且去雾后最好叠加“截断雾增强”策略——用enhance_haze把透射率下限从0.1抬到0.3保留一部分雾感避免过度恢复。实验对比是唯一的决策依据拿同一份真实雾天验证集把不去雾、DCP去雾、轻度去雾三条链路跑一遍用mAP说话。5.2 GUI界面卡死无响应现象点击视频推理后窗口直接变成“白屏无响应”几秒后系统提示程序未响应。原因推理在GUI主线程里同步执行视频每帧推理200ms以上事件循环被阻塞鼠标点击和窗口重绘全部排队。这是PyQt开发最典型的踩坑点。解决所有推理放到QThread主线程只负责接收信号刷新界面。注意线程里不要直接操作控件PyQt的控件只能在主线程访问跨线程操作会崩溃或无响应。用信号槽传数据是标准做法。另外视频结束时一定要在finished_all信号里做清理否则线程对象引用残留第二次点“选择视频”会同时启动两个推理线程帧率反而更低。5.3 视频推理显存持续增长直到溢出现象单张图片推理显存占用正常一跑视频十几分钟显存从4GB涨到8GB最后OOM崩溃。原因循环里每次predict的results对象被累积到列表里或者annotated帧被存进了全局变量不再使用的帧没有被释放。CUDA显存的分配和释放不是即时的TensorFlow和PyTorch都有缓存机制看起来占用只增不减。解决循环里不要累积results列表用完的frames要显式del必要时torch.cuda.empty_cache()回收显存。还有一个隐性因素视频分辨率大于640时ultralytics默认按长边缩放缩放后的尺寸如果并不是倍数对齐每次推理的输入shape会变化导致CUDA重新分配缓存。可以强制imgsz640让所有帧统一尺寸显存占用会稳定在一个区间内。5.4 摄像头画面延迟越来越高现象刚打开摄像头时延迟约200ms跑了5分钟后延迟增长到2秒以上画面对不上实际动作。原因采集线程读帧的速度远大于推理速度摄像头驱动缓冲区和内部队列都在积压旧帧。推理端处理第10帧时采集端已经读到了第50帧显示的是10帧前的内容。解决按4.3的队列方案把队列长度限制在2帧以内新帧到达时丢弃旧帧。另一个隐蔽参数是摄像头的曝光和白平衡自动调节——在雾天环境下自动白平衡会频繁漂移导致相邻帧亮度差异巨大检测置信度抖动。手动固定曝光和白平衡参数能显著提升稳定性cap.set(cv2.CAP_PROP_AUTO_WB, 0) cap.set(cv2.CAP_PROP_WHITE_BALANCE_BLUE_U, 4000)5.5 导出结果与显示画面坐标对不上现象检测框在GUI上显示位置正确但导出到CSV后用坐标在原图上画框框整体偏移了几个像素。原因GUI显示时做了缩放适配窗口大小但导出用的坐标是原图坐标两者坐标系不一致另外ultralytics的plot()画框自带线宽和字体偏移画出来的框本来就比原始xyxy坐标向外扩了一圈。解决导出CSV只记录原始检测坐标不要记录GUI缩放后的坐标。如果非要导出“所见即所得”的标注图直接用results[0].plot()的结果写盘而不是自己用cv2.rectangle叠加。如果自己裁剪目标区域用于存档注意xyxy是浮点数裁剪前要int()取整并且对越界坐标做clip否则OpenCV会报错或截断。6. 检测结果导出与性能优化让系统真正可用6.1 结果导出的三种格式与实现导出功能按用途分三种CSV记录检测明细、标注图片用于人工复核、标注视频用于汇报演示。CSV是最通用的交换格式标注图最直观标注视频适合留档。一个实用的导出模块可以这样组织import csv import cv2 def export_csv(results_list, output_path): 导出检测明细frame_id、类别、置信度、坐标 with open(output_path, w, newline) as f: writer csv.writer(f) writer.writerow([frame_id, class_name, confidence, xmin, ymin, xmax, ymax]) for frame_id, result in enumerate(results_list): for box in result.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) cls int(box.cls[0]) writer.writerow([frame_id, result.names[cls], f{conf:.4f}, int(x1), int(y1), int(x2), int(y2)]) def export_video(source_path, model, output_path, conf0.35): 逐帧推理并把标注结果写成视频 cap cv2.VideoCapture(source_path) fps cap.get(cv2.CAP_PROP_FPS) w int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) writer cv2.VideoWriter(output_path, cv2.VideoWriter_fourcc(*mp4v), fps, (w, h)) while True: ret, frame cap.read() if not ret: break result model.predict(frame, confconf, verboseFalse)[0] writer.write(result.plot()) cap.release() writer.release()csv格式里frame_id对应视频帧序号后续做数据分析或轨迹跟踪时可以用这个字段对齐。VideoWriter的编码器选mp4v兼容性最好如果输出文件无法播放换MJPG编码器试试。6.2 推理性能优化的几个实操技巧推理速度直接决定系统能不能用于实时监控几个提高吞吐量的技巧按优先级排列半精度推理是第一个要做的模型加载后调.half()在GPU上推理速度提升约40%显存占用减半但只在支持FP16的显卡上生效CPU不支持半精度加速。model YOLO(best.pt) model.half() # GPU推理先转半精度帧采样是第二个技巧监控场景目标不会在几帧内突变每推理一帧、后续两帧直接复用检测结果视频画面仍然连贯。这个策略在帧率60fps的监控下无感但推理负载降为原来的三分之一。第三个技巧是控制推理分辨率雾天场景imgsz提高到960对小目标友好但如果硬件带不动优先保持640并对输入做ROI截取——只在画面中下半部分做检测因为车辆和行人几乎不会出现在天空区域既能提升帧率又能减少雾天天空区域的误检。我现在的习惯是维护一个配置字典把conf、imgsz、采样间隔、导出格式集中管理换场景只要改配置不用动代码省了很多来回调试的功夫。这套方案从训练到落地的链路已经完整剩下的就是在你自己的数据上跑一轮对比实验找到合适的beta和conf组合。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询