1. HOG+SVM行人检测系统概述
第一次接触行人检测是在一个智能监控项目里,当时需要从摄像头画面中实时识别行人位置。试过几种方法后发现,HOG+SVM这个经典组合不仅效果好,而且特别适合新手入门。你可能听说过现在深度学习很火,但我要告诉你,这套传统方法在中等规模数据集上表现依然出色,更重要的是它能帮你真正理解计算机视觉的底层逻辑。
HOG(方向梯度直方图)就像给图像做"指纹采集"。想象你要在人群中找朋友,不会去记忆整张脸,而是记住他眼睛形状、鼻子轮廓这些关键特征。HOG也是这样,它把图像分成小格子(cell),统计每个格子里梯度方向的分布情况。我实测发现,当设置orientations=9(把360度分成9个方向区间)、pixels_per_cell=(8,8)时,既能保留足够信息又不会让特征维度爆炸。
SVM(支持向量机)则是个严格的"安检员"。训练时我给正样本(行人)贴标签1,负样本(背景)贴0。这个分类器会找到让两类数据间隔最大的决策边界。有次我偷懒没做数据平衡,结果SVM把所有样本都判为负类——这个教训让我明白,正负样本比例最好控制在1:1到1:3之间。
2. 数据准备与特征提取
2.1 构建高质量数据集
去年帮学校实验室搭建系统时,发现数据集质量直接影响最终效果。我的经验是正样本至少准备2000张,负样本要更多样化。INRIA数据集是个不错的起点,但建议自己补充些街拍照片。有个小技巧:用OpenCV的cv2.resize统一尺寸时,记得保持宽高比接近0.6(如96x160),这符合行人站立时的比例。
有次我图省事用随机裁剪的图片做负样本,结果模型把电线杆都识别成行人。后来改用包含建筑、车辆、道路的复杂场景,误检率立刻下降。建议负样本集包含这些类型:
- 城市街景(红绿灯、招牌)
- 交通工具(自行车、汽车轮胎)
- 室内场景(桌椅、门窗)
2.2 HOG特征提取实战
skimage的hog函数虽然方便,但参数设置很有讲究。经过多次实验,这套配置效果最稳定:
from skimage.feature import hog features = hog(img, orientations=9, pixels_per_cell=(8,8), cells_per_block=(2,2), transform_sqrt=True, # 增强阴影区域 feature_vector=True)特别注意transform_sqrt这个参数,它先对图像做平方根变换,能提升低对比度区域的可见度。有次处理逆光照片时,开启这个选项让检测率提升了15%。
存储特征时推荐用joblib而不是pickle,前者对大数组的序列化效率更高。我习惯把特征保存为.feet文件,目录结构这样组织:
Data/ ├── Features/ │ ├── pos_feat/ # 正样本特征 │ └── neg_feat/ # 负样本特征 ├── Images/ │ ├── pos/ # 正样本图片 │ └── neg/ # 负样本图片3. SVM模型训练技巧
3.1 参数调优实战
刚开始用默认参数训练SVM时,测试集准确率卡在82%上不去。后来发现LinearSVC的C参数(惩罚系数)对结果影响巨大。通过网格搜索找到最优值的方法如下:
from sklearn.model_selection import GridSearchCV parameters = {'C':[0.01, 0.1, 1, 10]} clf = GridSearchCV(LinearSVC(), parameters, cv=5) clf.fit(train_features, train_labels) print(f"最佳参数: {clf.best_params_}")有个容易踩的坑:特征向量未归一化会导致SVM收敛困难。建议训练前做标准化:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() train_features = scaler.fit_transform(train_features) test_features = scaler.transform(test_features)3.2 模型评估与改进
好的评估不能只看准确率。我通常会打印分类报告:
from sklearn.metrics import classification_report print(classification_report(y_true, y_pred))重点关注recall(查全率),因为行人检测宁可误报也不能漏检。如果发现负样本误检多,可以:
- 增加难负样本挖掘(hard negative mining)
- 调整SVM的decision_function阈值
- 在负样本集中添加更多类似误检目标的图片
保存模型时建议同时存储标准化器:
import joblib joblib.dump({'model':clf, 'scaler':scaler}, 'model.pkl')4. 滑动窗口与多尺度检测
4.1 滑动窗口实现细节
滑动窗口就像用放大镜在图像上逐块检查。这里有个效率陷阱——步长(step_size)设置太小时计算量激增。经过测试,(10,10)的步长在精度和速度间取得较好平衡。我的窗口滑动函数是这样实现的:
def sliding_window(image, window_size, step_size): for y in range(0, image.shape[0]-window_size[1], step_size[1]): for x in range(0, image.shape[1]-window_size[0], step_size[0]): yield (x, y, image[y:y+window_size[1], x:x+window_size[0]])处理大图时一定要用生成器(yield),避免内存爆炸。有次处理4K图像时直接生成所有窗口坐标,导致16GB内存瞬间占满。
4.2 多尺度金字塔优化
行人可能远近不同,必须配合图像金字塔。downscale参数我推荐1.25,比常见的2.0更精细。实测在监控场景中,这个设置对小尺寸行人检测率提升显著:
from skimage.transform import pyramid_gaussian for im_scaled in pyramid_gaussian(image, downscale=1.25): if im_scaled.shape[0] < window_size[1] or im_scaled.shape[1] < window_size[0]: break # 对各尺度图像执行滑动窗口有个性能优化技巧:先在最粗尺度检测,然后在检测区域附近做精细搜索。这样比全图多尺度搜索快3-5倍。
5. 非极大值抑制与结果优化
5.1 消除重复检测
滑动窗口会产生大量重叠框,这时非极大值抑制(NMS)就派上用场了。imutils库的实现最好用:
from imutils.object_detection import non_max_suppression rects = [[x,y,x+w,y+h] for (x,y,_,w,h) in detections] scores = [score for (_,_,score,_,_) in detections] pick = non_max_suppression(np.array(rects), probs=np.array(scores), overlapThresh=0.3)overlapThresh参数控制重叠阈值,我习惯从0.3开始调整。太高会保留重复框,太低可能漏掉真实目标。可视化对比时建议用subplot展示NMS前后效果:
import matplotlib.pyplot as plt plt.subplot(121) plt.imshow(original_image) plt.title('原始检测') plt.subplot(122) plt.imshow(nms_image) plt.title('NMS处理后')5.2 后处理技巧
有些场景需要特殊处理:
- 阴影干扰:在HOG前做直方图均衡化
- 人群密集:适当提高NMS的overlapThresh
- 小目标检测:增加金字塔层数,减小初始步长
最后推荐用OpenCV的dnn模块部署模型,速度比纯Python实现快8-10倍:
net = cv2.dnn.readNetFromONNX('model.onnx') blob = cv2.dnn.blobFromImage(image, scalefactor=1/255.) net.setInput(blob) detections = net.forward()