简介基于Python的行人检测视频与源码包面向计算机视觉初学者与进阶开发者解决视频流中行人实时检测与连续跟踪问题。系统以HOG方向梯度直方图提取特征结合SVM支持向量机分类器判断行人区域并集成简单跟踪算法提升帧间稳定性。压缩包共25个文件约155.2MB包含2个Python主程序、2段avi测试视频、11张bmp与5张png行人样本图片以及4个xml和1个iml项目配置说明可直接在OpenCV环境下运行体验。已有8173人学习下载适合作为行人检测入门实践或课程设计参考。读者可对照源码梳理HOG特征计算、SVM分类器训练与调用、检测窗口滑动、非极大值抑制及跟踪模块的实现细节借助附带的多样性样本和视频完成算法效果验证为后续拓展至其他目标检测任务打下基础。1. 行人检测视频项目的真实价值先跑通一条完整链路再谈调优在安防监控人流统计、城市巡检、辅助驾驶这类场景里行人检测视频处理是绕不开的计算机视觉基础需求。但很多人拿到“python行人检测视频源码”这类资源第一步就卡在环境上更别提后续的模型加载和视频回写。这份源码的核心是一条完整链路视频逐帧读取、模型推理、行人框标注、成品视频输出代码量不大却把视觉处理中最常碰到的视频解码、输入预处理、坐标映射、编码器参数全串了起来。适合想快速跑出一版可运行 demo 的从业者也适合想研究检测框如何与视频时间轴对齐的新手。先把这条链路跑通再考虑换模型提精度。2. 模型选型与环境搭建从 HOG 到 YOLO视频场景为什么优先选 SSD MobileNet2.1 三种常见行人检测方案的横向对比与选型逻辑行人检测在工程落地时候选方案基本分三类传统 HOG SVM、两阶段检测器Faster R-CNN 风格、单阶段检测器SSD、YOLO 风格。我拆过的很多视频检测源码默认带的是 SSD MobileNet 预训练权重这不是随手的决定背后是视频处理场景对延迟和吞吐的硬约束。方案精度表现单帧推理速度CPU硬件要求适合场景主要坑点HOG SVM背景简单时尚可遮挡/光照变化会漏较快纯 CPU 即可固定机位、简单背景对复杂背景鲁棒性差Faster R-CNN 系高小目标召回好很慢需要 GPU 加速离线分析、精度优先视频实时处理不现实SSD MobileNet 系中上平衡性好较快CPU 可跑有 GPU 更好视频实时检测小目标漏检输入尺寸敏感YOLO 系列v8 风格高速度快取决于版本推荐 GPU小模型可 CPU实时视频/边缘设备依赖配置复杂权重文件大视频检测和单张图片检测的差别在于单帧不能崩整体帧率必须稳定。Faster R-CNN 在 CPU 上单帧要几百毫秒处理 30fps 视频得跳掉大量帧检测结果前后跳变严重。HOG SVM 虽然快但对密集人群、部分遮挡几乎没有抵抗力。SSD MobileNet 的优点是把 MobileNet 作为 backbone模型小、推理快输出层直接用锚点回归多个尺度目标在一段 720P 视频上CPU 环境下每帧处理时间能控制在几十毫秒量级这就是视频场景优先选它的原因。YOLO 系在实际工程里精度确实更好比如最新版本的模型在小目标召回上提升明显但需要额外的权重转换、显存管理对新手而言拦路石更多。我的习惯是第一次跑视频链路用 SSD MobileNet 摸清流程后续需要提精度再替换成 YOLO 权重。这样检测头、坐标解码、NMS 逻辑都能复用改动集中在模型加载部分。2.2 虚拟环境、依赖安装与预训练权重准备拿到这份源码第一步不是直接改代码而是把运行环境隔离出来。Python 项目最怕全局环境里已装了大版本不一致的 cv2 和 numpy反复报错后你会怀疑自己是不是手残。我一般会在项目根目录下建独立虚拟环境。python -m venv ped_env source ped_env/bin/activate # Windows 下改为 ped_env\Scripts\activate pip install opencv-python numpy逻辑说明python -m venv创建独立环境避免污染其他项目source激活后pip安装的包只对当前环境生效。Windows 用户注意激活脚本路径在Scripts目录下不是bin。装包时建议同时确认版本cv2 的 API 在不同版本间变动不大但 numpy 版本过新时某些旧版 cv2 会出现二进制接口不兼容的提示一旦遇到undefined symbol之类报错先把 numpy 降到 1.x 版本再试。预训练权重这块源码里一般会附带模型描述文件prototxt 后缀和二进制权重文件。没有附带时需要去模型仓库下载 SSD MobileNet 对应的行人检测版本。注意权重文件必须和描述文件配套只看文件名容易混。我习惯的做法是下载后先检查文件大小prototxt 一般几十 KB权重文件几十 MB大小异常基本就是下载中断了。2.3 模型验证加载后先打印输出张量形状模型加载成功不等于一切正常。我每次跑新源码都会先单独写一小段脚本把模型输出来看一眼确认网络确实工作而不是等整个视频跑完才发现检测结果全是 0 或空白。import cv2 prototxt deploy.prototxt caffemodel ssd_mobilenet.caffemodel net cv2.dnn.readNetFromCaffe(prototxt, caffemodel) print(网络层名检查, net.getLayerNames()[:5]) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 用一个全黑图验证前向传播是否正常 fake_input cv2.dnn.blobFromImage(np.zeros((300, 300, 3), dtypenp.uint8)) net.setInput(fake_input) out net.forward() print(输出张量形状, out.shape)逻辑说明先加载网络打印前几个层名确认模型描述文件被正确解析然后构造一张全黑图做前向传播验证网络的输入输出链路。如果输出张量形状不是(1, 1, N, 7)说明模型版本或输入尺寸配置不对得回去检查描述文件里的输入维度。参数说明readNetFromCaffe第一个参数是描述文本文件路径第二个参数是二进制权重路径路径写错在这一步就会抛异常不会拖到后面。blobFromImage的输入尺寸要和模型描述文件里指定的维度一致常见的是300x300如果把尺寸改成别的值模型推理会静默出错检测结果全是垃圾数据。3. 视频行人检测的落地实现抽帧、推理、标注、回写四步闭环3.1 VideoCapture 读取视频与帧率控制视频处理的起点是读帧。cv2 的VideoCapture支持摄像头和视频文件两种模式源码里使用的是文件模式重点在于确认视频真的被打开、读取的帧率是否和视频原始帧率一致。import cv2 video_path input.mp4 cap cv2.VideoCapture(video_path) if not cap.isOpened(): raise IOError(f无法打开视频文件{video_path}) fps cap.get(cv2.CAP_PROP_FPS) total_frames cap.get(cv2.CAP_PROP_FRAME_COUNT) print(f视频帧率{fps}总帧数{total_frames}) frame_idx 0 while True: ret, frame cap.read() if not ret: print(f读取到第 {frame_idx} 帧时结束) break frame_idx 1 if frame_idx % 30 0: print(f已处理 {frame_idx} 帧) cap.release()逻辑说明cap.isOpened()返回False时直接抛异常能定位路径或编码问题CAP_PROP_FPS和CAP_PROP_FRAME_COUNT分别是视频的原始帧率和总帧数后面计算跳帧策略和 FPS 基线都会用到。cap.read()返回两个值ret为False表示视频读完了或读取失败必须把这个判断放在循环最前面否则frame为空时报错。参数说明视频路径最好用英文绝对路径中文路径在部分 Windows 环境下会让VideoCapture直接返回False。另外某些翻转封装格式比如手机录制的竖屏视频可能带旋转元数据read()读出来的画面是横着的检测前要先用cv2.rotate调整方向不然框的位置全错。3.2 基于 cv2 DNN 的行人推理与坐标映射检测部分是整条链路的中心。SSD MobileNet 的输出不是直接给你坐标而是经过归一化的检测结果必须正确解码才能映射回原图。import cv2 import numpy as np net cv2.dnn.readNetFromCaffe(deploy.prototxt, ssd_mobilenet.caffemodel) confidence_threshold 0.5 def detect_pedestrians(frame): h, w frame.shape[:2] blob cv2.dnn.blobFromImage(frame, 0.007843, (300, 300), 127.5) net.setInput(blob) detections net.forward() boxes [] for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] if confidence confidence_threshold: continue class_id int(detections[0, 0, i, 1]) # 假设行人对应类别编号为 1具体以模型训练时的类别表为准 if class_id ! 1: continue xmin int(detections[0, 0, i, 3] * w) ymin int(detections[0, 0, i, 4] * h) xmax int(detections[0, 0, i, 5] * w) ymax int(detections[0, 0, i, 6] * h) boxes.append((xmin, ymin, xmax, ymax, confidence)) return boxes逻辑说明blobFromImage完成三件事缩放、减均值、通道转换默认 BGR 转 RGB输出一个四维张量送入网络。net.forward()返回的detections形状是(1, 1, N, 7)N 是模型预设的最大检测候选数7 列包含图片编号、类别编号、置信度、xmin、ymin、xmax、ymax。关键点坐标是归一化的 0~1 浮点数必须乘回原图宽高否则框会画到一个角落里头去。参数说明缩放系数0.007843和均值127.5是 Caffe 模型里固定的预处理参数来源于 ImageNet 训练时的数据归一化方式这两个值不能随便改。类别编号 1 对应行人这是 Caffe 模型训练时的类别映射不同的预训练模型可能不同典型错误是把只检测行人的模型当成了 COCO 80 类模型导致类别编号错位。我建议先打印detections[0, 0, i, 1]的取值分布确认类别编号再写死。3.3 VideoWriter 输出成品视频与编码器选型检测框画完之后需要把帧写回视频文件。这里是最容易返工的地方源码里给出的编码器参数在几台机器上都有出入我把通用可用的写法整理如下。import cv2 output_path output.mp4 fourcc cv2.VideoWriter_fourcc(*mp4v) out_writer cv2.VideoWriter(output_path, fourcc, fps, (frame_width, frame_height)) while True: # 假设 frame 是当前帧annotated_frame 是画完框的帧 annotated_frame frame.copy() for (xmin, ymin, xmax, ymax, conf) in boxes: cv2.rectangle(annotated_frame, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) out_writer.write(annotated_frame) out_writer.release()逻辑说明VideoWriter_fourcc把四个字符编码成整数标识mp4v是 MP4 容器最常见的编码器兼容性最好。VideoWriter构造函数里的宽高必须和输入帧完全一致否则写入的文件要么直接是 0 字节要么播放器打不开。write方法接受一帧完整图像内部会自动编码追加到文件中。参数说明fps参数决定输出视频的时间轴密度一般沿用输入视频的原始帧率。如果实际处理速度低于原始帧率最终输出视频会呈现慢放效果这是正常的因为写入的帧数变少了但帧率保持不变。需要快放的话把fps调小需要慢放就调大。另外系统若没有对应的编码器VideoWriter构造时不会立刻报错而是在第一次write时静默失败所以写完文件后要检查文件大小是否合理。4. 参数调优与性能边界置信度、NMS、跳帧策略怎么配才不翻车4.1 置信度阈值与 NMS 阈值的配对关系源码里的置信度阈值默认 0.5NMS 阈值默认 0.4这两个值是经验配比但不一定适配你的真实视频。置信度阈值管的是“框要不要留”NMS 阈值管的是“重叠的框要不要合并”两者需要联动看。场景置信度阈值NMS 阈值观察结果固定机位、行人稀疏0.5~0.60.4漏检少误检少密集人群0.7~0.80.3抑制大量低分误检但可能漏掉被遮挡的行人远距离小目标0.3~0.40.5召回率提升但误检明显增多需要人工核对置信度阈值调低到 0.3 之后最直观的现象就是树干、路灯、车窗反光都会出框调高到 0.8 以上虽然误检少了但远处的行人很容易漏。我拆视频时习惯先跑一遍把置信度从低到高打印出检测框数量分布再确定阈值而不是一开始就抄默认值。NMS 阈值的作用容易被忽略。同一个行人被输出多个框时NMS 阈值越低重叠框被压得越狠但当两个行人靠得很近时过低的 NMS 会把真值行人也合并掉一个。如果你发现视频里两个人的框变成了一个框在两人之间来回晃大概率是 NMS 阈值设太低了。4.2 跳帧检测策略CPU 上处理 1080P 视频的折中方案视频处理速度和分辨率强相关。1080P 视频在 CPU 上跑 SSD MobileNet单帧推理加画框大约花 80~150 毫秒也就是每秒只能处理七八帧远远达不到 25fps 实时要求。这时候跳帧是不可避免的折中策略。import cv2 skip_frames 2 # 每 3 帧检测一次 last_boxes [] frame_idx 0 while True: ret, frame cap.read() if not ret: break if frame_idx % (skip_frames 1) 0: last_boxes detect_pedestrians(frame) annotated_frame draw_boxes(frame, last_boxes) else: # 中间帧沿用最近一次检测结果也可以用光流跟踪插值 annotated_frame draw_boxes(frame, last_boxes) out_writer.write(annotated_frame) frame_idx 1逻辑说明每 3 帧才做一次完整推理中间 2 帧直接沿用最近一次检测框。这样推理次数降为原来的三分之一整体处理速度约提升 2~3 倍。假设原始处理 8fps跳帧后能达到 20fps 以上视觉上框的跳动感也不会太明显。参数说明skip_frames的取值依据是单帧推理耗时。推理耗时 100 毫秒时跳 2 帧可以让每帧平均耗时压到 33 毫秒左右接近直播要求耗时 50 毫秒时跳 1 帧就够了。跳帧数过大时行人快速移动会让框明显落后于实际位置这是时间轴上的滞后不是检测模型的精度问题。后续要优化可以用光流法或者跟踪算法接在检测结果后面而不是无限加大跳帧数。4.3 检测结果统计用日志盯住漏检与误检调参不能靠肉眼反复拖动视频浪费精力不说还不容易对比。我习惯把每帧的检测结果输出成结构化日志用数据判断当前参数是否合适。import csv import time with open(detection_log.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame_idx, pedestrian_count, avg_confidence]) frame_idx 0 while True: ret, frame cap.read() if not ret: break boxes detect_pedestrians(frame) if len(boxes) 0: avg_conf sum(b[4] for b in boxes) / len(boxes) else: avg_conf 0 writer.writerow([frame_idx, len(boxes), round(avg_conf, 3)]) frame_idx 1逻辑说明把每帧的行人数量和平均置信度写入 CSV跑完视频后用表格软件拉一张折线图可以直观看到哪些时间段检测数量突然下降或飙升。比如某段从 5 个行人突然变成 0 再变回 5基本是漏检加恢复的过程那一段的置信度阈值需要单独调低。参数说明日志记录本身有 IO 损耗长期跑视频时建议每 30 帧写一次批量写入而不是逐帧writerow否则会拖慢整体速度。检测数量在阈值调整前后的对比是判断参数是否合理的硬指标。5. 行人检测视频常见问题排查五个实测踩坑记录与修复5.1 视频读取失败cap.isOpened() 返回 False现象代码执行时不报错但进不了读取循环程序直接结束或者frame一直是空的。原因最常见的有三种。视频路径存在中文或空格部分系统底层解码器不认视频文件本身是加密或损坏的容器格式太冷门比如某些老旧监控导出的私有格式。解决先确认路径完整改写为英文绝对路径把待处理视频用主流格式重新编码一版推荐 H.264 编码的 MP4。我平时会用格式转换工具先统一转一遍再跑检测代码。调试时打印cap.get(cv2.CAP_PROP_FRAME_COUNT)如果返回 0说明解码器根本没有读取到帧索引。5.2 检测框抖动漂移相邻帧结果不稳定现象画面静止时同一个行人身上的框忽大忽小或者框在行人身体边缘来回移动行人走动时框时有时无。原因这是单帧检测的典型问题。模型每次独立推理对同一目标在不同帧输出的边界框存在像素级抖动置信度也在阈值附近波动导致某些帧被过滤掉。解决最常见做法是对检测框做时间维度的平滑。把最近几帧的同一目标框做加权平均或者按 IoU 匹配前后帧的框如果新帧的检测框与上一帧的框重合度超过 0.5就用两者坐标的平均值替代当前框。还有一种更省事的方法是调低置信度阈值到 0.4 左右让更多候选框进入 NMS 合并流程输出的框稳定性会好一些。5.3 输出视频为空或无法播放编码器与分辨率不一致现象代码跑完输出文件大小接近 0 KB或者播放器提示文件损坏。原因VideoWriter创建时传入的宽高和实际frame的宽高不一致导致数据写入失败也可能是fourcc指定的编码器在当前系统上没有可用的动态库写入时静默失败。解决在创建 writer 之前打印frame.shape它返回(height, width, channels)而VideoWriter参数顺序是(width, height)顺序反了就会出现宽高不一致。编码器问题上mp4v兼容性最好如果你的系统有 x264 编码库可以试avc1压缩率更高文件更小。写完后第一时间检查输出文件大小小于 1MB 基本就是写失败了。5.4 模型加载报错prototxt 路径与层名不匹配现象readNetFromCaffe直接抛异常说找不到文件或网络解析失败。也有情况是不报错但检测结果全为空。原因prototxt 文件和 caffemodel 文件不配套比如描述文件是某个版本生成的权重文件来自另一个训练任务两者层名的对应关系对不上。或者是路径错误导致加载了一个残留的残缺文件。解决先确认两个文件确实存在且大小正常。然后加载时打印net.getLayerNames()看前几个层名是否符合预期。如果层名里出现不连续的编号说明模型结构描述和权重不匹配。最有效的办法是换用同一套模型仓库提供的 prototxt 和 caffemodel不要混搭版本。我见过某开发者把别名的权重文件改名后混用结果推理结果全为 0排查了一天最后发现是文件不配套。5.5 远距离小目标漏检输入尺寸与感受野的博弈现象画面中近处的行人能检测出来距离稍远的行人完全没框或者框的位置严重偏移。原因SSD MobileNet 的默认输入是 300x300图像被大幅压缩后小尺寸行人的特征几乎丢失另一个原因是 anchor 的尺度覆盖有限小目标对应的先验框数量不足。解决把blobFromImage的输入尺寸从 300x300 调大到 600x600小目标召回会明显提升但推理耗时也会翻倍。另一种更省资源的做法是只对图像的下半部分区域进行裁剪检测因为监控画面中行人通常集中在中下部区域。我把检测区域裁剪后放大两倍再推理小目标召回率提升了不少而整体耗时只增加了一部分。注意改输入尺寸前先确认 prototxt 里定义的输入层尺寸是否允许动态调整。部分旧模型把输入尺寸写死改大后会直接报错。6. 进阶验证把检测结果量化成 FPS 与轨迹数据6.1 用时间戳计算真实处理帧率视频检测项目交付时别人最常问的问题是“你能跑到多少帧”。不能用cap.get(CAP_PROP_FPS)冒充那是视频的原始帧率不是你的处理帧率。用代码里的时间戳统计才是真实数字。import time start_time time.time() processed_count 0 while True: ret, frame cap.read() if not ret: break # 检测、画框、写入都放在这里 processed_count 1 if processed_count % 30 0: elapsed time.time() - start_time fps_output processed_count / elapsed print(f已处理 {processed_count} 帧平均处理帧率{fps_output:.2f} FPS)逻辑说明用time.time()记录累计耗时每 30 帧算一次平均帧率这个数字才反映真实处理能力。如果输出帧率低于目标优先调整跳帧策略和输入尺寸而不是盲目换更重的模型。参数说明统计区间太短会抖动剧烈30 帧左右的窗口比较稳。视频最后不足 30 帧的剩余部分也要在循环结束后补算一次避免丢尾帧数据。6.2 检测框坐标导出 CSV 做轨迹后处理视频里的检测框坐标是研究行人轨迹的基础数据。把每帧的框写入 CSV后续可以统计某个区域的人流密度、计算行人的移动速度甚至做跨跟踪算法。import csv with open(tracks.csv, w, newline) as f: writer csv.writer(f) writer.writerow([frame_id, xmin, ymin, xmax, ymax, confidence]) frame_id 0 while True: ret, frame cap.read() if not ret: break boxes detect_pedestrians(frame) for (xmin, ymin, xmax, ymax, conf) in boxes: writer.writerow([frame_id, xmin, ymin, xmax, ymax, round(conf, 3)]) frame_id 1逻辑说明frame_id对应原始视频的帧序号即使跳帧处理也要按原始计数写入后续才能对齐时间轴。CSV 里每一行是一个检测框同一个行人在相邻帧的框靠 IoU 做关联就能粗糙拼出轨迹。参数说明坐标是像素值如果后续要做物理距离计算还需要结合摄像头的标定参数做透视变换这一步不在源码范围内但数据结构已经支持了。最后说一个我自己的教训。早期拆这类视频检测资源为了追求检测框看着连贯我把置信度阈值直接拉到 0.2结果误检框覆盖了大半个画面整体效果还更差。后来我明白了检测效果的瓶颈常常不在模型的单帧精度上而是在框的稳定性、处理帧率、数据记录这条完整链路的配合上。从那以后我每次拿到源码都强制自己先跑 5 分钟真实视频做参数基线再调阈值先看日志统计再调画面。这份源码的价值不在某一帧的精确框而在于你把视频处理的全流程真正走通了一次。希望帮到你。本文还有配套的精品资源点击获取