• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cosanlab / py-feat / 15090929758

19 Oct 2024 05:10AM UTC coverage: 54.553%. First build
15090929758

push

github

web-flow
Merge pull request #228 from cosanlab/huggingface

WIP: Huggingface Integration

702 of 1620 new or added lines in 46 files covered. (43.33%)

3409 of 6249 relevant lines covered (54.55%)

3.27 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.9
/feat/face_detectors/MTCNN/MTCNN_utils.py
1
"""
2
The codes in this file comes from the original codes at:
3
    https://github.com/timesler/facenet-pytorch/blob/master/models/mtcnn.py
4
The original paper on MTCNN is:
5
K. Zhang, Z. Zhang, Z. Li and Y. Qiao. Joint Face Detection and Alignment Using Multitask Cascaded Convolutional Networks, IEEE Signal Processing Letters, 2016
6
"""
7

8
import torch
6✔
9
import numpy as np
6✔
10
from torch.nn.functional import interpolate
6✔
11
from torchvision.ops.boxes import batched_nms
6✔
12
from feat.utils.image_operations import convert_image_to_tensor
6✔
13

14

15
def nms_numpy(boxes, scores, threshold, method):
6✔
16
    if boxes.size == 0:
×
17
        return np.empty((0, 3))
×
18

19
    x1 = boxes[:, 0].copy()
×
20
    y1 = boxes[:, 1].copy()
×
21
    x2 = boxes[:, 2].copy()
×
22
    y2 = boxes[:, 3].copy()
×
23
    s = scores
×
24
    area = (x2 - x1 + 1) * (y2 - y1 + 1)
×
25

26
    I = np.argsort(s)
×
27
    pick = np.zeros_like(s, dtype=np.int16)
×
28
    counter = 0
×
29
    while I.size > 0:
×
30
        i = I[-1]
×
31
        pick[counter] = i
×
32
        counter += 1
×
33
        idx = I[0:-1]
×
34

35
        xx1 = np.maximum(x1[i], x1[idx]).copy()
×
36
        yy1 = np.maximum(y1[i], y1[idx]).copy()
×
37
        xx2 = np.minimum(x2[i], x2[idx]).copy()
×
38
        yy2 = np.minimum(y2[i], y2[idx]).copy()
×
39

40
        w = np.maximum(0.0, xx2 - xx1 + 1).copy()
×
41
        h = np.maximum(0.0, yy2 - yy1 + 1).copy()
×
42

43
        inter = w * h
×
44
        if method == "Min":
×
45
            o = inter / np.minimum(area[i], area[idx])
×
46
        else:
47
            o = inter / (area[i] + area[idx] - inter)
×
48
        I = I[np.where(o <= threshold)]
×
49

50
    pick = pick[:counter].copy()
×
51
    return pick
×
52

53

54
def batched_nms_numpy(boxes, scores, idxs, threshold, method):
6✔
55
    device = boxes.device
×
56
    if boxes.numel() == 0:
×
57
        return torch.empty((0,), dtype=torch.int64, device=device)
×
58
    # strategy: in order to perform NMS independently per class.
59
    # we add an offset to all the boxes. The offset is dependent
60
    # only on the class idx, and is large enough so that boxes
61
    # from different classes do not overlap
62
    max_coordinate = boxes.max()
×
63
    offsets = idxs.to(boxes) * (max_coordinate + 1)
×
64
    boxes_for_nms = boxes + offsets[:, None]
×
65
    boxes_for_nms = boxes_for_nms.cpu().numpy()
×
66
    scores = scores.cpu().numpy()
×
67
    keep = nms_numpy(boxes_for_nms, scores, threshold, method)
×
68
    return torch.as_tensor(keep, dtype=torch.long, device=device)
×
69

70

71
def bbreg(boundingbox, reg):
6✔
72
    if reg.shape[1] == 1:
×
73
        reg = torch.reshape(reg, (reg.shape[2], reg.shape[3]))
×
74

75
    w = boundingbox[:, 2] - boundingbox[:, 0] + 1
×
76
    h = boundingbox[:, 3] - boundingbox[:, 1] + 1
×
77
    b1 = boundingbox[:, 0] + reg[:, 0] * w
×
78
    b2 = boundingbox[:, 1] + reg[:, 1] * h
×
79
    b3 = boundingbox[:, 2] + reg[:, 2] * w
×
80
    b4 = boundingbox[:, 3] + reg[:, 3] * h
×
81
    boundingbox[:, :4] = torch.stack([b1, b2, b3, b4]).permute(1, 0)
×
82

83
    return boundingbox
×
84

85

86
def fixed_batch_process(im_data, model):
6✔
87
    batch_size = 512
×
88
    out = []
×
89
    for i in range(0, len(im_data), batch_size):
×
90
        batch = im_data[i : (i + batch_size)]
×
91
        out.append(model(batch))
×
92

93
    return tuple(torch.cat(v, dim=0) for v in zip(*out))
×
94

95

96
def pad(boxes, w, h):
6✔
97
    boxes = boxes.trunc().int().cpu().numpy()
×
98
    x = boxes[:, 0]
×
99
    y = boxes[:, 1]
×
100
    ex = boxes[:, 2]
×
101
    ey = boxes[:, 3]
×
102

103
    x[x < 1] = 1
×
104
    y[y < 1] = 1
×
105
    ex[ex > w] = w
×
106
    ey[ey > h] = h
×
107

108
    return y, ey, x, ex
×
109

110

111
def rerec(bboxA):
6✔
112
    h = bboxA[:, 3] - bboxA[:, 1]
×
113
    w = bboxA[:, 2] - bboxA[:, 0]
×
114

115
    l = torch.max(w, h)
×
116
    bboxA[:, 0] = bboxA[:, 0] + w * 0.5 - l * 0.5
×
117
    bboxA[:, 1] = bboxA[:, 1] + h * 0.5 - l * 0.5
×
118
    bboxA[:, 2:4] = bboxA[:, :2] + l.repeat(2, 1).permute(1, 0)
×
119

120
    return bboxA
×
121

122

123
def generateBoundingBox(reg, probs, scale, thresh):
6✔
124
    stride = 2
×
125
    cellsize = 12
×
126

127
    reg = reg.permute(1, 0, 2, 3)
×
128

129
    mask = probs >= thresh
×
130
    mask_inds = mask.nonzero()
×
131
    image_inds = mask_inds[:, 0]
×
132
    score = probs[mask]
×
133
    reg = reg[:, mask].permute(1, 0)
×
134
    bb = mask_inds[:, 1:].type(reg.dtype).flip(1)
×
135
    q1 = ((stride * bb + 1) / scale).floor()
×
136
    q2 = ((stride * bb + cellsize - 1 + 1) / scale).floor()
×
137
    boundingbox = torch.cat([q1, q2, score.unsqueeze(1), reg], dim=1)
×
138
    return boundingbox, image_inds
×
139

140

141
def imresample(img, sz):
6✔
142
    im_data = interpolate(img, size=sz, mode="area")
×
143
    return im_data
×
144

145

146
def detect_face(imgs, minsize, pnet, rnet, onet, threshold, factor, device):
6✔
147
    imgs = convert_image_to_tensor(imgs)
×
148

149
    # if isinstance(imgs, (np.ndarray, torch.Tensor)):
150
    #     if isinstance(imgs, np.ndarray):
151
    #         imgs = torch.as_tensor(imgs.copy(), device=device)
152

153
    #     if isinstance(imgs, torch.Tensor):
154
    #         imgs = torch.as_tensor(imgs, device=device)
155

156
    #     if len(imgs.shape) == 3:
157
    #         imgs = imgs.unsqueeze(0)
158
    # else:
159
    #     if not isinstance(imgs, (list, tuple)):
160
    #         imgs = [imgs]
161
    #     if any(img.size != imgs[0].size for img in imgs):
162
    #         raise Exception(
163
    #             "MTCNN batch processing only compatible with equal-dimension images."
164
    #         )
165
    #     imgs = np.stack([np.uint8(img) for img in imgs])
166
    #     imgs = torch.as_tensor(imgs.copy(), device=device)
167
    # imgs = imgs.permute(0, 3, 1, 2).type(model_dtype)
168

NEW
169
    _ = next(pnet.parameters()).dtype
×
170

171
    batch_size = len(imgs)
×
172
    h, w = imgs.shape[2:4]
×
173
    m = 12.0 / minsize
×
174
    minl = min(h, w)
×
175
    minl = minl * m
×
176

177
    # Create scale pyramid
178
    scale_i = m
×
179
    scales = []
×
180
    while minl >= 12:
×
181
        scales.append(scale_i)
×
182
        scale_i = scale_i * factor
×
183
        minl = minl * factor
×
184

185
    # First stage
186
    boxes = []
×
187
    image_inds = []
×
188
    scale_picks = []
×
189

190
    # all_i = 0
191
    offset = 0
×
192
    for scale in scales:
×
193
        im_data = imresample(imgs, (int(h * scale + 1), int(w * scale + 1)))
×
194
        im_data = (im_data - 127.5) * 0.0078125
×
195
        reg, probs = pnet(im_data)
×
196

197
        boxes_scale, image_inds_scale = generateBoundingBox(
×
198
            reg, probs[:, 1], scale, threshold[0]
199
        )
200
        boxes.append(boxes_scale)
×
201
        image_inds.append(image_inds_scale)
×
202

203
        pick = batched_nms(boxes_scale[:, :4], boxes_scale[:, 4], image_inds_scale, 0.5)
×
204
        scale_picks.append(pick + offset)
×
205
        offset += boxes_scale.shape[0]
×
206

207
    boxes = torch.cat(boxes, dim=0)
×
208
    image_inds = torch.cat(image_inds, dim=0)
×
209

210
    scale_picks = torch.cat(scale_picks, dim=0)
×
211

212
    # NMS within each scale + image
213
    boxes, image_inds = boxes[scale_picks], image_inds[scale_picks]
×
214

215
    # NMS within each image
216
    pick = batched_nms(boxes[:, :4], boxes[:, 4], image_inds, 0.7)
×
217
    boxes, image_inds = boxes[pick], image_inds[pick]
×
218

219
    regw = boxes[:, 2] - boxes[:, 0]
×
220
    regh = boxes[:, 3] - boxes[:, 1]
×
221
    qq1 = boxes[:, 0] + boxes[:, 5] * regw
×
222
    qq2 = boxes[:, 1] + boxes[:, 6] * regh
×
223
    qq3 = boxes[:, 2] + boxes[:, 7] * regw
×
224
    qq4 = boxes[:, 3] + boxes[:, 8] * regh
×
225
    boxes = torch.stack([qq1, qq2, qq3, qq4, boxes[:, 4]]).permute(1, 0)
×
226
    boxes = rerec(boxes)
×
227
    y, ey, x, ex = pad(boxes, w, h)
×
228

229
    # Second stage
230
    if len(boxes) > 0:
×
231
        im_data = []
×
232
        for k in range(len(y)):
×
233
            if ey[k] > (y[k] - 1) and ex[k] > (x[k] - 1):
×
234
                img_k = imgs[
×
235
                    image_inds[k], :, (y[k] - 1) : ey[k], (x[k] - 1) : ex[k]
236
                ].unsqueeze(0)
237
                im_data.append(imresample(img_k, (24, 24)))
×
238
        im_data = torch.cat(im_data, dim=0)
×
239
        im_data = (im_data - 127.5) * 0.0078125
×
240

241
        # This is equivalent to out = rnet(im_data) to avoid GPU out of memory.
242
        out = fixed_batch_process(im_data, rnet)
×
243

244
        out0 = out[0].permute(1, 0)
×
245
        out1 = out[1].permute(1, 0)
×
246
        score = out1[1, :]
×
247
        ipass = score > threshold[1]
×
248
        boxes = torch.cat((boxes[ipass, :4], score[ipass].unsqueeze(1)), dim=1)
×
249
        image_inds = image_inds[ipass]
×
250
        mv = out0[:, ipass].permute(1, 0)
×
251

252
        # NMS within each image
253
        pick = batched_nms(boxes[:, :4], boxes[:, 4], image_inds, 0.7)
×
254
        boxes, image_inds, mv = boxes[pick], image_inds[pick], mv[pick]
×
255
        boxes = bbreg(boxes, mv)
×
256
        boxes = rerec(boxes)
×
257

258
    # Third stage
259
    points = torch.zeros(0, 5, 2, device=device)
×
260
    if len(boxes) > 0:
×
261
        y, ey, x, ex = pad(boxes, w, h)
×
262
        im_data = []
×
263
        for k in range(len(y)):
×
264
            if ey[k] > (y[k] - 1) and ex[k] > (x[k] - 1):
×
265
                img_k = imgs[
×
266
                    image_inds[k], :, (y[k] - 1) : ey[k], (x[k] - 1) : ex[k]
267
                ].unsqueeze(0)
268
                im_data.append(imresample(img_k, (48, 48)))
×
269
        im_data = torch.cat(im_data, dim=0)
×
270
        im_data = (im_data - 127.5) * 0.0078125
×
271

272
        # This is equivalent to out = onet(im_data) to avoid GPU out of memory.
273
        out = fixed_batch_process(im_data, onet)
×
274

275
        out0 = out[0].permute(1, 0)
×
276
        out1 = out[1].permute(1, 0)
×
277
        out2 = out[2].permute(1, 0)
×
278
        score = out2[1, :]
×
279
        points = out1
×
280
        ipass = score > threshold[2]
×
281
        points = points[:, ipass]
×
282
        boxes = torch.cat((boxes[ipass, :4], score[ipass].unsqueeze(1)), dim=1)
×
283
        image_inds = image_inds[ipass]
×
284
        mv = out0[:, ipass].permute(1, 0)
×
285

286
        w_i = boxes[:, 2] - boxes[:, 0] + 1
×
287
        h_i = boxes[:, 3] - boxes[:, 1] + 1
×
288
        points_x = w_i.repeat(5, 1) * points[:5, :] + boxes[:, 0].repeat(5, 1) - 1
×
289
        points_y = h_i.repeat(5, 1) * points[5:10, :] + boxes[:, 1].repeat(5, 1) - 1
×
290
        points = torch.stack((points_x, points_y)).permute(2, 1, 0)
×
291
        boxes = bbreg(boxes, mv)
×
292

293
        # NMS within each image using "Min" strategy
294
        # pick = batched_nms(boxes[:, :4], boxes[:, 4], image_inds, 0.7)
295
        pick = batched_nms_numpy(boxes[:, :4], boxes[:, 4], image_inds, 0.7, "Min")
×
296
        boxes, image_inds, points = boxes[pick], image_inds[pick], points[pick]
×
297

298
    boxes = boxes.cpu().numpy()
×
299
    points = points.cpu().numpy()
×
300

301
    image_inds = image_inds.cpu()
×
302

303
    batch_boxes = []
×
304
    batch_points = []
×
305
    for b_i in range(batch_size):
×
306
        b_i_inds = np.where(image_inds == b_i)
×
307
        batch_boxes.append(boxes[b_i_inds].copy())
×
308
        batch_points.append(points[b_i_inds].copy())
×
309

310
    batch_boxes, batch_points = np.array(batch_boxes), np.array(batch_points)
×
311

312
    return batch_boxes, batch_points
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc