opencv-hog原理的推导和代码完全注释版的实现

我找过很多c++关于hog的文章,都没有能够自己设置参设的可视化代码。只能找到关于官方给的hog默认构造的可视化代码。我才疏学浅,属于新手,自己尝试写过关于hog不同参设的可视化代码,最终都没成功。这里借鉴官方使用默认参数的可视化hog教程。 本文只是简单介绍hog,而在实际过程中,还需要gamma矫正,归一化等方式减噪,提高对比度等来提高特征的精确度。
hog又叫梯度直方图,是用来计算图片中特征向量的,我们在一张图片(a,b)中令一个[m,n]的矩阵win(a>=m,b>=n),在win中选定一个[x,y]的矩阵block((x<m,y<n),其中opencv默认的block参数是1616)以模板匹配的方式逐一遍历win,其中每次滑动的像素叫步长,记为s,则block匹配win的次数为((int(m-x)/s)+1)((int(n-y)/s)+1)=z次,特别注意步长s的取值不能大于bolck,否则中间滑动的像素就会缺少,也要注意步长s的取值应该是(win-block)/z的整数倍,保证最后一次滑动能够刚好匹配到win的最后一行一列像素。我们又用另一个矩阵cell(i,j)以模板匹配的方式遍历block,不过这次的步长必须是(x,y)的因数,也就是说滑动的像素不能重复。那么cell的总数为block*(x/i)(y/j)。接着我们计算每个cell的梯度,根据二元导数我们可知
在这里插入图片描述
在这里插入图片描述
则cell在x方向,和y方向上的梯度分别是
在这里插入图片描述
其中因为梯度的取值我们总是取绝对值,所以只需要每一个像素的梯度划分9个区域,记为bin[9],其中,我们统计cell中的各像素梯度权重,将它们(int)/10的方法记录到bin[9]中
在这里插入图片描述
梯度直方图如下在这里插入图片描述
现在我们有了cell的梯度统计,我们就可以实现特征向量的运算[y][x][bin],因为cell在block中的滑动是不重复的,而block在win的滑动中是有重复的步,所以cell也在统计中有重叠的部分 我们将cell的特征向量统计到block中,再统计block,就可以得到hog特征提取。其中特征维数=((int(m-x)/s)+1)((int(n-y)/s)+1)(x/i)(y/j)*9
下面是代码的实现

Mat get_hogdescriptor_visual_image(Mat& origImg,
	vector<float>& descriptorValues,//hog特征向量  
	Size winSize,//图片窗口大小  
	Size cellSize,//默认是8*8
	int scaleFactor,//缩放背景图像的比例  
	double viz_factor)//缩放hog特征的线长比例  
{
	Mat visual_image;//最后可视化的图像大小  
	resize(origImg, visual_image, Size(origImg.cols*scaleFactor, origImg.rows*scaleFactor));

	int gradientBinSize = 9;//因为一个cell有9个bin
	// dividing 180° into 9 bins, how large (in rad) is one bin?  
	float radRangeForOneBin = 3.14 / (float)gradientBinSize; //pi=3.14对应180°  

	// prepare data structure: 9 orientation / gradient strenghts for each cell  
	int cells_in_x_dir = winSize.width / cellSize.width;//x方向上的cell个数  
	int cells_in_y_dir = winSize.height / cellSize.height;//y方向上的cell个数  
	int totalnrofcells = cells_in_x_dir * cells_in_y_dir;//cell的总个数  
	//注意此处三维数组的定义格式  
	//int ***b;  
	//int a[2][3][4];  
	//int (*b)[3][4] = a;  
	//gradientStrengths[cells_in_y_dir][cells_in_x_dir][9]  
	float*** gradientStrengths = new float**[cells_in_y_dir];// 记录[y][x][9]个梯度
	int** cellUpdateCounter = new int*[cells_in_y_dir]; //统计cell[y][x]的重复计算次数 因为在block的滑动中,block会有像素的覆盖 cell会被重复计算
	for (int y = 0; y < cells_in_y_dir; y++)
	{
		gradientStrengths[y] = new float*[cells_in_x_dir];//因为c语言是按行储存数组的 cell_x实际是每一行中按列增加 这里是初始化每行
		cellUpdateCounter[y] = new int[cells_in_x_dir];
		for (int x = 0; x < cells_in_x_dir; x++)
		{
			gradientStrengths[y][x] = new float[gradientBinSize];//一行当中的每列,划分9个bin
			cellUpdateCounter[y][x] = 0;

			for (int bin = 0; bin < gradientBinSize; bin++)
				gradientStrengths[y][x][bin] = 0.0;//把每个cell的9个bin对应的梯度强度都初始化为0  
		}
	}

	// nr of blocks = nr of cells - 1  
	// since there is a new block on each cell (overlapping blocks!) but the last one  
	//相当于blockstride = (8,8)  
	int blocks_in_x_dir = cells_in_x_dir - 1;//这里是 block在x方向上的滑块次数统计,步长为8 得到的次数也就是(winhog.width-bolck.widht)/8+1;
	int blocks_in_y_dir = cells_in_y_dir - 1;

	// compute gradient strengths per cell  
	int descriptorDataIdx = 0;
	int cellx = 0;
	int celly = 0;

	for (int blockx = 0; blockx < blocks_in_x_dir; blockx++)//每个block(16,16),cell(8,8)有2*2个cell
	{
		for (int blocky = 0; blocky < blocks_in_y_dir; blocky++)
		{
			// 4 cells per block ...  
			for (int cellNr = 0; cellNr < 4; cellNr++)
			{
				// compute corresponding cell nr  
				int cellx = blockx;//开始滑动
				int celly = blocky;
				if (cellNr == 1) celly++;//cell在bolck中第一次滑动,也就是x方向的滑动
				if (cellNr == 2) cellx++;
				if (cellNr == 3)
				{
					cellx++;
					celly++;
				}

				for (int bin = 0; bin < gradientBinSize; bin++)
				{
					float gradientStrength = descriptorValues[descriptorDataIdx];//开始计算[y][x][bin]的每个特征向量,计算方法已经封装在hog.compute中
					descriptorDataIdx++;

					gradientStrengths[celly][cellx][bin] += gradientStrength;//因为C是按行存储  

				} // for (all bins)  


				// note: overlapping blocks lead to multiple updates of this sum!  
				// we therefore keep track how often a cell was updated,  
				// to compute average gradient strengths  
				cellUpdateCounter[celly][cellx]++;//由于block之间有重叠,所以要记录哪些cell被多次计算了  

			} // for (all cells)  


		} // for (all block x pos)  
	} // for (all block y pos)  


	// compute average gradient strengths  
	for (int celly = 0; celly < cells_in_y_dir; celly++)
	{
		for (int cellx = 0; cellx < cells_in_x_dir; cellx++)
		{

			float NrUpdatesForThisCell = (float)cellUpdateCounter[celly][cellx]; //求出cell重复的部分

			// compute average gradient strenghts for each gradient bin direction  
			for (int bin = 0; bin < gradientBinSize; bin++)
			{
				gradientStrengths[celly][cellx][bin] /= NrUpdatesForThisCell; //平均化重复的cell  可以强化特征向量的特征
			}
		}
	}


	cout << "原图大小 = " << winSize << endl;
	cout << "cell大小 = " << cellSize << endl;
	cout << "block大小= " << cellSize * 2 << endl;
	cout << "block滑动的次数 = " << blocks_in_x_dir << "×" << blocks_in_y_dir << endl;
	cout << "特征向量的总数= " << descriptorDataIdx << endl;

	// draw cells   这里是画出cell的梯度图
	for (int celly = 0; celly < cells_in_y_dir; celly++)
	{
		for (int cellx = 0; cellx < cells_in_x_dir; cellx++)
		{
			int drawX = cellx * cellSize.width;
			int drawY = celly * cellSize.height;

			int mx = drawX + cellSize.width / 2;
			int my = drawY + cellSize.height / 2;

			rectangle(visual_image,
				Point(drawX*scaleFactor, drawY*scaleFactor),
				Point((drawX + cellSize.width)*scaleFactor,
				(drawY + cellSize.height)*scaleFactor),
				CV_RGB(0, 0, 0),//cell框线的颜色  
				1);

			// draw in each cell all 9 gradient strengths  
			for (int bin = 0; bin < gradientBinSize; bin++)
			{
				float currentGradStrength = gradientStrengths[celly][cellx][bin];

				// no line to draw?  
				if (currentGradStrength == 0)
					continue;

				float currRad = bin * radRangeForOneBin + radRangeForOneBin / 2;//取每个bin里的中间值,如10°,30°,...,170°.  

				float dirVecX = cos(currRad);
				float dirVecY = sin(currRad);
				float maxVecLen = cellSize.width / 2;
				float scale = viz_factor; // just a visual_imagealization scale,  
				// to see the lines better  

				// compute line coordinates  
				float x1 = mx - dirVecX * currentGradStrength * maxVecLen * scale;
				float y1 = my - dirVecY * currentGradStrength * maxVecLen * scale;
				float x2 = mx + dirVecX * currentGradStrength * maxVecLen * scale;
				float y2 = my + dirVecY * currentGradStrength * maxVecLen * scale;

				// draw gradient visual_imagealization  
				line(visual_image,
					Point(x1*scaleFactor, y1*scaleFactor),
					Point(x2*scaleFactor, y2*scaleFactor),
					CV_RGB(255, 255, 255),//HOG可视化的cell的颜色  
					1);

			} // for (all bins)  

		} // for (cellx)  
	} // for (celly)  

	for (int y = 0; y < cells_in_y_dir; y++)
	{
		for (int x = 0; x < cells_in_x_dir; x++)
		{
			delete[] gradientStrengths[y][x];
		}
		delete[] gradientStrengths[y];
		delete[] cellUpdateCounter[y];
	}
	delete[] gradientStrengths;
	delete[] cellUpdateCounter;

	return visual_image;//返回最终的HOG可视化图像  

}


int main()
{

	HOGDescriptor hog;//使用的是默认的hog参数  

	Mat src = imread("C://Users//Administrator//Desktop/s1.png");//注意这里边的双斜杠
	int src_width = src.cols;
	int src_height = src.rows;
	int width = src_width;
	int height = src_height;
	hog.winSize = Size(width, height);
	vector<float> des;//HOG特征向量  

	Mat dst;
	resize(src, dst, Size(width, height));//规范图像尺寸  
	cout << src_width << " " << src_height;
	imshow("src", src);
	hog.compute(dst, des);//计算hog特征  
	Mat background = Mat::zeros(Size(width, height), CV_8UC1);//设置黑色背景图,因为要用白色绘制hog特征  

	Mat d = get_hogdescriptor_visual_image(background, des, hog.winSize, hog.cellSize, 3, 2.5);
	namedWindow("dst", 0);
	imshow("dst", d);
	imwrite("hogvisualize.jpg", d);
	waitKey();

	return 0;
}

在这里插入图片描述在这里插入图片描述
接下来我们还要统计winsize在图形中的滑块
我们可以看到上面默认参数下的hog可视化案例,而实际操作hog的过中可如下

int main()
{
	Mat src = imread("C://Users//Administrator//Desktop/s1.png");
	HOGDescriptor hog1(Size(40, 48), Size(16, 16), Size(8, 8), Size(8, 8), 9);
	HOGDescriptor hog2(Size(40, 48), Size(4, 4), Size(2, 2), Size(2, 2), 9);///win,block,blockstride,cell,bin
	cout << " 当win大小40*48,block16*16,blockstride8*8,cell8*8,bin=9是特征维数是(((40-16)/8)+1*((48-16)/8)+1)*(16/8)(16/8)*9=" << endl << hog1.getDescriptorSize() << endl;
	cout << " 当win大小40*48,block4*4,blockstride2*2,cell2*2,bin=9是特征维数是(((40-4)/2)+1*((48-4)/2)+1)*(4/2)(4/2)*9=" << endl << hog2.getDescriptorSize() << endl;
	vector<float>  descriptors;//特征向量
	hog2.compute(src, descriptors);//特征向量的计算 使用了默认构造函数size(1,1),size(0,0)其中1*1是win的滑块步长,0*0是防止winsize+win的滑块步长最后超过图片size做的填充
	int dim = descriptors.size();
	cout <<"win滑动步长为1="<< dim << endl;
	hog2.compute(src, descriptors,Size(8,8));
	int dim2 = descriptors.size();//特征向量的计算 使用了默认构造函数size(1,1),size(0,0)其中1*1是win的滑块步长,0*0是防止winsize+win的滑块步长最后超过图片size做的填充
	cout <<"win滑动步长为8="<< dim2 << endl;;
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值