forked from scikit-image/scikit-image
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_haar.pyx
314 lines (275 loc) · 13.3 KB
/
_haar.pyx
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#cython: cdivision=True
#cython: boundscheck=False
#cython: nonecheck=False
#cython: wraparound=False
#distutils: language=c++
import numpy as np
cimport numpy as cnp
from libcpp.vector cimport vector
from .._shared.fused_numerics cimport np_real_numeric
from .._shared.transform cimport integrate
FEATURE_TYPE = {'type-2-x': 0, 'type-2-y': 1,
'type-3-x': 2, 'type-3-y': 3,
'type-4': 4}
N_RECTANGLE = {'type-2-x': 2, 'type-2-y': 2,
'type-3-x': 3, 'type-3-y': 3,
'type-4': 4}
cdef vector[vector[Rectangle]] _haar_like_feature_coord(
Py_ssize_t width,
Py_ssize_t height,
unsigned int feature_type) nogil:
"""Private function to compute the coordinates of all Haar-like features.
"""
cdef:
Py_ssize_t max_feature = height * height * width * width
vector[vector[Rectangle]] rect_feat
Rectangle single_rect
Py_ssize_t n_rectangle
Py_ssize_t x, y, dx, dy
if feature_type == 0 or feature_type == 1:
n_rectangle = 2
elif feature_type == 2 or feature_type == 3:
n_rectangle = 3
else:
n_rectangle = 4
# Allocate for the number of rectangle (we know from the start)
rect_feat = vector[vector[Rectangle]](n_rectangle)
for y in range(height):
for x in range(width):
for dy in range(1, height):
for dx in range(1, width):
# type -> 2 rectangles split along x axis
if (feature_type == 0 and
(y + dy <= height and x + 2 * dx <= width)):
set_rectangle_feature(&single_rect,
y, x,
y + dy - 1, x + dx - 1)
rect_feat[0].push_back(single_rect)
set_rectangle_feature(&single_rect,
y, x + dx,
y + dy - 1, x + 2 * dx - 1)
rect_feat[1].push_back(single_rect)
# type -> 2 rectangles split along y axis
elif (feature_type == 1 and
(y + 2 * dy <= height and x + dx <= width)):
set_rectangle_feature(&single_rect,
y, x,
y + dy - 1, x + dx - 1)
rect_feat[0].push_back(single_rect)
set_rectangle_feature(&single_rect,
y + dy, x,
y + 2 * dy - 1, x + dx - 1)
rect_feat[1].push_back(single_rect)
# type -> 3 rectangles split along x axis
elif (feature_type == 2 and
(y + dy <= height and x + 3 * dx <= width)):
set_rectangle_feature(&single_rect,
y, x,
y + dy - 1, x + dx - 1)
rect_feat[0].push_back(single_rect)
set_rectangle_feature(&single_rect,
y, x + dx,
y + dy - 1, x + 2 * dx - 1)
rect_feat[1].push_back(single_rect)
set_rectangle_feature(&single_rect,
y, x + 2 * dx,
y + dy - 1, x + 3 * dx - 1)
rect_feat[2].push_back(single_rect)
# type -> 3 rectangles split along y axis
elif (feature_type == 3 and
(y + 3 * dy <= height and x + dx <= width)):
set_rectangle_feature(&single_rect,
y, x,
y + dy - 1, x + dx - 1)
rect_feat[0].push_back(single_rect)
set_rectangle_feature(&single_rect,
y + dy, x,
y + 2 * dy - 1, x + dx - 1)
rect_feat[1].push_back(single_rect)
set_rectangle_feature(&single_rect,
y + 2 * dy, x,
y + 3 * dy - 1, x + dx - 1)
rect_feat[2].push_back(single_rect)
# type -> 4 rectangles split along x and y axis
elif (feature_type == 4 and
(y + 2 * dy <= height and x + 2 * dx <= width)):
set_rectangle_feature(&single_rect,
y, x,
y + dy - 1, x + dx - 1)
rect_feat[0].push_back(single_rect)
set_rectangle_feature(&single_rect,
y, x + dx,
y + dy - 1, x + 2 * dx - 1)
rect_feat[1].push_back(single_rect)
set_rectangle_feature(&single_rect,
y + dy, x,
y + 2 * dy - 1, x + dx - 1)
rect_feat[3].push_back(single_rect)
set_rectangle_feature(&single_rect,
y + dy, x + dx,
y + 2 * dy - 1, x + 2 * dx - 1)
rect_feat[2].push_back(single_rect)
return rect_feat
cpdef haar_like_feature_coord_wrapper(width, height, feature_type):
"""Compute the coordinates of Haar-like features.
Parameters
----------
width : int
Width of the detection window.
height : int
Height of the detection window.
feature_type : str
The type of feature to consider:
- 'type-2-x': 2 rectangles varying along the x axis;
- 'type-2-y': 2 rectangles varying along the y axis;
- 'type-3-x': 3 rectangles varying along the x axis;
- 'type-3-y': 3 rectangles varying along the y axis;
- 'type-4': 4 rectangles varying along x and y axis.
Returns
-------
feature_coord : (n_features, n_rectangles, 2, 2), ndarray of list of \
tuple coord
Coordinates of the rectangles for each feature.
feature_type : (n_features,), ndarray of str
The corresponding type for each feature.
"""
cdef:
vector[vector[Rectangle]] rect
Py_ssize_t n_rectangle, n_feature
Py_ssize_t i, j
# cast the height and width to the right type
Py_ssize_t height_win = <Py_ssize_t> height
Py_ssize_t width_win = <Py_ssize_t> width
rect = _haar_like_feature_coord(width_win, height_win,
FEATURE_TYPE[feature_type])
n_feature = rect[0].size()
n_rectangle = rect.size()
# allocate the output based on the number of rectangle
output = np.empty((n_feature,), dtype=object)
for j in range(n_feature):
coord_feature = []
for i in range(n_rectangle):
coord_feature.append([(rect[i][j].top_left.row,
rect[i][j].top_left.col),
(rect[i][j].bottom_right.row,
rect[i][j].bottom_right.col)])
output[j] = coord_feature
return output, np.array([feature_type] * n_feature, dtype=object)
cdef np_real_numeric[:, ::1] _haar_like_feature(
np_real_numeric[:, ::1] int_image,
vector[vector[Rectangle]] coord,
Py_ssize_t n_rectangle, Py_ssize_t n_feature):
"""Private function releasing the GIL to compute the integral for the
different rectangle."""
cdef:
np_real_numeric[:, ::1] rect_feature = np.empty(
(n_rectangle, n_feature), dtype=int_image.base.dtype)
Py_ssize_t idx_rect, idx_feature
with nogil:
for idx_rect in range(n_rectangle):
for idx_feature in range(n_feature):
rect_feature[idx_rect, idx_feature] = integrate(
int_image,
coord[idx_rect][idx_feature].top_left.row,
coord[idx_rect][idx_feature].top_left.col,
coord[idx_rect][idx_feature].bottom_right.row,
coord[idx_rect][idx_feature].bottom_right.col)
return rect_feature
cpdef haar_like_feature_wrapper(
cnp.ndarray[np_real_numeric, ndim=2] int_image,
r, c, width, height, feature_type, feature_coord):
"""Compute the Haar-like features for a region of interest (ROI) of an
integral image.
Haar-like features have been successfully used for image classification and
object detection [1]_. It has been used for real-time face detection
algorithm proposed in [2]_.
Parameters
----------
int_image : (M, N) ndarray
Integral image for which the features need to be computed.
r : int
Row-coordinate of top left corner of the detection window.
c : int
Column-coordinate of top left corner of the detection window.
width : int
Width of the detection window.
height : int
Height of the detection window.
feature_type : str
The type of feature to consider:
- 'type-2-x': 2 rectangles varying along the x axis;
- 'type-2-y': 2 rectangles varying along the y axis;
- 'type-3-x': 3 rectangles varying along the x axis;
- 'type-3-y': 3 rectangles varying along the y axis;
- 'type-4': 4 rectangles varying along x and y axis.
Returns
-------
haar_features : (n_features,) ndarray
Resulting Haar-like features. Each value is equal to the subtraction of
sums of the positive and negative rectangles. The data type depends of
the data type of `int_image`: `int` when the data type of `int_image`
is `uint` or `int` and `float` when the data type of `int_image` is
`float`.
References
----------
.. [1] https://en.wikipedia.org/wiki/Haar-like_feature
.. [2] Oren, M., Papageorgiou, C., Sinha, P., Osuna, E., & Poggio, T.
(1997, June). Pedestrian detection using wavelet templates.
In Computer Vision and Pattern Recognition, 1997. Proceedings.,
1997 IEEE Computer Society Conference on (pp. 193-199). IEEE.
http://tinyurl.com/y6ulxfta
:DOI:`10.1109/CVPR.1997.609319`
.. [3] Viola, Paul, and Michael J. Jones. "Robust real-time face
detection." International journal of computer vision 57.2
(2004): 137-154.
https://www.merl.com/publications/docs/TR2004-043.pdf
:DOI:`10.1109/CVPR.2001.990517`
"""
cdef:
vector[vector[Rectangle]] coord
Py_ssize_t n_rectangle, n_feature
Py_ssize_t idx_rect, idx_feature
np_real_numeric[:, ::1] rect_feature
# FIXME: currently cython does not support read-only memory views.
# Those are used with joblib when using Parallel. Therefore, we use
# ndarray as input. We take a copy of this ndarray to create a memory
# view to be able to release the GIL in some later processing.
# Check the following issue to check the status of read-only memory
# views in cython:
# https://github.com/cython/cython/issues/1605 to be resolved
np_real_numeric[:, ::1] int_image_memview = int_image[
r : r + height, c : c + width].copy()
if feature_coord is None:
# compute all possible coordinates with a specific type of feature
coord = _haar_like_feature_coord(width, height,
FEATURE_TYPE[feature_type])
n_feature = coord[0].size()
n_rectangle = coord.size()
else:
# build the coordinate from the set provided
n_rectangle = N_RECTANGLE[feature_type]
n_feature = len(feature_coord)
# the vector can be directly pre-allocated since that the size is known
coord = vector[vector[Rectangle]](n_rectangle,
vector[Rectangle](n_feature))
for idx_rect in range(n_rectangle):
for idx_feature in range(n_feature):
set_rectangle_feature(
&coord[idx_rect][idx_feature],
feature_coord[idx_feature][idx_rect][0][0],
feature_coord[idx_feature][idx_rect][0][1],
feature_coord[idx_feature][idx_rect][1][0],
feature_coord[idx_feature][idx_rect][1][1])
rect_feature = _haar_like_feature(int_image_memview,
coord, n_rectangle, n_feature)
# convert the memory view to numpy array and convert it to signed array if
# necessary to avoid overflow during subtraction
rect_feature_ndarray = np.asarray(rect_feature)
data_type = rect_feature_ndarray.dtype
if 'uint' in data_type.name:
rect_feature_ndarray = rect_feature_ndarray.astype(
data_type.name.replace('u', ''))
# the rectangles with odd indices can always be subtracted to the rectangle
# with even indices
return (np.sum(rect_feature_ndarray[1::2], axis=0) -
np.sum(rect_feature_ndarray[::2], axis=0))