拓冰建站拓冰建站
首页 / 资讯中心 / 正文

HOG+SVM行人检测实战:从环境配置到滑窗检测的完整指南

简介这份资源是一套基于HOG特征与SVM分类器的行人检测Python与C实现面向计算机、人工智能、通信工程等专业的在校学生及自学者可用于课程设计、毕业设计或项目初期立项演示。包内共19个文件以5个cpp源文件、1个头文件、1个md说明文档为主辅以若干jpg与png测试图片及txt样本列表压缩包约626KB整体结构紧凑便于快速理解算法流程。项目完整覆盖负样本裁剪、HOG特征提取、SVM训练、hard negative样本挖掘以及最终模型生成与测试等环节代码均经过实际运行验证答辩评审平均分达96分。已有159人学习下载适合具备一定基础、希望在此基础上修改扩展功能或作为毕设参考的读者下载后建议先阅读README.md了解目录组织与运行方式。1. 从一张 64×128 的灰度图说起HOGSVM 行人检测到底在做什么如果你手头有一段监控视频想框出画面里走动的人又不打算一上来就上深度学习那 HOGSVM 行人检测是一条绕不开的经典路线。它的输入是一张 64×128 的灰度图输出是「这个窗口里有没有人」。整套流程拆开就是两步HOG 负责把图像变成一串对光照和轻微形变不敏感的特征向量SVM 负责在这串向量上画一条分界线判断是人还是背景。它不需要 GPU在普通笔记本上就能跑模型文件也就几百 KB非常适合嵌入式或算力受限的场景。这篇笔记面向的是想真正把这份 Python 源码跑起来、并且搞清楚每个参数为什么这么设的人不是泛泛了解概念。我会从环境配置讲到滑窗检测再到训练自己的数据把能抄的代码和会翻车的地方都摆出来。2. 环境配置与源码结构先把 cv2 和 sklearn 装对2.1 Python 环境与依赖安装的稳妥顺序很多人卡在第一步不是算法而是import cv2直接报错。HOGSVM 行人检测的 Python 实现通常依赖三个库OpenCV 提供 HOG 描述子和内置行人检测器scikit-learn 提供 SVM 训练与预测NumPy 负责数组运算。我一般会先建一个干净的虚拟环境避免和系统里已有的包打架。如果你用的是 Windows装 OpenCV 时优先走国内源能省掉不少等待时间。# 创建虚拟环境Python 3.8 到 3.10 兼容性最好 python -m venv hog_svm_env # 激活环境Windows 用下面这行 hog_svm_env\Scripts\activate # Linux 或 macOS 用这行 source hog_svm_env/bin/activate # 安装核心依赖指定国内源加速 pip install opencv-python4.8.1.78 scikit-learn1.3.2 numpy1.24.3 -i https://pypi.tuna.tsinghua.edu.cn/simple这里固定版本不是玄学。OpenCV 4.8 的 HOGDescriptor 接口稳定scikit-learn 1.3 的 SVM 默认参数和旧版有差异固定下来能保证你复现的结果和我一致。装完后用一行命令验证import cv2 import sklearn import numpy as np print(cv2.__version__, sklearn.__version__, np.__version__)如果cv2导入失败九成是 opencv-python 和 numpy 版本不匹配先卸载再按上面顺序重装。VS Code 或 PyCharm 里记得把解释器切到刚建的虚拟环境否则你装了半天编辑器用的还是系统 Python。2.2 源码目录里每个文件该放在哪一份典型的 HOGSVM 行人检测 Python 源码目录结构不会太复杂但文件放错位置就会报路径错误。常见布局是这样的文件或目录作用放置注意detect.py加载 SVM 模型对图片或视频做滑窗检测和模型文件同级train.py读取正负样本提取 HOG 特征并训练 SVM需要指向数据集根目录hog_svm_model.pkl训练好的 SVM 模型用 pickle 保存不要手动改文件名data/pos/正样本只含行人的小图图片尺寸统一 64×128data/neg/负样本不含行人的背景图数量建议是正样本的 2 到 3 倍utils.py滑窗、非极大值抑制等辅助函数被 detect.py 导入我见过有人把data文件夹放在train.py的上一级结果脚本里写的是相对路径./data/pos一运行就找不到文件。稳妥做法是在train.py开头用os.path.dirname(os.path.abspath(__file__))拿到脚本所在目录再拼接数据路径。这样无论你从哪个目录执行命令路径都不会错。import os # 以脚本所在目录为基准避免相对路径踩坑 BASE_DIR os.path.dirname(os.path.abspath(__file__)) POS_DIR os.path.join(BASE_DIR, data, pos) NEG_DIR os.path.join(BASE_DIR, data, neg) MODEL_PATH os.path.join(BASE_DIR, hog_svm_model.pkl) print(正样本目录:, POS_DIR) print(模型保存路径:, MODEL_PATH)这段代码不涉及算法但它是后面所有步骤的地基。路径对了训练和检测才能稳定复现。3. HOG 特征提取参数怎么设向量为什么是 3780 维3.1 把 64×128 图像切成 cell 和 block 的逻辑HOG 的核心思想是局部区域的梯度方向分布能描述形状而行人最明显的形状就是竖直的轮廓。OpenCV 的HOGDescriptor默认参数就是为行人检测调过的窗口 64×128cell 8×8block 16×16block 步长 8。一张 64×128 的图按 8×8 切成 8 列 16 行共 128 个 cell。每 2×2 个 cell 组成一个 blockblock 在水平和垂直方向各滑动 8 像素于是有 7×15 个 block。每个 block 内 4 个 cell每个 cell 的梯度方向直方图分 9 个 bin所以特征维度是 7×15×4×9 3780。这个数字不是随便来的它直接决定了 SVM 输入向量的长度。import cv2 import numpy as np # 定义 HOG 描述子参数与 OpenCV 默认行人检测器一致 hog cv2.HOGDescriptor( _winSize(64, 128), # 检测窗口大小 _blockSize(16, 16), # 每个 block 覆盖 2x2 个 cell _blockStride(8, 8), # block 滑动步长 _cellSize(8, 8), # 每个 cell 8x8 像素 _nbins9 # 梯度方向分 9 个区间 ) # 读一张图并统一到 64x128 灰度 img cv2.imread(data/pos/person_001.png, cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (64, 128)) # 提取 HOG 特征 feature hog.compute(img) print(特征维度:, feature.shape) # 期望输出 (3780, 1)hog.compute返回的是列向量训练前通常用feature.flatten()转成一维。如果你改了_winSize或_cellSize维度会变SVM 模型也必须重新训练不能混用。3.2 特征归一化与 SVM 输入格式的对接HOG 特征本身已经做过 block 归一化但不同图片的对比度差异仍会影响 SVM 的收敛。我一般会在送入 SVM 前再做一次整体归一化把特征缩放到相近范围。scikit-learn 的StandardScaler是常见选择但要注意训练时 fit 的 scaler 必须保存下来检测时用同一个 scaler 做 transform否则分布对不上准确率会掉得莫名其妙。from sklearn.preprocessing import StandardScaler import pickle # 假设 X_train 是 N 行 3780 列的 HOG 特征矩阵 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) # 保存 scaler检测阶段必须复用 with open(hog_scaler.pkl, wb) as f: pickle.dump(scaler, f) print(归一化后均值范围:, X_train_scaled.mean(axis0).min(), X_train_scaled.mean(axis0).max())这一步的坑在于有人训练时归一化了检测时忘了加载 scaler直接拿原始 HOG 特征去 predict结果框出一堆误检。记住scaler 和 SVM 模型是一对缺一不可。4. SVM 训练与滑窗检测从正负样本到画面上的框4.1 正负样本的组织与 SVM 核函数选择正样本就是只包含行人的小图统一缩放到 64×128。负样本是不含行人的背景图数量建议是正样本的 2 到 3 倍否则 SVM 容易偏向背景类。标签用 1 表示行人-1 表示背景。核函数方面HOG 特征维度高但样本量通常不大线性核LinearSVC或SVC(kernellinear)是首选训练快且不容易过拟合。如果正负样本都上万可以考虑SVC(kernelrbf)但调参成本会明显上升。import os import cv2 import numpy as np from sklearn.svm import SVC from sklearn.preprocessing import StandardScaler import pickle hog cv2.HOGDescriptor(_winSize(64,128), _blockSize(16,16), _blockStride(8,8), _cellSize(8,8), _nbins9) def load_features(pos_dir, neg_dir): X, y [], [] for label, folder in [(1, pos_dir), (-1, neg_dir)]: for fname in os.listdir(folder): path os.path.join(folder, fname) img cv2.imread(path, cv2.IMREAD_GRAYSCALE) if img is None: continue img cv2.resize(img, (64, 128)) feat hog.compute(img).flatten() X.append(feat) y.append(label) return np.array(X), np.array(y) X, y load_features(data/pos, data/neg) scaler StandardScaler() X_scaled scaler.fit_transform(X) # 线性核 SVMC 值从 0.01 到 1 之间调 clf SVC(kernellinear, C0.1, probabilityTrue) clf.fit(X_scaled, y) with open(hog_svm_model.pkl, wb) as f: pickle.dump((clf, scaler), f) print(训练样本数:, len(y), 正样本:, sum(y1), 负样本:, sum(y-1))C是惩罚系数越大越不能容忍错分容易过拟合越小则允许更多错分泛化可能更好。我一般从 0.01 开始试看验证集上的误检和漏检再微调。4.2 多尺度滑窗与非极大值抑制的配合训练好的 SVM 只能判断一个 64×128 窗口。要检测整张图里不同大小的行人就得用图像金字塔加滑窗。常见做法是把原图按不同比例缩小每个尺度上以固定步长滑动窗口提取 HOG 特征后送 SVM 打分。这样会产生大量重叠框必须用非极大值抑制NMS合并。OpenCV 自带的HOGDescriptor.detectMultiScale已经封装了这套流程但如果你想自己控制参数也可以手写滑窗。import cv2 import numpy as np import pickle # 加载模型和 scaler with open(hog_svm_model.pkl, rb) as f: clf, scaler pickle.load(f) hog cv2.HOGDescriptor(_winSize(64,128), _blockSize(16,16), _blockStride(8,8), _cellSize(8,8), _nbins9) def detect_person(image_path, scale1.05, step8, threshold0.5): img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) boxes [] current_scale 1.0 while gray.shape[0] // current_scale 128 and gray.shape[1] // current_scale 64: resized cv2.resize(gray, None, fx1/current_scale, fy1/current_scale) h, w resized.shape for y in range(0, h - 128 1, step): for x in range(0, w - 64 1, step): window resized[y:y128, x:x64] feat hog.compute(window).flatten().reshape(1, -1) feat scaler.transform(feat) score clf.decision_function(feat)[0] if score threshold: boxes.append([int(x*current_scale), int(y*current_scale), int(64*current_scale), int(128*current_scale)]) current_scale * scale # 非极大值抑制 if boxes: boxes np.array(boxes) indices cv2.dnn.NMSBoxes(boxes.tolist(), [1.0]*len(boxes), 0.3, 0.4) for i in indices: x, y, w, h boxes[i] cv2.rectangle(img, (x, y), (xw, yh), (0, 255, 0), 2) cv2.imwrite(result.jpg, img) return boxes detect_person(test.jpg)scale控制金字塔缩放步长1.05 表示每次缩小 5%越小越精细但越慢。step是滑窗步长8 像素是常见折中。threshold是 SVM 决策分数阈值调高减少误检但可能漏检调低相反。NMS 的score_threshold和nms_threshold也要根据实际画面调没有一组参数能通吃所有场景。5. 避坑与排查那些让检测器集体翻车的瞬间5.1 现象画面里全是框误检率高得离谱原因通常是负样本太少或太单一。如果负样本全是纯色墙面SVM 没见过复杂背景一遇到树叶、栏杆就判成行人。解决方法是增加负样本多样性从实际场景里截取不含人的区域数量至少是正样本的两倍。另外检查 scaler 是否在检测时正确加载忘了归一化也会导致分数普遍偏高。5.2 现象同一个人被框出好几个重叠框这是 NMS 没生效或阈值设得太松。cv2.dnn.NMSBoxes的nms_threshold默认 0.4如果重叠框的 IoU 低于这个值就不会被合并。可以降到 0.3 试试。另外滑窗步长太小也会产生大量近似框把step从 4 调到 8 或 12 能明显缓解。5.3 现象训练时准确率很高一换视频就检测不到人典型的过拟合。正样本可能都来自同一个数据集光照、姿态、衣着单一。解决办法是加入不同场景的正样本做水平翻转、随机裁剪、亮度扰动等增强。SVM 的C值也可以调小比如从 1 降到 0.1让模型不那么激进。5.4 现象程序跑几分钟就卡死或内存暴涨多尺度滑窗在低scale步长下会生成海量窗口每个窗口都提 HOG 特征计算量爆炸。建议限制金字塔层数比如最小检测尺寸不低于 64×128或者先用背景减除筛掉无人区域再滑窗。另外hog.compute每次都会重新分配内存循环里尽量复用变量。5.5 现象换一台机器加载模型报版本不兼容pickle 保存的模型和 scikit-learn 版本强相关。训练用 1.3加载用 1.2 就可能报错。稳妥做法是在文档里写清依赖版本或者改用joblib并固定环境。如果必须跨版本重新训练比折腾兼容更省时间。6. 用 OpenCV 内置检测器做基线对比与参数微调自己训练的 SVM 到底行不行最直接的验证方式是拿 OpenCV 自带的HOGDescriptor_getDefaultPeopleDetector跑同一张图对比两者的框和耗时。内置检测器是在 INRIA 数据集上训好的开箱即用但它的参数是固定的遇到特殊场景不一定比你的模型好。我一般会把它当基线如果自己的模型误检更多就回头查负样本和阈值。import cv2 import time # 加载 OpenCV 内置行人检测器 hog cv2.HOGDescriptor() hog.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector()) img cv2.imread(test.jpg) start time.time() boxes, weights hog.detectMultiScale(img, winStride(8,8), padding(16,16), scale1.05) elapsed time.time() - start for (x, y, w, h) in boxes: cv2.rectangle(img, (x, y), (xw, yh), (0, 0, 255), 2) cv2.imwrite(baseline_result.jpg, img) print(f内置检测器耗时: {elapsed:.2f}s, 检测到 {len(boxes)} 个人)winStride控制滑窗步长padding是边缘填充scale是金字塔缩放。内置检测器的hitThreshold默认 0可以通过hog.detectMultiScale(..., hitThreshold0.5)调高来减少误检。对比时重点看三个指标漏检的人、误检的框、单帧耗时。如果内置检测器在你的场景里漏检严重说明 HOG 特征对那个场景本身就不够用这时候考虑换方向比死调参数更明智。我自己的习惯是任何 HOGSVM 项目先跑通内置检测器确认环境和数据没问题再换自己的模型。这样出问题时能快速定位是代码错还是模型差。另外检测结果不要只看一张图至少抽 20 帧不同光照和遮挡程度的画面统计漏检和误检比例再决定要不要继续投入。这套方案在算力受限、实时性要求不极端的场景里依然值得做但如果你的人体姿态变化大、遮挡严重早点转向基于深度学习的检测器会更省心。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门