opencv 特征提取 - HOG

本文详细介绍了一种传统的计算机视觉方法——方向梯度直方图(HOG)在目标检测中的应用,包括原理、实施步骤、代码演示及其优缺点。HOG通过统计图像局部梯度方向分布,构建对光照变化不敏感的特征,用于目标定位和识别。
摘要由CSDN通过智能技术生成

一、HOG特征

其实在深度学习中我们已经介绍了目标检测和目标识别的概念、为了照顾一些没有学过深度学习的童鞋,这里我重新说明一次:目标检测是用来确定图像上某个区域是否有我们要识别的对象,目标识别是用来判断图片上这个对象是什么。识别通常只处理已经检测到对象的区域,例如,人们总是会在已有的人脸图像的区域去识别人脸。

传统的目标检测方法与识别不同于深度学习方法,后者主要利用神经网络来实现分类和回归问题。在这里我们主要介绍如何利用OpecnCV来实现传统目标检测和识别,在计算机视觉中有很多目标检测和识别的技术,这里我们主要介绍下面几块内容:

  • 方向梯度直方图HOG(Histogram of Oriented Gradient);
  • 图像金字塔;
  • 滑动窗口;

HOG(Histograms of Oriented Gradients)梯度方向直方图

HOG不是基于颜色值而是基于梯度来计算直方图的,它通过计算和统计图像局部区域的梯度方向直方图来构建特征

主要思想:

局部目标的外表和形状可以被局部梯度或边缘方向的分布很好的描述,即使我们不知道对应的梯度和边缘的位置。(本质:梯度的统计信息,梯度主要存在于边缘的地方)

实施方法

  首先将图像分成很多小的连通区域,我们把它叫做细胞单元,然后采集细胞单元中各像素点的梯度和边缘方向,然后在每个细胞单元中累加出一个一维的梯度方向直方图。

为了对光照和阴影有更好的不变性,需要对直方图进行对比度归一化,这可以通过把这些直方图在图像的更大的范围内(我们把它叫做区间或者block)进行对比度归一化。首先我们计算出各直方图在这个区间中的密度,然后根据这个密度对区间中的各个细胞单元做归一化。我们把归一化的块描述符叫作HOG描述子。

二、HOG特征提取流程

1)灰度图像 转换(将图像看做一个x,y,z(灰度)的三维图像);


2)采用Gamma校正法对输入图像进行颜色空间的标准化(归一化);目的是调节图像的对比度,降低图像局部的阴影和光照变化所造成的影响,同时可以抑制噪音的干扰;

首先采用Gamma校正法对输入图像的颜色空间进行标准化(或者说是归一化)。

所谓的Gamma校正可以理解为提高图像中偏暗或者偏亮部分的图像对比效果,能够有效地降低图像局部的阴影和光照变化。更详细的内容可以点击这里查看图像处理之gamma校正。

 

 

γ<1在低灰度值区域内,动态范围变大,图像对比度增加强;在高灰度值区域,动态范围变小,图像对比度降低,同时,图像的整体灰度值变大;

γ>1在低灰度值区域内,动态范围变小,图像对比度降低;在高灰度值区域,动态范围变大,图像对比度提高,同时,图像的整体灰度值变小;

#if 1 // 图像增强算法 --gamma
int  Gamma = 2;
int main(int args, char* arg)
{
	Mat	src = imread("C:\\Users\\19473\\Desktop\\opencv_images\\88.jpg");
	if (!src.data)
	{
		printf("could not  load  image....\n");
	}
	imshow("原图像", src);
	// 注意点 : CV_32FC3
	Mat dst(src.size(), CV_32FC3);

	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			// 对bgr 的每个通道都进行计算
			dst.at<Vec3f>(i, j)[0] = pow(src.at<Vec3b>(i, j)[0], Gamma);
			dst.at<Vec3f>(i, j)[1] = pow(src.at<Vec3b>(i, j)[1], Gamma);
			dst.at<Vec3f>(i, j)[2] = pow(src.at<Vec3b>(i, j)[2], Gamma);
		}
	}
	// 归一化
	normalize(dst, dst, 0, 255, CV_MINMAX);

	convertScaleAbs(dst, dst);

	imshow("增强后的图像", dst);
	waitKey(0);
	return -1;
}
#endif

图像平滑(具体视情况而定)

对于灰度图像,一般为了去除噪点,所以会先利用高斯函数进行平滑:高斯函数在不同的平滑尺度下对灰度图像进行平滑操作

3)计算图像每个像素的梯度(包括大小和方向);主要是为了捕获轮廓信息,同时进一步弱化光照的干扰

 

Mat non_max_supprusion(Mat  dx, Mat dy)  //传进来的是两个方向上的差分矩阵  3*3 的掩膜
{
	//边缘强度=sqrt(dx 的平方+dy 的平方) 
	Mat  edge;
	magnitude(dx, dy, edge);// 计算幅度值
	int  rows = dx.rows;
	int  cols = dy.cols;
	//边缘强度的非极大值抑制
	Mat edgemag_nonMaxSup = Mat::zeros(dx.size(), dx.type());
	// 用两个循序计算出  和梯度方向 并且转换成angleMatrix
	for (int row = 1; row < rows - 1; row++)
	{
		for (int col = 1; col < cols - 1; col++)
		{
			float x = dx.at<float>(row, col);
			float y = dx.at<float>(row, col);
			// 梯度的方向---atan2f

			float  angle = atan2f(y, x) / CV_PI * 180;
			// 当前位置的边缘强度
			float  mag = edge.at<float>(row, col);
			// 找到左右两个方向
			if (abs(angle) < 22.5 || abs(angle) > 157.5)
			{
				float  left = edge.at<float>(row, col - 1);
				float  right = edge.at<float>(row, col + 1);
				// 判断两个方向上的
				if (mag > left && mag > right) {
					edgemag_nonMaxSup.at<float>(row, col) = mag;
				}

			}
			// 左上和右下两个方向
			if ((abs(angle) >= 22.5 && abs(angle) < 67.5) || (abs(angle) < -112.5 && abs(angle) > 157.5))
			{
				float  lefttop = edge.at<float>(row - 1, col - 1);
				float  rightbottom = edge.at<float>(row + 1, col + 1);
				// 判断两个方向上的
				if (mag > lefttop && mag > rightbottom) {
					edgemag_nonMaxSup.at<float>(row, col) = mag;
				}
			}

			//  上  下 方向
			if ((abs(angle) >= 67.5 && abs(angle) <= 112.5) || (abs(angle) >= -112.5 && abs(angle) <= -67.5))
			{
				float  top = edge.at<float>(row - 1, col);
				float  down = edge.at<float>(row + 1, col);
				// 判断两个方向上的
				if (mag > top && mag > down) {
					edgemag_nonMaxSup.at<float>(row, col) = mag;
				}
			}




			//  右上  左下 方向
			if ((abs(angle) > 122.5 && abs(angle) < 157.5) || (abs(angle) > -67.5 && abs(angle) <= -22.5))
			{
				float  leftdown = edge.at<float>(row - 1, col + 1);
				float  rightup = edge.at<float>(row + 1, col - 1);
				// 判断两个方向上的
				if (mag > leftdown && mag > rightup) {
					edgemag_nonMaxSup.at<float>(row, col) = mag;
				}
			}
		}
	}
	return  edgemag_nonMaxSup;
}


4)直方图计算:将图像划分成小cells(例如8* 8像素 / cell); 算出每个cell的梯度大小及方向.然后将每像素的梯度方向在 区间内(无向:0-180,有向:0-360)平均分为9个bins,每个cell内的像素用幅值来表示权值,为其所在的梯度直方图进行加权投票.

如果cell中某一个像素的梯度方向是20~40°,直方图第2个bin的计数就要加1,这样对cell中的每一个像素用梯度方向在直方图中进行加权投影(权值大小等于梯度幅值),将其映射到对应的角度范围块内,就可以得到这个cell的梯度方向直方图了,就是该cell对应的9维特征向量。对于梯度方向位于相邻bin的中心之间(如20°、40°等)需要进行方向和位置上的双线性插值。

采用梯度幅值量级本身得到的检测效果最佳,而使用二值的边缘权值表示会严重降低效果。采用梯度幅值作为权重,可以使那些比较明显的边缘的方向信息对特征表达影响增大,这样比较合理,因为HOG特征主要就是依靠这些边缘纹理。

根据Dalal等人的实验,在行人目标检测中,在无符号方向角度范围并将其平均分成9份(bins)能取得最好的效果,当bin的数目继续增大效果改变不明显,故一般在人体目标检测中使用bin数目为9范围0~180°的度量方式。

5)统计每个cell的梯度直方图(不同梯度的个数),即可形成每个cell的descriptor;  快描述籽
6)对block归一化 将每几个cell组成一个block(例如3 * 3个cell / block),一个block内所有cell的特征descriptor串联起来便得到该block的HOG特征descriptor。快描述籽归一化

最终的描述子是检测窗口内所有块内的细胞单元的直方图构成的向量,事实上,块之间是有重叠的,也就是说,每个细胞单元的直方图都会被多次用于最终的描述子的计算,块之间的重叠看起来有冗余,但可以显著的提升性能 。

通常使用的HOG结构大致有三种:矩形HOG(简称为R-HOG),圆形HOG和中心环绕HOG。它们的单位都是Block(即块)。Dalal的试验证明矩形HOG和圆形HOG的检测效果基本一致,而环绕形HOG效果相对差一些

如上图,一个块由2×2个cell组成,每一个cell包含8×8个像素点,每个cell提取9个直方图通道,因此一个块的特征向量长度为2×2×9


7)将图像image内的所有block的HOG特征descriptor串联起来就可以得到该image(你要检测的目标)的HOG特征descriptor了。这个就是最终的可供分类使用的特征向量了 特征数据与检测窗口

对于大小为128×64大小的图像,采用8*8像素的sell,2×2个cell组成的16×16像素的block,采用8像素的block移动步长,这样检测窗口block的数量有((128-16)/8+1)×((64-16)/8+1)=15×7.则HOG特征描述符的维数为15×7×4×9.

8) 匹配方法

HOG的缺点:


速度慢,实时性差;难以处理遮挡问题

三、代码演示

int main(int args, char* arg)
{
	//目标图像
	src = imread("C:\\Users\\19473\\Desktop\\opencv_images\\153.jpg");
	if (!src.data)
	{
		printf("could not  load  image....\n");
	}
	namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE);

	//namedWindow(OUT_TITLE, CV_WINDOW_AUTOSIZE);
	imshow(INPUT_TITLE, src);
	/*
	 // 将图像重新规定大小
	resize(src, dst,Size(64,128));

	cvtColor(dst, src_gary, CV_BGR2GRAY);

	HOGDescriptor  detector(Size(64,128), Size(16,16), Size(8,8),Size(8,8),9);
	vector<float>  descripers;
	vector<Point>  locations;
	detector.compute(src_gary, descripers, Size(0,0), Size(0, 0), locations);
	printf("num  of  HOG:  %d\n", descripers.size());
	*/


	//SVM分类器 --描述子
	HOGDescriptor  hog = HOGDescriptor();
	hog.setSVMDetector(hog.getDefaultPeopleDetector());
	vector<Rect>   foundloactions;
	// 多尺度检测
	hog.detectMultiScale(src, foundloactions, 0, Size(8, 8), Size(32, 32), 1.05, 2, false);
	//若rects有嵌套,则取最外面的矩形存入rect
	for (size_t i = 0; i < foundloactions.size(); i++)
	{
		rectangle(src, foundloactions[i], Scalar(0, 0, 255), 2, 8.0);
	}

	namedWindow(OUT_TITLE, CV_WINDOW_AUTOSIZE);
	imshow(OUT_TITLE, src);
	waitKey(0);
	return 0;
}

 

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值