前言

本教程是教程是介紹如何使用Tensorflow實現的MTCNN和MobileFaceNet實現的人臉識別,並不介紹如何訓練模型。關於如何訓練MTCNN和MobileFaceNet,請閱讀這兩篇教程 MTCNN-TensorflowMobileFaceNet_TF ,這兩個模型都是比較輕量的模型,所以就算這兩個模型在CPU環境下也有比較好的預測速度,衆所周知,筆者比較喜歡輕量級的模型,如何讓我從準確率和預測速度上選擇,我會更傾向於速度,因本人主要是研究深度學習在移動設備等嵌入式設備上的的部署。好了,下面就來介紹如何實現這兩個模型實現三種人臉識別,使用路徑進行人臉註冊和人臉識別,使用攝像頭實現人臉註冊和人臉識別,通過HTTP實現人臉註冊和人臉識別。

本教程源碼:https://github.com/yeyupiaoling/Tensorflow-FaceRecognition

本地人臉圖像識別

本地人臉圖像識別就是要通過路徑讀取本地的圖像進行人臉註冊或者人臉識別,對應的代碼爲path_infer.py。首先要加載好人臉識別的兩個模型,一個是人臉檢測和關鍵點檢測模型MTCNN和人臉識別模型MobileFaceNet,加載這兩個模型已經封裝在一個工具中了,方便加載。
然後add_faces()這個函數是從temp路徑中讀取手動添加的圖片的人臉庫中,具體來說,例如你有100張已經用人臉中對應人名字來命名圖片文件名,但是你不能直接添加到人臉庫face_db中,因爲人臉庫中是存放經過MTCNN模型處理過的圖片,所以大規模添加人臉圖片需要通過暫存在temp文件夾中的方式來然程序自動添加。最後是讀取人臉庫中圖像,通過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)

人臉註冊是通過圖像路徑讀取人臉圖像,然後使用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文件,主要是以下兩個表單和一個拍攝即時顯示的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)
小夜