ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从HOG到YOLO:用OpenCV和Python实现行人识别与计数系统

从HOG到YOLO:用OpenCV和Python实现行人识别与计数系统 简介面向计算机视觉方向学习者的一份docx格式的本科毕业论文围绕基于Python的行人识别系统展开覆盖研究背景、算法原理到系统实现的完整流程可作为毕业设计选题思路、论文框架结构及实验安排的有力参考。文档按“概述—算法原理—数据集与评价指标—系统设计与实现—实验与分析—总结与展望”六个部分推进着重梳理了HOG、YOLO、Faster R-CNN等检测算法CNN特征提取卡尔曼滤波、DeepSORT等跟踪方法并给出了数据预处理、特征提取和分类器训练等模块的Python实现思路适合用来对照搭建自己的实验流程或撰写论文相关章节。资源包仅1个docx文件约35KB轻便易用目前已有311人浏览学习内容经过了降重与格式整理尤其适合需要快速理解行人识别系统实现脉络并规范论文写作的本科阶段读者。1. 行人识别系统从“毕设标题”到能跑起来的摄像头应用用 Python 实现行人识别系统听起来像是毕业设计里才会出现的题目但放到实际项目里它对应的是园区安防、门店客流统计、工地安全通道管理等一堆真实需求。客户不会说要一套“行人识别系统”而是说“帮我写一个能数人、能报警的程序”。这个标题背后的链路其实很清晰摄像头采集视频检测模型找出画面里的行人跟踪模块给同一个行人稳定的编号最后完成计数和告警。难点通常不在模型本身而在数据、速度、环境兼容这些容易被低估的地方。这篇文章就按我能直接落地的方式把整条链路拆开讲清楚。适合看这篇的人有两类一类是刚接到相关需求、想用最短时间做出可演示原型的学生或工程师另一类是想从“模型能跑通”升级到“系统能交付”的开发者。我会默认你已经懂一点 Python 基础但不会假设你熟悉 PyTorch 或目标检测。2. 检测方案选型为什么放弃 HOGSVM转向 YOLOv8n OpenCV行人识别系统的核心是目标检测检测方案的选型决定了后面所有工作。很多老教程会先讲 HOG SVM因为它确实是个经典方案。但如果你要做的是一个能实时处理摄像头画面的系统深度学习检测模型是更稳的选择。这一章先讲清两类方案的分界点再给出我常用的算法栈。2.1 HOG SVM 的识别逻辑能跑但只适合固定场景HOG 全称是方向梯度直方图核心思路是把图像切成小格子统计每个格子里的梯度方向分布得到一个描述局部轮廓的特征向量再用 SVM 分类器判断这个向量像不像行人。OpenCV 里甚至内置了一个预训练的行人检测器几行代码就能跑起来import cv2 hog cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) boxes, weights hog.detectMultiScale( frame, winStride(8, 8), padding(0, 0), scale1.05 )这段代码里winStride是滑动窗口的步长(8, 8)表示窗口每 8 个像素移动一次步长越小越容易漏检越大越容易漏检但速度快。scale是图像金字塔的缩放比例1.05表示每次缩小 5%这个值越接近 1检测越精细但要处理的金字塔层数也越多。实际用过就会发现HOG 方案对固定背景下、姿态比较标准的行人表现尚可但遇到行人抬胳膊、蹲下、两个人重叠、或者逆光场景检测框就开始乱跳。更麻烦的是OpenCV 内置的这组 SVM 权重训练数据比较早对现在动辄 1080P 甚至 4K 的监控画面人物尺寸变化范围太大误检和漏检都很难调。我见过不少初学者在 HOG 上卡了一两周最后换模型半天就解决了。如果只是做一个实验室演示HOG 够用要做实时多行人计数建议直接看深度方案。2.2 深度学习模型对比YOLO、Faster R-CNN、SSD 的取舍深度目标检测模型大致分两派两阶段模型和单阶段模型。Faster R-CNN 是两阶段的代表先让区域建议网络生成一批可能包含目标的候选框再对每个候选框做分类和回归精度高但速度慢一张图动辄几百毫秒很难跑实时视频流。SSD 是单阶段方案把检测当成回归问题一次完成速度上去了但对小目标的召回率一般。YOLO 也是单阶段但经过多个版本迭代在速度和精度之间找到了工程上最舒服的平衡点。工程选型不能只看理论指标还要看生态。YOLO 的开源体系里有完整的数据标注格式、训练脚本、验证工具导出 ONNX 或 TensorRT 的教程也很多遇到问题搜一下就有答案。Florence、Grounding DINO 这些新的基础模型虽然精度高但部署体积大、推理依赖复杂在普通工控机上跑不起来。做一个交付级项目首选还是 YOLO。模型阶段实时视频能力小目标能力工程生态Faster R-CNN两阶段弱较强一般SSD单阶段中等较弱一般YOLOv5 / YOLOv8单阶段强中上活跃这里要注意YOLO 的版本迭代里v5 和 v8 并不是严格继承关系但对使用者来说接口和训练方式高度类似选 v8 就能获得更新的预训练权重和更多后处理工具。2.3 推荐的算法栈YOLOv8n OpenCV-Python NumPy我的默认组合是YOLOv8n 做检测OpenCV-Python 做摄像头读取和画框NumPy 做坐标运算PyTorch 做训练和推理。选 nano 而不是 large是因为行人识别系统经常要部署在监控主机或边缘盒子上显存可能只有 4G 左右nano 在保证可接受精度的同时单帧推理能跑到几十毫秒后面还可以切成 ONNX 进一步提速。动手前先花十分钟把环境装干净。我一般用 conda 创建独立环境避免把系统 Python 搞乱conda create -n pedestrian python3.10 conda activate pedestrian pip install opencv-python numpy torch ultralytics然后跑一个极简检查脚本确认关键包能正常导入import cv2 import torch import numpy as np from ultralytics import YOLO print(cv2:, cv2.__version__) print(torch:, torch.__version__) print(cuda available:, torch.cuda.is_available())这里的cuda available非常关键。如果输出False说明 PyTorch 装的是 CPU 版后面训练会慢到让人怀疑人生。遇到这种情况别自己配 CUDA直接去 PyTorch 官网用站方给的命令按你的 CUDA 版本安装这是最靠谱的路。OpenCV 版本打印不出来多半是包没装好或者和本机已有的 OpenCV 冲突切忌在已有系统的 Python 里硬装。这套环境检查也是一份“后悔药”能提前暴露 90% 的环境问题。我见过太多人模型代码都写好了结果在安装阶段卡了一下午得不偿失。3. 数据准备与模型训练把“行人”从 80 类里单独拎出来并微调检测模型不能直接拿预训练权重上线。COCO 预训练的 YOLO 能识别 80 类其中 person 只是其中一类它虽然也能找出行人但在监控视角、夜间、密集场景下表现不够稳定。要让系统贴合实际场景必须准备自己的行人数据并微调模型。这一章讲清楚数据集怎么选、标注怎么转、训练参数怎么定。3.1 公开数据集怎么选COCO、CrowdHuman、MOT16 的区别做行人识别的训练数据常见来源有三个COCO 数据集、CrowdHuman 数据集、MOT16/MOT17 数据集。COCO 覆盖场景广里面有 person 类别但每个画面里的行人数量不多适合做预训练。CrowdHuman 专门为密集行人设计每张图里人多且遮挡严重适合做拥挤场景的微调。MOT16 系列带轨迹标注除了检测框还有 ID 信息适合同时训练跟踪。数据集内容特点适合用途COCO80 类通用目标行人场景日常预训练初始权重CrowdHuman密集场景遮挡多密集行人检测微调MOT16 / MOT17行人视频序列带 ID检测加跟踪联合优化实际做项目建议先用 COCO 预训练权重作为起点再采集自己场景的监控视频抽帧标注 500 到 1000 张图微调一个专用模型。这比从零训练经济得多。要注意训练集和验证集不能从同一段视频里连续抽帧否则模型会“记住”画面验证指标虚高。我一般每隔 10 到 20 帧抽一帧并且把抽到的帧按时间顺序切成两段前段做训练后段做验证。3.2 用 LabelImg 标注自己的数据VOC 转 YOLO 格式的脚本标注工具我习惯用 LabelImg它能导出 Pascal VOC 格式的 XML 文件。YOLO 训练需要的是 TXT 标注每行格式是“类别 中心点x 中心点y 宽 高”其中坐标全部归一化到 0 到 1 之间。手动一个个转太慢写个脚本一步到位import xml.etree.ElementTree as ET import os CLASSES [person] # 当前只关心行人 def convert_voc(xml_path, txt_path, img_w, img_h): tree ET.parse(xml_path) root tree.getroot() with open(txt_path, w, encodingutf-8) as fw: for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) # 过滤掉宽高为零或坐标反了的脏标注 if xmax xmin or ymax ymin: continue x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h fw.write(f0 {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n)这段代码里CLASSES用来过滤非行人的目标XML 里可能误标了车、椅子这些不用出现在训练文件里。xmax xmin的脏数据必须提前过滤否则训练会报错或者产生异常的 loss。图片的宽高必须从图片本身读取不能用 XML 里的值因为 LabelImg 有时会把图片尺寸写成 0。转换完之后自己抽查几份 TXT看坐标是否落在合理范围内。3.3 训练命令与关键超参数batch、epoch、imgsz、学习率数据集准备好之后目录结构按 YOLO 要求的格式组织datasets/person/ images/ train/ val/ labels/ train/ val/再写一个数据配置文件person.yamlpath: ./datasets/person train: images/train val: images/val names: 0: person训练命令可以这样写yolo train dataperson.yaml modelyolov8n.pt epochs100 imgsz640 batch16 lr00.01中间几个参数需要根据显存和场景反复调。batch是每批次图片数6G 显存从 16 起跑显存溢出就降到 8epochs建议先跑 100模型如果早收敛可以在results.csv里看到验证 loss 不再下降后续训练会自己触发早停设置imgsz是输入分辨率默认 640如果画面里小目标多可以提高到 960 或 1280但显存占用和推理耗时也随之上涨lr0是初始学习率默认 0.01 对大多数情况够用loss 震荡严重时降低到 0.001 试试。训练输出会在runs/detect/train/下生成weights/best.pt这就是后面系统用的模型。验证一下训练结果yolo val modelruns/detect/train/weights/best.pt dataperson.yaml imgsz640重点看 val 的 mAP50 和 mAP50-95 两个指标。mAP50 是检测框与真值框 IoU 超过 0.5 才认为是正确检测mAP50-95 则更严格综合多个 IoU 阈值。行人识别这种业务mAP50 达到 0.85 以上基本能跑但夜间和遮挡场景还得靠实际视频验证。4. 系统实现摄像头取帧、目标跟踪、计数告警的完整 Python 代码模型训练只是第一步真正的系统要能把模型接进摄像头、输出稳定的计数结果。这一章按照一个可运行的最小系统来写代码结构可以一直沿用到项目迭代。4.1 系统总体架构采集、推理、业务分离监控视频流不能像单张图片那样一下处理完必须考虑实时性。最常踩的坑是把摄像头读取和模型推理放在同一个循环里导致读帧阻塞时推理空转画面一直卡顿。我一般把系统拆成三个线程采集线程负责持续读帧并放入队列推理线程从队列取帧跑检测和跟踪业务线程负责画框、计数、写日志。队列长度要限制我习惯设为 3。太长会增加延迟太短遇到检测慢会丢帧。这个设计看起来简单却决定了系统能不能稳定跑小时级。4.2 核心推理代码封装一个行人检测器让模型只输出行人框需要指定classes[0]COCO 预训练模型里 person 这个类别对应的索引就是 0。不指定的后果是画面里的公交车、自行车都会被画框计数全部乱掉。核心代码封装成类from ultralytics import YOLO class PedestrianDetector: def __init__(self, weightsbest.pt, conf0.25, iou0.45): self.model YOLO(weights) self.conf conf self.iou iou def infer(self, frame): results self.model.predict( sourceframe, confself.conf, iouself.iou, classes[0], verboseFalse, ) boxes results[0].boxes.xyxy.cpu().numpy() scores results[0].boxes.conf.cpu().numpy() return boxes, scoresconf是置信度阈值默认 0.25调低能减少漏检但会增加误检调高则相反。iou是 NMS 阈值用来合并重叠框默认 0.45太低会让并排的行人只保留一个框太高又会把同一个行人框成几个。我实际项目里一般先用默认值跑一遍再用真实视频调这两参数。verboseFalse是必需的否则会把每个目标的检测信息都打印到控制台刷新速度感人。4.3 用 ByteTrack 做跟踪和计数persist 模式与计数逻辑检测只给出一帧的框要想“数出到底有多少人走过”就得跟踪给每个行人一个稳定 ID。Ultralytics 提供了现成的跟踪接口内部实现了 ByteTrack 算法不需要自己写匹配逻辑results self.model.track( sourceframe, conf0.25, classes[0], trackerbytetrack.yaml, persistTrue, ) if results[0].boxes is not None and results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.int().cpu().numpy()这里persistTrue很关键它告诉模型当前视频帧和上一帧属于同一个视频序列要延续跟踪 ID如果设成默认 False每帧都会重新分配 ID计数逻辑全乱。拿到 ID 之后做越线计数就很简单。设定一条水平线line_y记录每个 ID 上一次的中心点如果上一帧中心点在线上方、这一帧到了下方说明行人向下越线人数加一def update_count(boxes, ids, track_last, line_y, count): for box, track_id in zip(boxes, ids): x1, y1, x2, y2 box cy int((y1 y2) / 2) if track_id in track_last and track_last[track_id] line_y and cy line_y: count 1 track_last[track_id] cy return count这个逻辑只处理了“向下越线”但已经够解释整个思路。更严格的场景还要加“向上越线时不计”以及 ID 丢失后重新出现不重复计数。这些细节必须靠现场视频逐帧验证我不建议一开始就堆各种复杂状态先把最简单的跑通再迭代。4.4 把结果叠加到画面并输出日志为了让系统可演示、可排查检测框和计数结果要实时画在帧上同时输出到本地日志。绘制代码用 OpenCV 足够for box, score, track_id in zip(boxes, scores, ids): x1, y1, x2, y2 [int(v) for v in box] cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText( frame, fID {track_id} {score:.2f}, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2, ) cv2.line(frame, (0, line_y), (frame.shape[1], line_y), (0, 0, 255), 1)画框的绿色(0, 255, 0)在监控画面上辨识度很高红色画越界线。字体大小 0.6 在 1080P 画面里刚好窗口小的话需要调大。除了画框我会同时把每一帧的人数、越线总人数写入一个 CSV 文件后续统计和核对都靠它。5. 避坑指南行人识别系统上线前最容易翻车的 4 个环节这一章踩过的坑都是真金白银换来的。我会按“现象 → 原因 → 解决”的顺序写每一条都能直接对应到你的调试过程里。5.1 白天正常、晚上漏检数据分布和相机增益问题现象系统白天跑得好好的一到傍晚开始漏检路灯下尤其严重。原因训练数据里基本没有暗光样本模型没学过低亮度、高噪声的行人外观同时监控相机会自动提高 ISO夜间画面噪声很大检测框开始抖动。解决第一选择是采集傍晚到夜间的真实视频帧补进训练集数据增强里加亮度抖动和椒盐噪声也有帮助。如果来不及重新训练可以用伽马校正预处理。伽马值小于 1 能压暗部提亮让模型更容易看到人import numpy as np gamma 0.7 lookup ((np.arange(256) / 255.0) ** gamma * 255).astype(uint8) frame cv2.LUT(frame, lookup)这只是一个救急办法真正可靠的是让训练数据贴近现场光照。这里有个原则模型见过什么才能稳定识别什么。5.2 远处行人太小小目标检测的尺度和分辨率矛盾现象摄像头装在 6 米高的杆子上画面里远处行人只有十几个像素高检测框时有时无计数严重偏低。原因YOLO 模型输入缩放到 640 像素远处的小目标经过多层下采样之后特征图上的信息几乎丢失。模型并非“看不见”而是参与判断的像素太少。解决把imgsz提高到 960 或 1280小目标信息保留更多。显存不够时可以换成YOLOv8n的imgsz1280推理耗时大约翻倍也可以把原始图像切成四块分别推理再合并结果模型不需要改变但推理时间更久适合对实时性要求不高的场景。调试时记得看results[0].boxes里有没有小尺寸框出现如果没有优先提分辨率而不是降阈值。5.3 推理速度上不去摄像头读取阻塞和动态 shape 开销现象GPU 利用率不到 50%但视频处理帧率只有 10 FPSCPU 的使用率反而很高。原因大概率是摄像头读取和推理在同一个线程里cv2.VideoCapture.read()遇到网络摄像头或 RTSP 流时会阻塞等待下一帧推理线程被拖住。另一个常见原因是模型每帧输入 shape 不同触发动态图优化耗时明显上升。解决把摄像头读取拆到独立线程通过队列传递帧import queue import cv2 frame_queue queue.Queue(maxsize3) def read_loop(rtsp_url): cap cv2.VideoCapture(rtsp_url) while cap.isOpened(): ok, frame cap.read() if ok: frame_queue.put(frame)推理端固定输入尺寸model.predict里明确imgsz640不要用None或自动推断。如果 RTSP 流在低带宽下频繁断流优先在相机端降低码率或改子码流而不是在代码里反复重连。5.4 PyTorch 和 CUDA 版本不匹配环境安装翻车现象torch.cuda.is_available()一直返回False或者运行到一半报 “CUDA error: no kernel image is available for execution”。原因最常见的是用pip install torch装了默认 CPU 版或者电脑的显卡驱动和 PyTorch 对应的 CUDA 版本不一致。解决直接看 PyTorch 官网提供的安装命令它会自动匹配你的 CUDA 版本。比如你本机是 CUDA 11.8就复制官网那个--index-url命令安装不要自己拼版本号。用 conda 管理一个独立环境再用nvidia-smi确认驱动版本支持的目标 CUDA 版本这能把很多玄学问题挡在门外。6. 部署与进阶导出 ONNX 并封装成可调用的检测服务模型验证完系统还停在“在笔记本上能跑”的阶段。真正能交付需要让模型脱离 PyTorch 环境运行并给外部程序提供调用接口。这一章讲最轻量的部署路线和验证方法。6.1 用 ONNX 导出模型脱离 PyTorch 依赖把训练好的best.pt导出成 ONNX可以用 ONNXRuntime 做推理这样部署机器上不用装一堆深度学习框架model YOLO(best.pt) model.export(formatonnx, imgsz640, dynamicFalse, opset12)dynamicFalse表示输入 shape 固定适合视频流和请求式识别性能更稳。ONNX 导出后用onnxruntime加载import onnxruntime as ort session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider])如果部署机上没有 NVIDIA GPU把providers改成[CPUExecutionProvider]就行。导出这一步常出现 PyTorch 和 ONNX 版本不匹配导出报错时优先查一下onnx和onnxruntime的版本这两者要配套更新。6.2 封装成 HTTP 服务接收图片返回 JSON业务方一般不会直接调用 Python 接口封装一个轻量 Flask 服务更实用。这个接口接收一张图片返回检测到的行人数和坐标from flask import Flask, request, jsonify import cv2 import numpy as np app Flask(pedestrian) detector PedestrianDetector(best.pt) app.route(/detect, methods[POST]) def detect(): file request.files[image] data np.frombuffer(file.read(), np.uint8) frame cv2.imdecode(data, cv2.IMREAD_COLOR) boxes, scores detector.infer(frame) return jsonify({count: len(boxes), boxes: boxes.tolist()}) # app.run(host0.0.0.0, port9000)cv2.imdecode用来把传输的图片字节流解码成 OpenCV 图像注意它默认按 BGR 通道读取和模型训练的输入一致。启动服务后用curl -F imagetest.jpg http://localhost:9000/detect就能拿到结果。生产环境可以换成 FastAPI 或多 worker但单路请求这个写法足够。6.3 验证方法别只看模型指标要看真实视频回放最后说验证。只盯着 mAP 数值是不够的我习惯录一段 5 分钟的现场视频把系统跑一遍统计三件事正确检测数、漏检数、误检数。先人工把视频里每一帧出现的行人数量数出来再和系统输出对比算出召回率和精确率。计数模块单独测让行人实际越线 N 次系统计数值落在 ±5% 以内才算通过。这个习惯救了我很多次。曾经有个项目模型 mAP50 很高但上线第一天就被夜间漏检和重复计数打脸。从那以后我会把“真实视频回放”作为上线前置步骤如果没有现场视频也要用一台摄像机拍模拟场景把每一帧作为验收用例存下来。希望这套设计思路能帮你少走点弯路也希望你交付的行人识别系统真正跑得稳、数得准。本文还有配套的精品资源点击获取
返回列表