想用 YOLOv5 做实时摄像头检测的人,基本都会卡在同一个地方:模型跑起来了,但画面卡成 PPT;视频是流畅了,但目标框乱飘,该检的检不到,不该框的框一堆。这条路上我踩了无数次坑,把官方detect.py改到几乎重写,才摸清楚那套“复制即用”的配置到底该怎么调。
这篇内容不是一个简单的环境安装教程,而是一整套围绕“实时视频检测”的优化方案。除了把 YOLOv5 跑起来,还会着重处理三个核心痛点:帧率低导致卡顿、漏检和误检并存、摄像头画面与模型预期不匹配。内容面向两类人:一类是想快速把模型集成进业务系统的开发者,另一类是正在参加竞赛或做课程设计、需要演示稳定效果的同学。我会把涉及的关键参数、排查思路、甚至代码结构全部拆开讲清楚,照着抄就能拿到一个流畅且相对精准的实时检测效果,不用再从零摸索。
1. 整体设计思路与项目架构拆解
1.1 为什么 YOLOv5 依然是实时视频检测的稳妥选择
实时目标检测可以选的模型很多,比如 YOLOv8、YOLOX、RT-DETR,但 YOLOv5 在最看重“部署稳定”的场景里依然是很好的选择。原因有两个实际因素:一是它的预训练权重和社区生态极度成熟,环境兼容性问题少,几乎任何显卡都能跑起来;二是它配套的训练、导出、量化工具链完整,从pytorch权重到onnx、tensorrt的转换路径非常顺滑。
和更新版本的模型相比,YOLOv5 在网络结构上虽然没有那么多新花样,但它的检测头和 NMS 逻辑久经考验,在低算力设备上的表现非常可控。很多人盲目追求高精度的新模型,却忽略了一个现实问题:实时视频检测和单张图片检测的难点根本不是同一个维度。单张图你只需要关心准不准,而视频流你必须同时关心“每一帧都能在限制时间内处理完”。YOLOv5 在处理这种连续帧场景时,模型体积和计算量的平衡点最好调,这也是我最终选择它的核心理由。
1.2 完整的数据流与模块划分
整个实时检测项目可以划分为五个核心模块:视频源接入、帧预处理、模型推理、后处理过滤、结果可视化与输出。每个模块单独解耦,才能做到某一环出现性能瓶颈时精准优化。
我常用的架构是生产者-消费者模式。视频读取线程作为生产者,只负责从摄像头或视频文件读取原始帧并放入队列;推理线程作为消费者,从队列取出帧做预处理、推理和 NMS。这样的好处是视频解码和模型推理互不阻塞,即使某一帧推理稍慢,也不会导致画面卡顿,只会轻微增加延迟。
用 OpenCV 读取视频的默认行为是同步阻塞模式,如果直接循环cap.read()后立刻推理,帧率完全受推理时间支配。改成线程分离后,摄像头采集可以保持满帧率运行,而推理线程按自身能力消费帧,画面输出的流畅度会明显提升,这在低算力设备上效果尤其显著。
2. 环境准备与依赖配置详解
2.1 环境版本搭配原则
YOLOv5 项目对版本兼容性比较敏感,尤其是 PyTorch 和 OpenCV 的版本组合。以我实际部署的经验,推荐使用Python 3.8 或 3.9、PyTorch 1.13 或 2.0、OpenCV 4.5 以上的组合。Python 3.10 以上虽然也能跑,但在部分老版本torchvision上会出现算子兼容问题。
CUDA 版本的匹配是整个环境配置中最容易翻车的点。建议直接通过 PyTorch 官方的pip命令安装对应 CUDA 版本的 PyTorch,不要单独安装显卡驱动官网的 CUDA Toolkit。pip安装的 PyTorch 自带 CUDA 运行库,和其他系统组件耦合度低,环境更干净。
注意:YOLOv5 在
pip install后还需要执行pip install -r requirements.txt,其中有几个库必须精确安装特定版本,比如numpy不能高于 1.24(否则部分旧版 OpenCV 会报错),protobuf需要 3.20.x 版本。很多“跑不起来”的问题都出在这两个版本上。
2.2 模型权重文件的选型策略
选模型权重绝对不是越大的越好,而是要根据你的推理设备和帧率要求来决定。我整理过一个完整的选择表:
| 模型文件 | 输入分辨率 | mAP 水平 | 推理耗时参考 | 适用场景 |
|---|---|---|---|---|
| yolov5s.pt | 640x640 | 中等 | 快,约 5-8ms | 实时摄像头、边缘设备 |
| yolov5m.pt | 640x640 | 中高 | 中等,约 10-15ms | 算力充足的桌面设备 |
| yolov5l.pt | 640x640 | 高 | 较慢,约 20ms+ | 离线视频分析或高端 GPU |
| yolov5x.pt | 640x640 | 最高 | 很慢,约 30ms+ | 精度优先的离线任务 |
这里的耗时数据在我自己的设备(某款 6GB 显存的显卡)上实测得到,不同设备会有差异,但量级可以供参考。对于绝大多数实时视频检测需求,yolov5s和yolov5m是性价比最高的。根据我的实测,s模型在性能不是很好的显卡上依然能保持稳定输出,而m模型精度更高但帧率会有所下降,选型时依据自己的硬件条件来就好。
2.3 OpenCV 编译特性检查
OpenCV 对实时视频的影响远超很多人想象。默认 pip 安装的 OpenCV 不包含 FFmpeg 支持,这会导致某些视频格式读取失败或帧率剧烈波动,尤其是 MP4 封装格式。查一下是否使用了带 FFmpeg 的构建版本很容易:执行cv2.getBuildInformation(),如果输出里Video I/O部分的FFMPEG为YES,说明是完整版。
我遇到过多次采集正常但视频文件无法播放的问题,最后检查发现是安装了精简版 OpenCV,换掉之后问题直接解决。摄像头调用和视频文件解复用这两个功能,对 OpenCV 版本的依赖非常强,把它弄错是最容易被忽略的环境问题。
3. 性能优化实战:根治卡顿
3.1 瓶颈定位的黄金法则
想优化性能,第一步不是调代码,而是找到瓶颈在哪个模块。我的经验是,先关闭所有处理逻辑,只做视频读取并显示,记录实时帧率;再开启推理,对比帧率下降幅度。如果开启推理后帧率骤降,说明瓶颈在模型推理阶段;如果帧率没变但显示延迟很大,说明是队列积压和缓冲问题。
这种对比测试法能给你最直观的优化方向。正常情况下的目标是:视频读取线程能保持 30fps 左右,推理线程每帧耗时控制在 50ms 以内,显示缓冲控制在 2-3 帧以内。如果任一指标超出范围,就直接定位到需要优化的位置,对症下药。
3.2 从数据源头降载:预处理优化
预处理环节是最容易浪费算力的地方。YOLOv5 默认预处理包含三个操作:仿射变换、颜色通道转换、归一化。其中letterbox函数在每次推理时都会被调用,这项操作在 Python 里用循环处理像素是巨慢的,几乎肯定会拖慢整个流程。
解决方式:不要用官方代码里的letterbox做逐像素缩放,改为直接调用cv2.resize加上自己实现的填充逻辑。cv2.resize底层是 C++ 实现,速度比 Python 循环快几个数量级。另外,归一化操作可以合并到模型中,在导出 ONNX 或使用半精度推理时,把除以255这一步直接融合进权重里,推理时完全不额外计算。
实操心得:推理线程内部不要做任何涉及逐像素的 Python 循环,所有图像操作一律用 OpenCV 或 NumPy 的向量化操作。这是个很关键的加速思路,把预处理时间从 5-8ms 压到 1-2ms 是很常见的提升。
3.3 推理核心加速:精度、批处理与引擎选择
推理环节主要靠三招提速:半精度、批处理和推理引擎优化。
半精度(FP16)是最简单的加速方式。默认 YOLOv5 加载模型后是 32 位浮点,保存显存浪费在精度完全用不上的系数上。调用model.half()后,模型使用 16 位浮点推理,显存占用直接减半,推理速度提升 15%-30%,而精度损失在实时检测任务里几乎不可感知。
批处理是容易被忽略的技术手段。单帧推理时,GPU 的并行计算能力没有得到充分利用。如果你的场景是同时分析多路摄像头的视频流,可以把多路的帧拼成一个 batch 一起推理,吞吐量提升非常可观。举个实际例子:一路摄像头用batch=1推理,三路摄像头如果把帧拼接后用batch=3推理,总耗时通常只有单路的 1.5 倍左右,三路同时跑的帧率反而比分别跑更高。
推理引擎方面,实测效果排序为:TensorRT 大于 ONNX Runtime GPU 约 30%,ONNX Runtime GPU 又比原生 PyTorch 高约 25%。如果你有部署环境并且追求性能极限,TensorRT 是最值得投入的方向。但这里有一个非常关键的细节:用 TensorRT 导出静态尺寸模型时,推理分辨率会被固定。如果训练时用的是 640x640,那么部署时也必须是 640x640,否则会报错或强制回退动态尺寸(动态尺寸启动耗时极高)。这一点在导出前就要想清楚。
3.4 控制延迟的关键:推理队列与跳帧策略
前面提到生产者-消费者模式,实际落地时队列长度需要严格控制。实时视频检测最怕的不是帧率低,而是延迟高。如果队列积压了几十帧,画面显示的是几秒前的状态,即使帧率显示 30fps,用户体验也完全不可用。
我这里给出一个经过实测的队列参数:视频源队列长度上限为 3 帧,显示缓冲队列上限为 2 帧。超过上限直接丢弃旧帧,而不是丢弃新帧。这个细节很关键:丢旧帧保证画面永远是“最新”的,丢新帧则会导致画面卡顿感明显加重。
跳帧策略则要视推理耗时而定。如果单帧推理耗时在 30ms 以内,不需要跳帧;如果推理耗时接近 50ms 甚至更久,就必须考虑每隔一帧推理一次、另一帧直接复用上一次的检测结果。这样设置的实际效果是:画面刷新率保持 30fps,检测输出每秒 15 帧,但由于检测框在连续帧之间变化很小,肉眼基本感知不到差异,而流畅度却从 15fps 直接提升到 30fps。
3.5 显存泄漏与显存占用优化
长时间运行实时视频检测,显存泄漏是最隐蔽的坑。症状很明显:刚开始跑 GPU 占用 2GB,跑一个小时后占用逐步涨到 5GB,最后崩掉。隐患源通常是两个:一是每次推理都新建输出张量而没有及时释放引用;二是数据加载线程和推理线程之间传递图像数据时不小心被累积成了历史引用链。
排查显存泄漏可以用一条简单命令逐帧观察:nvidia-smi --query-gpu=memory.used --format=csv,如果数值单调递增不回落,那就是有引用未释放。常规解法是避免在循环里重复创建大数组,尽量复用预先分配好的缓冲区。例如:
# 正确做法:一次性定义输出列表,每轮循环只修改内容而不是新建 result_buffer = [] while True: frame = queue.get() # 清空旧的检测结果,重用同一块内存 result_buffer.clear() results = model(frame) # 将新的检测结果填充进去 result_buffer.extend(results.xyxy[0].cpu().numpy())同时,在推理循环里定期调用torch.cuda.empty_cache()也是个不错的习惯。它不会解决真正的内存引用泄漏,但能帮助显存碎片整理,让显存使用更持久稳定。
4. 解决检测不准:漏检与误检的调优方案
4.1 置信度阈值與 IoU 阈值的组合艺术
很多人对置信度阈值理解得太简单,以为调高阈值能减少误检,调低能减少漏检,实际没那么直接。YOLOv5 推理时的两个关键参数分别是conf_thres和iou_thres,它们会互相影响,必须一起调整才能达到精准效果。
对于实时视频检测,我通常把conf_thres设置在 0.35 到 0.5 之间,iou_thres设置在 0.4 到 0.5 之间。举例来说:如果目标是检测道路上的车辆,车辆特征明显,误检本来就少,可以把置信度阈值调高到 0.45,过滤掉远处模糊的小目标带来的噪声;如果场景是检测密集人群或小目标,特征被遮挡严重,置信度阈值要降到 0.25 左右,否则会漏掉大量真实的检测目标。
这里有个经验法则:当目标物体容易被遮挡、尺寸偏小、或者摄像头画质不佳时,置信度阈值不要高于 0.3;当目标特征清晰、摄像头分辨率足够、画面光照稳定时,置信度阈值可以提高以避免不必要的误报。视频检测和单帧图检测不一样,连续帧之间有大量冗余信息,稍微降低单帧的置信度阈值,然后通过时间序列过滤掉一闪而过的误检,效果远好于单帧里把阈值调得很高。
4.2 摄像头画面质量对检测精度的影响
摄像头自带的画面处理机制往往是检测不准的元凶之一。手机摄像头或网络摄像头默认启用的自动白平衡、自动曝光和自动对焦,会在画面内容变化时不断调整成像参数,导致颜色和亮度发生偏移。而 YOLOv5 预训练模型学习的是静态图片的数据分布,画面参数一旦严重偏离训练集分布,精度立刻暴跌。
最典型的场景是摄像头自动白平衡导致整个画面偏绿或偏蓝,模型把一些目标识别成颜色相近的类别。解决办法有两个思路:一是手动锁定相机参数,把cv2.CAP_PROP_AUTO_WB设为 0,然后手动设置白平衡值;二是加上简单的图像预处理,调整亮度饱和度和对比度,把画面拉回正常范围。
我常用的一套预处理参数是:对整体亮度偏暗的摄像头画面,将对比度增强系数设为 1.2,亮度偏移设为 10。这个参数实测能显著提升暗光环境下的检测置信度,但要注意摄像头画面过曝时反而要反向调节。
4.3 目标尺度与输入分辨率的关系
“小目标检测不到”是漏检问题里最普遍的。YOLOv5 在训练时把输入图片 resize 到 640x640,如果原始画面中目标只占很小比例,resize 后目标可能只占个位数像素,模型自然检测不到。改善这个问题的核心思路是提高输入分辨率,但高分辨率又会直接拖慢推理速度,此处存在明显的权衡。
例如原分辨率为 1280x720 的画面,扩到 640x640 推理时目标尺寸被压缩,原本 30 像素的目标缩到 17 像素左右,勉强能被检出;如果把输入分辨率升到 1280x1280,目标回到 30 像素,检测精度会大幅提升,但推理耗时可能是原来的 3 倍,实时性可能保不住。
折中的方案是使用 ROI 区域裁剪检测:先用较大步长跑低分辨率的全局检测找到目标大致位置,再对目标区域进行裁剪,用低分辨率模型检测细节。这样全局帧率不掉太多,局部的小目标也能被检出。对于多目标分散的场景,这种“全局定位 + 局部放大”的组合策略效果很好。
4.4 分类瓶颈:相似类别导致的误检
不同类别的相似性会造成持续误检。比如“人”和“背包”在遮挡严重时会被模型摇摆分类,同一目标有时框成“人”,有时框成“背包”。这种问题的根源在于模型训练的类别区分度不足,单纯调推理参数已经无法彻底解决。
缓解办法之一:修改模型输出层的类别过滤规则,把容易混淆的两个类别在输出阶段合并处理。例如检测业务里同时需要“车辆”和“卡车”两类,但在实际场景中区分度不够,可以将它们合并为“车辆”,在后处理逻辑里通过目标尺寸再区分细类,这样能大幅减少分类抖动。
更根本的解决方案是在自己的数据集上做短时间微调。哪怕只有几百张带有标注的现场截图,对模型进行 20-30 轮的微调训练,在场景特异性较强的项目里,这个操作带来的精度提升可能比任何推理参数调整都大。YOLOv5 提供了非常简洁的迁移学习接口,在预训练权重基础上只训练自己的几个类别,极低的配置成本就能完成,强烈建议尝试。
5. 完整实操:全优化方案的模块化实现
5.1 工程目录结构与模块划分
项目结构会直接决定后续迭代的顺畅程度。我把文件组织成下面的结构,每个模块职责清晰,优化时能找到对应位置而不互相干扰:
yolo_realtime/ ├── main.py # 入口,控制整个流程 ├── config.py # 全部参数集中管理 ├── dataloader.py # 视频源读取与队列管理 ├── detector.py # 推理核心封装 ├── postprocess.py # 后处理与跟踪逻辑 ├── visualizer.py # 画面渲染与显示 └── utils.py # 工具函数集中管理参数是提高迭代效率的重要手段。我在config.py中用一个大字典存放模型路径、输入尺寸、置信度阈值、IoU 阈值、队列长度、跳帧策略等所有可调值,实际调试时只需要改一个文件,不用满代码找参数,也方便多场景切换配置文件。
5.2 视频源接入模块实现
视频源是数据流的起点,选择正确的读取方式对稳定性和效率影响很大。Opencv 的VideoCapture虽然简单,但在某些设备上会出现延迟问题。关键在于添加CAP_PROP_BUFFERSIZE属性设置,减少内部缓冲带来的延迟。
# dataloader.py 核心代码 import cv2 import threading from collections import deque class VideoStream: def __init__(self, source, queue_size=3): self.cap = cv2.VideoCapture(source) # 关键设置:关闭 OpenCV 内部缓冲,避免积压旧帧 self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) self.cap.set(cv2.CAP_PROP_FPS, 30) self.queue = deque(maxlen=queue_size) self.running = False self.thread = None def start(self): self.running = True self.thread = threading.Thread(target=self._update, args=()) self.thread.daemon = True self.thread.start() return self def _update(self): while self.running: ret, frame = self.cap.read() if not ret: continue # 如果队列满了,直接替换掉最旧的那帧 if len(self.queue) == self.queue.maxlen: self.queue.popleft() self.queue.append(frame) def read(self): if len(self.queue) > 0: return True, self.queue.popleft() return False, None def stop(self): self.running = False self.thread.join(timeout=1) self.cap.release()这个模块的核心逻辑就是:后台线程持续读帧填入有限队列,检测主线程从队列按需取用。任何时刻队列里保存的都是最新帧,实现了“帧永远最新”的目标。
5.3 推理核心封装
推理模块的核心是对 YOLOv5 的torch.hub.load或本地加载方式封装的统一接口。我建议使用本地加载而不是在线下载,保证部署环境离线可用和版本稳定可控。
# detector.py 核心代码 import torch import numpy as np class YOLODetector: def __init__(self, model_path, device='cuda', img_size=640, conf=0.35, iou=0.45): self.model = torch.hub.load('本地仓库目录', 'custom', path=model_path, source='local') self.device = device self.img_size = img_size self.conf = conf self.iou = iou # 初始化设备与精度模式 if device == 'cuda': self.model = self.model.to(device) self.model.half() # 半精度,大幅提速 else: self.model = self.model.float() self.model.eval() def preprocess(self, frame): # 复用 cv2.resize 实现快速 letterbox h, w = frame.shape[:2] scale = min(self.img_size / w, self.img_size / h) new_w, new_h = int(w * scale), int(h * scale) resized = cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_LINEAR) canvas = np.full((self.img_size, self.img_size, 3), 114, dtype=np.uint8) offset_x, offset_y = (self.img_size - new_w) // 2, (self.img_size - new_h) // 2 canvas[offset_y:offset_y + new_h, offset_x:offset_x + new_w] = resized return canvas, scale, offset_x, offset_y def detect(self, frame): input_tensor, scale, padx, pady = self.preprocess(frame) # BGR2RGB 和 HWC2CHW + 归一化整合 input_tensor = cv2.cvtColor(input_tensor, cv2.COLOR_BGR2RGB) input_tensor = input_tensor.transpose(2, 0, 1).astype(np.float32) / 255.0 input_tensor = torch.from_numpy(input_tensor).unsqueeze(0) if self.device == 'cuda': input_tensor = input_tensor.half().to(self.device) else: input_tensor = input_tensor.float().to(self.device) with torch.no_grad(): preds = self.model(input_tensor)[0] return self._postprocess(preds, scale, padx, pady)这里的postprocess需要对检测框做坐标还原计算,把模型输出的 640x640 坐标系转换回原始帧的坐标系,否则画框位置会偏移。这是很多教程容易忽略的细节。
5.4 后处理与坐标还原实现
# postprocess.py 核心代码 def _postprocess(self, preds, scale, padx, pady): # preds 尺寸为 [1, num_preds, 6],6 列分别为 x1, y1, x2, y2, conf, class preds = preds.squeeze(0).cpu().numpy() # 过滤低置信度预测(这里做了预过滤,后面 NMS 更可控) mask = preds[:, 4] >= self.conf preds = preds[mask] if len(preds) == 0: return np.empty((0, 6)) # 使用 YOLOv5 utils 自带的 NMS 方法做多类别 NMS from utils.general import non_max_suppression tensor_preds = torch.from_numpy(preds).unsqueeze(0) nmsed = non_max_suppression(tensor_preds, self.conf, self.iou)[0] results = [] if nmsed is not None: nmsed = nmsed.cpu().numpy() for det in nmsed: x1, y1, x2, y2 = det[:4] conf, cls = det[4], int(det[5]) # 还原回原图像坐标 x1 = (x1 - padx) / scale y1 = (y1 - pady) / scale x2 = (x2 - padx) / scale y2 = (y2 - pady) / scale # 坐标边界裁剪,防止画出画面外 x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(frame_w, x2), min(frame_h, y2) results.append([x1, y1, x2, y2, conf, cls]) return np.array(results)通过分层置信度预过滤加后续 NMS,能有效控制输出框数量,减少无意义的计算。实时视频检测里,检测结果数量直接影响画框和渲染的性能,这个预过滤步骤对流畅度贡献很大。
5.5 主流程:队列、跳帧与渲染的最终组装
主流程要把前面几个模块串起来,并加上时间序列平滑逻辑(亮点)。
# main.py 关键逻辑 import cv2 from dataloader import VideoStream from detector import YOLODetector def main(): # 加载配置参数 stream = VideoStream(0, queue_size=3).start() detector = YOLODetector(model_path='best.pt', device='cuda') prev_detections = [] frame_count = 0 while True: ret, frame = stream.read() if not ret: continue frame_count += 1 # 跳帧策略:每两帧推理一次,中间一帧用上次结果 if frame_count % 2 == 1: detections = detector.detect(frame) prev_detections = detections else: detections = prev_detections # 在原始帧上绘制检测框,渲染逻辑独立于推理 for det in detections: x1, y1, x2, y2, conf, cls = det label = f'{cls_name[int(cls)]} {conf:.2f}' cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, label, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) cv2.imshow('YOLOv5 Realtime', frame) if cv2.waitKey(1) == ord('q'): break stream.stop() cv2.destroyAllWindows() if __name__ == '__main__': main()整体跑起来之后,核心性能指标应该至少达到:常规模块下原分辨率 720p 画面稳定 20-30fps 输出,且画面无积压延迟。这个标准已经能覆盖大多数课程设计和轻量业务场景。
6. 常见问题排查与避坑速查
6.1 典型问题排查对照表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 画面严重卡顿,每秒 5 帧 | 推理阻塞在读帧循环里,分辨率过高 | 分离读帧与推理线程,启用跳帧 |
| 摄像头画面延迟数秒 | OpenCV 内部缓冲积压 | 设置CAP_PROP_BUFFERSIZE=1,清空旧帧 |
| 检测框全部偏左上 | 坐标还原公式错误 | 检查padx/pady和scale的计算是否正确 |
| 小目标完全检测不到 | 输入分辨率过低,或置信度阈值过高 | 提升输入分辨率,降低conf_thres到 0.25 |
| 相同目标反复横跳类别 | 语义相似类别太多 | 合并易混淆类别,后处理按尺寸细分 |
| 长期运行后突然崩掉 | 显存泄漏 | 复用输出缓冲区,周期性执行empty_cache() |
| 画面颜色发绿/发蓝导致误检 | 自动白平衡干扰 | 锁定CAP_PROP_AUTO_WB=0,手动设白平衡 |
| 模型加载耗时极长 | 使用 PyTorch 自带 JIT 加载 | 第一次推理后保存为 TorchScript 避免重复加载 |
| 视频文件读取帧率不稳 | OpenCV 不支持该编码格式 | 使用带 FFmpeg 的 OpenCV 版本 |
6.2 推理速度与精度的可调参数推荐表
以下数值是我在多个实际项目中调试出的一组可靠基线,可以直接作为启动配置使用:
| 应用场景 | conf_thres | iou_thres | 输入尺寸 | 推荐模型 |
|---|---|---|---|---|
| 室内摄像头固定机位 | 0.35 | 0.45 | 640 | yolov5s |
| 户外车辆检测(光照变化大) | 0.30 | 0.40 | 640 | yolov5m |
| 密集人群人形检测 | 0.25 | 0.50 | 1280 | yolov5m |
| 小目标远处检测(无人机视角) | 0.20 | 0.45 | 1280 | yolov5l |
| 高精度离线视频逐帧分析 | 0.15 | 0.50 | 1280 | yolov5x |
这些参数不是固定的绝对答案,但可以当作起步点。实际项目里我经常在同一个算法流程里,对不同区域或不同时间段动态切换阈值组合。场景光照变化剧烈时,定时重置自定义白平衡参数比固定阈值更实用,配合跳帧策略,整个系统的稳定运行很可靠。调优时每次只改一个参数,保持其他不变,用控制变量法寻找最佳组合,这样能找到真正的规律,而不是靠瞎试碰运气。
6.3 几条足够硬核的排错经验
排查“检测明显不准”的问题时,建议先用官方自带权重跑同一个场景,再换成自己训练过的权重做对比。如果官方权重输出正常而自定义权重很差,问题基本在模型本身而非代码流程,优先回去补训练数据,调推理参数只能临时缓解。
排查卡顿问题时,彻底关闭推理只跑视频流,如果这时的帧率都比不上手机摄像头的实时预览体验,问题可能出在视频流本身而和 YOLO 优化无关。
调试时建议每改一组参数,就把画面帧率和每帧详细耗时打印到终端。这些原始数据看起来琐碎,但对判断优化方向帮助极大。我一般会在代码里加一行:
print(f"FPS: {fps:.1f}, Preprocess: {t_pre:.1f}ms, Inference: {t_inf:.1f}ms, NMS: {t_nms:.1f}ms")这行输出能让你在跑视频的时候直接看到每个阶段耗时占比,判断瓶颈到底在哪。
做了一个完整的 YOLOv5 实时视频检测项目后,我个人的最大体会是:实时检测的难点其实不在模型本身,而在工程层面的数据调度、资源管理和场景适配。想拿到流畅且精准的效果,一定要从全链路视角考量每个环节,把读帧、预处理、推理、后处理、渲染拆开优化,再用策略把它们重新组合起来。本文提供的代码结构和参数基线可以直接用于复现。真正面向特定需求时,建议拍摄现场视频素材,截取有代表性的几百帧图片做迁移学习微调,得到的效果会远好于通用权重直接部署。祝你把这套方案跑顺,少踩几个我趟过的坑。