图像处理之HOG特征(C++)

图像处理之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的物体检测

  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值