简介:这是一份基于OpenCV内置方法的行人检测实战项目资源,面向需要快速上手物体检测的初学者或正在完成相关课程设计的开发者,能够帮助理解如何利用传统计算机视觉手段实现行人的识别与定位,无需依赖深度学习框架即可入门。压缩文件共包含5个文件:1个PDF说明文档用于讲解检测原理与代码结构,1个Python可直接运行的检测脚本,以及3张JPG测试图片用于检验和对比检测效果;整体包大小约1.03MB,轻量且便于下载。目前已有1340人学习该资源,口碑和可读性初步得到验证。使用这份资源时,读者可以从零开始搭建一个行人检测示例:先通过PDF掌握OpenCV内置方法的基本流程与参数含义,再运行Python脚本观察实际输出,并借助多张测试图片分析不同场景下的检测表现。整个过程能帮助读者同时积累代码调试经验和目标检测的基础实践能力,适合作为后续学习更复杂检测算法或深度模型的过渡参考。
1. 物体检测实战:OpenCV 内置方法做行人检测,为什么不建议直接跑默认参数
「物体检测实战:使用OpenCV内置方法实现行人检测.zip」——这个标题把三件事说清楚了:检测对象是行人,技术栈锁定在 OpenCV 自带能力,交付物是可以直接落地的代码包。用 OpenCV 做行人检测,最快三行就能出效果:加载官方默认行人检测器、绑定到 HOGDescriptor、detectMultiScale 扫一遍图。但真把这套默认参数扔到园区监控或者店门口的视频里,大多数人第一反应是「这什么玩意儿」——框乱飘、树影当人、漏检严重,帧率还掉到个位数。这个方案的价值恰恰在这里:它是理解检测原理最好的入门素材,也是做原型最快的一条路,不需要训练、不需要深度学习环境,CPU 就能跑。下面从原理到参数、从避坑到 DNN 进阶,把它一次讲透。
2. HOG 特征与 SVM 分类:OpenCV 内置行人检测的原理与最小代码
2.1 为什么说 HOG+SVM 是「内置方法」的标准答案
OpenCV 里的行人检测「内置方法」,标准答案是 HOG + 线性 SVM,不是深度学习。HOG 全称方向梯度直方图,思路很朴素:把图像切成小块,统计每一块里梯度方向的分布,用这种统计特征描述人的轮廓。行人通常直立行走,头肩和四肢的梯度分布有共性,一个训练好的线性 SVM 就能把「像人的窗口」和「背景窗口」分开。
OpenCV 在发布包里直接内置了一个在 INRIA Person 数据集上训练好的 SVM 系数向量,不需要你自己训练、不需要权重文件,cv2.HOGDescriptor_getDefaultPeopleDetector()一行取出来就能用。这个设计让行人检测在 OpenCV 里几乎没有门槛,也正因为门槛低,很多人拿默认参数跑完就开始怀疑人生。
判断一个场景适不适合用内置方法,就看两点:检测对象是否姿态相对固定,以及能不能容忍一定比例的误检。行人正好介于「固定」和「不固定」之间,所以 HOG 能跑,但需要调参数。在 opencv>=4.2 的环境里,这套接口的命名已经非常稳定,Python 和 C++ 行为一致,下面的代码都是基于 4.x 写的。
2.2 跑通第一张图:detectMultiScale 最小代码与返回值的坑
先让代码跑起来,再聊调参。新建一个hog_detect_image.py,把下面这段贴进去:
import cv2 # 创建 HOG 描述符并加载内置的行人检测 SVM 系数 hog = cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) # 读图,灰度化不是必须的,但能省一点内存带宽 img = cv2.imread("street.jpg") if img is None: raise IOError("图片读取失败,检查路径") # 多尺度检测:返回矩形框列表和对应的权重列表 rects, weights = hog.detectMultiScale( img, winStride=(4, 4), padding=(8, 8), scale=1.05, ) for (x, y, w, h) in rects: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.imwrite("result.jpg", img) print(f"检测到 {len(rects)} 个行人")逻辑说明:cv2.HOGDescriptor()创建一个默认参数的 HOG 描述符,setSVMDetector把官方训练好的 SVM 系数灌进去,之后detectMultiScale在图像金字塔上滑动窗口逐一打分。返回的两个值分别是矩形框(x, y, w, h)列表和置信度权重列表,权重越高代表越像人。
这里有个新手必踩的坑:detectMultiScale返回两个值,如果你只写rects = hog.detectMultiScale(...)会直接报ValueError: too many values to unpack。倒不是说 Python 不能接收多余返回值,而是这个 API 在 OpenCV 4.x 里固定返回二元组,习惯只接一个值的人十有八九会翻车。
detectMultiScale 的核心参数语义如下表,这些参数到视频阶段还要继续调:
| 参数 | 默认值 | 作用 | 调参方向 |
|---|---|---|---|
| winStride | (4, 4) | 滑窗步长,越小检测越密越慢 | 追求速度调到 (8, 8) |
| padding | (8, 8) | 窗口边缘填充,缓解目标贴边问题 | 一般不动 |
| scale | 1.05 | 图像金字塔缩放系数,越接近 1 层数越多越慢 | 1.03~1.1 之间试 |
| finalThreshold | 2.0 | 重叠框合并阈值,相当于内置 NMS | 误检多时调大 |
第一次跑通后建议做的事:把scale改成 1.1 再跑一遍,框的数量会有肉眼可见的变化,这是感受金字塔缩放系数最直接的方式。
2.3 认识 HOGDescriptor 的构造参数:知道在哪调,但先别动
很多人查 OpenCV 文档时会看到 HOGDescriptor 有一大堆构造参数,以为调它们能提升精度。实际上项目实战里这些参数很少动,因为它们是 HOG 特征的骨架,改了特征维度就变了,原本训练好的 SVM 系数可能直接失效。
# 默认参数显式写出来是这样的,实际项目中很少手动改 win_size = (64, 128) # 检测窗口尺寸,行人默认 64x128 block_size = (16, 16) # 归一化块大小 block_stride = (8, 8) # 块滑动步长 cell_size = (8, 8) # 胞元大小,梯度直方图统计单位 n_bins = 9 # 每个胞元的梯度方向分箱数 hog_custom = cv2.HOGDescriptor( win_size, block_size, block_stride, cell_size, n_bins, )参数说明:win_size决定了检测窗口最小尺寸,如果你在 2.2 节用默认构造,win_size就是 (64, 128),这也是为什么视频流里minSize一般不设小于这个值——窗口本身就那么大。block_stride和cell_size共同决定特征向量的长度,改动任意一个,SVM 系数维度就对不上了。
所以我的建议是:构造参数保持默认,要调的是detectMultiScale的参数。如果真的需要更精细的特征,比如检测远距离小目标,优先考虑缩小输入图像而不是改 HOG 内部参数。内置检测器是「黑匣子」,我们拿到的只是 SVM 系数,不是训练代码,改特征结构等于自己重新训练,那就脱离「内置方法」的范畴了。
3. 把检测搬到视频流:摄像头与视频文件的行人检测实战骨架
3.1 逐帧检测 + 跳帧 + 缩放的标准骨架
静态图跑通之后,下一个诉求几乎必然是「能不能跑视频」。直接对每一帧调用detectMultiScale不是不行,而是大概率撑不住实时性。HOG 在 640×480 分辨率下单帧耗时通常在 50~150ms 之间,也就是纯 CPU 上大约 7~20 FPS,这还是在没有做其他图像处理的前提下。
所以视频流的第一个实战动作是「跳帧」:每 N 帧选一帧做检测,中间的帧直接复用上一帧结果。对行人这种低速目标,跳 1~2 帧完全够用。
import cv2 cap = cv2.VideoCapture("street.mp4") if not cap.isOpened(): raise IOError("无法打开视频,检查路径和编码器") # 初始化 HOG 检测器 hog = cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) skip = 2 # 每 2 帧处理 1 帧 frame_idx = 0 out = cv2.VideoWriter( "result.mp4", cv2.VideoWriter_fourcc(*"mp4v"), 15, (640, 480), ) while True: ret, frame = cap.read() if not ret: break frame_idx += 1 if frame_idx % skip != 0: continue # 统一缩放,HOG 对输入尺寸不敏感,但缩放能显著降低耗时 frame = cv2.resize(frame, (640, 480)) rects, weights = hog.detectMultiScale( frame, winStride=(4, 4), padding=(8, 8), scale=1.05, ) for (x, y, w, h) in rects: cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) out.write(frame) cv2.imshow("pedestrian", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break cap.release() out.release() cv2.destroyAllWindows()逻辑说明:VideoCapture支持视频文件和摄像头索引,video.mp4换成0就是读取默认摄像头。frame_idx % skip控制跳帧节奏,skip=2表示每两帧取一帧处理,能把有效 FPS 感知提升一倍。resize到 640×480 是把推理耗时压到可控范围的关键一步,HOG 的耗时和图像面积近似成正比,分辨率减半,耗时能降到原来的四分之一左右。
这里有个容易忽略的点:VideoWriter的输出分辨率和写入帧的分辨率必须一致,否则合成的视频打不开。代码里把帧先 resize 再写入,正好规避了这个问题。
3.2 detectMultiScale 三个必调参数:scale、winStride、minNeighbors
视频骨架跑起来后,紧接着就是调参。HOG 行人检测真正值得反复调的只有三个参数:scale、winStride、minNeighbors。等等,minNeighbors哪来的?detectMultiScale在推理阶段没有这个参数,它是cv2.HOGDescriptor.detectMultiScale内部通过finalThreshold做分组合并时隐含的逻辑。真正需要你手动调的,是finalThreshold和上面两个参数。
先看scale。它控制图像金字塔的缩放系数。scale=1.05表示每层缩小到上一层的 95%,行人越大,需要的层数越多,耗时越高。调节规律是:目标在画面里普遍偏小时用 1.03~1.05,目标离镜头近、占比大时用 1.1。一个经验值是scale从 1.05 改成 1.1,耗时能下降 30%~50%,但小目标漏检率会上升。
再看winStride。它控制滑窗步长,(4, 4)表示窗口每次移动 4 像素。步长越小,窗口位置越密,检测越细致,但计算量成倍上涨。对于 640×480 的输入,(8, 8)是一个速度与精度都比较平衡的起点。如果冷静评估后发现漏检不多但很卡,先把winStride调大,比调scale见效快。
finalThreshold是重叠框合并阈值,可以理解成内置的非极大值抑制强度。值越大,越多的重叠框会被合并成一个,误检会减少,但两个人挨得太近时有可能被并成一个框。默认 2.0 在人群稀疏的场景够用,密度大时建议保持默认,通过置信度权重过滤(见第 4 章)而不是靠调大这个值来压误检。
调参的顺序建议是:先定winStride=(8, 8)把速度稳住,再根据漏检情况调scale,最后用权重过滤处理误检,而不是一上来三个参数一起改——那样出了问题根本不知道是谁的锅。
3.3 让检测框跟得住人:minSize、ROI 与框过滤的组合用法
调完上面三个参数,检测框基本能跟住人,但还有一个高频问题:远处的行人太小,近处的行人太大,总有一端是漏的。HOG 的检测窗口是固定 64×128,输入图像里的人和这个尺寸差太多就会检测不到,所以需要手动框定合适的尺寸范围。
rects, weights = hog.detectMultiScale( frame, winStride=(8, 8), padding=(8, 8), scale=1.05, finalThreshold=2.0, ) filtered = [] for (x, y, w, h), weight in zip(rects, weights): # 权重太低说明只是"有点像人",过滤掉 if weight < 0.5: continue # 尺寸明显不合理的人为框:太扁、太大、太小都排除 if w < 40 or h < 100 or w > 400 or h > 500: continue # 长宽比在 0.3~0.8 之间才像直立行人 aspect = w / h if aspect > 0.8 or aspect < 0.3: continue filtered.append((x, y, w, h))逻辑说明:minSize和maxSize虽然可以直接传给detectMultiScale,但在真实场景里,固定尺寸上限会漏掉走近的近距离行人,所以更稳的做法是检测完之后再做一轮过滤。weight是每个框的置信度,zip(rects, weights)把它们配对遍历,逐条判断是否保留。
这套过滤条件里最实用的是长宽比判断:行人直立时高宽比通常在 0.3~0.8 之间,树影、垃圾桶、车窗反光的框往往高宽比离谱,一个aspect判断能干掉一多半误检。镜头固定的话,还可以再加 ROI 屏蔽:把不可能出现行人的区域(比如天空、树冠)用黑色掩码盖住再送进检测器,既能减少误检又能省时间。ROI 的做法很简单,用cv2.fillPoly画个多边形区域置黑就行。
4. 行人检测避坑:5 个高频问题与排查路径
4.1 No module named 'cv2':anaconda prompt 里装完 opencv-python 还是报错
现象:在 anaconda prompt 里执行pip install opencv-python成功后,运行脚本仍然报ModuleNotFoundError: No module named 'cv2'。
原因:几乎都是环境错位。anaconda prompt 默认进的是base环境,你 pip 装的包进了 base,但运行脚本用的解释器是另一个虚拟环境;或者机器上有多个 Python,pip 装到了 A 版本,命令行跑的是 B 版本。还有一种情况是包名和导入名不一致——安装包叫opencv-python,导入名是cv2,网上很多教程混着写,新手容易以为自己装错了包。
解决:先确认当前环境再装包。
conda activate your_env pip install opencv-python python -c "import cv2; print(cv2.__version__)"最后一行能打印版本号说明环境没问题。如果下载慢,可以加国内 pip 镜像源:pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple。注意装完不要关闭当前 prompt 窗口,另开新窗口会导致环境变量重新加载,偶尔会出现「明明装了却 import 不到」的假象。
4.2 detectMultiScale 返回值解包失败:ValueError: too many values to unpack
现象:单独接收rects = hog.detectMultiScale(img)时,代码直接抛异常,提示返回值太多。
原因:detectMultiScale在 OpenCV 4.x 的 Python 绑定里始终返回(rects, weights)二元组,这是 API 设计决定的,不是代码写错。
解决:用rects, weights = hog.detectMultiScale(img)接收,如果不需要权重就写rects, _ = ...。这里有个细节:权重虽然叫 weight,但数值能到 0.6~2.0 以上,不是 0~1 的概率值,所以第 3.3 节里用weight < 0.5做过滤是安全的,不会误伤正常的检测框。
4.3 误检框乱飞:树影、招牌当成行人,怎么压下来
现象:固定机位视频里,树的晃动、路牌、空调外机被反复框出来,而且框的位置每帧都跳。
原因:HOG 特征描述的是梯度分布,很多竖条状物体的梯度分布和行人轮廓高度相似,线性 SVM 打不出足够高的置信度差异。默认参数下finalThreshold=2.0只做了框合并,没有做置信度筛选,所以「有点人样」的物体全被放出来了。
解决:在detectMultiScale返回值上叠加两层过滤。第一层按置信度权重过滤,第二层按几何约束过滤(长宽比、面积上下限)。分步骤操作:先打印所有rects和weights,看看误检框的权重普遍落在哪个区间,再决定阈值。常见做法是权重阈值的初始值取 0.5,然后根据打印结果上下微调。如果误检还是压不下去,基本可以断定是场景本身太杂,HOG 的上限到了,直接跳到第 5 章换 DNN 模型。
4.4 视频流卡成 PPT:单帧耗时的估算与跳帧策略
现象:跑视频时 FPS 只有个位数,画面严重卡顿,甚至跟不上实时画面。
原因:HOG 单帧 640×480 推理耗时随场景复杂度变化很大,行人多、背景纹理丰富时能达到 150ms+,逐帧处理当然卡。另一个隐蔽因素是scale和winStride太激进,等于把计算量全砸在金字塔和密集滑窗上。
解决:先量化,再优化。写一段测速代码,看看你的机器和场景下真实耗时是多少:
import time start = time.perf_counter() rects, weights = hog.detectMultiScale(frame, winStride=(8, 8), scale=1.05) elapsed_ms = (time.perf_counter() - start) * 1000 print(f"单帧推理耗时: {elapsed_ms:.1f} ms") # 目标实时率假设是 15 FPS,每帧预算 66ms budget = 1000.0 / 15 max_skip = max(1, int(elapsed_ms / budget)) print(f"建议跳帧数: {max_skip}")逻辑说明:先测出单帧耗时的量级,再用耗时除以目标帧预算得到跳帧数。比如单帧 120ms,目标 15 FPS,那就是 66ms/帧的预算,120 / 66 ≈ 2,跳 2 帧可以达到感知上的实时。这里算的是「处理节奏」,不是把每帧的处理时间压进预算,因为跳帧意味着中间帧直接复用上一帧的检测结果,CPU 占用率是下降的。如果测出来单帧超过 300ms,说明你的场景不适合继续调 HOG 了,考虑缩小输入分辨率到 480×360,或者直接换 DNN 方案。
4.5 Linux 装 CUDA 版 OpenCV 太折腾:HOG 加速到底值不值
现象:网上教程说用 CUDA 版本 OpenCV 能给 HOG 加速,结果装的时候发现pip install opencv-python只有 CPU 版本,要 GPU 加速就得从头 cmake 编译,步骤繁琐还容易编译失败。
原因:官方 pip 包的约定就是 CPU 版本,CUDA 支持需要自己在 Linux 源码编译。cmake 编译步骤本身不难,难的是 CUDA、cuDNN、OpenCV 三者版本要匹配,任何一个对不上就白跑几个小时。
解决:我的建议是,先把第 4.4 节的测速结果拿出来算笔账。如果你只是做原型验证或者小规模部署,HOG 在 CPU 上跑到 10 FPS 配合跳帧已经够用,犯不着为它上 CUDA。如果确实有低延迟需求,更值的路径是直接上第 5 章的 DNN 模型,配合 OpenVINO 推理,同等硬件条件下效果比 HOG 好得多,而且 OpenVINO 的安装比从源码编译 OpenCV 省心得多。真要编译,先确认你的 CMake 版本和 CUDA 版本在 OpenCV 官方支持矩阵里,这个排查过程一次就能磨掉你半天耐心。
5. 内置方法的天花板与 DNN 转进:用 OpenCV DNN 模块加载预训练行人检测模型
5.1 HOG 的边界在哪:什么场景必须转 DNN
调参调得再精细,HOG + SVM 的检测精度也有天花板,这个天花板来自两个设计选择:特征是手工设计的、分类器是线性的。手工特征意味着它对遮挡、姿态变化、光照突变的表达力有限;线性分类器意味着它无法捕捉特征之间的复杂交互。当场景出现以下任一种情况时,HOG 基本就跪了:
- 行人互相遮挡,只露出半个身子
- 行人姿态夸张,跑步、弯腰、骑电动车
- 夜间红外画面,梯度信息被噪声淹没
- 密集人流,大量重叠框合并后只剩一团
所以实战项目里,HOG 适合做「野外初步筛选」或「资源受限的轻量逻辑」,一旦涉及稳定交付,DNN 几乎是必经之路。OpenCV 的 DNN 模块不需要你搭建深度学习训练环境,只需要一个预训练模型文件和加载代码,推理时仍然调用 OpenCV 的接口,这也算「OpenCV 内置」的一种延伸,只不过模型权重不再是打包在库里的那套 SVM 系数。
对比一下两条路线的选型依据:
| 维度 | HOG + SVM | DNN + MobileNet SSD |
|---|---|---|
| 模型来源 | OpenCV 内置,零额外文件 | 需要单独的模型文件 |
| CPU 单帧耗时 | 50~150ms(640×480) | 100~200ms(300×300 输入) |
| 遮挡/密集场景 | 明显吃力 | 明显更好 |
| 部署体积 | 极小 | 模型文件约 20MB 级 |
| 调参复杂度 | 低,核心参数少 | 低,主要调置信度阈值 |
5.2 用 Caffe 模型在 DNN 模块里跑行人检测的最小代码
DNN 模块加载模型的常见做法是readNetFromCaffe,需要两个文件:网络结构的 prototxt 和训练好的 caffemodel。这里用 MobileNet SSD 的 VOC 版本,它检测的 20 类里 index=15 对应 person。模型文件如果压缩包里没有,去 Caffe 模型仓库或 OpenCV 模型 zoo 按名字找即可。
import cv2 import numpy as np # 加载网络:prototxt 是网络结构,caffemodel 是训练好的权重 net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "mobilenet_iter_24000.caffemodel") img = cv2.imread("street.jpg") h, w = img.shape[:2] # 转成网络输入的 blob:缩放系数、均值、通道顺序三个要素 blob = cv2.dnn.blobFromImage( img, 0.007843, # 像素缩放系数 (300, 300), # 网络输入尺寸 (127.5, 127.5, 127.5), # 均值,Caffe 风格减均值 swapRB=True, ) net.setInput(blob) detections = net.forward() # 形状: (1, 1, N, 7) for i in range(detections.shape[2]): conf = detections[0, 0, i, 2] # 置信度 class_id = int(detections[0, 0, i, 1]) # 类别索引 if conf > 0.5 and class_id == 15: # person 类 box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) x1, y1, x2, y2 = box.astype(int) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imwrite("result_dnn.jpg", img)逻辑说明:blobFromImage的四个参数是固定套路,(300, 300)是网络输入,过大过小都会导致性能或精度问题;swapRB=True是因为 Caffe 模型按 BGR 训练,OpenCV 读入的图片默认 BGR,要保持一致。detections的四维数组里,第三维N是一张图上检测出的候选框总量,每个候选框是 7 个值:批次索引、类别索引、置信度、归一化坐标 x1/y1/x2/y2。
需要注意conf阈值:MobileNet SSD 的输出置信度分布比 HOG 的权重更「像概率」,0.5 是常用起点。如果误检多就提到 0.6,漏检多就降到 0.4,这是一个纯粹由场景决定的参数。类别的陷阱也要当心:不同数据集的类别索引不一样,VOC 里 person 是 15,COCO 里 person 是 1,如果换了模型文件,记得核对类别列表,这是 DNN 方案里最容易翻车的地方。
5.3 从 HOG 换到 DNN,视频骨架要改什么
DNN 方案和 HOG 的视频骨架基本相同,差异集中在三个地方:
第一,detectMultiScale换成net.setInput+net.forward()。第二,DNN 的输出是一组原始候选框,没有像finalThreshold那样的内置合并逻辑,需要自己调cv2.dnn.NMSBoxes去掉重叠框:
boxes = [] confs = [] for i in range(detections.shape[2]): conf = detections[0, 0, i, 2] if conf > 0.5 and int(detections[0, 0, i, 1]) == 15: box = detections[0, 0, i, 3:7] * np.array([w, h, w, h]) x1, y1, x2, y2 = box.astype(int) boxes.append([x1, y1, x2 - x1, y2 - y1]) confs.append(float(conf)) # NMS 过滤重叠框,nms_threshold 越大保留的重叠框越多 keep = cv2.dnn.NMSBoxes(boxes, confs, score_threshold=0.5, nms_threshold=0.4) for idx in keep: x, y, bw, bh = boxes[idx] cv2.rectangle(frame, (x, y), (x + bw, y + bh), (0, 255, 0), 2)参数说明:NMSBoxes接收框列表和置信度列表,score_threshold在这里再过滤一次低置信度框,nms_threshold=0.4表示两个框的 IoU 超过 0.4 就合并。这个值调小能压重叠误检,调大能保留密集场景下的独立行人,比如两个人并排走时,0.3可能把两个框并成一个,0.5一般能分开。
第三,DNN 推理耗时比 HOG 高一个档,跳帧策略从skip=2起步,实测后按第 4.4 节的方法重新计算。DNN 模型在 300×300 输入下的耗时相对稳定,不像 HOG 那样受图像内容影响大,预算估算反而更简单。
6. 把检测接入业务:FPS 预算、跨线计数与三个值得投入的优化方向
检测跑通、坑也踩平之后,下一步是把行人检测变成业务功能,最常见的是统计某个区域的人流量。这里分享一个我常用的最小跨线计数逻辑:取检测框底边中心点坐标,判断它是否从预设线的一侧移动到另一侧。核心代码只需要几行:
centroid_y = y + h # 框底边中心,比框中心更贴近脚底位置 if prev_side == 0 and centroid_y > line_y: count += 1 prev_side = 1 if centroid_y > line_y else 0逻辑说明:只用当前帧的检测框坐标不够,还要记住上一帧的目标位置。用框底边而不是框中心,是因为行人上身会前后摆动,底边相对稳定,误触发的概率更低。这个逻辑简单到没有调参空间,但也正因如此,它能不能跑对完全取决于上游检测框的质量。
业务上线前还要做一次严肃的性能压测,算法上能不能跑和业务上能不能扛是两个问题。按路数算预算:一路摄像头的处理预算 = 1000ms / 目标 FPS,如果目标 15 FPS,每路每帧 66ms。你有 4 路摄像头,就是 264ms 的计算载荷,单 CPU 扛不住就得考虑串行转并行,或者降低每路的输入分辨率。
「配置参数时先从minNeighbors类参数起步」这段话我经常对同事说,但 HOG 里没有这个参数,我真正想强调的是:先用最简单的参数组合跑通,测耗时,再谈优化。我现在的习惯是固定摄像头场景先用 HOG + 权重过滤做一版,跑一周收集误检样本;如果误检率超过业务容忍线,直接把 DNN 模型换成自己场景微调过的版本。换句话说,HOG 是判断项目值不值得做的低成本探针,DNN 是稳定交付的保底方案,前者花的半天时间绝对不会白费。
如果要在这个方向继续投入,三个优化方向按性价比排序:一是把模型切成 OpenVINO 的 IR 格式推理,同样的 CPU 能快 2~4 倍;二是用公开的行人检测数据集做预训练,往自己的场景里标几百张图微调一个小模型;三是给固定场景加背景减除或深度信息做前置过滤,把「非人区域」直接拦住。三个方向不用全做,按你实际部署的硬件选一个先落地。
希望这些参数和踩坑记录能帮你少走一段弯路,也希望你能从这套内置方法出发,真正把一个行人检测项目落到自己的场景里。
本文还有配套的精品资源,点击获取