HOG特征的源码中给出的检测窗口大小的默认值为128*64,网上很多范例都会将原图resize为该尺寸。那么HOG特征检测时,检测窗口尺寸一定要处理为128*64吗?No,我们只要遵守必要的规则,检测窗口可以设置为任意尺寸。本文记录此问题点的验证方法和使用HOG特征点提取时的注意事项。
1. HOGDescriptor参数详解
HOGDescriptor是HOG特征类,使用该类时,涉及到的参数有:ImageSize,winSize,blockSize,cellSize,blockStride, winStride。我们先通过一张图来大致了解一下HOG特征提取和参数设置的规则。这张图来源于参考文献opencv源码解析之(6):hog源码分析,感谢原作者,我在这张图上添加了一些参数设置规则的说明。
上图中的规则在HOG的源码中可以找到,HOG源码的路径为D:\opencv源码路径\sources\modules\objdetect\src\hog.cpp。本文中的”opencv源码路径“指的是正在使用的opencv的sources所在的路径。该语句在函数getDescriptorSize中,该函数用于计算HOG特征点的维度。
在opencv3.1的hog源码的注释中,有提到HOG检测算法的原理,大致如下:
检测算法以4层嵌套循环在计算,每一层循环均为二维,理论上其动作分别如下:
用设置的winSize以winStride为单位遍历ImageSize
用设置的blockSize以blockStride为单位遍历winSize
用cellSize为单位遍历blockSize
遍历cellSize中的每一个像素
但实际上源码中并未这样做,因为这样计算的速度将会非常非常慢。为了加快HOG提取的速度,HOG算法的作者使用了查表法,将pixData和blockData计算完成后存在内存中,在遍历过程中用到相应数据时,直接查表即可,这样可以省去大量重复计算的时间。
HOGDescriptor其中一个包含默认参数的构造函数如下,这些默认值也是我们在很多范例中见到的设定值。HOGDescriptor的构造函数在opencv3.1源码中的路径为D:\opencv源码路径\sources\modules\objdetect\include\opencv2\objdetect.hpp。
2. HOGDescriptor参数设置实例
在opencv3.1的源码中,有一个名为train_HOG的范例,其路径为D:\opencv源码路径\sources\samples\cpp\train_HOG.cpp,该范例中有一个将HOG特征可视化的函数。我对该文件进行改造后用来验证HOGDescriptor尺寸设置的问题。
源图像尺寸为448*280,我设置的参数如下,winSize为图像尺寸,blockSize,blockStride,cellSize的设置则遵守本文图1的规则,其他参数保持默认值。这样既可以得到图像的HOG特征点:9个方向的梯度直方图。
其可视化结果得到的结果如下图所示,每一个cell中都有一只绿色“小蜘蛛”。我们可以观察到图像右侧和下方有一部分无特征点,因为这部分的值不足以构成一个cell。
上图的特征点过于粗糙,而且图像中还有一部分区域并未检测到特征点。我将参数进行调整,调整后参数和图像如下所示。在这一组参数下,特征点覆盖到了整幅图像,特征点更加密集。
实验证明,只要遵守响应的规则,我们可以根据实际需求自行调整各个参数。
3. HOG特征点可视化函数
在train_HOG.cpp中,HOG特征点可视化函数名为get_hogdescriptor_visu,该函数实现算法的链接为http://www.juergenwiki.de/work/wiki/doku.php?id=public:hog_descriptor_computation_and_visualization (需fq)。
该函数在使用时需要注意两点:该函数会把原图扩大3被后绘制HOG特征点,若要调整尺寸,需要自行修改响应代码;该函数默认cellSize为8*8,因此若需要显示任意尺寸的HOG特征点,还需要将函数中使用的cellSize修正为实际使用值。我将该函数进行了调整,在调用时可以设置放大倍数和cellSize,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
// From http://www.juergenwiki.de/work/wiki/doku.php?id=public:hog_descriptor_computation_and_visualization
Mat
get_hogdescriptor_visu
(
const
Mat
&
color_origImg
,
vector
<
float
>
&
descriptorValues
,
const
Size
&
size
,
const
Size
&
cellSize
,
const
float
&
zoomFac
)
{
const
int
DIMX
=
size
.
width
;
const
int
DIMY
=
size
.
height
;
Mat
visu
;
resize
(
color_origImg
,
visu
,
Size
(
(
int
)
(
color_origImg
.
cols*
zoomFac
)
,
(
int
)
(
color_origImg
.
rows*
zoomFac
)
)
)
;
//int cellSizeW = cellSize.width;
int
gradientBinSize
=
9
;
float
radRangeForOneBin
=
(
float
)
(
CV_PI
/
(
float
)
gradientBinSize
)
;
// dividing 180 into 9 bins, how large (in rad) is one bin?
// prepare data structure: 9 orientation / gradient strenghts for each cell
int
cells_in_x_dir
=
DIMX
/
cellSize
.
width
;
int
cells_in_y_dir
=
DIMY
/
cellSize
.
height
;
float
*
*
*
gradientStrengths
=
new
float
*
*
[
cells_in_y_dir
]
;
int
*
*
cellUpdateCounter
=
new
int
*
[
cells_in_y_dir
]
;
for
(
int
y
=
0
;
y
<
cells_in_y_dir
;
y
++
)
{
gradientStrengths
[
y
]
=
new
float
*
[
cells_in_x_dir
]
;
cellUpdateCounter
[
y
]
=
new
int
[
cells_in_x_dir
]
;
for
(
int
x
=
0
;
x
<
cells_in_x_dir
;
x
++
)
{
gradientStrengths
[
y
]
[
x
]
=
new
float
[
gradientBinSize
]
;
cellUpdateCounter
[
y
]
[
x
]
=
0
;
for
(
int
bin
=
0
;
bin
<
gradientBinSize
;
bin
++
)
gradientStrengths
[
y
]
[
x
]
[
bin
]
=
0.0
;
}
}
// nr of blocks = nr of cells - 1
// since there is a new block on each cell (overlapping blocks!) but the last one
int
blocks_in_x_dir
=
cells_in_x_dir
-
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
++
)
{
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
cellx
=
blockx
;
celly
=
blocky
;
if
(
cellNr
==
1
)
celly
++
;
if
(
cellNr
==
2
)
cellx
++
;
if
(
cellNr
==
3
)
{
cellx
++
;
celly
++
;
}
for
(
int
bin
=
0
;
bin
<
gradientBinSize
;
bin
++
)
{
float
gradientStrength
=
descriptorValues
[
descriptorDataIdx
]
;
descriptorDataIdx
++
;
gradientStrengths
[
celly
]
[
cellx
]
[
bin
]
+=
gradientStrength
;
}
// 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
]
++
;
}
// for (all cells)
}
// for (all block x pos)
}
// for (all block y pos)
// compute average gradient strengths
for
(
celly
=
0
;
celly
<
cells_in_y_dir
;
celly
++
)
{
for
(
cellx
=
0
;
cellx
<
cells_in_x_dir
;
cellx
++
)
{
float
NrUpdatesForThisCell
=
(
float
)
cellUpdateCounter
[
celly
]
[
cellx
]
;
// compute average gradient strenghts for each gradient bin direction
for
(
int
bin
=
0
;
bin
<
gradientBinSize
;
bin
++
)
{
gradientStrengths
[
celly
]
[
cellx
]
[
bin
]
/=
NrUpdatesForThisCell
;
}
}
}
// draw cells
for
(
celly
=
0
;
celly
<
cells_in_y_dir
;
celly
++
)
{
for
(
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
(
visu
,
Point
(
(
int
)
(
drawX*
zoomFac
)
,
(
int
)
(
drawY*
zoomFac
)
)
,
Point
(
(
int
)
(
(
drawX
+
cellSize
.
width
)
*
zoomFac
)
,
(
int
)
(
(
drawY
+
cellSize
.
height
)
*
zoomFac
)
)
,
Scalar
(
100
,
100
,
100
)
,
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
;
float
dirVecX
=
cos
(
currRad
)
;
float
dirVecY
=
sin
(
currRad
)
;
float
maxVecLen
=
(
cellSize
.
width
>
cellSize
.
height
)
?
(
float
)
(
cellSize
.
width
/
2.f
)
:
(
float
)
(
cellSize
.
height
/
2.f
)
;
float
scale
=
2.5
;
// just a visualization 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 visualization
line
(
visu
,
Point
(
(
int
)
(
x1*
zoomFac
)
,
(
int
)
(
y1*
zoomFac
)
)
,
Point
(
(
int
)
(
x2*
zoomFac
)
,
(
int
)
(
y2*
zoomFac
)
)
,
Scalar
(
0
,
255
,
0
)
,
1
)
;
}
// for (all bins)
}
// for (cellx)
}
// for (celly)
// don't forget to free memory allocated by helper data structures!
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
visu
;
}
// get_hogdescriptor_visu
|