HOG+SVM的物体检测

想做物体检测,可以试试HOG!例如我们在下面的图片中检测这位美女。
HOG+SVM的物体检测_第1张图片

HOG全名 Histogram of Oriented Gradients,也就是方向梯度的直方图,它主要利用梯度的直方图构建特征向量。
最经典的是用作行人检测,也可以用作其他物体检测。

方向梯度(oriented Gradients)

一维梯度可以认为是一阶导数: d y d x = y ‘ \frac{\mathrm{d} y }{\mathrm{d} x} = y^{`} dxdy=y

HOG+SVM的物体检测_第2张图片
z = f ( x , y ) z=f(x,y) z=f(x,y)的二维梯度
g r a d f = f x ( x , y ) ∗ i + f y ( x , y ) ∗ j grad f = f_x(x,y) *i + f_y(x,y)*j gradf=fx(x,y)i+fy(x,y)j
f x ( x , y ) f_x(x,y) fx(x,y)表示y保持不变时,f(x,y)关于x的偏导数。

f y ( x , y ) f_y(x,y) fy(x,y)表示保持x不变时,f(x,y)关于y的偏导数。

HOG+SVM的物体检测_第3张图片

由公式可知,二维梯度是有方向的。可以将其转换了梯度的幅值 g g g和幅度 θ \theta θ
g = f x 2 + f y 2 θ = arctan ⁡ f x f y g = \sqrt{f_x^{2} + f_y^{2}} \\ \theta = \arctan{\frac{f_x}{f_y}} g=fx2+fy2 θ=arctanfyfx
数字图像中的微分通过减法来代替。这里使用的水平微分核为 [ − 1 , 0 , 1 ] [-1, 0, 1] [1,0,1], 垂直微分核为 [ − 1 , 0 , 1 ] T [-1, 0,1]^T [1,0,1]T。可以使用其他的微分核代替,例如sober算子。

HOG+SVM的物体检测_第4张图片 HOG算法中最重要的一步就是,计算梯度的幅值和幅度,然后根据幅度统计直方图。具体怎么做的见算法流程。

算法流程

HOG+SVM的物体检测_第5张图片 HOG+SVM的物体检测_第6张图片

检测窗口(detection window)

可以认为是从一张大图找提取出一部分作为检测图像。

HOG+SVM的物体检测_第7张图片

归一化图像(normalize gamma & colour)

HOG是通过梯度信息去构建特征,所有并不需要颜色信息,可以转换为灰度图。
为了减少光照的影响,提高对比度,对图像进行伽玛矫正。但是有的人说可以直接跳过这一步,因为后面会对更小的块区域进行归一化,而且局部归一化的效果会更好

计算梯度(compute gradient)

计算方法见方向梯度部分,效果如下

HOG+SVM的物体检测_第8张图片 计算每个像素的梯度,彩色图取幅值最大的一个通道。这里将图分成了8*8的cell,一张64*128的图像,被分割成 8 * 16 个cell。

计算方向梯度直方图

HOG+SVM的物体检测_第9张图片

以梯度幅度作为直方图的统计值, 梯度幅值作为统计量。这里并不是之间将幅值分到一个bin,而是根据幅度将其按比例分到最近的两个bin。这样可以避免梯度信息的跳变。所以称为 accumulate weighted vote for gradient orientation over spatial cells。形成9*1的向量。

对块内的cell进行归一化

为了适应亮度变化,以及前景与背景对比度的变换,以块为单位进行归一化。块的大小为2*2.,归一化的方式可以选择NORM_L1,NORM_L2,NORM_MINMAX,NORM_INF等。

关于block 的大小以及cell 的大小,相关论文中给出了如下测试结果,在 Block size=3*3, cell size=6*6 时错误率最小,但是这里并没有采用。 HOG+SVM的物体检测_第10张图片

生成特征向量

将所有Block向量组合在一起,形成feature vector.
检测图像大小为(image size):64 * 128;
块大小(Block size):2 * 2;
单元大小(cell size):8 * 8;
每个Block 归一化后形成的36*1的向量。(2 * 2 * 9 = 36)
Block 的数目为水平7个( 64 / 8 − 1 64/8-1 64/81),垂直15个( 16 − 1 16-1 161)。
最后形成特性大小为: 7 ∗ 8 ∗ 36 = 3780 7 * 8 * 36=3780 7836=3780

在opencv中使用 HOG

官方HOG文档
实现英文介绍
不想看英文接口介绍的可以看接口中文翻译

首先来个比较简单的,直接利用官方训练好的 SVM 分类器做行人检测,感受一下效果。
将要使用一下接口:

    /** @brief Returns coefficients of the classifier trained for people detection (for 64x128 windows).
    */
	std::vector<float> getDefaultPeopleDetector() //获取训练好的行人检测系数
    /**@brief Sets coefficients for the linear SVM classifier.
    @param svmdetector coefficients for the linear SVM classifier.
    */
	virtual void setSVMDetector(InputArray svmdetector); // 设置SVM分类器的系数
    /** @brief Detects objects of different sizes in the input image. The detected objects are returned as a list
    of rectangles.
    */   
    // 多尺度检测物体
    virtual void detectMultiScale(InputArray img, CV_OUT std::vector<Rect>& foundLocations,
                                  double hitThreshold = 0, Size winStride = Size(),
                                  Size padding = Size(), double scale = 1.05,
                                  double finalThreshold = 2.0, bool useMeanshiftGrouping = false) const;

行人检测程序(使用官方训练好的分类系数)

#include 
#include 

using namespace std;
using namespace cv;
using namespace cv::ml;

int main()
{
	//输入检测图像
	cv::Mat testImage = imread(R"(D:\dlData\test1_.jpg)");
	if (testImage.empty())
	{
		cout << "test image is empty! \n";
		return 0;
	}

	HOGDescriptor hog;
	vector<Rect> foundLocations;
	hog.setSVMDetector(hog.getDefaultPeopleDetector());//设置SVM分类器的系数
	hog.detectMultiScale(testImage, foundLocations);   //执行物体检测

	// 将检测结果(物体位置)绘制到图像上
	for (auto &rect: foundLocations)
	{
		rectangle(testImage, rect, Scalar(0, 0, 255),3);
	}
	// 显示图像
	string winTitle = "people detector";
	namedWindow(winTitle);
	imshow(winTitle, testImage);
	waitKey(0);
	return 0;
}

HOG+SVM的物体检测_第11张图片

HOG物体检测

如果我们想做其他物体检测,那么就需要自己训练分类器。其实有了HOG提取的特征向量,训练SVM是非常容易的事情,大致可以分为三步。

  1. 准备训练数据和标签
  2. 利用训练数据训练分类器
  3. 使用测试数据对分类器做测试

例如使用INRIA 行人数据集 (INRIA Person Dataset)进行训练和测试。其他训练和测试代码如下:

#include 
#include 

using namespace std;
using namespace cv;
using namespace cv::ml;

string positive_dir = R"(D:\dlData\pos)";
string negative_dir = R"(D:\dlData\neg)";
string test_img = R"(D:\dlData\Test\pos\test.png)";
const string svm_model_dir = R"(.\hog_svm.yml)";

void get_hog_descripor(Mat &image, vector<float> &desc);
void generate_dataset(Mat &trainData, Mat &labels);
void svm_train(Mat &trainData, Mat &labels);
void svm_test();

int main()
{
	Mat  trainData, labels;
	generate_dataset(trainData, labels);
	svm_train(trainData, labels);
	svm_test();

	waitKey(6000);
	return 0;
}

void get_hog_descripor(Mat &image, vector<float> &desc) {
	HOGDescriptor hog;
	int h = image.rows;
	int w = image.cols;
	float rate = 64.0 / w;
	Mat img, gray;
	resize(image, img, Size(64, int(rate*h)));
	cvtColor(img, gray, COLOR_BGR2GRAY);
	Mat result = Mat::zeros(Size(64, 128), CV_8UC1);
	result = Scalar(127);
	Rect roi;
	roi.x = 0;
	roi.width = 64;
	roi.y = (128 - gray.rows) / 2;
	roi.height = gray.rows;
	gray.copyTo(result(roi));
	hog.compute(result, desc, Size(8, 8), Size(0, 0));
}

void generate_dataset(Mat &trainData, Mat &labels)
{
	vector<string> posImageNames, negaImageNames;
	glob(positive_dir, posImageNames);
	glob(negative_dir, negaImageNames);
	int positveNum = posImageNames.size();
	int negativeNum = negaImageNames.size();

	vector<vector<float> > featureBatch;
	for (auto &name : posImageNames)
	{
		Mat image = imread(name);
		vector<float> fv;
		get_hog_descripor(image, fv);
		printf_s("image:%s, feature length :%d\n", name.c_str(), fv.size());
		featureBatch.push_back(std::move(fv));
	}
	for (auto &name : negaImageNames)
	{
		Mat image = imread(name);
		vector<float> fv;
		get_hog_descripor(image, fv);
		printf_s("image:%s, feature length :%d\n", name.c_str(), fv.size());
		featureBatch.push_back(std::move(fv));
	}

	int rows = positveNum + negativeNum;
	int cols = featureBatch.at(0).size();
	trainData = Mat::zeros(rows, cols, CV_32FC1);
	labels = Mat::zeros(rows, 1, CV_32SC1);
	for (int row = 0; row < rows; row++)
	{
		for (int col = 0; col < cols; col++)
		{
			trainData.at<float>(row, col) = featureBatch[row][col];
		}
		labels.at<int>(row) = row < positveNum ? 1 : -1;
	}



}

void svm_train(Mat &trainData, Mat &labels) {
	printf_s("\n start SVM training... \n");
	Ptr< SVM > svm = SVM::create();
	/* Default values to train SVM */
	svm->setGamma(5.383);
	svm->setKernel(SVM::LINEAR);
	svm->setC(2.67);
	svm->setType(SVM::C_SVC);
	svm->train(trainData, ROW_SAMPLE, labels);
	cout << "...[done]" << endl;

	// save xml
	svm->save(svm_model_dir);
}

void svm_test()
{
	Ptr<SVM> svm = SVM::load(svm_model_dir);
	Mat  testImage = imread(test_img);
	Mat image;
	resize(testImage, image, cv::Size(0, 0), 0.2, 0.2);
	//imshow("test image", image);

	Rect  winRect;
	winRect.width = 64;
	winRect.height = 128;

	int sum_x = 0, sum_y = 0;
	int count = 0;
	for (int row = 0; row < image.rows - winRect.height; row += 4)
	{
		for (int col = 0; col < image.cols - winRect.width; col += 4)
		{
			winRect.x = col;
			winRect.y = row;
			vector<float> fv;
			get_hog_descripor(image(winRect), fv);
			Mat testMat(1, fv.size(), CV_32FC1, fv.data());
			float result = svm->predict(testMat);
			if (result > 0)
			{
				rectangle(image, winRect, Scalar(0, 0, 255));
				sum_x += winRect.x;
				sum_y += winRect.y;
				count++;
			}
		}
	}

	winRect.x = sum_x / count;
	winRect.y = sum_y / count;
	rectangle(image, winRect, Scalar(0, 0, 255));
	imshow("HOG result", image);

}

本代码参考于网络。

参考资料

Histogram of Oriented Gradients
Histograms of Oriented Gradients for Human Detection论文
Histograms of Oriented Gradients for Human Detection论文翻译
HOG:从理论到OpenCV实践
INRIA 行人数据集 (INRIA Person Dataset)
物体检测参考实现

你可能感兴趣的:(AI,图像处理)