[笔记]C++ & C#影像处理HOG特征

前言

上次介绍LBP特征主要是由邻近像素取得特征,而HOG则是取得梯度直方图特征,两者的想法其实有点相像,这次主要参考[1]介绍一般的HOG原理并实作。

颜色空间归一化

简介

在做HOG之前为了得到更明显的特征因此会先将图像归一化,而这里的归一化主要调整亮度,所使用的方法为Gamma转换,在[2]提到Gamma转换为非线性转换较符合人类眼睛,里面也举了一个范例将原先亮度高的衣服调整为亮度低的,然而就可以观察到较细微的部分,以下就开始实作。

运算步骤

  1. 依照公式取得Gamma表,减少计算量。
  2. 走访每个像素指派转换的Gamma像素。

代码

void Library::Gamma8bit(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height
	, C_DOUBLE gamma)
{
	// 1. get gamma table
	UCHAE gammaLUT[256];

	for (UINT32 index = 0; index < 256; index++)
	{
		double pix = (index + 0.5) / 256.0; //归一化
		pix = pow(pix, gamma);
		gammaLUT[index] = static_cast((pix * 256 - 0.5)); //反归一化
	}

	// 2. set gamma pixel
	UCHAE* purEnd = pur + width * height;

	while (pur < purEnd)
	{
		*pur = gammaLUT[*src];
		pur++;
		src++;
	}
}

结果图

https://ithelp.ithome.com.tw/upload/images/20181123/20110564FNEP3dAhs7.png
Gamma=1.5

HOG类

私有成员

  • _cellX:一个cell的宽。
  • _cellY:一个cell的高。
  • _blockX:一个block的宽。
  • _blockY:一个block的高。
  • _bin:一个cell直方图数量。

换算函数

  • FixWidth:取得修正至cellX可整除的宽度。
  • FixHeight:取得修正至cellY可整除的高度。
  • CellXSize:取得cellX数量。
  • CellYSize:取得cellY数量。
  • BlockXSize:取得blockX数量。
  • BlockYSize:取得blockY数量。
  • BlockHisSize:取得一个block直方图的数量。

代码

inline C_UINT32 HOG::FixWidth(C_UINT32& width) const
{
	return static_cast(ceil(static_cast(width) / _cellX) * _cellX);
}

inline C_UINT32 HOG::FixHeight(C_UINT32& height) const
{
	return static_cast(ceil(static_cast(height) / _cellY) * _cellY);
}

inline C_UINT32 HOG::CellXSize(C_UINT32& width) const
{
	return static_cast(ceil(static_cast(width) / _cellX));
}

inline C_UINT32 HOG::CellYSize(C_UINT32& height) const
{
	return static_cast(ceil(static_cast(height) / _cellY));
}

inline C_UINT32 HOG::BlockXSize(C_UINT32& cellXSize) const
{
	return cellXSize - _blockX + 1;
}

inline C_UINT32 HOG::BlockYSize(C_UINT32& cellYSize) const
{
	return cellYSize - _blockX + 1;
}
inline C_UINT32 HOG::BlockHisSize() const
{
	return _blockX * _blockY * _bin;
}

C_UINT32 HOG::CellHisTotalSize(C_UINT32& width, C_UINT32& height) const
{
	return CellXSize(width)
		* CellYSize(height)
		* _bin;
}

C_UINT32 HOG::BlockHisTotalSize(C_UINT32& width, C_UINT32& height) const
{
	return BlockXSize(CellXSize(width))
		* BlockYSize(CellYSize(height))
		* BlockHisSize();
}

梯度

简介

HOG梯度水平与垂直这里只取左右或上下两个元素的差来当特征,梯度主要使用L2范式计算,角度则是一样用atan计算,下图为计算公式。这里取出L2计算出来的梯度作为可视化。
https://ithelp.ithome.com.tw/upload/images/20181128/20110564MfJLM97nbt.png
图来源[2]

运算步骤

  1. 填补。
  2. 走访并依照公式计算梯度。
  3. 指派梯度的值给图像。

代码

这里将梯度搬出来设一个函数,因后面也会使用到,所以GradienView主要是完成第三步。

void HOG::Gradient(C_UCHAE* src
	, C_UINT32 width, C_UINT32 height
	, double* amplitudes, double* angles)
{
	// 1. padding
	C_DOUBLE angle = 180.0 / MNDT::PI;
	C_UINT32 padWidth = width + 2;
	C_UINT32 padHeight = height + 2;
	UCHAE* padData = new UCHAE[padWidth * padHeight];
	
	MNDT::ImagePadding8bit(src, padData, width, height, 1);

	Image padImage(padData, padWidth, padHeight, MNDT::ImageType::GRAY_8BIT);

	// 2. calculate hog of gradient
	for (UINT32 row = 1; row < padHeight - 1; row++)
	{
		for (UINT32 col = 1; col < padWidth - 1; col++)
		{
			C_FLOAT Gx = static_cast(padImage.image[row][col + 1])
				- static_cast(padImage.image[row][col - 1]);

			C_FLOAT Gy = static_cast(padImage.image[row + 1][col])
				- static_cast(padImage.image[row - 1][col]);

			*angles = abs(atan2(Gy, Gx) * angle);
			*amplitudes = sqrt(Gx * Gx + Gy * Gy);

			angles++;
			amplitudes++;
		}
	}

	delete[] padData;
	padData = nullptr;
}

void HOG::GradienView(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	C_UINT32 size = width * height;
	double* amplitudes = new double[size];
	double* angles = new double[size];

	Gradient(src, width, height
		, amplitudes, angles);

	delete[] angles;
	angles = nullptr;

	UCHAE* endPur = pur + size;
	double* amplitudesPointer = amplitudes;

	while (pur < endPur)
	{
		*pur = static_cast(*amplitudesPointer);

		pur++;
		amplitudesPointer++;
	}

	delete[] amplitudes;
	amplitudes = nullptr;
}

结果图

https://ithelp.ithome.com.tw/upload/images/20181128/20110564g3G49srPIe.png

Cell HOG

简介

cell主要是将图片依照cell宽度和高度将图片分割,若原始图片无法整除cell则先将图片大小调整至可整除,再计算梯度直方图后在范式(计算步伐X为cellX,Y为cellY)。而绘图主要将直方图设为圆心已360度分为bin个方向,在依照比例画出即可。

计算步骤

  1. 调整大小。
  2. 取得梯度。
  3. 计算直方图。
  4. 范式直方图。
  5. 画出直方图特征。

代码

  • CellHistogram走访计算直方图。
  • CalcCellHistogram为计算单个直方图累积数量,依照180 / bin去分区。
  • HOGDrawCell计算出cellX和cellY的中心点,并360度划分bin个区域使用范式直方图画出。
void HOG::HOGCellView(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	C_UINT32 cellHisTotalSize = CellHisTotalSize(width, height);
	float* histogram = new float[cellHisTotalSize]{ 0.0f };

	// step 1.2.3
	CellHistogram(src, width, height
		, histogram);

	// 4. normalization
	HistogramNorm(histogram
		, cellHisTotalSize, _bin
		, 1);

	// 5. draw
	HOGDrawCell(pur
		, width, height
		, histogram);

	delete[] histogram;
	histogram = nullptr;
}

void HOG::CellHistogram(C_UCHAE* src, C_UINT32 width, C_UINT32 height
	, float* histogram)
{
	// 1. fix size
	UCHAE* resizeData = nullptr;

	ReSize(src, &resizeData, width, height);



	// 2. get gradient
	C_UINT32 reWidth = FixWidth(width);
	C_UINT32 reHeight = FixHeight(height);
	C_UINT32 size = reWidth * reHeight;
	double* amplitudes = new double[size];
	double* angles = new double[size];

	Gradient(resizeData, reWidth, reHeight
		, amplitudes, angles);

	delete[] amplitudes;
	amplitudes = nullptr;

	if (resizeData != src)
	{
		delete[] resizeData;
		resizeData = nullptr;
	}



	// 3. calculate histogram
	for (UINT32 row = 0; row < reHeight; row += _cellY)
	{
		for (UINT32 col = 0; col < reWidth; col += _cellX)
		{
			CalcCellHistogram(angles, reWidth
				, histogram
				, col, row
				, col + _cellX, row + _cellY);
			histogram += _bin;
		}
	}

	delete[] angles;
	angles = nullptr;

}

void HOG::CalcCellHistogram(C_DOUBLE* angles, C_UINT32 width
	, float* histogram
	, C_UINT32 sWidth, C_UINT32 sHeight
	, C_UINT32 eWidth, C_UINT32 eHeight)
{
	// bin is 9
	C_DOUBLE space = 20.0;

	for (UINT32 row = sHeight; row < eHeight; row++)
	{
		C_UINT32 rowIndex = row * width;

		for (UINT32 col = sWidth; col < eWidth; col++)
		{
			int32_t binIndex = static_cast(ceil(angles[rowIndex + col] / space)) - 1;

			binIndex = binIndex < 0 ? 0 : binIndex; histogram[binIndex]++; } } } void HOG::HOGDrawCell(UCHAE* pur , C_UINT32 width, C_UINT32 height , float* cellHistogram) { C_UINT32 reWidth = FixWidth(width); C_UINT32 reHeight = FixHeight(height); C_UINT32 cellXSize = CellXSize(reWidth); C_UINT32 cellYSize = CellYSize(reHeight); C_UINT32 offsetX = _cellX >> 1;
	C_UINT32 offsetY = _cellY >> 1;
	Image purImgae(pur, reWidth, reHeight, MNDT::ImageType::GRAY_8BIT);

	for (UINT32 row = 0; row < cellYSize; row++)
	{
		C_UINT32 centerY = row * _cellY + offsetY - 1;

		for (UINT32 col = 0; col < cellXSize; col++)
		{
			C_UINT32 centerX = col * _cellX + offsetX - 1;
			Point centerPoint(centerX, centerY);

			for (UINT32 index = 0; index < _bin; index++)
			{
				C_FLOAT x = centerX + static_cast(offsetX * cellHistogram[index] * MNDT::FixValue(cos(2.0 * MNDT::PI * index / _bin)));
				C_FLOAT y = centerY + static_cast(offsetY * cellHistogram[index] * MNDT::FixValue(sin(2.0 * MNDT::PI * index / _bin)));
				if (x < 0)
				{
					int i = 0;
				}
				Point binPoint(static_cast(x), static_cast(y));

				MNDT::DrawLine8bit(purImgae, centerPoint, binPoint);
			}
			cellHistogram += _bin;
		}
	}
}

结果图

https://ithelp.ithome.com.tw/upload/images/20181128/201105647yl6VzNChC.png
cell大小16*16。

Block HOG

简介

block则是依照cell宽度和高度去计算切割的数量,将blockX * blockY块的cell的直方图串再一起做范式(计算步伐为1)。而绘制可视化则是使用block直方图计算每一个cell的平均数量在使用上述的函数HOGDrawCell画出。

计算步骤

  1. 计算cell直方图。
  2. cell直方图数据依据blockX * blockY直方图复制到block直方图。
  3. 使用block直方图计算每个cell的总和和出现次数。
  4. 计算每个cell的平均。
  5. 范式直方图。
  6. 画出直方图特征。

代码

  • BlockHistogram以步伐1走访cell,链接每个cell为一个block
  • CalcBlockHistogramblock范围内每一个cell直方图链接再一起。
  • HOGViewSum累计一个block内的cell值和数量,索引值为依照计算block的方式推导。
  • HOGViewAvg使用cell直方图的值和数量计算的平均。
void HOG::HOGBlockView(C_UCHAE* src, UCHAE* pur
	, C_UINT32 width, C_UINT32 height)
{
	float* blockHistogram = new float[BlockHisTotalSize(width, height)];

	// step 1.2.3.4
	BlockHistogram(src, width, height
		, blockHistogram);




	C_UINT32 reWidth = FixWidth(width);
	C_UINT32 reHeight = FixHeight(height);
	C_UINT32 cellXSize = CellXSize(reWidth);
	C_UINT32 cellYSize = CellYSize(reHeight);
	C_UINT32 cellWidth = _bin * cellXSize;
	C_UINT32 blockHisSize = BlockHisSize();
	C_UINT32 blockXSize = BlockXSize(cellXSize);
	C_UINT32 blockYSize = BlockYSize(cellYSize);
	C_UINT32 cellHisTotalSize = CellHisTotalSize(width, height);
	C_FLOAT* blockHistogramPointer = blockHistogram;
	float* cellHistogram = new float[cellHisTotalSize] { 0.0f };
	UINT32* cellHisCount = new UINT32[cellHisTotalSize]{ 0 };

	// 5. calculate the block histogram to cell histogram
	for (UINT32 row = 0; row < blockYSize; row++)
	{
		for (UINT32 col = 0; col < blockXSize; col++)
		{
			HOGViewSum(blockHistogramPointer, cellWidth
				, cellHistogram, cellHisCount
				, col, row
				, col + _blockX, row + _blockY);
			blockHistogramPointer += blockHisSize;
		}
	}

	delete[] blockHistogram;
	blockHistogram = nullptr;


	// 6. calculate average of the cell histogram
	HOGViewAvg(cellHistogram, cellHisCount
		, cellHisTotalSize);

	delete[] cellHisCount;
	cellHisCount = nullptr;


	// 7. calculate normalization of the cell histogram
	HistogramNorm(cellHistogram
		, cellHisTotalSize, _bin
		, 1);


	// 8. draw for the cell histogram
	HOGDrawCell(pur
		, width, height
		, cellHistogram);

	delete[] cellHistogram;
	cellHistogram = nullptr;
}

void HOG::BlockHistogram(C_UCHAE* src, C_UINT32 width, C_UINT32 height
	, float* histogram)
{
	C_UINT32 reWidth = FixWidth(width);
	C_UINT32 reHeight = FixHeight(height);
	C_UINT32 cellXSize = CellXSize(reWidth);
	C_UINT32 cellYSize = CellYSize(reHeight);
	float* cellHistogram = new float[CellHisTotalSize(reWidth, reHeight)]{ 0 };

	// step 1.2.3
	CellHistogram(src, width, height
		, cellHistogram);

	// 4. copy the cell histogram to the block histogram
	C_UINT32 cellWidth = _bin * cellXSize;
	C_UINT32 blockHisSize = BlockHisSize();
	C_UINT32 blockXSize = BlockXSize(cellXSize);
	C_UINT32 blockYSize = BlockYSize(cellYSize);

	for (UINT32 row = 0; row < blockYSize; row++)
	{
		for (UINT32 col = 0; col < blockXSize; col++)
		{
			CalcBlockHistogram(cellHistogram, cellWidth
				, histogram
				, col, row
				, col + _blockX, row + _blockY);
			//MNDT::SetNormalizedHistogram8bit(histogram, blockHisSize, MNDT::Normalized::L2);
			histogram += blockHisSize;
		}
	}

	delete[] cellHistogram;
	cellHistogram = nullptr;
}

void HOG::CalcBlockHistogram(C_FLOAT* cellHistogram, C_UINT32 cellWidth
	, float* histogram
	, C_UINT32 sCellX, C_UINT32 sCellY
	, C_UINT32 eCellX, C_UINT32 eCellY)
{
	C_UINT32 copySize = _bin * sizeof(float);

	for (UINT32 row = sCellY; row < eCellY; row++)
	{
		C_UINT32 rowIndex = row * cellWidth;

		for (UINT32 col = sCellX; col < eCellX; col++)
		{
			C_UINT32 index = rowIndex + col * _bin;
			memcpy(histogram, cellHistogram + index, copySize);
			histogram += _bin;
		}
	}
}

void HOG::HOGViewSum(C_FLOAT* blockHistogram, C_UINT32 cellXSize
	, float* cellHistogram
	, UINT32* cellHisCount
	, C_UINT32 sCellX, C_UINT32 sCellY
	, C_UINT32 eCellX, C_UINT32 eCellY)
{
	for (UINT32 row = sCellX; row < eCellX; row++)
	{
		C_UINT32 rowIndex = row * cellXSize;

		for (UINT32 col = sCellY; col < eCellY; col++)
		{
			C_UINT32 index = rowIndex + col * _bin;

			for (UINT32 binIndex = 0; binIndex < _bin; binIndex++)
			{
				*(cellHistogram + index + binIndex) += *(blockHistogram + binIndex);
				*(cellHisCount + index + binIndex) += 1;
			}
			blockHistogram += _bin;
		}
	}
}

void HOG::HOGViewAvg(float* cellHistogram, C_UINT32* cellHisCount
	, C_UINT32 cellTotalSize)
{
	C_FLOAT* cellHistogramEnd = cellHistogram + cellTotalSize;

	while (cellHistogram < cellHistogramEnd)
	{
		*cellHistogram /= *cellHisCount;
		cellHistogram++;
		cellHisCount++;
	}
}

结果图

https://ithelp.ithome.com.tw/upload/images/20181128/20110564VEHxmDfGnj.png
参数cell:88,block:22。

训练用特征

训练用特征只需将上述要求到block直方图并且将数据使用SetNormalizedHistogram8bitL2范式,而使用HOG比较要注意的地方为,输入的训练图像尽量大小必须一致,这样所得到的直方图数量才会相等。

结论

这次主要介绍这两种特征,其实还有许多特征方法,但其实做法都大同小异,比较不同的算法应该是Haar特征,原本有打算要做但基于有好多东西要去学习所以特征先暂时介绍到这里,若有问题或错误欢迎提问。

参考文献

[1]hujingshuang(2015).【特征检测】HOG特征算法 from: https://blog.csdn.net/hujingshuang/article/details/47337707 (2018.11.23)
[2]CSDN博客(2014). Gamma校正的理解 from: https://read01.com/zh-tw/xDD062.html#.W_gaH2gzZPY (2018.11.23)

 

发表评论