MTCNNとMobileFaceNetによる顔認識


原文ブログ:Doi技術チームリンクアドレス:https://blog.doiduoyi.com/authors/1584446358138初心者:優れたDoiテクノロジーチームの学習経験を記録本リンク:MTCNNとMobileFaceNetに基づいて実現された顔認識

前言


このチュートリアルでは、Tensorflowで実装されたMTCNNとMobileFaceNetで実装された顔認識の使い方を説明し、モデルのトレーニング方法を説明しません.MTCNNとMobileFaceNetのトレーニング方法については、この2つのチュートリアルMTCNN-TensorflowとMobileFaceNetを参照してください.TF、この2つのモデルはいずれも比較的軽量なモデルなので、この2つのモデルがCPU環境でも比較的良い予測速度を持っているとしても、周知のように、筆者は軽量級のモデルが好きで、どのように私に正確率と予測速度から選択させるか、私はもっと速度に傾いて、本人は主にモバイル機器などの組み込み機器の配置を深く勉強しているからです.では、この2つのモデルを実装して3つの顔認識を実現する方法を紹介し、経路を用いて顔登録と顔認識を行い、カメラを用いて顔登録と顔認識を実現し、HTTPを通じて顔登録と顔認識を実現する.
このチュートリアルのソース:https://github.com/yeyupiaoling/Tensorflow-FaceRecognition

ローカル顔画像認識


ローカル顔画像認識とは、パスを介してローカルの画像を読み出して顔登録または顔認識を行い、対応するコードはpath_infer.pyである.まず、顔認識の2つのモデルをロードします.1つは顔検出とキー検出モデルMTCNNと顔認識モデルMobileFaceNetです.ロードの2つのモデルはすでに1つのツールにパッケージされており、ロードが便利です.その後、add_faces()という関数は、tempパスから手動で追加されたピクチャを読み取る顔ライブラリです.具体的には、例えば、顔の中の対応する人の名前でピクチャファイル名を命名した100枚がありますが、顔ライブラリface_dbに直接追加することはできません.顔ライブラリにはMTCNNモデルで処理されたピクチャが格納されているので、したがって、大規模な顔画像の追加は、tempフォルダに一時的に存在することによってプログラムが自動的に追加される必要がある.最後に顔ライブラリの画像を読み出し,MobileFaceNet予測により各顔の特徴値を1つのリストに格納し,その後の顔コントラスト認識を待つ.
#  
mtcnn_detector = load_mtcnn()
#  
face_sess, inputs_placeholder, embeddings = load_mobilefacenet()
#  
add_faces(mtcnn_detector)
#  
faces_db = load_faces(face_sess, inputs_placeholder, embeddings)

顔登録は、画像パスを介して顔画像を読み取り、MTCNNを使用して画像中の顔を検出し、顔キーを介して顔の位置合わせを行い、最後に112*112の画像にトリミングしてスケーリングし、登録名命名ファイルとして顔ライブラリに格納する.
def face_register(img_path, name):
    image = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), 1)
    faces, landmarks = mtcnn_detector.detect(image)
    if faces.shape[0] is not 0:
        faces_sum = 0
        bbox = []
        points = []
        for i, face in enumerate(faces):
            if round(faces[i, 4], 6) > 0.95:
                bbox = faces[i, 0:4]
                points = landmarks[i, :].reshape((5, 2))
                faces_sum += 1
        if faces_sum == 1:
            nimg = face_preprocess.preprocess(image, bbox, points, image_size='112,112')
            cv2.imencode('.png', nimg)[1].tofile('face_db/%s.png' % name)
            print(" !")
        else:
            print(' , ')
    else:
        print(' , ')

顔認識は、認識する顔を画像経路で読み取り、MTCNNの検出により顔とそれに対してMobileFaceNetを用いて顔の特徴を予測し、最終的には特徴と顔ライブラリにおける特徴値の比較類似度を得、最終的に閾値が0.6を超える最高類似度結果を得、対応する名称がその顔認識の結果である.最後に結果を画像のフレームとタグに名前を付けて表示します.
def face_recognition(img_path):
    image = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), 1)
    faces, landmarks = mtcnn_detector.detect(image)
    if faces.shape[0] is not 0:
        faces_sum = 0
        for i, face in enumerate(faces):
            if round(faces[i, 4], 6) > 0.95:
                faces_sum += 1
        if faces_sum > 0:
            #  
            info_location = np.zeros(faces_sum)
            info_location[0] = 1
            info_name = []
            probs = []
            #  
            input_images = np.zeros((faces.shape[0], 112, 112, 3))
            for i, face in enumerate(faces):
                if round(faces[i, 4], 6) > 0.95:
                    bbox = faces[i, 0:4]
                    points = landmarks[i, :].reshape((5, 2))
                    nimg = face_preprocess.preprocess(image, bbox, points, image_size='112,112')
                    nimg = nimg - 127.5
                    nimg = nimg * 0.0078125
                    input_images[i, :] = nimg

            #  
            feed_dict = {inputs_placeholder: input_images}
            emb_arrays = face_sess.run(embeddings, feed_dict=feed_dict)
            emb_arrays = sklearn.preprocessing.normalize(emb_arrays)
            for i, embedding in enumerate(emb_arrays):
                embedding = embedding.flatten()
                temp_dict = {}
                #  
                for com_face in faces_db:
                    ret, sim = feature_compare(embedding, com_face["feature"], 0.70)
                    temp_dict[com_face["name"]] = sim
                dict = sorted(temp_dict.items(), key=lambda d: d[1], reverse=True)
                if dict[0][1] > VERIFICATION_THRESHOLD:
                    name = dict[0][0]
                    probs.append(dict[0][1])
                    info_name.append(name)
                else:
                    probs.append(dict[0][1])
                    info_name.append("unknown")

            for k in range(faces_sum):
                #  
                x1, y1, x2, y2 = faces[k][0], faces[k][1], faces[k][2], faces[k][3]
                x1 = max(int(x1), 0)
                y1 = max(int(y1), 0)
                x2 = min(int(x2), image.shape[1])
                y2 = min(int(y2), image.shape[0])
                prob = '%.2f' % probs[k]
                label = "{}, {}".format(info_name[k], prob)
                cv2img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
                pilimg = Image.fromarray(cv2img)
                draw = ImageDraw.Draw(pilimg)
                font = ImageFont.truetype('font/simfang.ttf', 18, encoding="utf-8")
                draw.text((x1, y1 - 18), label, (255, 0, 0), font=font)
                image = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)
                cv2.rectangle(image, (x1, y1), (x2, y2), (255, 0, 0), 2)
    cv2.imshow('image', image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

最後の動きは、顔登録か顔認識かを選択します.
if __name__ == '__main__':
    i = int(input(" ,1 ,2 :"))
    image_path = input(" :")
    if i == 1:
        user_name = input(" :")
        face_register(image_path, user_name)
    elif i == 2:
        face_recognition(image_path)
    else:
        print(" ")

ログの出力は次のとおりです.
loaded face:  .png
loaded face:  .png
 ,1 ,2 :1
 :test.png
 : 
 !

カメラ顔認識

camera_infer.pyでカメラを用いた顔認識を実現し、カメラを呼び出して画像を取得し、顔登録と顔認識を行い、顔登録または顔認識を用いる前に同様に顔検出モデルMTCNNとMobileFaceNetをロードし、一時tempフォルダの顔をMTCNN処理により顔ライブラリに追加し、最後に顔ライブラリの顔をMobileFaceNetで予測して特徴値を得る.特徴値と対応する顔名をリストに格納します.
#  
mtcnn_detector = load_mtcnn()
#  
face_sess, inputs_placeholder, embeddings = load_mobilefacenet()
#  
add_faces(mtcnn_detector)
#  
faces_db = load_faces(face_sess, inputs_placeholder, embeddings)

カメラを使ってリアルタイムで画像を取得することで、顔登録ここでカメラが顔を撮影した後、yボタンをクリックして写真を撮影することができ、写真を撮影した後、MTCNNの検出を経て顔が存在するかどうかを判断する必要があり、検出に成功した後、顔を裁断し、登録名で直接顔ライブラリにface_db保存する.
def face_register():
    print(" y !")
    cap = cv2.VideoCapture(0)
    while True:
        ret, frame = cap.read()
        if ret:
            cv2.imshow('image', frame)
            if cv2.waitKey(1) & 0xFF == ord('y'):
                faces, landmarks = mtcnn_detector.detect(frame)
                if faces.shape[0] is not 0:
                    faces_sum = 0
                    bbox = []
                    points = []
                    for i, face in enumerate(faces):
                        if round(faces[i, 4], 6) > 0.95:
                            bbox = faces[i, 0:4]
                            points = landmarks[i, :].reshape((5, 2))
                            faces_sum += 1
                    if faces_sum == 1:
                        nimg = face_preprocess.preprocess(frame, bbox, points, image_size='112,112')
                        user_name = input(" :")
                        cv2.imencode('.png', nimg)[1].tofile('face_db/%s.png' % user_name)
                        print(" !")
                    else:
                        print(' , ')
                else:
                    print(' , ')
                break

顔認識では、カメラを呼び出してリアルタイムで画像を取得し、MTCNNを用いて顔の位置を検出し、MobileFaceNetを用いて認識し、最終的には画像に枠を描いて認識の名前を書くことで、結果としてカメラが取得した画像についてリアルタイムで認識することになる.
def face_recognition():
    cap = cv2.VideoCapture(0)
    while True:
        ret, frame = cap.read()
        if ret:
            faces, landmarks = mtcnn_detector.detect(frame)
            if faces.shape[0] is not 0:
                faces_sum = 0
                for i, face in enumerate(faces):
                    if round(faces[i, 4], 6) > 0.95:
                        faces_sum += 1
                if faces_sum == 0:
                    continue
                #  
                info_location = np.zeros(faces_sum)
                info_location[0] = 1
                info_name = []
                probs = []
                #  
                input_images = np.zeros((faces.shape[0], 112, 112, 3))
                for i, face in enumerate(faces):
                    if round(faces[i, 4], 6) > 0.95:
                        bbox = faces[i, 0:4]
                        points = landmarks[i, :].reshape((5, 2))
                        nimg = face_preprocess.preprocess(frame, bbox, points, image_size='112,112')
                        nimg = nimg - 127.5
                        nimg = nimg * 0.0078125
                        input_images[i, :] = nimg

                #  
                feed_dict = {inputs_placeholder: input_images}
                emb_arrays = face_sess.run(embeddings, feed_dict=feed_dict)
                emb_arrays = sklearn.preprocessing.normalize(emb_arrays)
                for i, embedding in enumerate(emb_arrays):
                    embedding = embedding.flatten()
                    temp_dict = {}
                    #  
                    for com_face in faces_db:
                        ret, sim = feature_compare(embedding, com_face["feature"], 0.70)
                        temp_dict[com_face["name"]] = sim
                    dict = sorted(temp_dict.items(), key=lambda d: d[1], reverse=True)
                    if dict[0][1] > VERIFICATION_THRESHOLD:
                        name = dict[0][0]
                        probs.append(dict[0][1])
                        info_name.append(name)
                    else:
                        probs.append(dict[0][1])
                        info_name.append("unknown")

                for k in range(faces_sum):
                    #  
                    x1, y1, x2, y2 = faces[k][0], faces[k][1], faces[k][2], faces[k][3]
                    x1 = max(int(x1), 0)
                    y1 = max(int(y1), 0)
                    x2 = min(int(x2), frame.shape[1])
                    y2 = min(int(y2), frame.shape[0])
                    prob = '%.2f' % probs[k]
                    label = "{}, {}".format(info_name[k], prob)
                    cv2img = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    pilimg = Image.fromarray(cv2img)
                    draw = ImageDraw.Draw(pilimg)
                    font = ImageFont.truetype('font/simfang.ttf', 18, encoding="utf-8")
                    draw.text((x1, y1 - 18), label, (255, 0, 0), font=font)
                    frame = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)
                    cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2)
        cv2.imshow('image', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

起動プログラムでは、機能を選択することで、登録顔または認識顔を選択します.
if __name__ == '__main__':
    i = int(input(" ,1 ,2 :"))
    if i == 1:
        face_register()
    elif i == 2:
        face_recognition()
    else:
        print(" ")

ログの出力は次のとおりです.
loaded face:  .png
loaded face:  .png
 ,1 ,2 :1
 y !
 : 

 !

サービスインタフェースによる識別


プログラムはserver_main.pyで実装され、Flaskを使用してネットワークサービスインタフェースを提供することにより、ドメイン間アクセスを許可するためにCORS(app)を設定する必要がある場合、本プログラムはデフォルトでドメイン間アクセスを開くが、ブラウザ上でカメラを呼び出すことができるように、起動するhostはlocalhostに設定される.また、MTCNNモデルとMobileFaceNetモデルをロードし、顔ライブラリの画像をプログラムにロードします.
app = Flask(__name__)
#  
CORS(app)

#  
VERIFICATION_THRESHOLD = config.VERIFICATION_THRESHOLD
#  
mtcnn_detector = load_mtcnn()
#  
face_sess, inputs_placeholder, embeddings = load_mobilefacenet()
#  
faces_db = load_faces(face_sess, inputs_placeholder, embeddings)
/registerの顔登録インタフェースを提供し、フォームにアップロードされた画像と登録名を通じて、MTCNNの検出を経て、顔が含まれているかどうかを検出し、登録に成功すれば、画像を切り取って顔ライブラリにface_db保存する.ロードされた顔ライブラリを更新し、すべての更新を再読み込みすることに注意します.
@app.route("/register", methods=['POST'])
def register():
    global faces_db
    upload_file = request.files['image']
    user_name = request.values.get("name")
    if upload_file:
        try:
            image = cv2.imdecode(np.frombuffer(upload_file.read(), np.uint8), cv2.IMREAD_UNCHANGED)
            faces, landmarks = mtcnn_detector.detect(image)
            if faces.shape[0] is not 0:
                faces_sum = 0
                bbox = []
                points = []
                for i, face in enumerate(faces):
                    if round(faces[i, 4], 6) > 0.95:
                        bbox = faces[i, 0:4]
                        points = landmarks[i, :].reshape((5, 2))
                        faces_sum += 1
                if faces_sum == 1:
                    nimg = face_preprocess.preprocess(image, bbox, points, image_size='112,112')
                    cv2.imencode('.png', nimg)[1].tofile('face_db/%s.png' % user_name)
                    #  
                    faces_db = load_faces(face_sess, inputs_placeholder, embeddings)
                    return str({"code": 0, "msg": "success"})
            return str({"code": 3, "msg": "image not or much face"})
        except:
            return str({"code": 2, "msg": "this file is not image or not face"})
    else:
        return str({"code": 1, "msg": "file is None"})
/recognition人の顔認識インタフェースを提供し、画像をアップロードすることによって顔認識を行い、認識の結果をユーザーに返し、返された結果には認識の名前だけでなく、顔枠とキーも含まれている.ブラウザでカメラを呼び出して画像を予測するのに便利なis_chrome_cameraパラメータも提供されているため、ブラウザで撮影した画像を直接予測するとエラーが発生するため、ブラウザで撮影認識する場合は、先に記憶してから読み直す必要がある.
@app.route("/recognition", methods=['POST'])
def recognition():
    start_time1 = time.time()
    upload_file = request.files['image']
    is_chrome_camera = request.values.get("is_chrome_camera")
    if upload_file:
        try:
            img = cv2.imdecode(np.frombuffer(upload_file.read(), np.uint8), cv2.IMREAD_UNCHANGED)
            #  
            if is_chrome_camera == "True":
                cv2.imwrite('test.png', img)
                img = cv2.imdecode(np.fromfile('test.png', dtype=np.uint8), 1)
        except:
            return str({"error": 2, "msg": "this file is not image"})
        try:
            info_name, probs, info_bbox, info_landmarks = recognition_face(img)
            if info_name is None:
                return str({"error": 3, "msg": "image not have face"})
        except:
            return str({"error": 3, "msg": "image not have face"})
        #  
        data_faces = []
        for i in range(len(info_name)):
            data_faces.append(
                {"name": info_name[i], "probability": probs[i],
                 "bbox": list_to_json(np.around(info_bbox[i], decimals=2).tolist()),
                 "landmarks": list_to_json(np.around(info_landmarks[i], decimals=2).tolist())})
        data = str({"code": 0, "msg": "success", "data": data_faces}).replace("'", '"')
        print('duration:[%.0fms]' % ((time.time() - start_time1) * 1000), data)
        return data
    else:
        return str({"error": 1, "msg": "file is None"})
templatesディレクトリの下にindex.htmlファイルが作成され、主に以下の2つのフォームと1つの撮影リアルタイム表示のvideoであり、撮影された画像はcanvasに表示され、最後にアップロードされる.
<form action="/register" enctype="multipart/form-data" method="post"><input type="file" required accept="image/*" name="image"><br><input type="text" name="name"><br>

    <input type="submit" value=" ">
form>

<br/><br/><br/>
<form action="/recognition" enctype="multipart/form-data" method="post"><input type="file" required accept="image/*" name="image"><br>
    <input type="submit" value=" ">
form>

<br/><br/><br/>
<video id="video" width="640" height="480" autoplay>video>
<button id="snap"> button>
<br/><br/>
<canvas id="canvas" width="640" height="480">canvas>
<button id="upload"> button>

以下でサービス全体を起動します.
@app.route('/')
def home():
    return render_template("index.html")


if __name__ == '__main__':
    app.run(host=config.HOST, port=config.POST)

ログの出力は次のとおりです.
loaded face:  .png
loaded face:  .png
 * Serving Flask app "server_main" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://localhost:5000/ (Press CTRL+C to quit)