MTCNNテストソース

8788 ワード

コード実装(MXNet):https://github.com/pangyupo/mxnet_mtcnn_face_detection
コード本体からmain.py開始
detector = MtcnnDetector(model_folder='model', ctx=mx.cpu(0), num_worker = 4 , accurate_landmark = False)  #  


    img = cv2.imread('test.jpg')

    # run detector
    results = detector.detect_face(img)

    if results is not None:

        total_boxes = results[0]
        points = results[1]

        # extract aligned face chips
        chips = detector.extract_image_chips(img, points, 144, 0.37)
        for i, chip in enumerate(chips):
            cv2.imshow('chip_'+str(i), chip)
            cv2.imwrite('chip_'+str(i)+'.png', chip)

        draw = img.copy()
        for b in total_boxes:
            cv2.rectangle(draw, (int(b[0]), int(b[1])), (int(b[2]), int(b[3])), (255, 255, 255))

        for p in points:
            for i in range(5):
                cv2.circle(draw, (p[i], p[i + 5]), 1, (0, 0, 255), 2)

        cv2.imshow("detection result", draw)
        cv2.waitKey(0)
  • 写真
  • を読み込む
  • は写真に対して顔を検出し、landmarkと予測ボックス
  • を出力する.
  • crop + alignment

  • ステップ2の関数detect_face分析
        def detect_face(self, img):
          
    
            # check input
            MIN_DET_SIZE = 12
    
            if img is None:
                return None
    
            # only works for color image
            if len(img.shape) != 3:
                return None
    
            # detected boxes
            total_boxes = []
    
            height, width, _ = img.shape
            minl = min( height, width)
    
            # get all the valid scales
            scales = []
            m = MIN_DET_SIZE/self.minsize
            minl *= m
            factor_count = 0
            while minl > MIN_DET_SIZE:
                scales.append(m*self.factor**factor_count)
                minl *= self.factor
                factor_count += 1
    

    上部は縮小プロセスで、scalesはすべての縮小サイズです.検出ウィンドウは12*12に固定されているため、画像中の異なる大きさの顔を検出するためには、画像を異なる大きさに縮小し、順次ネットワークに入力する必要がある.次はP-net
    		#############################################
            # first stage
            #############################################
            #for scale in scales:
            #    return_boxes = self.detect_first_stage(img, scale, 0)
            #    if return_boxes is not None:
            #        total_boxes.append(return_boxes)
            
            sliced_index = self.slice_index(len(scales))
            total_boxes = []
            for batch in sliced_index:
                local_boxes = self.Pool.map(detect_first_stage_warpper, \
                        zip(repeat(img), self.PNets[:len(batch)], [scales[i] for i in batch], repeat(self.threshold[0])) )
                total_boxes.extend(local_boxes)
            
            # remove the Nones 
            total_boxes = [ i for i in total_boxes if i is not None]
    
            if len(total_boxes) == 0:
                return None
            
            total_boxes = np.vstack(total_boxes)
    
            if total_boxes.size == 0:
                return None
    
            # merge the detection from first stage
            pick = nms(total_boxes[:, 0:5], 0.7, 'Union')
            total_boxes = total_boxes[pick]
    
            bbw = total_boxes[:, 2] - total_boxes[:, 0] + 1
            bbh = total_boxes[:, 3] - total_boxes[:, 1] + 1
    
            # refine the bboxes
            total_boxes = np.vstack([total_boxes[:, 0]+total_boxes[:, 5] * bbw,
                                     total_boxes[:, 1]+total_boxes[:, 6] * bbh,
                                     total_boxes[:, 2]+total_boxes[:, 7] * bbw,
                                     total_boxes[:, 3]+total_boxes[:, 8] * bbh,
                                     total_boxes[:, 4]
                                     ])
    
            total_boxes = total_boxes.T
            total_boxes = self.convert_to_square(total_boxes)
            total_boxes[:, 0:4] = np.round(total_boxes[:, 0:4])
    

    self.slice_indexはself.num_workerは、異なるサイズのピクチャインデックスをバッチ化します.self.num_workerは、最初のステップで使用したプロセスの数です.各batchをPnetに送り、対応するサイズのピクチャのすべての候補ボックスを返します.各画像のlocal_boxesのsizeはN*9で、4つの座標値、1つの可能性、4つのスケールオフセットです.
    先にdetect_をfirst_ステージ関数解析
    def detect_first_stage(img, net, scale, threshold):
        """
            run PNet for first stage
        
        Parameters:
        ----------
            img: numpy array, bgr order
                input image
            scale: float number
                how much should the input image scale
            net: PNet
                worker
        Returns:
        -------
            total_boxes : bboxes
        """
        height, width, _ = img.shape
        hs = int(math.ceil(height * scale))
        ws = int(math.ceil(width * scale))
        
        im_data = cv2.resize(img, (ws,hs))
        
        # adjust for the network input
        input_buf = adjust_input(im_data)  # input_buf size ( 1, c, h, w)
        output = net.predict(input_buf)
        boxes = generate_bbox(output[1][0,1,:,:], output[0], scale, threshold)
    
        if boxes.size == 0:
            return None
    
        # nms
        pick = nms(boxes[:,0:5], 0.5, mode='Union')
        boxes = boxes[pick]
        return boxes
    

    output[0]size:1,4,m,n各ボックスdx 1,dy 1,dx 2,dy 2のスケールオフセット.output[1]size:1,2,m,n各ボックスの存在顔の可能性.m=(hs−12)/2+1,n=(ws−12)/2+1 boxesは4つの元の寸法の座標を返し,1つの可能性,4つのスケール縮小値を返した.
    first stageに戻ってtotal_によるとboxesはnmsを行う.boxesの座標値を縮小値で調整し(調整前のboxesはすべて正方形で、辺長は12*scale)、boxesを正方形に調整します.
    total_boxes入力R-net
            #############################################
            # second stage
            #############################################
            num_box = total_boxes.shape[0]
    
            # pad the bbox
            [dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph] = self.pad(total_boxes, width, height)
            # (3, 24, 24) is the input shape for RNet
            input_buf = np.zeros((num_box, 3, 24, 24), dtype=np.float32)
    
            for i in range(num_box):
                tmp = np.zeros((tmph[i], tmpw[i], 3), dtype=np.uint8)
                tmp[dy[i]:edy[i]+1, dx[i]:edx[i]+1, :] = img[y[i]:ey[i]+1, x[i]:ex[i]+1, :]
                input_buf[i, :, :, :] = adjust_input(cv2.resize(tmp, (24, 24)))
    
            output = self.RNet.predict(input_buf)
    
            # filter the total_boxes with threshold
            passed = np.where(output[1][:, 1] > self.threshold[1])
            total_boxes = total_boxes[passed]
    
            if total_boxes.size == 0:
                return None
    
            total_boxes[:, 4] = output[1][passed, 1].reshape((-1,))
            reg = output[0][passed]
    
            # nms
            pick = nms(total_boxes, 0.7, 'Union')
            total_boxes = total_boxes[pick]
            total_boxes = self.calibrate_box(total_boxes, reg[pick])
            total_boxes = self.convert_to_square(total_boxes)
            total_boxes[:, 0:4] = np.round(total_boxes[:, 0:4])
    

    self.padはbox座標を調整します.元の画像の範囲を超える座標があるからです.すべての候補ボックスresizeから2424にR-Netを入力し、Output Output[0]N 4各候補ボックスの4座標のスケールオフセットOutput[1]N*2各候補ボックスの可能性スクリーニング可能性が閾値に達していないnmsを返し、Output[0]に従って候補ボックスを調整する
    最後のステップO-net第2のステップで得られた候補ボックスを入力
    #############################################
            # third stage
            #############################################
            num_box = total_boxes.shape[0]
    
            # pad the bbox
            [dy, edy, dx, edx, y, ey, x, ex, tmpw, tmph] = self.pad(total_boxes, width, height)
            # (3, 48, 48) is the input shape for ONet
            input_buf = np.zeros((num_box, 3, 48, 48), dtype=np.float32)
    
            for i in range(num_box):
                tmp = np.zeros((tmph[i], tmpw[i], 3), dtype=np.float32)
                tmp[dy[i]:edy[i]+1, dx[i]:edx[i]+1, :] = img[y[i]:ey[i]+1, x[i]:ex[i]+1, :]
                input_buf[i, :, :, :] = adjust_input(cv2.resize(tmp, (48, 48)))
    
            output = self.ONet.predict(input_buf)
    
            # filter the total_boxes with threshold
            passed = np.where(output[2][:, 1] > self.threshold[2])
            total_boxes = total_boxes[passed]
    
            if total_boxes.size == 0:
                return None
    
            total_boxes[:, 4] = output[2][passed, 1].reshape((-1,))
            reg = output[1][passed]
            points = output[0][passed]
    
            # compute landmark points
            bbw = total_boxes[:, 2] - total_boxes[:, 0] + 1
            bbh = total_boxes[:, 3] - total_boxes[:, 1] + 1
            points[:, 0:5] = np.expand_dims(total_boxes[:, 0], 1) + np.expand_dims(bbw, 1) * points[:, 0:5]
            points[:, 5:10] = np.expand_dims(total_boxes[:, 1], 1) + np.expand_dims(bbh, 1) * points[:, 5:10]
    
            # nms
            total_boxes = self.calibrate_box(total_boxes, reg)
            pick = nms(total_boxes, 0.7, 'Min')
            total_boxes = total_boxes[pick]
            points = points[pick]
            
            if not self.accurate_landmark:
                return total_boxes, points
    

    R-Netと同様にpadと4848に縮小した後、ONetに入力し、outputに戻る.output[0]N 10各候補ボックス5個のキーの座標output[1]N 4各候補ボックス4個の座標点の割合オフセットoutput[2]N 2各候補ボックスの0/1可能性output[2]フィルタ候補ボックス計算landmarkにより境界ボックスnmsを調整し、ここではboxesとpointsをminで出力する.
    境界ボックスの後ろにextended stageがあります.