├── .gitignore ├── mtcnn_cv2 ├── onet.onnx ├── pnet.onnx ├── rnet.onnx ├── __init__.py └── mtcnn_opencv.py ├── setup.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | mtcnn_opencv.egg-info/ 2 | .DS_Store 3 | .idea/ 4 | -------------------------------------------------------------------------------- /mtcnn_cv2/onet.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linxiaohui/mtcnn-opencv/HEAD/mtcnn_cv2/onet.onnx -------------------------------------------------------------------------------- /mtcnn_cv2/pnet.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linxiaohui/mtcnn-opencv/HEAD/mtcnn_cv2/pnet.onnx -------------------------------------------------------------------------------- /mtcnn_cv2/rnet.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linxiaohui/mtcnn-opencv/HEAD/mtcnn_cv2/rnet.onnx -------------------------------------------------------------------------------- /mtcnn_cv2/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from .mtcnn_opencv import MTCNN 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="mtcnn-opencv", 8 | version="1.0.2", 9 | author="linxiaohui", 10 | author_email="llinxiaohui@126.com", 11 | description="MTCNN face detection using OpenCV", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/linxiaohui/mtcnn-opencv", 15 | packages=setuptools.find_packages(), 16 | package_data={ 17 | 'mtcnn_cv2': ['pnet.onnx', 'rnet.onnx', 'onet.onnx'] 18 | }, 19 | classifiers=[ 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.6", 22 | "Programming Language :: Python :: 3.8", 23 | "License :: OSI Approved :: MIT License", 24 | "Operating System :: POSIX :: Linux", 25 | "Operating System :: Microsoft :: Windows", 26 | "Operating System :: MacOS :: MacOS X", 27 | ], 28 | python_requires='>=3.6', 29 | ) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 linxiaohui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MTCNN-OpenCV 2 | ============ 3 | MTCNN Face Detector using OpenCV, no reqiurement for tensorflow/pytorch. 4 | 5 | # INSTALLATION 6 | * `pip3 install opencv-python` or `pip3 install opencv-python-headless` 7 | * `pip3 install mtcnn-opencv` 8 | 9 | # USAGE 10 | ```python 11 | import cv2 12 | from mtcnn_cv2 import MTCNN 13 | 14 | detector = MTCNN() 15 | test_pic = "t.jpg" 16 | 17 | image = cv2.cvtColor(cv2.imread(test_pic), cv2.COLOR_BGR2RGB) 18 | result = detector.detect_faces(image) 19 | 20 | # Result is an array with all the bounding boxes detected. Show the first. 21 | print(result) 22 | 23 | if len(result) > 0: 24 | keypoints = result[0]['keypoints'] 25 | 26 | cv2.rectangle(image, 27 | (bounding_box[0], bounding_box[1]), 28 | (bounding_box[0]+bounding_box[2], bounding_box[1] + bounding_box[3]), 29 | (0,155,255), 30 | 2) 31 | 32 | cv2.circle(image,(keypoints['left_eye']), 2, (0,155,255), 2) 33 | cv2.circle(image,(keypoints['right_eye']), 2, (0,155,255), 2) 34 | cv2.circle(image,(keypoints['nose']), 2, (0,155,255), 2) 35 | cv2.circle(image,(keypoints['mouth_left']), 2, (0,155,255), 2) 36 | cv2.circle(image,(keypoints['mouth_right']), 2, (0,155,255), 2) 37 | 38 | cv2.imwrite("result.jpg", cv2.cvtColor(image, cv2.COLOR_RGB2BGR)) 39 | 40 | # 生成标记了的人脸的图片 41 | with open(test_pic, "rb") as fp: 42 | marked_data = detector.mark_faces(fp.read()) 43 | with open("marked.jpg", "wb") as fp: 44 | fp.write(marked_data) 45 | ``` 46 | -------------------------------------------------------------------------------- /mtcnn_cv2/mtcnn_opencv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | 根据https://github.com/ipazc/mtcnn对FaceNet中align的重新实现 5 | 转换模型,使用OpenCV进行推理 6 | 不依赖Tensorlow/keras 7 | """ 8 | import os 9 | import imghdr 10 | 11 | import cv2 12 | import numpy as np 13 | 14 | class StageStatus(object): 15 | """ 16 | Keeps status between MTCNN stages 17 | """ 18 | def __init__(self, pad_result: tuple = None, width=0, height=0): 19 | self.width = width 20 | self.height = height 21 | self.dy = self.edy = self.dx = self.edx = self.y = self.ey = self.x = self.ex = self.tmpw = self.tmph = [] 22 | if pad_result is not None: 23 | self.update(pad_result) 24 | 25 | def update(self, pad_result: tuple): 26 | s = self 27 | s.dy, s.edy, s.dx, s.edx, s.y, s.ey, s.x, s.ex, s.tmpw, s.tmph = pad_result 28 | 29 | 30 | class MTCNN(object): 31 | """ 32 | Allows to perform MTCNN Detection -> 33 | a) Detection of faces (with the confidence probability) 34 | b) Detection of keypoints (left eye, right eye, nose, mouth_left, mouth_right) 35 | """ 36 | def __init__(self, min_face_size: int = 20, steps_threshold: list = None, 37 | scale_factor: float = 0.709): 38 | """ 39 | Initializes the MTCNN. 40 | :param min_face_size: minimum size of the face to detect 41 | :param steps_threshold: step's thresholds values 42 | :param scale_factor: scale factor 43 | """ 44 | if steps_threshold is None: 45 | steps_threshold = [0.6, 0.7, 0.7] 46 | 47 | self._min_face_size = min_face_size 48 | self._steps_threshold = steps_threshold 49 | self._scale_factor = scale_factor 50 | 51 | pnet_path = os.path.join(os.path.dirname(__file__), "pnet.onnx") 52 | rnet_path = os.path.join(os.path.dirname(__file__), "rnet.onnx") 53 | onet_path = os.path.join(os.path.dirname(__file__), "onet.onnx") 54 | self._pnet = cv2.dnn.readNetFromONNX(pnet_path) 55 | self._rnet = cv2.dnn.readNetFromONNX(rnet_path) 56 | self._onet = cv2.dnn.readNetFromONNX(onet_path) 57 | 58 | @property 59 | def min_face_size(self): 60 | return self._min_face_size 61 | 62 | @min_face_size.setter 63 | def min_face_size(self, mfc=20): 64 | try: 65 | self._min_face_size = int(mfc) 66 | except ValueError: 67 | self._min_face_size = 20 68 | 69 | def __compute_scale_pyramid(self, m, min_layer): 70 | scales = [] 71 | factor_count = 0 72 | 73 | while min_layer >= 12: 74 | scales += [m * np.power(self._scale_factor, factor_count)] 75 | min_layer = min_layer * self._scale_factor 76 | factor_count += 1 77 | 78 | return scales 79 | 80 | @staticmethod 81 | def __scale_image(image, scale: float): 82 | """ 83 | Scales the image to a given scale. 84 | :param image: 85 | :param scale: 86 | :return: 87 | """ 88 | height, width, _ = image.shape 89 | 90 | width_scaled = int(np.ceil(width * scale)) 91 | height_scaled = int(np.ceil(height * scale)) 92 | 93 | im_data = cv2.resize(image, (width_scaled, height_scaled), interpolation=cv2.INTER_AREA) 94 | 95 | # Normalize the image's pixels 96 | im_data_normalized = (im_data - 127.5) * 0.0078125 97 | 98 | return im_data_normalized 99 | 100 | @staticmethod 101 | def __generate_bounding_box(imap, reg, scale, t): 102 | 103 | # use heatmap to generate bounding boxes 104 | stride = 2 105 | cellsize = 12 106 | 107 | imap = np.transpose(imap) 108 | dx1 = np.transpose(reg[:, :, 0]) 109 | dy1 = np.transpose(reg[:, :, 1]) 110 | dx2 = np.transpose(reg[:, :, 2]) 111 | dy2 = np.transpose(reg[:, :, 3]) 112 | 113 | y, x = np.where(imap >= t) 114 | 115 | if y.shape[0] == 1: 116 | dx1 = np.flipud(dx1) 117 | dy1 = np.flipud(dy1) 118 | dx2 = np.flipud(dx2) 119 | dy2 = np.flipud(dy2) 120 | 121 | score = imap[(y, x)] 122 | reg = np.transpose(np.vstack([dx1[(y, x)], dy1[(y, x)], dx2[(y, x)], dy2[(y, x)]])) 123 | 124 | if reg.size == 0: 125 | reg = np.empty(shape=(0, 3)) 126 | 127 | bb = np.transpose(np.vstack([y, x])) 128 | 129 | q1 = np.fix((stride * bb + 1) / scale) 130 | q2 = np.fix((stride * bb + cellsize) / scale) 131 | boundingbox = np.hstack([q1, q2, np.expand_dims(score, 1), reg]) 132 | 133 | return boundingbox, reg 134 | 135 | @staticmethod 136 | def __nms(boxes, threshold, method): 137 | """ 138 | Non Maximum Suppression. 139 | :param boxes: np array with bounding boxes. 140 | :param threshold: 141 | :param method: NMS method to apply. Available values ('Min', 'Union') 142 | :return: 143 | """ 144 | if boxes.size == 0: 145 | return np.empty((0, 3)) 146 | 147 | x1 = boxes[:, 0] 148 | y1 = boxes[:, 1] 149 | x2 = boxes[:, 2] 150 | y2 = boxes[:, 3] 151 | s = boxes[:, 4] 152 | 153 | area = (x2 - x1 + 1) * (y2 - y1 + 1) 154 | sorted_s = np.argsort(s) 155 | 156 | pick = np.zeros_like(s, dtype=np.int16) 157 | counter = 0 158 | while sorted_s.size > 0: 159 | i = sorted_s[-1] 160 | pick[counter] = i 161 | counter += 1 162 | idx = sorted_s[0:-1] 163 | 164 | xx1 = np.maximum(x1[i], x1[idx]) 165 | yy1 = np.maximum(y1[i], y1[idx]) 166 | xx2 = np.minimum(x2[i], x2[idx]) 167 | yy2 = np.minimum(y2[i], y2[idx]) 168 | 169 | w = np.maximum(0.0, xx2 - xx1 + 1) 170 | h = np.maximum(0.0, yy2 - yy1 + 1) 171 | 172 | inter = w * h 173 | 174 | if method is 'Min': 175 | o = inter / np.minimum(area[i], area[idx]) 176 | else: 177 | o = inter / (area[i] + area[idx] - inter) 178 | 179 | sorted_s = sorted_s[np.where(o <= threshold)] 180 | 181 | pick = pick[0:counter] 182 | 183 | return pick 184 | 185 | @staticmethod 186 | def __pad(total_boxes, w, h): 187 | # compute the padding coordinates (pad the bounding boxes to square) 188 | tmpw = (total_boxes[:, 2] - total_boxes[:, 0] + 1).astype(np.int32) 189 | tmph = (total_boxes[:, 3] - total_boxes[:, 1] + 1).astype(np.int32) 190 | numbox = total_boxes.shape[0] 191 | 192 | dx = np.ones(numbox, dtype=np.int32) 193 | dy = np.ones(numbox, dtype=np.int32) 194 | edx = tmpw.copy().astype(np.int32) 195 | edy = tmph.copy().astype(np.int32) 196 | 197 | x = total_boxes[:, 0].copy().astype(np.int32) 198 | y = total_boxes[:, 1].copy().astype(np.int32) 199 | ex = total_boxes[:, 2].copy().astype(np.int32) 200 | ey = total_boxes[:, 3].copy().astype(np.int32) 201 | 202 | tmp = np.where(ex > w) 203 | edx.flat[tmp] = np.expand_dims(-ex[tmp] + w + tmpw[tmp], 1) 204 | ex[tmp] = w 205 | 206 | tmp = np.where(ey > h) 207 | edy.flat[tmp] = np.expand_dims(-ey[tmp] + h + tmph[tmp], 1) 208 | ey[tmp] = h 209 | 210 | tmp = np.where(x < 1) 211 | dx.flat[tmp] = np.expand_dims(2 - x[tmp], 1) 212 | x[tmp] = 1 213 | 214 | tmp = np.where(y < 1) 215 | dy.flat[tmp] = np.expand_dims(2 - y[tmp], 1) 216 | y[tmp] = 1 217 | 218 | return dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph 219 | 220 | @staticmethod 221 | def __rerec(bbox): 222 | # convert bbox to square 223 | height = bbox[:, 3] - bbox[:, 1] 224 | width = bbox[:, 2] - bbox[:, 0] 225 | max_side_length = np.maximum(width, height) 226 | bbox[:, 0] = bbox[:, 0] + width * 0.5 - max_side_length * 0.5 227 | bbox[:, 1] = bbox[:, 1] + height * 0.5 - max_side_length * 0.5 228 | bbox[:, 2:4] = bbox[:, 0:2] + np.transpose(np.tile(max_side_length, (2, 1))) 229 | return bbox 230 | 231 | @staticmethod 232 | def __bbreg(boundingbox, reg): 233 | # calibrate bounding boxes 234 | if reg.shape[1] == 1: 235 | reg = np.reshape(reg, (reg.shape[2], reg.shape[3])) 236 | 237 | w = boundingbox[:, 2] - boundingbox[:, 0] + 1 238 | h = boundingbox[:, 3] - boundingbox[:, 1] + 1 239 | b1 = boundingbox[:, 0] + reg[:, 0] * w 240 | b2 = boundingbox[:, 1] + reg[:, 1] * h 241 | b3 = boundingbox[:, 2] + reg[:, 2] * w 242 | b4 = boundingbox[:, 3] + reg[:, 3] * h 243 | boundingbox[:, 0:4] = np.transpose(np.vstack([b1, b2, b3, b4])) 244 | return boundingbox 245 | 246 | def detect_faces(self, img) -> list: 247 | """ 248 | Detects bounding boxes from the specified image. 249 | :param img: image to process 250 | :return: list containing all the bounding boxes detected with their keypoints. 251 | """ 252 | if img is None or not hasattr(img, "shape"): 253 | raise Exception("Image not valid.") 254 | 255 | height, width, _ = img.shape 256 | stage_status = StageStatus(width=width, height=height) 257 | 258 | m = 12 / self._min_face_size 259 | min_layer = np.amin([height, width]) * m 260 | 261 | scales = self.__compute_scale_pyramid(m, min_layer) 262 | 263 | stages = [self.__stage1, self.__stage2, self.__stage3] 264 | result = [scales, stage_status] 265 | 266 | # We pipe here each of the stages 267 | for stage in stages: 268 | result = stage(img, result[0], result[1]) 269 | 270 | [total_boxes, points] = result 271 | 272 | bounding_boxes = [] 273 | 274 | for bounding_box, keypoints in zip(total_boxes, points.T): 275 | x = max(0, int(bounding_box[0])) 276 | y = max(0, int(bounding_box[1])) 277 | width = int(bounding_box[2] - x) 278 | height = int(bounding_box[3] - y) 279 | bounding_boxes.append({ 280 | 'box': [x, y, width, height], 281 | 'confidence': bounding_box[-1], 282 | 'keypoints': { 283 | 'left_eye': (int(keypoints[0]), int(keypoints[5])), 284 | 'right_eye': (int(keypoints[1]), int(keypoints[6])), 285 | 'nose': (int(keypoints[2]), int(keypoints[7])), 286 | 'mouth_left': (int(keypoints[3]), int(keypoints[8])), 287 | 'mouth_right': (int(keypoints[4]), int(keypoints[9])), 288 | } 289 | }) 290 | 291 | return bounding_boxes 292 | 293 | def mark_faces(self, image_data) -> bytes: 294 | """ 295 | Mark all the faces 296 | """ 297 | ext = imghdr.what(None, image_data) 298 | im = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR) 299 | image = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) 300 | results = self.detect_faces(image) 301 | for result in results: 302 | bounding_box = result['box'] 303 | keypoints = result['keypoints'] 304 | cv2.rectangle(image, 305 | (bounding_box[0], bounding_box[1]), 306 | (bounding_box[0] + bounding_box[2], bounding_box[1] + bounding_box[3]), 307 | (0, 155, 255), 308 | 2) 309 | cv2.circle(image, (keypoints['left_eye']), 2, (0, 155, 255), 2) 310 | cv2.circle(image, (keypoints['right_eye']), 2, (0, 155, 255), 2) 311 | cv2.circle(image, (keypoints['nose']), 2, (0, 155, 255), 2) 312 | cv2.circle(image, (keypoints['mouth_left']), 2, (0, 155, 255), 2) 313 | cv2.circle(image, (keypoints['mouth_right']), 2, (0, 155, 255), 2) 314 | image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) 315 | is_success, im_buf_arr = cv2.imencode("." + ext, image) 316 | return im_buf_arr 317 | 318 | def __stage1(self, image, scales: list, stage_status: StageStatus): 319 | """ 320 | First stage of the MTCNN. 321 | :param image: 322 | :param scales: 323 | :param stage_status: 324 | :return: 325 | """ 326 | total_boxes = np.empty((0, 9)) 327 | status = stage_status 328 | 329 | for scale in scales: 330 | scaled_image = self.__scale_image(image, scale) 331 | 332 | img_x = np.expand_dims(scaled_image, 0) 333 | img_y = np.transpose(img_x, (0, 2, 1, 3)) 334 | 335 | self._pnet.setInput(img_y) 336 | out = self._pnet.forward(['conv2d_4', 'softmax']) 337 | 338 | out0 = np.transpose(out[0], (0, 2, 1, 3)) 339 | out1 = np.transpose(out[1], (0, 2, 1, 3)) 340 | 341 | boxes, _ = self.__generate_bounding_box(out1[0, :, :, 1].copy(), 342 | out0[0, :, :, :].copy(), scale, self._steps_threshold[0]) 343 | 344 | # inter-scale nms 345 | pick = self.__nms(boxes.copy(), 0.5, 'Union') 346 | if boxes.size > 0 and pick.size > 0: 347 | boxes = boxes[pick, :] 348 | total_boxes = np.append(total_boxes, boxes, axis=0) 349 | 350 | numboxes = total_boxes.shape[0] 351 | 352 | if numboxes > 0: 353 | pick = self.__nms(total_boxes.copy(), 0.7, 'Union') 354 | total_boxes = total_boxes[pick, :] 355 | 356 | regw = total_boxes[:, 2] - total_boxes[:, 0] 357 | regh = total_boxes[:, 3] - total_boxes[:, 1] 358 | 359 | qq1 = total_boxes[:, 0] + total_boxes[:, 5] * regw 360 | qq2 = total_boxes[:, 1] + total_boxes[:, 6] * regh 361 | qq3 = total_boxes[:, 2] + total_boxes[:, 7] * regw 362 | qq4 = total_boxes[:, 3] + total_boxes[:, 8] * regh 363 | 364 | total_boxes = np.transpose(np.vstack([qq1, qq2, qq3, qq4, total_boxes[:, 4]])) 365 | total_boxes = self.__rerec(total_boxes.copy()) 366 | 367 | total_boxes[:, 0:4] = np.fix(total_boxes[:, 0:4]).astype(np.int32) 368 | status = StageStatus(self.__pad(total_boxes.copy(), stage_status.width, stage_status.height), 369 | width=stage_status.width, height=stage_status.height) 370 | 371 | return total_boxes, status 372 | 373 | def __stage2(self, img, total_boxes, stage_status: StageStatus): 374 | """ 375 | Second stage of the MTCNN. 376 | :param img: 377 | :param total_boxes: 378 | :param stage_status: 379 | :return: 380 | """ 381 | 382 | num_boxes = total_boxes.shape[0] 383 | if num_boxes == 0: 384 | return total_boxes, stage_status 385 | 386 | # second stage 387 | tempimg = np.zeros(shape=(24, 24, 3, num_boxes)) 388 | 389 | for k in range(0, num_boxes): 390 | tmp = np.zeros((int(stage_status.tmph[k]), int(stage_status.tmpw[k]), 3)) 391 | 392 | tmp[stage_status.dy[k] - 1:stage_status.edy[k], stage_status.dx[k] - 1:stage_status.edx[k], :] = \ 393 | img[stage_status.y[k] - 1:stage_status.ey[k], stage_status.x[k] - 1:stage_status.ex[k], :] 394 | 395 | if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0: 396 | tempimg[:, :, :, k] = cv2.resize(tmp, (24, 24), interpolation=cv2.INTER_AREA) 397 | 398 | else: 399 | return np.empty(shape=(0,)), stage_status 400 | 401 | tempimg = (tempimg - 127.5) * 0.0078125 402 | tempimg1 = np.transpose(tempimg, (3, 1, 0, 2)) 403 | 404 | self._rnet.setInput(tempimg1) 405 | out = self._rnet.forward(['dense_2', 'softmax_1']) 406 | 407 | out0 = np.transpose(out[0]) 408 | out1 = np.transpose(out[1]) 409 | 410 | score = out1[1, :] 411 | 412 | ipass = np.where(score > self._steps_threshold[1]) 413 | 414 | total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), np.expand_dims(score[ipass].copy(), 1)]) 415 | 416 | mv = out0[:, ipass[0]] 417 | 418 | if total_boxes.shape[0] > 0: 419 | pick = self.__nms(total_boxes, 0.7, 'Union') 420 | total_boxes = total_boxes[pick, :] 421 | total_boxes = self.__bbreg(total_boxes.copy(), np.transpose(mv[:, pick])) 422 | total_boxes = self.__rerec(total_boxes.copy()) 423 | 424 | return total_boxes, stage_status 425 | 426 | def __stage3(self, img, total_boxes, stage_status: StageStatus): 427 | """ 428 | Third stage of the MTCNN. 429 | :param img: 430 | :param total_boxes: 431 | :param stage_status: 432 | :return: 433 | """ 434 | num_boxes = total_boxes.shape[0] 435 | if num_boxes == 0: 436 | return total_boxes, np.empty(shape=(0,)) 437 | 438 | total_boxes = np.fix(total_boxes).astype(np.int32) 439 | 440 | status = StageStatus(self.__pad(total_boxes.copy(), stage_status.width, stage_status.height), 441 | width=stage_status.width, height=stage_status.height) 442 | 443 | tempimg = np.zeros((48, 48, 3, num_boxes)) 444 | 445 | for k in range(0, num_boxes): 446 | 447 | tmp = np.zeros((int(status.tmph[k]), int(status.tmpw[k]), 3)) 448 | 449 | tmp[status.dy[k] - 1:status.edy[k], status.dx[k] - 1:status.edx[k], :] = \ 450 | img[status.y[k] - 1:status.ey[k], status.x[k] - 1:status.ex[k], :] 451 | 452 | if tmp.shape[0] > 0 and tmp.shape[1] > 0 or tmp.shape[0] == 0 and tmp.shape[1] == 0: 453 | tempimg[:, :, :, k] = cv2.resize(tmp, (48, 48), interpolation=cv2.INTER_AREA) 454 | else: 455 | return np.empty(shape=(0,)), np.empty(shape=(0,)) 456 | 457 | tempimg = (tempimg - 127.5) * 0.0078125 458 | tempimg1 = np.transpose(tempimg, (3, 1, 0, 2)) 459 | 460 | self._onet.setInput(tempimg1) 461 | out = self._onet.forward(['dense_5', 'dense_6', 'softmax_2']) 462 | out0 = np.transpose(out[0]) 463 | out1 = np.transpose(out[1]) 464 | out2 = np.transpose(out[2]) 465 | 466 | score = out2[1, :] 467 | 468 | points = out1 469 | 470 | ipass = np.where(score > self._steps_threshold[2]) 471 | 472 | points = points[:, ipass[0]] 473 | 474 | total_boxes = np.hstack([total_boxes[ipass[0], 0:4].copy(), np.expand_dims(score[ipass].copy(), 1)]) 475 | 476 | mv = out0[:, ipass[0]] 477 | 478 | w = total_boxes[:, 2] - total_boxes[:, 0] + 1 479 | h = total_boxes[:, 3] - total_boxes[:, 1] + 1 480 | 481 | points[0:5, :] = np.tile(w, (5, 1)) * points[0:5, :] + np.tile(total_boxes[:, 0], (5, 1)) - 1 482 | points[5:10, :] = np.tile(h, (5, 1)) * points[5:10, :] + np.tile(total_boxes[:, 1], (5, 1)) - 1 483 | 484 | if total_boxes.shape[0] > 0: 485 | total_boxes = self.__bbreg(total_boxes.copy(), np.transpose(mv)) 486 | pick = self.__nms(total_boxes.copy(), 0.7, 'Min') 487 | total_boxes = total_boxes[pick, :] 488 | points = points[:, pick] 489 | 490 | return total_boxes, points 491 | --------------------------------------------------------------------------------