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

前言

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

颜色空间归一化

简介

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

运算步骤

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

程式码

void Library::Gamma8bit(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_DOUBLE gamma){// 1. get gamma tableUCHAE gammaLUT[256];for (UINT32 index = 0; index < 256; index++){double pix = (index + 0.5) / 256.0; //归一化pix = pow(pix, gamma);gammaLUT[index] = static_cast<UCHAE>((pix * 256 - 0.5)); //反归一化}// 2. set gamma pixelUCHAE* purEnd = pur + width * height;while (pur < purEnd){*pur = gammaLUT[*src];pur++;src++;}}

结果图

http://img2.58codes.com/2024/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<UINT32>(ceil(static_cast<float>(width) / _cellX) * _cellX);}inline C_UINT32 HOG::FixHeight(C_UINT32& height) const{return static_cast<UINT32>(ceil(static_cast<float>(height) / _cellY) * _cellY);}inline C_UINT32 HOG::CellXSize(C_UINT32& width) const{return static_cast<UINT32>(ceil(static_cast<float>(width) / _cellX));}inline C_UINT32 HOG::CellYSize(C_UINT32& height) const{return static_cast<UINT32>(ceil(static_cast<float>(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计算出来的梯度作为可视化。
http://img2.58codes.com/2024/20110564MfJLM97nbt.png
图来源[2]

运算步骤

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

程式码

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

void HOG::Gradient(C_UCHAE* src, C_UINT32 width, C_UINT32 height, double* amplitudes, double* angles){// 1. paddingC_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 gradientfor (UINT32 row = 1; row < padHeight - 1; row++){for (UINT32 col = 1; col < padWidth - 1; col++){C_FLOAT Gx = static_cast<float>(padImage.image[row][col + 1])- static_cast<float>(padImage.image[row][col - 1]);C_FLOAT Gy = static_cast<float>(padImage.image[row + 1][col])- static_cast<float>(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<UCHAE>(*amplitudesPointer);pur++;amplitudesPointer++;}delete[] amplitudes;amplitudes = nullptr;}

结果图

http://img2.58codes.com/2024/20110564g3G49srPIe.png

Cell HOG

简介

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

计算步骤

调整大小。取得梯度。计算直方图。正规化直方图。画出直方图特徵。

程式码

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.3CellHistogram(src, width, height, histogram);// 4. normalizationHistogramNorm(histogram, cellHisTotalSize, _bin, 1);// 5. drawHOGDrawCell(pur, width, height, histogram);delete[] histogram;histogram = nullptr;}void HOG::CellHistogram(C_UCHAE* src, C_UINT32 width, C_UINT32 height, float* histogram){// 1. fix sizeUCHAE* resizeData = nullptr;ReSize(src, &resizeData, width, height);// 2. get gradientC_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 histogramfor (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 9C_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<int32_t>(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<float>(offsetX * cellHistogram[index] * MNDT::FixValue(cos(2.0 * MNDT::PI * index / _bin)));C_FLOAT y = centerY + static_cast<float>(offsetY * cellHistogram[index] * MNDT::FixValue(sin(2.0 * MNDT::PI * index / _bin)));if (x < 0){int i = 0;}Point binPoint(static_cast<UINT32>(x), static_cast<UINT32>(y));MNDT::DrawLine8bit(purImgae, centerPoint, binPoint);}cellHistogram += _bin;}}}

结果图

http://img2.58codes.com/2024/201105647yl6VzNChC.png
cell大小16*16。

Block HOG

简介

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

计算步骤

计算cell直方图。将cell直方图资料依据blockX * blockY直方图複製到block直方图。使用block直方图计算每个cell的总和和出现次数。计算每个cell的平均。正规化直方图。画出直方图特徵。

程式码

BlockHistogram以步伐1走访cell,连结每个cell为一个blockCalcBlockHistogramblock範围内每一个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.4BlockHistogram(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 histogramfor (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 histogramHOGViewAvg(cellHistogram, cellHisCount, cellHisTotalSize);delete[] cellHisCount;cellHisCount = nullptr;// 7. calculate normalization of the cell histogramHistogramNorm(cellHistogram, cellHisTotalSize, _bin, 1);// 8. draw for the cell histogramHOGDrawCell(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.3CellHistogram(src, width, height, cellHistogram);// 4. copy the cell histogram to the block histogramC_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++;}}

结果图

http://img2.58codes.com/2024/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)


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章