news 2026/10/3 9:15:58

HOG+SVM行人检测全流程解析:从特征提取到难例挖掘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HOG+SVM行人检测全流程解析:从特征提取到难例挖掘

简介:本资源是一套基于HOG特征提取与SVM分类器实现的行人检测完整项目,面向人工智能、计算机科学、自动化等专业的在校学生、教师及初学者,适用于课程设计、毕业设计、算法实践与CV入门学习。项目包含从负样本裁剪、HOG特征提取、SVM模型训练、难例挖掘到最终检测验证的全流程代码与说明,所有模块均经实测运行成功,答辩评审平均分达96分。压缩包共19个文件,涵盖5个核心C++源码(如detection.cpp、crop_image.cpp、find_save_hardexample.cpp)、4个文本配置与说明文件(含pos/neg样本路径及参数设置)、5张测试图像、2张效果示意图、1份README.md使用指南及1个头文件,整体大小仅626KB,结构紧凑、便于快速部署与二次开发。目前已有159人下载学习,配套文档清晰、步骤可复现,特别适合理解传统机器视觉目标检测的经典Pipeline,并为后续深度学习方案打下扎实基础。

1. 基于HOG+SVM的行人检测:不是调库跑通就完事,而是亲手拆解特征工程与分类器协同的黑匣子

你用OpenCV的HOGDescriptor跑过demo,但模型在自己拍的街景图上一检测就满屏误报?你照着教程用sklearn.SVC训练了10分钟,结果测试集AP只有32%?这不是你代码写错了——是没真正摸清HOG特征怎么“看”人、SVM怎么“判”人、负样本怎么“喂”才不翻车。这份毕设级源码(Pedestrain_Detection-master.zip)不是封装好的API,而是一套可逐层调试的完整流水线:从原始图像裁剪→HOG向量提取→正负样本构造→SVM硬间隔训练→难例挖掘迭代→最终模型导出。它用纯C++实现核心计算(detection.cpp/crop_image.cpp),Python仅作胶水脚本和可视化(test_main.py隐含逻辑),所有中间产物(normalized_images/、sum_pos.txt、sample_neg.txt)都暴露给你——你能看到每张负样本图被裁成多少块、每个HOG块的梯度方向直方图数值、SVM支持向量的索引位置。适合正在啃《计算机视觉中的多视图几何》第7章、刚学完SVM对偶问题推导、想把课设答辩做到96分的学生;也适合需要快速验证传统CV pipeline baseline、为后续YOLOv8对比实验打底的企业工程师。别急着pip install,先搞懂为什么crop_image要按滑动窗口切负样本、为什么hardneg必须在初代模型上跑、为什么test_main.cpp里那个cv::HOGDescriptor::setSVMDetector()传入的vector长度必须是3780——这些才是你复现时真正卡住的点。

2. HOG特征提取与SVM训练:从图像像素到分类超平面的四步转化链

HOG+SVM行人检测不是“调个参数就出结果”的魔法,而是一条环环相扣的转化链:图像→梯度→块直方图→归一化向量→SVM决策。这份源码把每一步都拆成独立可验证的模块,拒绝黑盒。我们按实际执行顺序(crop_image → detection.cpp → find_save_hardexample → detection.cpp再训)还原技术流,重点说清每个环节的物理意义和参数依据。

2.1 负样本裁剪:为什么必须用crop_image.cpp而不是直接resize?

行人检测的负样本不是随便找几张背景图就行。真实场景中,负样本需覆盖各种尺度、光照、纹理干扰——比如广告牌反光、树叶抖动、砖墙阴影。crop_image.cpp做的不是简单缩放,而是滑动窗口+随机裁剪+尺寸归一化三重处理:

// crop_image.cpp 关键逻辑节选 for(int y = 0; y < img.rows - win_h; y += step_y) { for(int x = 0; x < img.cols - win_w; x += step_x) { cv::Rect roi(x, y, win_w, win_h); cv::Mat patch = img(roi).clone(); cv::resize(patch, patch, cv::Size(64,128)); // 强制归一化到HOG标准尺寸 // 保存路径:img_dir/neg_XXXX.jpg } }
  • win_w=64,win_h=128:这是Dalal-Triggs论文验证的最佳行人宽高比,非经验参数。若改成128x128,HOG描述子维度会从3780跳到15120,SVM训练内存暴涨3倍。
  • step_x=8,step_y=8:步长决定负样本密度。太小(如1)生成数万张图,硬盘爆满;太大(如32)漏掉关键干扰模式。原文档README.md未明说,但从sample_neg.txt行数反推,作者实际用了8像素步长。
  • 输出目录img_dir/下所有.jpg文件名被写入sample_neg.txt——这是SVM训练时读取负样本的唯一索引文件,不能手动增删,否则detection.cpp会因文件缺失崩溃。

提示:crop_image.cpp编译需OpenCV 3.4+,若报错cv::Rect未定义,检查是否漏了#include <opencv2/opencv.hpp>。Ubuntu下编译命令:g++ -o crop_image crop_image.cpppkg-config --cflags --libs opencv4``(注意opencv4而非opencv)

2.2 HOG特征提取:为什么detection.cpp里BlockSize设为16×16?

HOG的核心是“梯度方向直方图”,但直方图统计单元(cell)、组合单元(block)、滑动步长(block stride)三者必须严格匹配。detection.cpp中这段初始化决定了最终特征向量长度:

// detection.cpp 片段 cv::HOGDescriptor hog; hog.winSize = cv::Size(64,128); // 检测窗口大小 hog.blockSize = cv::Size(16,16); // Block尺寸:4个cell×4个cell hog.blockStride = cv::Size(8,8); // Block滑动步长:半重叠 hog.cellSize = cv::Size(8,8); // Cell尺寸:2×2像素梯度统计 hog.nbins = 9; // 梯度方向bin数(0-180°分9份) // 计算特征向量长度:(64/8-1)*(128/8-1)*(16/8)*(16/8)*9 = 7*15*2*2*9 = 3780
  • cellSize=8×8:小于8像素无法稳定计算梯度方向,大于16像素丢失细节。实测将cellSize改为16×16后,在Test2.jpg上漏检3个穿黑衣行人。
  • blockSize=16×16:必须是cellSize的整数倍。若设为32×32,则每个block含4×4=16个cell,归一化后向量维度爆炸,SVM训练时间从2分钟升至23分钟。
  • nbins=9:这是Dalal-Triggs的结论。改用18 bins虽提升精度0.5%,但特征维数翻倍,且sum_pos.txt里预存的正样本HOG向量仍是9-bin格式,强行修改会导致训练数据错位。

2.3 SVM训练:硬间隔vs软间隔,为什么这里必须用硬间隔?

detection.cpp调用的是OpenCV的cv::ml::SVM,但关键参数藏在train()函数前:

// detection.cpp 中SVM配置 cv::Ptr<cv::ml::SVM> svm = cv::ml::SVM::create(); svm->setType(cv::ml::SVM::C_SVC); // 分类任务 svm->setKernel(cv::ml::SVM::LINEAR); // 线性核(HOG特征天然线性可分) svm->setC(1.0); // C=1.0即硬间隔(无松弛变量) svm->setTermCriteria(cv::TermCriteria(CV_TERMCRIT_ITER, 1000, 1e-6));
  • C=1.0不是随意取值:HOG特征在64×128窗口下具有强线性可分性,Dalal-Triggs实验表明C>1对AP提升<0.3%,但会显著增加支持向量数量(从约1200增至1800),导致检测时setSVMDetector()加载变慢。
  • LINEAR核是唯一合理选择:RBF核在3780维空间上训练极慢,且无理论依据。曾有学生尝试setGamma(0.001),结果训练耗时47分钟,AP反降1.2%。
  • TermCriteria中CV_TERMCRIT_ITER指最大迭代次数,1000次足够收敛。若训练中途退出,检查pos1.txt和sum_pos.txt是否包含重复路径——detection.cpp不会去重,重复正样本会导致SVM权重异常。

2.4 正样本构造:sum_pos.txt与pos1.txt的分工陷阱

正样本不是越多越好,而是要覆盖姿态多样性。源码用两个文件分工:

文件名内容作用修改风险
pos1.txt绝对路径列表,每行一个64×128裁剪后的正样本图初始训练集可增删,但必须保证所有图尺寸严格为64×128
sum_pos.txt同样是路径列表,但包含作者收集的额外正样本(如Test3.jpg中截取的行人)扩充训练集,提升泛化禁止删除!detection.cpp默认读此文件,删后训练报错"no positive samples"
  • sum_pos.txt里的图片来自Test*.jpg,作者已用detection_crop.cpp(未在项目正文列出但实际存在)人工标注并裁剪。若你新增正样本,必须用相同工具裁剪,否则因光照/对比度差异导致HOG直方图偏移。
  • 验证正样本质量:运行python check_pos.py(需自行编写,见第5章)检查所有pos1.txt图片的平均亮度是否在[85,175]区间。超出范围的图在HOG梯度计算时会产生大量零值,拖累SVM判别力。

3. 难例挖掘(Hard Negative Mining):让模型在失败中进化的核心机制

SVM训练完成后,直接部署到新图上必然出现大量误检(false positives)。传统做法是加更多负样本重训,但效率低下。find_save_hardexample.cpp实现的难例挖掘(Hard Negative Mining)是本项目的精华:它用初代模型扫描整张图,把那些被错误判为“行人”的高置信度负区域抠出来,加入负样本池再训。这不是锦上添花,而是AP从68%→82%的关键跃迁。

3.1 Hard Example的定义:为什么阈值设为0.99而不是0.5?

find_save_hardexample.cpp的核心逻辑是:

// find_save_hardexample.cpp 片段 double score = hog.detectMultiScale(img, found, 0, cv::Size(8,8), cv::Size(32,32), 1.05, 2); // 对每个检测框,计算SVM原始输出score(非概率) if (score > 0.99) { // 关键阈值! cv::Mat hard_neg = img(roi).clone(); cv::resize(hard_neg, hard_neg, cv::Size(64,128)); // 保存为hard_neg_XXXX.jpg 并追加到sample_neg.txt }
  • score > 0.99:这是OpenCV SVM的决策函数值(decision function value),非概率。0.99意味着该区域与超平面距离极近,模型“犹豫不决”——正是最难区分的负样本。若设为0.5,会抓取大量明显背景(如天空、道路),污染负样本池。
  • cv::Size(32,32):检测窗口最小尺寸。小于32×32的区域HOG特征不稳定,detectMultiScale会跳过。实测将此值改为16×16后,在Test5.jpg上产生237个hard neg,其中62%是图像噪声,导致再训后AP下降4.1%。
  • 输出文件hard_neg_*.jpg自动追加到sample_neg.txt末尾——不要手动编辑此文件,否则detection.cpp读取时会因路径不存在崩溃。

3.2 Hard Neg的二次筛选:为什么必须人工复查?

自动生成的hard neg有32%是无效样本(据作者答辩PPT第12页)。典型无效类型:

  • 运动模糊区域:Test4.jpg中快速移动的自行车轮,HOG梯度方向混乱,加入训练反而降低模型鲁棒性。
  • 强反射区域:广告牌玻璃反光,形成伪边缘,HOG直方图峰值异常。
  • 小尺寸物体:小于40×40的垃圾桶,被detectMultiScale误检为行人。

正确做法:

  1. 运行./find_save_hardexample Test4.jpg生成hard_neg_*.jpg
  2. 用ls -la img_dir/hard_neg_*.jpg | wc -l确认生成数量(理想值15~25张)
  3. 人工打开所有hard_neg_*.jpg,删除明显无效样本(保留有效hard neg不少于12张)
  4. 手动更新sample_neg.txt:用sed -i '/hard_neg/d' sample_neg.txt清空旧记录,再cat img_dir/hard_neg_*.jpg >> sample_neg.txt

注意:find_save_hardexample.cpp输出的hard neg默认保存在img_dir/下,与原始负样本混存。若你希望隔离管理,修改代码中cv::imwrite("img_dir/hard_neg_"+std::to_string(i)+".jpg", hard_neg);为cv::imwrite("hard_neg_pool/hard_neg_"+std::to_string(i)+".jpg", hard_neg);,并同步更新sample_neg.txt路径。

3.3 Hard Neg再训练:两次detection.cpp调用的参数差异

项目说明中“运行detection.cpp两次”绝非重复操作,第二次调用有3处关键变更:

参数第一次训练第二次训练(Hard Neg后)影响
sample_neg.txt内容原始负样本+hard neg仅hard neg(作者在README中暗示)避免负样本过载,防止SVM过拟合背景
svm->setC(1.0)固定值改为svm->setC(0.5)(需手动修改代码)Hard neg更难区分,需降低C值放宽间隔
TermCriteria迭代次数1000提升至2000Hard neg使优化曲面更复杂,需更多迭代
  • C=0.5的依据:作者答辩材料中提到,当hard neg占比超过负样本总数的15%时,C值需下调。实测C=1.0时,hard neg再训后支持向量数激增37%,检测速度下降2.3倍。
  • 修改位置:detection.cpp第127行附近,找到svm->setC(1.0);改为svm->setC(0.5);
  • 迭代次数提升:在setTermCriteria中将1000改为2000,否则训练提前终止,模型未收敛。

3.4 Hard Neg效果验证:用test_main.cpp做AP量化评估

test_main.cpp不是简单显示检测框,而是内置AP(Average Precision)计算器。关键逻辑:

// test_main.cpp 片段 std::vector<cv::Rect> ground_truth = load_gt("Test2_gt.txt"); // 加载真值框 std::vector<cv::Rect> detections = hog.detectMultiScale(img, ...); double ap = calculate_ap(detections, ground_truth, 0.5); // IoU阈值0.5 printf("AP@0.5: %.2f%%\n", ap*100);
  • Test2_gt.txt等真值文件需你手动创建(格式:x y width height每行一个框)。若无真值,test_main.cpp仍会运行,但AP值为0——此时只能目视评估。
  • calculate_ap()实现的是PASCAL VOC标准AP计算(11-point interpolation),非COCO的积分法。这意味着即使你只标了3个真值框,AP计算依然有效。
  • 实测数据:初代模型在Test2.jpg上AP=68.3%,加入12个hard neg再训后AP=82.1%,提升13.8个百分点。若AP提升<5%,说明hard neg质量差或数量不足。

4. 模型部署与检测:从XML导出到实时推理的落地闭环

训练完成的SVM模型不能直接用于检测,必须转换为OpenCV可加载的XML格式,并注入HOG描述符。test_main.cpp完成了这个闭环,但其内部参数和流程常被忽略——而这恰恰是部署时最易翻车的环节。

4.1 XML模型导出:为什么必须用cv::ml::SVM::save()而非pickle?

detection.cpp末尾的模型保存:

// detection.cpp 末尾 svm->save("pedestrian_svm.xml"); // OpenCV原生XML格式 // ❌ 错误做法:用sklearn的joblib.dump(svm, "model.pkl") —— OpenCV无法读取
  • pedestrian_svm.xml包含两部分:SVM超平面参数(rho, support vectors)和HOG描述符配置(winSize, blockSize等)。若你用Python sklearn训练,再转XML,会丢失HOG配置,导致setSVMDetector()失败。
  • XML文件大小约1.2MB(含3780维支持向量),不可用文本编辑器打开修改。曾有学生试图手动改<rho>值,导致XML解析失败,cv::FileStorage报错"Invalid node"。
  • 验证XML有效性:运行python verify_xml.py(见第5章),检查是否能成功cv::ml::SVM::load()并输出getSupportVectors().rows。

4.2 setSVMDetector()的向量长度玄学:3780维的生死线

test_main.cpp中这行代码决定检测成败:

// test_main.cpp cv::HOGDescriptor hog; hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector()); // 默认模型 // ✅ 正确:用训练好的模型 std::vector<float> my_detector = get_svm_detector(svm); // 从SVM提取3780维向量 hog.setSVMDetector(my_detector);
  • my_detector长度必须严格等于3780。若你在detection.cpp中修改了HOG参数(如nbins=18),则此处向量长度变为7560,setSVMDetector()静默失败,检测框全消失。
  • get_svm_detector()函数在detection.h中定义,它将SVM的alphay_ix_i求和后拼接rho值。rho必须放在向量末尾,顺序错一位就会全部误检。
  • 调试技巧:在get_svm_detector()返回前加printf("detector size: %d\n", (int)detector.size());,确保输出3780。

4.3 detectMultiScale参数调优:检测速度与精度的平衡术

hog.detectMultiScale()的5个参数直接影响结果:

hog.detectMultiScale( img, // 输入图 found, // 输出检测框vector 0, // hitThreshold: SVM决策阈值,默认0(即用rho) cv::Size(8,8), // winStride: 滑动窗口步长,越小越准越慢 cv::Size(32,32), // padding: 边界填充,影响边缘检测 1.05, // scaleFactor: 图像金字塔缩放因子 2 // minNeighbors: 邻域合并阈值 );
  • hitThreshold=0:使用SVM原始输出。若设为0.5,会过滤掉弱响应,但可能漏检遮挡行人。作者在Test3.jpg上实测,0→0.3导致漏检1个戴帽子行人。
  • winStride=cv::Size(8,8):最佳平衡点。改为(4,4)检测精度+1.2%,但速度-63%;改为(16,16)速度+45%,但AP-5.8%。
  • scaleFactor=1.05:每层缩放5%。小于1.03时金字塔层数过多(>25层),内存溢出;大于1.08时漏检小尺度行人(如远处骑车人)。
  • minNeighbors=2:合并重叠框。设为0则满屏小框;设为5则过度合并,把并排两人判为一个。

4.4 实时检测优化:为什么test_main.cpp不支持视频流?

test_main.cpp设计为单图检测,若强行喂入视频帧:

// ❌ 危险操作 cv::VideoCapture cap(0); while(cap.read(frame)) { hog.detectMultiScale(frame, found); // 每帧重新初始化HOG,CPU飙升 }
  • cv::HOGDescriptor初始化(含内存分配)耗时约12ms,每帧都new会拖慢30%帧率。
  • 正确做法:在循环外初始化hog,并在detectMultiScale前加hog.setSVMDetector(my_detector);(detector只需设一次)。
  • 进阶优化:添加ROI(Region of Interest)限制检测区域。例如只扫画面下半部(行人集中区):
    cv::Rect roi(0, frame.rows/2, frame.cols, frame.rows/2); cv::Mat roi_frame = frame(roi); hog.detectMultiScale(roi_frame, found); // 检测框坐标需映射回原图:for(auto& r : found) r.y += frame.rows/2;

5. 避坑指南:血泪换来的5个致命陷阱与自救方案

这份源码虽经答辩验证,但在你本地复现时,90%的失败源于以下5个隐蔽陷阱。它们不报错、不崩溃,却让检测结果完全失效——我当年在实验室熬了38小时才定位清楚。

5.1 现象:crop_image生成的负样本全是黑图

原因:crop_image.cpp中cv::resize(patch, patch, cv::Size(64,128))调用时,patch为cv::Mat空矩阵。根源是img.rows - win_h计算为负数——输入图尺寸小于64×128。
解决:检查Test2.jpg等测试图尺寸。若小于64×128,用cv::resize(img, img, cv::Size(640,480))预处理。绝对禁止在crop_image.cpp里加if(img.rows<128) continue;,这会导致sample_neg.txt行数错乱。

5.2 现象:detection.cpp编译通过,但运行时报错"OpenCV Error: Assertion failed (samples.checkVector(2) > 0)"

原因:sum_pos.txt或sample_neg.txt中某行路径含中文或空格,cv::imread()返回空Mat,trainData矩阵维度异常。
解决:用sed -i 's/ /_/g' sum_pos.txt替换空格;用iconv -f GBK -t UTF-8 sum_pos.txt > tmp && mv tmp sum_pos.txt转码;最后用awk '{print NF}' sum_pos.txt | sort -u确认每行字段数为1。

5.3 现象:hard_neg_*.jpg生成成功,但再训后AP不升反降

原因:find_save_hardexample.cpp中cv::HOGDescriptor hog;未设置winSize,导致检测窗口尺寸为默认64×128,但你的正样本是其他尺寸(如作者用Test3.jpg裁的正样本实际为72×144)。
解决:在find_save_hardexample.cpp的hog声明后立即加hog.winSize = cv::Size(64,128);,与detection.cpp保持一致。这是作者源码的隐藏bug,答辩PPT第18页承认此问题。

5.4 现象:test_main.cpp检测框密密麻麻,但全是错的

原因:setSVMDetector()传入的向量长度错误,或rho值符号反了。OpenCV要求rho为负数(因SVM公式为f(x)=w·x+b,b=-rho)。
解决:在get_svm_detector()中打印svm->getDecisionFunction(0, sv, alpha),确认alpha[i]*y[i]乘积和rho符号。若rho为正,手动加负号:detector.push_back(-rho)。

5.5 现象:Linux下编译成功,Windows下报错"undefined reference to `cv::HOGDescriptor::setSVMDetector'"

原因:Windows版OpenCV 4.x默认关闭opencv_objdetect模块(HOG在此模块)。
解决:CMake时加-D BUILD_opencv_objdetect=ON;或下载预编译版时选择contrib包(如opencv-4.5.5-vc14.exe含objdetect)。切勿用pip install opencv-python,它不含HOG模块。

6. 进阶实战:用Python胶水重构全流程,绕过C++编译地狱

C++模块虽高效,但调试成本高。我后来用Python重写了整个pipeline(保留原HOG参数和SVM逻辑),既避免编译报错,又便于插入断点查特征。核心是用sklearn.svm.SVC替代OpenCV SVM,但必须复现OpenCV的HOG提取逻辑——否则特征不一致,模型无效。

6.1 Python版HOG提取:与OpenCV 100%对齐的numpy实现

OpenCV的HOG有3个独特点:梯度计算用Scharr算子、cell直方图用双线性插值、block归一化用L2-Hys。以下函数输出与cv::HOGDescriptor.compute()完全一致的3780维向量:

import numpy as np import cv2 from skimage.feature import hog as sk_hog def opencv_compatible_hog(img): # 1. Scharr梯度(OpenCV默认) grad_x = cv2.Scharr(img, cv2.CV_32F, 1, 0) grad_y = cv2.Scharr(img, cv2.CV_32F, 0, 1) # 2. 梯度幅值和角度(0-180°,非0-360°) mag = np.sqrt(grad_x**2 + grad_y**2) angle = np.arctan2(grad_y, grad_x) * 180 / np.pi angle[angle < 0] += 180 # 映射到0-180 # 3. cell直方图(8x8 cell,9 bins) cell_size = (8, 8) bin_width = 180 / 9 h, w = img.shape n_cells_y, n_cells_x = h // cell_size[0], w // cell_size[1] hist = np.zeros((n_cells_y, n_cells_x, 9)) for i in range(n_cells_y): for j in range(n_cells_x): cell_mag = mag[i*8:(i+1)*8, j*8:(j+1)*8] cell_angle = angle[i*8:(i+1)*8, j*8:(j+1)*8] for y in range(8): for x in range(8): bin_idx = int(cell_angle[y,x] // bin_width) if bin_idx == 9: bin_idx = 0 # 180°归入0° # 双线性插值(简化版:最近邻) hist[i,j,bin_idx] += cell_mag[y,x] # 4. block归一化(16x16 block,步长8x8) block_size = (2, 2) # 2x2 cells stride = (1, 1) # 步长1 cell n_blocks_y = n_cells_y - block_size[0] + 1 n_blocks_x = n_cells_x - block_size[1] + 1 block_norm = np.zeros((n_blocks_y, n_blocks_x, 9*4)) # 4 cells per block for i in range(n_blocks_y): for j in range(n_blocks_x): block_hist = hist[i:i+2, j:j+2].reshape(-1) # 4x9=36 # L2-Hys归一化 l2_norm = np.sqrt(np.sum(block_hist**2)) if l2_norm > 0: block_hist = np.clip(block_hist / l2_norm, 0, 0.2) # 截断0.2 block_hist /= np.sqrt(np.sum(block_hist**2) + 1e-8) block_norm[i,j] = block_hist return block_norm.reshape(-1) # 3780维 # 验证:与OpenCV结果误差<1e-5 img = cv2.imread('Test2.jpg', 0) img = cv2.resize(img, (64,128)) opencv_feat = cv2.HOGDescriptor().compute(img) python_feat = opencv_compatible_hog(img) print(f"Max diff: {np.max(np.abs(opencv_feat - python_feat))}") # 应<1e-5

6.2 Python版SVM训练:用sklearn复现OpenCV的硬间隔

OpenCV SVM的硬间隔等价于sklearn.svm.SVC(kernel='linear', C=1.0, max_iter=1000),但需注意:

  • sklearn的decision_function()输出与OpenCV的predict()不同,需用coef_和intercept_手动计算:score = np.dot(X, clf.coef_[0]) + clf.intercept_[0]
  • 支持向量索引:clf.support_给出训练样本索引,clf.n_support_给出各类支持向量数
  • 导出detector向量:np.hstack([clf.coef_[0], clf.intercept_[0]]),长度3781(3780维特征+1维bias)
from sklearn.svm import SVC import joblib # 构造X_train (n_samples, 3780), y_train (n_samples,) X_train, y_train = load_hog_features() # 用上述函数提取所有正负样本 clf = SVC(kernel='linear', C=1.0, max_iter=1000, random_state=42) clf.fit(X_train, y_train) # 导出detector向量(供test_main.cpp使用) detector = np.hstack([clf.coef_[0], clf.intercept_[0]]) np.save('pedestrian_detector.npy', detector) # 3781维 # 验证:用detector向量在test_main.cpp中setSVMDetector() # 注意:OpenCV要求detector长度3780,所以去掉最后一维bias # 实际部署时,detector = detector[:-1] # 3780维

6.3 自动化Pipeline:一键完成从数据到检测的6步脚本

把所有步骤封装为run_pipeline.py,避免手动敲12条命令:

#!/usr/bin/env python3 import subprocess import os def run_cmd(cmd, desc=""): print(f"[RUN] {desc}") result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if result.returncode != 0: print(f"❌ {desc} FAILED:\n{result.stderr}") exit(1) else: print(f"✅ {desc} OK") if __name__ == "__main__": # 步骤1:裁剪负样本 run_cmd("g++ -o crop_image crop_image.cpp `pkg-config --cflags --libs opencv4`", "Compile crop_image") run_cmd("./crop_image Test2.jpg", "Crop negative samples") # 步骤2:提取HOG特征 run_cmd("python extract_hog.py", "Extract HOG features for all samples") # 步骤3:训练SVM run_cmd("python train_svm.py", "Train SVM with hard margin") # 步骤4:难例挖掘 run_cmd("python find_hard_neg.py Test3.jpg", "Find hard negatives") # 步骤5:再训练 run_cmd("python train_svm.py --hard-neg", "Retrain with hard negatives") # 步骤6:检测 run_cmd("g++ -o test_main test_main.cpp `pkg-config --cflags --libs opencv4`", "Compile test_main") run_cmd("./test_main Test4.jpg", "Run detection on Test4.jpg")

从那以后我每次复现传统CV项目,都强制走一遍Python胶水重构——不是为了取代C++,而是用Python的debug能力,把HOG的梯度计算、SVM的决策边界、hard neg的筛选逻辑,一层层剥开验证。当opencv_compatible_hog()输出与OpenCV的compute()误差小于1e-5时,那种“原来如此”的顿悟感,比跑通100个YOLO demo都扎实。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 9:13:22

Spring Boot微信小程序商城毕业设计:从源码到全栈实战解析

1. 项目整体解读&#xff1a;从标题看透毕业设计的真实需求先把这个标题拆开看——“springboot商城微信小程序-计算机毕业设计源码34906”&#xff0c;它其实包含了好几个信息层。如果你正在找毕设方向&#xff0c;或者已经拿到这套源码准备跑起来&#xff0c;那这篇文章就是为…

作者头像 李华
网站建设 2026/10/3 9:10:55

Java+SpringBoot+SSM智能包裹配送管理系统开发实战与避坑指南

每年这个时候&#xff0c;我都会收到一批“基于JavaSpringBootSSM的XX管理系统”的求助帖&#xff0c;最近咨询量最大的就是这套“智能包裹配送服务管理系统”。标题里同时出现SpringBoot和SSM&#xff0c;其实很多人会把它们当成两套互斥框架&#xff0c;这是个常见的误解。Sp…

作者头像 李华
网站建设 2026/10/3 9:10:44

SpringBoot+Vue打造图书阅读与商城一体化系统实战

我自己做线上图书项目已经不是第一次了&#xff0c;SpringBoot加Vue这个技术组合更是用了很多年。之前帮人做过一个纯商城版的图书销售系统&#xff0c;也见过不少同学把阅读器和商城分开做&#xff0c;结果系统上线后发现&#xff0c;用户买完书要去另一个地方看&#xff0c;体…

作者头像 李华
网站建设 2026/10/3 9:09:36

SSM+Flask混合架构实战:社区流浪动物救助领养系统开发全记录

做社区流浪动物救助领养系统这类题目&#xff0c;每年都能在各大毕业设计选题清单里看到。很多同学看到“Java SSM Flask”三个词叠在一起就懵了&#xff0c;下意识觉得这是不是把两套后端硬塞到一个项目里&#xff0c;技术栈太乱。但恰恰相反&#xff0c;我在用这套组合完整…

作者头像 李华
网站建设 2026/10/3 9:09:24

Servlet高校图书管理系统源码解析:从环境配置到核心模块实战

很多同学拿着这套“servlet高校图书管理信息系统”的源码找到我&#xff0c;第一句话都是&#xff1a;学长&#xff0c;代码我打开看了&#xff0c;下一步该干什么&#xff1f;还有同学直接双击运行&#xff0c;发现打不开&#xff0c;跑来问我是不是源码有问题。其实问题不在源…

作者头像 李华
网站建设 2026/10/3 9:07:18

Visual Studio二月更新解析:升级避坑与高频问题排查指南

每年二月的 Visual Studio 更新&#xff0c;在微软的发布节奏里通常是个承前启后的版本&#xff1a;既要把年初预览阶段定下来的功能做一轮收口&#xff0c;又要为三四月的重头戏铺路。今年的二月更新我看完之后&#xff0c;第一感觉是“稳”&#xff0c;第二感觉是“某些坑终于…

作者头像 李华