MTCNNテストソース
8788 ワード
コード実装(MXNet):https://github.com/pangyupo/mxnet_mtcnn_face_detection
コード本体からmain.py開始写真 を読み込むは写真に対して顔を検出し、landmarkと予測ボックス を出力する. crop + alignment
ステップ2の関数detect_face分析
上部は縮小プロセスで、scalesはすべての縮小サイズです.検出ウィンドウは12*12に固定されているため、画像中の異なる大きさの顔を検出するためには、画像を異なる大きさに縮小し、順次ネットワークに入力する必要がある.次はP-net
self.slice_indexはself.num_workerは、異なるサイズのピクチャインデックスをバッチ化します.self.num_workerは、最初のステップで使用したプロセスの数です.各batchをPnetに送り、対応するサイズのピクチャのすべての候補ボックスを返します.各画像のlocal_boxesのsizeはN*9で、4つの座標値、1つの可能性、4つのスケールオフセットです.
先にdetect_をfirst_ステージ関数解析
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
self.padはbox座標を調整します.元の画像の範囲を超える座標があるからです.すべての候補ボックスresizeから2424にR-Netを入力し、Output Output[0]N 4各候補ボックスの4座標のスケールオフセットOutput[1]N*2各候補ボックスの可能性スクリーニング可能性が閾値に達していないnmsを返し、Output[0]に従って候補ボックスを調整する
最後のステップO-net第2のステップで得られた候補ボックスを入力
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があります.
コード本体から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)
ステップ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があります.