AI|HOG+SVM的物体检测
想做物体检测,可以试试HOG!例如我们在下面的图片中检测这位美女。
文章图片
HOG全名 Histogram of Oriented Gradients,也就是方向梯度的直方图,它主要利用梯度的直方图构建特征向量。
最经典的是用作行人检测,也可以用作其他物体检测。
方向梯度(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? ?θ=arctanfy?fx??
数字图像中的微分通过减法来代替。这里使用的水平微分核为[ ? 1 , 0 , 1 ] [-1, 0, 1] [?1,0,1], 垂直微分核为 [ ? 1 , 0 , 1 ] T [-1, 0,1]^T [?1,0,1]T。可以使用其他的微分核代替,例如sober算子。
文章图片
HOG算法中最重要的一步就是,计算梯度的幅值和幅度,然后根据幅度统计直方图。具体怎么做的见算法流程。
算法流程
文章图片
文章图片
检测窗口(detection window)
可以认为是从一张大图找提取出一部分作为检测图像。
文章图片
归一化图像(normalize gamma & colour)
HOG是通过梯度信息去构建特征,所有并不需要颜色信息,可以转换为灰度图。
为了减少光照的影响,提高对比度,对图像进行伽玛矫正。但是有的人说可以直接跳过这一步,因为后面会对更小的块区域进行归一化,而且局部归一化的效果会更好
计算梯度(compute gradient)
【AI|HOG+SVM的物体检测】计算方法见方向梯度部分,效果如下
文章图片
计算每个像素的梯度,彩色图取幅值最大的一个通道。这里将图分成了8*8的cell,一张64*128的图像,被分割成 8 * 16 个cell。
计算方向梯度直方图
文章图片
以梯度幅度作为直方图的统计值, 梯度幅值作为统计量。这里并不是之间将幅值分到一个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 时错误率最小,但是这里并没有采用。
文章图片
生成特征向量
将所有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;
}
文章图片
HOG物体检测
如果我们想做其他物体检测,那么就需要自己训练分类器。其实有了HOG提取的特征向量,训练SVM是非常容易的事情,大致可以分为三步。
- 准备训练数据和标签
- 利用训练数据训练分类器
- 使用测试数据对分类器做测试
#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)
物体检测参考实现
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量