AI|HOG+SVM的物体检测

想做物体检测,可以试试HOG!例如我们在下面的图片中检测这位美女。
AI|HOG+SVM的物体检测
文章图片

HOG全名 Histogram of Oriented Gradients,也就是方向梯度的直方图,它主要利用梯度的直方图构建特征向量。
最经典的是用作行人检测,也可以用作其他物体检测。
方向梯度(oriented Gradients) 一维梯度可以认为是一阶导数: d y d x = y ‘ \frac{\mathrm{d} y }{\mathrm{d} x} = y^{`} dxdy?=y‘
AI|HOG+SVM的物体检测
文章图片

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的偏导数。
AI|HOG+SVM的物体检测
文章图片

由公式可知,二维梯度是有方向的。可以将其转换了梯度的幅值 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? ?θ=arctanfy?fx??
数字图像中的微分通过减法来代替。这里使用的水平微分核为[ ? 1 , 0 , 1 ] [-1, 0, 1] [?1,0,1], 垂直微分核为 [ ? 1 , 0 , 1 ] T [-1, 0,1]^T [?1,0,1]T。可以使用其他的微分核代替,例如sober算子。
AI|HOG+SVM的物体检测
文章图片
HOG算法中最重要的一步就是,计算梯度的幅值和幅度,然后根据幅度统计直方图。具体怎么做的见算法流程。 算法流程 AI|HOG+SVM的物体检测
文章图片
AI|HOG+SVM的物体检测
文章图片
检测窗口(detection window)
可以认为是从一张大图找提取出一部分作为检测图像。
AI|HOG+SVM的物体检测
文章图片
归一化图像(normalize gamma & colour)
HOG是通过梯度信息去构建特征,所有并不需要颜色信息,可以转换为灰度图。
为了减少光照的影响,提高对比度,对图像进行伽玛矫正。但是有的人说可以直接跳过这一步,因为后面会对更小的块区域进行归一化,而且局部归一化的效果会更好
计算梯度(compute gradient)
【AI|HOG+SVM的物体检测】计算方法见方向梯度部分,效果如下
AI|HOG+SVM的物体检测
文章图片
计算每个像素的梯度,彩色图取幅值最大的一个通道。这里将图分成了8*8的cell,一张64*128的图像,被分割成 8 * 16 个cell。 计算方向梯度直方图
AI|HOG+SVM的物体检测
文章图片
以梯度幅度作为直方图的统计值, 梯度幅值作为统计量。这里并不是之间将幅值分到一个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 时错误率最小,但是这里并没有采用。 AI|HOG+SVM的物体检测
文章图片
生成特征向量
将所有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/8?1),垂直15个( 16 ? 1 16-1 16?1)。
最后形成特性大小为: 7 ? 8 ? 36 = 3780 7 * 8 * 36=3780 7?8?36=3780
在opencv中使用 HOG 官方HOG文档
实现英文介绍
不想看英文接口介绍的可以看接口中文翻译
首先来个比较简单的,直接利用官方训练好的 SVM 分类器做行人检测,感受一下效果。
将要使用一下接口:

/** @brief Returns coefficients of the classifier trained for people detection (for 64x128 windows). */ std::vector 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& 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 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; }

AI|HOG+SVM的物体检测
文章图片
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 &desc); void generate_dataset(Mat &trainData, Mat &labels); void svm_train(Mat &trainData, Mat &labels); void svm_test(); int main() { MattrainData, labels; generate_dataset(trainData, labels); svm_train(trainData, labels); svm_test(); waitKey(6000); return 0; }void get_hog_descripor(Mat &image, vector &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> posImageNames, negaImageNames; glob(positive_dir, posImageNames); glob(negative_dir, negaImageNames); int positveNum = posImageNames.size(); int negativeNum = negaImageNames.size(); vector > featureBatch; for (auto &name : posImageNames) { Mat image = imread(name); vector 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 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 = https://www.it610.com/article/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(row, col) = featureBatch[row][col]; } labels.at(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::load(svm_model_dir); MattestImage = imread(test_img); Mat image; resize(testImage, image, cv::Size(0, 0), 0.2, 0.2); //imshow("test image", image); RectwinRect; 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 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)
物体检测参考实现

    推荐阅读