图像处理之HOG特征(C++)
前言
HOG是Histogram of Oriented Gradients的缩写,意为梯度方向直方图,是一种在计算机视觉领域中常用的特征提取算法。它通过计算图像中不同区域的梯度方向直方图来描述图像的局部特征。HOG特征具有旋转不变性和部分光照不变性的特点,因此在目标检测和行人识别等任务中得到广泛应用。
一、HOG原理
理论部分参考:OpenCV HOG+SVM的物体检测
1.图像灰度化,gamma校正和归一化处理;
2.首先计算图像每一个像素点的梯度幅值和角度;
3.计算输入图像的每个cell单元的梯度直方图,形成每个cell的descriptor,比如输入图像为128×64可以得到16×8个cell,每个cell由9个bin组成;
4.将2×2个cell组成一个block,一个block内所有cell的特征串联起来得到该block的HOG特征descriptor,并进行归一化处理,将图像内所有block的HOG特征descriptor串联起来得到该图像的HOG特征descriptor(3780维),这就是最终分类的特征向量;
二、代码实现
1.实现步骤
1.图片Gamma和颜色归一化
2.计算梯度
3.构建直方图
4.Block混叠空间块归一化
5.构建HOG特征描述子
2.手动实现代码
#include <iostream>
#include <opencv.hpp>
using namespace std;
/*
* @param cv::Mat src 输入图像
* @param cv::Mat& dst 输出图像
* @param float c 常数,用于控制变化幅度
* @param float gamma 指数
* @breif 伽马变换
*/
void GammaGrayTrans(const cv::Mat& src, cv::Mat& dst, float c, float gamma)
{
//建立灰度映射表
float grayTable[256];
for (int i = 0; i < 256; i++)
{
grayTable[i] = c * pow(i, gamma);
}
//遍历修改灰度值
//为了防止发生截断(像素值超过255或者小于0),对dst图像进行数据类型转换
//dst.convertTo(dst, CV_32F);
int temp = 0;
for (int i = 0; i < src.rows; i++)
for (int j = 0; j < src.cols; j++)
{
temp = src.at<uchar>(i, j);
dst.at<float>(i, j) = grayTable[temp];
}
//将数据缩放到0-255
cv::normalize(dst, dst, 0, 255, cv::NORM_MINMAX);
//dst的数据类型还原为CV_8UC1
dst.convertTo(dst, CV_8UC1);
}
/*
* @param cv::Mat angleImg 梯度方向矩阵
* @param cv::Mat magnitudeImg 梯度幅值矩阵
* @param cv::Size cellSize cell的尺寸
* @param std::vector<std::vector<double>> cellVector 存储每个cell的特征向量
* @brief 计算每个cell的特征向量
*/
void getCellVector(cv::Mat& angleImg,cv::Mat& magnitudeImg,cv::Size cellSize, std::vector<std::vector<double>>& cellVector)
{
for(int cell_i=0; cell_i < angleImg.rows; cell_i=cell_i+cellSize.height)
for (int cell_j = 0; cell_j < angleImg.cols; cell_j=cell_j + cellSize.width)
{
//映射直方图
//[0, 180] 度以20度为一个bin,平均分成9份
std::vector<double> table(9);
int temp_floor = 0;
double scale = 0;
for (int i = cell_i; i < cell_i + cellSize.height; i++)
for (int j = cell_j; j < cell_j + cellSize.width; j++)
{
temp_floor = std::floor(angleImg.at<float>(i, j) / 20); //向下取整
switch (temp_floor) {
case 0:
scale = (angleImg.at<float>(i, j) - 0) / 20;
table[0] += magnitudeImg.at<float>(i, j) * scale;
table[1] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 1:
scale = (angleImg.at<float>(i, j) - 20) / 20;
table[1] += magnitudeImg.at<float>(i, j) * scale;
table[2] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 2:
scale = (angleImg.at<float>(i, j) - 40) / 20;
table[2] += magnitudeImg.at<float>(i, j) * scale;
table[3] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 3:
scale = (angleImg.at<float>(i, j) - 60) / 20;
table[3] += magnitudeImg.at<float>(i, j) * scale;
table[4] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 4:
scale = (angleImg.at<float>(i, j) - 80) / 20;
table[4] += magnitudeImg.at<float>(i, j) * scale;
table[5] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 5:
scale = (angleImg.at<float>(i, j) - 100) / 20;
table[5] += magnitudeImg.at<float>(i, j) * scale;
table[6] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 6:
scale = (angleImg.at<float>(i, j) - 120) / 20;
table[6] += magnitudeImg.at<float>(i, j) * scale;
table[7] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 7:
scale = (angleImg.at<float>(i, j) - 140) / 20;
table[7] += magnitudeImg.at<float>(i, j) * scale;
table[8] += magnitudeImg.at<float>(i, j) * (1 - scale);
break;
case 8:
table[8] += magnitudeImg.at<float>(i, j);
break;
}
}
cellVector.push_back(table);
}
}
/*
* @param cv::Mat src 输入图像(CV_8U),灰度图
* @param cv::Mat dst 输出图像
* @param int thresh OTSU最大类间方差的灰度值
* @brief 计算HOG描述子
*/
void getHOGDescriptor(cv::Mat& src, std::vector<double>& HOGVector)
{
//gamma变换
cv::Mat gammaImg(src.size(),CV_32F);
GammaGrayTrans(src, gammaImg, 1, 0.5);
//计算梯度
cv::Mat sobel_x, sobel_y;
cv::Sobel(gammaImg, sobel_x, CV_32F, 1, 0);
cv::Sobel(gammaImg, sobel_y, CV_32F, 0, 1);
cv::Mat magnitudeImg(sobel_x.size(),CV_32F), angleImg(sobel_x.size(), CV_32F);
double temp_mag = 0, temp_angle = 0;
for (int i = 0; i < sobel_x.rows; i++)
for (int j = 0; j < sobel_x.cols; j++)
{
//temp_mag = cv::sqrt(sobel_x.at<float>(i, j) * sobel_x.at<float>(i, j) + sobel_y.at<float>(i, j) * sobel_y.at<float>(i, j));
temp_mag = cv::abs(sobel_x.at<float>(i, j)) + cv::abs(sobel_y.at<float>(i, j));
temp_angle = cv::fastAtan2(sobel_y.at<float>(i, j), sobel_x.at<float>(i, j));
magnitudeImg.at<float>(i, j) = temp_mag;
if ((temp_angle > 180)) temp_angle -= 180; //将角度映射到0-180°,成为“无符号”梯度
angleImg.at<float>(i, j) = temp_angle;
}
//构建直方图
//设置cell的尺寸8*8,获得每个cell的向量
cv::Size cellSize(8,8);
std::vector<std::vector<double>> cellVector;
getCellVector(angleImg, magnitudeImg, cellSize, cellVector);
计算每个block向量,选用2*2的block
std::vector < std::vector < double >> blockVector;
int block_i_end = angleImg.rows / cellSize.height;
int block_j_end = angleImg.cols / cellSize.width;
for (int block_i = 0; block_i < block_i_end-1; block_i++) {
for (int block_j = 0; block_j < block_j_end-1; block_j++)
{
std::vector<double> block;
block.insert(block.end(), cellVector[block_i*block_j_end+block_j].begin(), cellVector[block_i * block_j_end + block_j].end());
block.insert(block.end(), cellVector[block_i * block_j_end + block_j + 1].begin(), cellVector[block_i * block_j_end + block_j + 1].end());
block.insert(block.end(), cellVector[(block_i+1) * block_j_end + block_j].begin(), cellVector[(block_i+1) * block_j_end + block_j].end());
block.insert(block.end(), cellVector[(block_i+1) * block_j_end + block_j + 1].begin(), cellVector[(block_i+1) * block_j_end + block_j + 1].end());
cv::normalize(block, block, 1, cv::NORM_L1);
blockVector.push_back(block);
}
}
//构建HOG特征描述子,合并每个block
for (int i = 0; i < blockVector.size(); i++)
{
for (int j = 0; j < blockVector[i].size(); j++)
{
HOGVector.push_back(blockVector[i][j]);
}
}
}
int main()
{
//读取图片
string filepath = "F://work_study//algorithm_demo//baby.jpg";
cv::Mat src = cv::imread(filepath, cv::IMREAD_GRAYSCALE);
if (src.empty())
{
std::cout << "imread error" << std::endl;
return -1;
}
cv::resize(src, src, cv::Size(64, 128));
std::vector<double> HOGVector;
getHOGDescriptor(src,HOGVector);
cv::waitKey(0);
return 0;
}
总结
本文手动实现了经典的HOG特征提取方法,因为本人能力有限,欢迎大家批评指正。
理论部分参考:OpenCV HOG+SVM的物体检测