OpenCV实战4: HOG+SVM实现行人检测

时间:2024-03-26 08:09:05

     目前基于机器学习方法的行人检测的主流特征描述子之一是HOG(Histogram of Oriented Gradient, 方向梯度直方图)。HOG特征是用于目标检测的特征描述子,它通过计算和统计图像局部区域的梯度方向直方图来构成特征,用这些特征描述原始图像。

      HOG的核心思想是所检测的局部物体外形能够被光强梯度或边缘方向的分布所描述。通过将整幅图像分割成小的连接区域(称为cells),每个cell生成一个方向梯度直方图或者cell中pixel的边缘方向,这些直方图的组合可表示出(所检测目标的目标)描述子。为改善准确率,局部直方图可以通过计算图像中一个较大区域(称为block)的光强作为measure被对比标准化,然后用这个值(measure)归一化这个block中的所有cells.这个归一化过程完成了更好的照射/阴影不变性。与其他描述子相比,HOG得到的描述子保持了几何和光学转化不变性(除非物体方向改变)。因此HOG描述子尤其适合人的检测。

      OpenCV实现了两种类型的基于HOG特征的行人检测,分别是SVM和Cascade,OpenCV自带的级联分类器的文件的位置在“XX\opencv\sources\data\hogcascades”(OpenCV4.x版本可用)。

               代码:https://blog.csdn.net/dcrmg/article/details/53047009

这里给出OpenCV中sample代码示例:

做适当修改:创建Detector对象时,

Detector detector(0)表示使用getDefaultPeopleDetector;

Detector detector(1)表示使用getDaimlerPeopleDetector;

#include <opencv2/objdetect.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <iostream>
#include <iomanip>

	using namespace cv;
	using namespace std;

	
	class Detector
	{
		//enum Mode { Default, Daimler } m;
		enum { Default, Daimler };//定义枚举类型
		int m;
		HOGDescriptor hog, hog_d;
	public:
		//Detector() : m(Daimler), hog(), hog_d(Size(48, 96), Size(16, 16), Size(8, 8), Size(8, 8), 9)//构造函数,初始化对象时自动调用,m,hog,hog_d是数据成员,后跟一个放在圆括号中的初始化形式
		//{
		//	hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());
		//	hog_d.setSVMDetector(HOGDescriptor::getDaimlerPeopleDetector());
		//}
		Detector(int a) : m(a), hog(), hog_d(Size(48, 96), Size(16, 16), Size(8, 8), Size(8, 8), 9)//构造函数,初始化对象时自动调用,m,hog,hog_d是数据成员,后跟一个放在圆括号中的初始化形式
		{
			hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());
			hog_d.setSVMDetector(HOGDescriptor::getDaimlerPeopleDetector());
		}
		void toggleMode() { m = (m == Default ? Daimler : Default); }
		string modeName() const { return (m == Default ? "Default" : "Daimler"); }
		vector<Rect> detect(InputArray img)
		{
			// Run the detector with default parameters. to get a higher hit-rate
			// (and more false alarms, respectively), decrease the hitThreshold and
			// groupThreshold (set groupThreshold to 0 to turn off the grouping completely).
			vector<Rect> found;
			if (m == Default)
				hog.detectMultiScale(img, found, 0, Size(8, 8), Size(32, 32), 1.05, 2, false);
			else if (m == Daimler)
				hog_d.detectMultiScale(img, found, 0.5, Size(8, 8), Size(32, 32), 1.05, 2, true);
			return found;
		}
		void adjustRect(Rect & r) const
		{
			// The HOG detector returns slightly larger rectangles than the real objects,
			// so we slightly shrink the rectangles to get a nicer output.
			r.x += cvRound(r.width*0.1);
			r.width = cvRound(r.width*0.8);
			r.y += cvRound(r.height*0.07);
			r.height = cvRound(r.height*0.8);
		}
	};

	static const string keys = "{ help h   |   | print help message }"
		"{ camera c | 0 | capture video from camera (device index starting from 0) }"
		"{ video v  | D:/Program Files/OpenCV/opencv/sources/samples/data/vtest.avi| use video as input }";

	int main(int argc, char** argv)
	{
		CommandLineParser parser(argc, argv, keys);
		parser.about("This sample demonstrates the use ot the HoG descriptor.");
		if (parser.has("help"))
		{
			parser.printMessage();
			return 0;
		}
		int camera = parser.get<int>("camera");
		string file = parser.get<string>("video");
		if (!parser.check())
		{
			parser.printErrors();
			return 1;
		}

		VideoCapture cap;
		if (file.empty())
			cap.open(camera);
		else
			cap.open(file.c_str());
		if (!cap.isOpened())
		{
			cout << "Can not open video stream: '" << (file.empty() ? "<camera>" : file) << "'" << endl;
			return 2;
		}

		cout << "Press 'q' or <ESC> to quit." << endl;
		cout << "Press <space> to toggle between Default and Daimler detector" << endl;
		
		Detector detector(1);

		Mat frame;
		for (;;)
		{
			cap >> frame;
			if (frame.empty())
			{
				cout << "Finished reading: empty frame" << endl;
				break;
			}
			int64 t = getTickCount();
			vector<Rect> found = detector.detect(frame);
			t = getTickCount() - t;

			// show the window
			{
				ostringstream buf;
				buf << "Mode: " << detector.modeName() << " ||| "
					<< "FPS: " << fixed << setprecision(1) << (getTickFrequency() / (double)t);
				putText(frame, buf.str(), Point(10, 30), FONT_HERSHEY_PLAIN, 2.0, Scalar(0, 0, 255), 2, LINE_AA);
			}
			for (vector<Rect>::iterator i = found.begin(); i != found.end(); ++i)
			{
				Rect &r = *i;
				detector.adjustRect(r);
				rectangle(frame, r.tl(), r.br(), cv::Scalar(0, 255, 0), 2);
			}
			imshow("People detector", frame);

			// interact with user
			const char key = (char)waitKey(30);
			if (key == 27 || key == 'q') // ESC
			{
				cout << "Exit requested" << endl;
				break;
			}
			else if (key == ' ')
			{
				detector.toggleMode();
			}
		}
		return 0;
	}

代码: https://github.com/liuzheCSDN/OpenCV/blob/master/facedelete/pedestrianHOG.cpp


1、enum:

C++ 中会使用const或者#define定义整型常量,当整型常量有多个且之间的值的全部或部分有递加的时候,定义起来稍显繁琐,此时用枚举显得很简洁,枚举类型相较于#define的优势在于,定义常量简洁且易于管理,可以自动赋值且值不相等,类型安全检测。

OpenCV实战4: HOG+SVM实现行人检测OpenCV实战4: HOG+SVM实现行人检测

//使用枚举//定义一个枚举变量,此变量可以具有多个可能的值

typedef  enum  weekDay{
MON=1,        //枚举类型中数据都是整型且从0开始,此处将第1个值设为1,则TUE ,        // 以下均从1开始递加
TUE,
WED,     //C++中逗号不是一条语句,不是一条语句就可以用回车分行
THU,      //这样有助于写注释
FRI,
SAT=7,   //可以再重新赋值,此时SAT=7,而不是6
SUN       //SUN=8,而不是7
}week_day;
week_day week=SUN;

如果不为其赋值,默认枚举类型从0依次增大,如上面代码中,定义enum后,代码中只要出现Default,它就是“int 0”;Default=0;Daimler=1。

enum { Default, Daimler };

其实enum的作用就是以英文单词代表数字常量。防止代码中数字常量过多,不知道他们具体代表什么意思。

2、构造函数与类名相同:

Detector(int a) : m(a), hog(), hog_d(Size(48, 96), Size(16, 16), Size(8, 8), Size(8, 8), 9)//构造函数,初始化对象时自动调用,m,hog,hog_d是数据成员,后跟一个放在圆括号中的初始化形式
		{
			hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());
			hog_d.setSVMDetector(HOGDescriptor::getDaimlerPeopleDetector());
		}

     构造函数初始化列表在构造函数名后添加一个冒号,冒号后是以逗号分隔的数据成员列表,每个数据成员后跟一个放在圆括号中的初始化形式。圆括号中的初始化形式可以是任意复杂的表达式。


行人检测: HOG + SVM 

       HOG: 方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征. HOG的缺点: 速度慢,实时性差;难以处理遮挡问题。
       SVM: (Support Vector Machine)指的是支持向量机,是常见的一种判别方法。在机器学习领域,是一个有监督的学习模型,通常用来进行模式识别、分类以及回归分析, 在行人检测中可以用作区分行人和非行人的分类器。

        在使用HOG + SVM进行行人检测时, 采集HOG特征的主要思想是通过对一幅图像进行分析, 局部目标的表象和形状可以被剃度或者边缘密度方向分布很好的好的描述. 我们对图像的各个像素点采集土堆或者边缘的方向直方图, 根据直方图的信息就可以描述图片的特征. 好在OpenCv 中已经提供了计算HOG特征的方法, 根据采集到的HOG特征向量, 供SVM分类使用. SVM简单来说就是一个分类器, 在行人检测中就可以转化为行人与非行人的两类分类问题, 在OpenCv中运用到的是基于网格法的SVM.使用采集到的正样本(行人)和负样本(非行人, 可以是汽车, 树木, 路灯等等)的HOG特征, 然后使用SVM分类器进行训练, 得到行人检测模型, 进行行人检测.

HOGDescriptor

HOGDescriptor的构造函数:

 CV_WRAP HOGDescriptor() : winSize(64,128), blockSize(16,16), blockStride(8,8),
        cellSize(8,8), nbins(9), derivAperture(1), winSigma(-1),
        histogramNormType(HOGDescriptor::L2Hys), L2HysThreshold(0.2), gammaCorrection(true),
        free_coef(-1.f), nlevels(HOGDescriptor::DEFAULT_NLEVELS), signedGradient(false)
    {}

重要参数:

窗口大小 winSize(64,128), 块大小blockSize(16,16), 块滑动增量blockStride(8,8), 胞元大小cellSize(8,8), 梯度方向数nbins(9)。

上面这些都是HOGDescriptor的成员变量,括号里的数值是它们的默认值,它们反应了HOG描述子的参数。这里做了几个示意图来表示它们的含义。

OpenCV实战4: HOG+SVM实现行人检测OpenCV实战4: HOG+SVM实现行人检测

OpenCV实战4: HOG+SVM实现行人检测

nBins表示在一个胞元(cell)中统计梯度的方向数目,例如nBins=9时,在一个胞元内统计9个方向的梯度直方图,每个方向为180/9=20度。

HOG特征计算原理:

HOG特征的提取过程为:

  1. Gamma归一化;
  2. 计算梯度;
  3. 划分cell
  4. 组合成block,统计block直方图;
  5. 梯度直方图归一化;
  6. 收集HOG特征。


1、Gamma归一化: 对图像颜色进行Gamma归一化处理,降低局部阴影及背景因素的影响.

 2、计算梯度:通过差分计算出图像在水平方向上及垂直方向上的梯度,然后得到各个像素点的梯度的幅值及方向:

                    OpenCV实战4: HOG+SVM实现行人检测       OpenCV实战4: HOG+SVM实现行人检测

3、划分cell 
     将整个窗口划分成大小相同互不重叠的细胞单元cell(如8×8像素),计算出每个cell的梯度大小及方向.然后将每像素的梯度方向在0−180(无向:0-180,有向:0-360)平均分为9个bins,统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor. 

     采用9个bin的直方图来统计这8*8个像素的梯度信息。也就是将cell的梯度方向360度分成9个方向块,例如:如果这个像素的梯度方向是20-40度,直方图第2个bin的计数就加一,这样,对cell内每个像素用梯度方向在直方图中进行加权投影(映射到固定的角度范围),就可以得到这个cell的梯度方向直方图了,就是该cell对应的9维特征向量(因为有9个bin)。

       像素梯度方向用到了,那么梯度大小呢?梯度大小就是作为投影的权值的。例如说:这个像素的梯度方向是20-40度,然后它的梯度大小是2,那么直方图第2个bin的计数就不是加一了,而是加二(假设啊)。

4、组合成block,统计block直方图 

   将2×2个相邻的cell组成大小为16×16的像素块即block.依次将block大小的滑动窗口从左到右从上到下滑动,求其梯度方向直方图向量.一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。

  由于局部光照的变化以及前景-背景对比度的变化,使得梯度强度的变化范围非常大。这就需要对梯度强度做归一化。归一化能够进一步地对光照、阴影和边缘进行压缩。

        作者采取的办法是:把各个细胞单元组合成大的、空间上连通的区间(blocks)。这样,一个block内所有cell的特征向量串联起来便得到该block的HOG特征。这些区间是互有重叠的,这就意味着:每一个单元格的特征会以不同的结果多次出现在最后的特征向量中。我们将归一化之后的块描述符(向量)就称之为HOG描述符。

  区间有两个主要的几何形状——矩形区间(R-HOG)和环形区间(C-HOG)。R-HOG区间大体上是一些方形的格子,它可以有三个参数来表征:每个区间中细胞单元的数目、每个细胞单元中像素点的数目、每个细胞的直方图通道数目。

       例如:行人检测的最佳参数设置是:3×3细胞/区间、6×6像素/细胞、9个直方图通道。则一块的特征数为:3*3*9;

不同大小的cell与不同大小的block作用下的效果对比:

OpenCV实战4: HOG+SVM实现行人检测OpenCV实战4: HOG+SVM实现行人检测

5、梯度直方图归一化 
作者对比了L2-norm、L1-norm、L1-sqrt等归一化方法,发现都比非标准数据有显着的改善.其中L2-norm和L1-sqrt效果最好,而L1-norm检测效果要比L2-norm和L1-sqrt低5%

6、收集HOG特征

      最后一步就是将检测窗口中所有重叠的块进行HOG特征的收集,并将它们结合成最终的特征向量供分类使用。那么一个图像的HOG特征维数是多少呢?

        总结:Dalal提出的Hog特征提取的过程:把样本图像分割为若干个像素的单元(cell),把梯度方向平均划分为9个区间(bin),在每个单元里面对所有像素的梯度方向在各个方向区间进行直方图统计,每个细胞单元得到一个9维的特征向量,每相邻的4个单元构成一个块(block),把一个块内的特征向量联起来得到36维的特征向量,用块对样本图像进行扫描,扫描步长为一个单元。最后将所有块的特征串联起来,就得到了人体的特征。

        例如,对于64*128的图像而言,每8*8的像素组成一个cell,每2*2个cell组成一个block(16×16像素),因为每个cell有9个特征,所以每个block内有4*9=36个特征,以8个像素为步长,那么,水平方向将有7个扫描窗口(block),垂直方向将有15个扫描窗口(block),这样检测窗口block的数量有((128-16)/8+1)×((64-16)/8+1)=15×7。也就是说,64*128的图片,总共有36*7*15=3780个特征。

from :https://www.cnblogs.com/voyagflyer/p/5329146.html

from :https://blog.csdn.net/akadiao/article/details/79685323