想做物体检测,可以试试HOG!例如我们在下面的图片中检测这位美女。
HOG全名 Histogram of Oriented Gradients,也就是方向梯度的直方图,它主要利用梯度的直方图构建特征向量。
最经典的是用作行人检测,也可以用作其他物体检测。
一维梯度可以认为是一阶导数: d y d x = y ‘ \frac{\mathrm{d} y }{\mathrm{d} x} = y^{`} dxdy=y‘
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的偏导数。
由公式可知,二维梯度是有方向的。可以将其转换了梯度的幅值 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是通过梯度信息去构建特征,所有并不需要颜色信息,可以转换为灰度图。
为了减少光照的影响,提高对比度,对图像进行伽玛矫正。但是有的人说可以直接跳过这一步,因为后面会对更小的块区域进行归一化,而且局部归一化的效果会更好
计算方法见方向梯度部分,效果如下
计算每个像素的梯度,彩色图取幅值最大的一个通道。这里将图分成了8*8的cell,一张64*128的图像,被分割成 8 * 16 个cell。以梯度幅度作为直方图的统计值, 梯度幅值作为统计量。这里并不是之间将幅值分到一个bin,而是根据幅度将其按比例分到最近的两个bin。这样可以避免梯度信息的跳变。所以称为 accumulate weighted vote for gradient orientation over spatial cells。形成9*1的向量。
为了适应亮度变化,以及前景与背景对比度的变换,以块为单位进行归一化。块的大小为2*2.,归一化的方式可以选择NORM_L1,NORM_L2,NORM_MINMAX,NORM_INF等。
关于block 的大小以及cell 的大小,相关论文中给出了如下测试结果,在 Block size=3*3, cell size=6*6 时错误率最小,但是这里并没有采用。将所有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
官方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是非常容易的事情,大致可以分为三步。
例如使用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)
物体检测参考实现