简介这份资源是一套基于Python与OpenCV实现视频人数识别的完整项目源码面向计算机、人工智能、通信工程、自动化等相关专业的在校学生、教师及企业员工尤其适合作为毕业设计、课程设计或项目初期立项演示的参考方案。压缩包共包含3个文件以1个Python主程序、1段mp4测试视频和1份md说明文档为主整体约9.39MB结构精简便于快速上手运行与调试。项目代码经过实际测试功能可正常执行能够对视频画面中的人物进行检测并统计人数同时带有可视化窗体展示识别结果。读者可借此学习OpenCV图像处理、目标检测与视频流分析的基本流程理解从读取视频、逐帧处理到结果输出的完整思路并在此基础上修改扩展实现其他识别或统计功能。目前已有368人学习下载适合希望掌握计算机视觉入门实践、需要可运行参考代码的学习者使用。1. 基于 PythonOpencv 识别视频统计人数从拉流到计数的完整落地路径用 Python 加 Opencv 做视频人数统计最容易被低估的不是检测模型本身而是「视频从哪来、帧怎么取、人怎么算一个」。我见过太多人拿着一段本地 mp4 跑通了cv2.VideoCapture就以为项目成了结果一换成摄像头或者网络视频流帧率抖动、画面卡顿、同一人被重复计数的问题全冒出来。这个方向真正要解决的是在一条持续输入的图像序列里稳定地找出人、跟踪人、并给出当前画面内的数量。它适合有 Python 基础、想入门计算机视觉落地的开发者也适合需要做区域人流监控、门店客流粗统计的工程场景。下面按「先跑通最小闭环再补跟踪去重最后处理真实视频流的坑」这条线讲透。2. 环境搭建与最小可运行闭环先让一帧画面里的人被框出来2.1 依赖选型为什么用 opencv-python 而不是从源码编译做视频人数统计核心依赖就两个Opencv 负责读写视频和图像处理检测器负责找人。检测器可以选 Opencv 自带的 HOG 行人检测、DNN 模块加载预训练模型或者外接 YOLO 系列。新手最容易卡在第一步——装不上 Opencv。常见做法是直接用 pip 装预编译包不要一上来就折腾源码编译。opencv-python是包含主模块的包opencv-contrib-python额外带 contrib 模块比如部分跟踪器。如果你只做基础检测和视频读写装前者就够。很多人搜「modulenotfounderror: no module named opencv」或者「anaconda prompt 里面没有 opencv」本质是装错了环境——pip 装到了系统 Python代码却跑在 conda 环境里。# 建议在独立虚拟环境里装避免污染全局 python -m venv venv # Linux/macOS 激活 source venv/bin/activate # Windows 激活 # venv\Scripts\activate # 安装主包numpy 会被自动带上 pip install opencv-python numpy # 验证是否装好能打印版本号就说明 cv2 可导入 python -c import cv2; print(cv2.__version__)逻辑说明虚拟环境把项目依赖和系统隔离避免「明明装了却 import 不到」。opencv-python自带 FFmpeg 后端能直接读常见视频格式不需要你单独配解码器。参数上如果后续要用cv2.TrackerCSRT这类 contrib 跟踪器把包名换成opencv-contrib-python即可两者不要同时装会互相覆盖。提示装完 import 报ImportError: libGL.so.1这类错是 Linux 缺系统图形库装libgl1即可跟 Opencv 本身无关。2.2 用 HOG 检测器跑通第一帧最小检测代码先用 Opencv 自带的 HOG SVM 行人检测器它不需要额外下载模型文件适合验证整条链路是否通。缺点是只对站立行人效果好遮挡和坐姿容易漏。import cv2 # 初始化 HOG 行人检测器这是 Opencv 内置的无需下载权重 hog cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) # 打开视频0 表示默认摄像头也可传本地文件路径 cap cv2.VideoCapture(0) if not cap.isOpened(): raise RuntimeError(视频源打开失败检查设备索引或文件路径) while True: ret, frame cap.read() if not ret: break # 读到结尾或流中断 # 缩小尺寸能显著提速检测完再按比例映射回原图 scale 0.5 small cv2.resize(frame, None, fxscale, fyscale) # detectMultiScale 返回矩形框和权重 # winStride 滑动步长padding 边缘填充scale 金字塔缩放步长 rects, weights hog.detectMultiScale( small, winStride(8, 8), padding(8, 8), scale1.05 ) for (x, y, w, h) in rects: # 坐标还原到原始分辨率 x, y, w, h int(x / scale), int(y / scale), int(w / scale), int(h / scale) cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, fCount: {len(rects)}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(people count, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明detectMultiScale是滑动窗口 多尺度金字塔检测winStride越小越细但越慢scale控制每层缩放比例1.05 是精度和速度的常见折中。缩小帧再检测是提速的关键手段检测框坐标必须除以缩放系数还原否则框会偏。参数说明winStride(8,8)适合 640 宽度左右的画面画面更大时适当加大步长。scale1.05比 1.1 更准但更慢实时场景常用 1.1。len(rects)直接当人数是最粗糙的做法它会把同一个人在多帧里反复计数也会把重叠框算成多个这就是下一章要解决的去重问题。3. 从「每帧框人」到「稳定计数」跟踪去重与计数逻辑3.1 为什么逐帧检测不能直接当人数逐帧检测的输出是「这一帧里有几个框」不是「画面里有几个人」。同一个人站着不动每帧都被检测到如果你把每帧的框数累加数字会爆炸。即使只看单帧HOG 也常对一个人输出多个重叠框或者相邻两帧框位置跳动导致计数忽高忽低。要得到稳定人数必须引入跟踪给每个检测到的人分配一个 ID跨帧维持这个 ID只有新 ID 出现时才让计数加一。Opencv 提供两类工具一是cv2.MultiTracker系列如 CSRT、KCF适合手动初始化少量目标二是把检测器和跟踪器结合检测负责发现新人跟踪负责维持旧人。人数统计场景推荐后者因为人可能随时进出画面。3.2 检测 跟踪的计数框架下面用一个简化但可运行的框架每隔几帧做一次检测检测框和已有跟踪框做 IoU 匹配匹配上的沿用旧 ID匹配不上的新建 ID连续多帧丢失的 ID 才注销。import cv2 def iou(box_a, box_b): # 计算两个框的交并比用于判断是否为同一个人 ax, ay, aw, ah box_a bx, by, bw, bh box_b x1, y1 max(ax, bx), max(ay, by) x2, y2 min(ax aw, bx bw), min(ay ah, by bh) inter max(0, x2 - x1) * max(0, y2 - y1) union aw * ah bw * bh - inter return inter / union if union 0 else 0 hog cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) cap cv2.VideoCapture(0) tracks {} # id - [x, y, w, h, lost_frames] next_id 0 frame_idx 0 DETECT_INTERVAL 5 # 每 5 帧检测一次其余帧只做匹配 IOU_THRESH 0.3 # 匹配阈值 MAX_LOST 10 # 连续丢失超过该值就注销 ID while True: ret, frame cap.read() if not ret: break frame_idx 1 if frame_idx % DETECT_INTERVAL 0: small cv2.resize(frame, None, fx0.5, fy0.5) rects, _ hog.detectMultiScale(small, winStride(8, 8), padding(8, 8), scale1.05) detections [[int(x/0.5), int(y/0.5), int(w/0.5), int(h/0.5)] for (x, y, w, h) in rects] matched set() for tid, t in tracks.items(): best_iou, best_det 0, -1 for di, det in enumerate(detections): if di in matched: continue score iou(t[:4], det) if score best_iou: best_iou, best_det score, di if best_iou IOU_THRESH: tracks[tid][:4] detections[best_det] tracks[tid][4] 0 matched.add(best_det) else: tracks[tid][4] 1 # 未匹配的检测框视为新出现的人 for di, det in enumerate(detections): if di not in matched: tracks[next_id] det [0] next_id 1 # 清理长时间丢失的 ID tracks {k: v for k, v in tracks.items() if v[4] MAX_LOST} for tid, t in tracks.items(): x, y, w, h, _ t cv2.rectangle(frame, (x, y), (x w, y h), (0, 255, 0), 2) cv2.putText(frame, fID {tid}, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.putText(frame, fPeople: {len(tracks)}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(count, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明核心是「检测补新人、IoU 维持旧人、丢失计数注销」。DETECT_INTERVAL越大越省算力但新人出现到被发现的延迟越高。IOU_THRESH太低会把两个人合并成一个 ID太高会让同一个人频繁换 ID 导致计数虚高。MAX_LOST决定一个人短暂被遮挡后还能不能保住原 ID。参数说明DETECT_INTERVAL5在普通 CPU 上能跑到接近实时IOU_THRESH0.3是行人场景的常用起点人走得快就调到 0.2人密集就调到 0.4MAX_LOST10对应约 0.3 秒的容忍遮挡严重的场景可以加大但会延迟注销已经离开的人。注意这套框架的计数是「当前画面内跟踪中的 ID 数」不是累计客流。要做累计进出得再划一条虚拟线判断 ID 的移动方向那是另一个量级的工作。4. 真实视频流的避坑与排查拉流中断、误检、性能三座大山4.1 视频源打开失败或中途断流现象cap.read()返回False画面卡住不动或者程序直接退出。原因通常是网络视频流不稳定、摄像头被其他进程占用、或者文件路径含中文。解决读失败时不要立刻 break先重试几次再决定是否退出网络流建议用带超时的打开方式并记录失败次数。import cv2, time cap cv2.VideoCapture(rtsp://user:passip:554/stream) # 换成你的流地址 fail_count 0 while True: ret, frame cap.read() if not ret: fail_count 1 if fail_count 30: print(连续读取失败尝试重连) cap.release() time.sleep(2) cap cv2.VideoCapture(rtsp://user:passip:554/stream) fail_count 0 continue fail_count 0 # 正常处理 frame逻辑说明网络流抖动是常态直接 break 会让程序在短暂丢包时退出。重试 重连能显著提升长时间运行的稳定性。参数上重试阈值 30 对应约 1 秒按 30fps 算重连前 sleep 2 秒给设备恢复时间。4.2 同一人被反复计数或多人被合并现象画面里明明 3 个人计数显示 7或者两个人挨着走ID 变成一个。原因IoU 阈值不合适或者检测框抖动导致匹配失败。解决先固定检测间隔观察 ID 变化把IOU_THRESH在 0.2 到 0.5 之间调记录哪种最稳对检测框做轻微平滑如指数移动平均能减少抖动。4.3 帧率掉到个位数现象视频播放明显卡顿waitKey响应迟钝。原因每帧都跑 HOG 检测CPU 吃满。解决降低检测频率加大DETECT_INTERVAL、缩小检测分辨率、或者换更轻的检测器。如果机器有 GPU可以考虑用 Opencv DNN 模块加载轻量模型把推理放到 GPU 上。4.4 光照和角度导致的漏检现象逆光、夜间、俯拍角度下几乎检测不到人。原因HOG 是在正立行人数据上训练的对俯视和低对比度场景泛化差。解决调整摄像头安装角度尽量平视补光或者换用对多角度更鲁棒的检测模型。这是检测器本身的边界不是调参能完全解决的。4.5 环境装错导致 import cv2 失败现象命令行能 importIDE 里报ModuleNotFoundError。原因IDE 用的解释器和 pip 装包的解释器不是同一个。解决在 IDE 里显式指定虚拟环境的解释器路径或者用python -m pip install确保装到当前解释器。VS Code 和 PyCharm 都要检查右下角/项目设置里的解释器。5. 进阶技巧用 DNN 模块替换 HOG把准确率和速度同时拉起来HOG 的局限在真实场景里很快会暴露遮挡漏检、俯拍失效、密集人群合并。想再上一个台阶常见做法是换成基于深度学习的检测器。Opencv 的dnn模块能直接加载 ONNX 等格式的模型不需要额外装推理框架对已经用 Opencv 读视频的项目来说改动最小。以加载一个轻量检测模型为例流程是cv2.dnn.readNetFromONNX读模型blobFromImage做预处理net.forward出结果再按置信度过滤。相比 HOGDNN 检测器对角度和光照鲁棒得多代价是需要下载模型文件、对输入尺寸有要求、CPU 上单帧推理更慢。import cv2 import numpy as np # 加载 ONNX 模型模型文件需自行准备 net cv2.dnn.readNetFromONNX(model.onnx) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 有 CUDA 环境可换 DNN_TARGET_CUDA cap cv2.VideoCapture(0) INPUT_SIZE 640 CONF_THRESH 0.4 while True: ret, frame cap.read() if not ret: break # 构造 blob缩放、减均值、换通道顺序 blob cv2.dnn.blobFromImage(frame, 1/255.0, (INPUT_SIZE, INPUT_SIZE), swapRBTrue, cropFalse) net.setInput(blob) outputs net.forward() h, w frame.shape[:2] boxes [] for det in outputs[0]: conf det[4] if conf CONF_THRESH: continue cx, cy, bw, bh det[0], det[1], det[2], det[3] x int((cx - bw / 2) * w / INPUT_SIZE) y int((cy - bh / 2) * h / INPUT_SIZE) boxes.append([x, y, int(bw * w / INPUT_SIZE), int(bh * h / INPUT_SIZE)]) for (x, y, bw, bh) in boxes: cv2.rectangle(frame, (x, y), (x bw, y bh), (0, 255, 0), 2) cv2.putText(frame, fPeople: {len(boxes)}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.imshow(dnn count, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明blobFromImage的1/255.0是归一化swapRBTrue把 Opencv 默认的 BGR 转成模型期望的 RGB这两步漏一个结果就会全错。输出解析里det[4]是置信度后面才是框坐标不同模型的输出排列不一样拿到新模型先打印outputs.shape确认。坐标从 640 输入尺寸映射回原图尺寸时宽高要分别用w/INPUT_SIZE和h/INPUT_SIZE不能混用。参数说明CONF_THRESH0.4是召回和误检的平衡点人多遮挡多就降到 0.3误检多就升到 0.5。INPUT_SIZE越大越准越慢640 是实时场景的常见选择。DNN_TARGET_CPU换成DNN_TARGET_CUDA前要确认 Opencv 编译时带了 CUDA 支持否则会静默回退到 CPU白高兴一场。验证替换是否有效别只看单帧效果。我的习惯是录一段有代表性的视频分别用 HOG 和 DNN 跑完整段统计漏检帧数和平均帧耗时两个数字放一起看。如果 DNN 准确率上去了但帧率掉到不可用就降输入尺寸或加检测间隔而不是硬扛。这套东西没有一劳永逸的参数换一个摄像头、换一个安装角度阈值就得重调。我踩过最深的坑就是在一个场景调好的参数直接搬到另一个场景结果计数全乱后来养成习惯每换一次部署环境先跑十分钟观察 ID 稳定性再上线。希望帮到你。本文还有配套的精品资源点击获取