File size: 7,778 Bytes
ca46a75
 
 
 
 
 
 
 
 
402b504
 
 
 
 
 
ca46a75
23cd1cf
 
402b504
23cd1cf
ca46a75
23cd1cf
9b2289b
23cd1cf
ca46a75
 
402b504
 
ca46a75
 
23cd1cf
ca46a75
23cd1cf
ca46a75
 
 
 
 
 
 
 
 
 
 
 
9b2289b
 
 
23cd1cf
ca46a75
 
9b2289b
ca46a75
23cd1cf
ca46a75
 
 
 
23cd1cf
9b2289b
23cd1cf
 
 
 
9b2289b
 
 
 
 
 
 
 
 
 
23cd1cf
9b2289b
23cd1cf
 
 
 
 
 
 
 
 
 
 
 
 
06fbec3
 
23cd1cf
 
 
 
 
 
 
 
9b2289b
 
23cd1cf
 
 
 
 
 
 
 
 
 
 
 
 
9b2289b
 
 
 
 
 
 
 
 
 
 
 
23cd1cf
 
 
 
 
9b2289b
23cd1cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402b504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9b2289b
 
 
 
402b504
 
 
9b2289b
402b504
 
 
 
 
9b2289b
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!/usr/bin/env python
# -*- coding: utf-8 -*-
r"""
@DATE: 2024/9/5 19:32
@File: face_detector.py
@IDE: pycharm
@Description:
    人脸检测器
"""
try:
    from mtcnnruntime import MTCNN
except ImportError:
    raise ImportError(
        "Please install mtcnn-runtime by running `pip install mtcnn-runtime`"
    )
from .context import Context
from hivision.error import FaceError, APIError
from hivision.utils import resize_image_to_kb_base64
from hivision.creator.retinaface import retinaface_detect_faces
import requests
import cv2
import os
import numpy as np


mtcnn = None
base_dir = os.path.dirname(os.path.abspath(__file__))
RETINAFCE_SESS = None


def detect_face_mtcnn(ctx: Context, scale: int = 2):
    """
    基于MTCNN模型的人脸检测处理器,只进行人脸数量的检测
    :param ctx: 上下文,此时已获取到原始图和抠图结果,但是我们只需要原始图
    :param scale: 最大边长缩放比例,原图:缩放图 = 1:scale
    :raise FaceError: 人脸检测错误,多个人脸或者没有人脸
    """
    global mtcnn
    if mtcnn is None:
        mtcnn = MTCNN()
    image = cv2.resize(
        ctx.origin_image,
        (ctx.origin_image.shape[1] // scale, ctx.origin_image.shape[0] // scale),
        interpolation=cv2.INTER_AREA,
    )
    # landmarks 是 5 个关键点,分别是左眼、右眼、鼻子、左嘴角、右嘴角,
    faces, landmarks = mtcnn.detect(image, thresholds=[0.8, 0.8, 0.8])

    # print(len(faces))
    if len(faces) != 1:
        # 保险措施,如果检测到多个人脸或者没有人脸,用原图再检测一次
        faces, landmarks = mtcnn.detect(ctx.origin_image)
    else:
        # 如果只有一个人脸,将人脸坐标放大
        for item, param in enumerate(faces[0]):
            faces[0][item] = param * 2
    if len(faces) != 1:
        raise FaceError("Expected 1 face, but got {}".format(len(faces)), len(faces))

    # 计算人脸坐标
    left = faces[0][0]
    top = faces[0][1]
    width = faces[0][2] - left + 1
    height = faces[0][3] - top + 1
    ctx.face["rectangle"] = (left, top, width, height)

    # 根据landmarks计算人脸偏转角度,以眼睛为标准,计算的人脸偏转角度,用于人脸矫正
    # 示例landmarks [106.37181  150.77415  127.21012  108.369156 144.61522  105.24723 107.45625  133.62355  151.24269  153.34407 ]
    landmarks = landmarks[0]
    left_eye = np.array([landmarks[0], landmarks[5]])
    right_eye = np.array([landmarks[1], landmarks[6]])
    dy = right_eye[1] - left_eye[1]
    dx = right_eye[0] - left_eye[0]
    roll_angle = np.degrees(np.arctan2(dy, dx))

    ctx.face["roll_angle"] = roll_angle


def detect_face_face_plusplus(ctx: Context):
    """
    基于Face++ API接口的人脸检测处理器,只进行人脸数量的检测
    :param ctx: 上下文,此时已获取到原始图和抠图结果,但是我们只需要原始图
    :param scale: 最大边长缩放比例,原图:缩放图 = 1:scale
    :raise FaceError: 人脸检测错误,多个人脸或者没有人脸
    """
    url = "https://api-cn.faceplusplus.com/facepp/v3/detect"
    api_key = os.getenv("FACE_PLUS_API_KEY")
    api_secret = os.getenv("FACE_PLUS_API_SECRET")

    print("调用了face++")

    image = ctx.origin_image
    # 将图片转为 base64, 且不大于2MB(Face++ API接口限制)
    image_base64 = resize_image_to_kb_base64(image, 2000, mode="max")

    files = {
        "api_key": (None, api_key),
        "api_secret": (None, api_secret),
        "image_base64": (None, image_base64),
        "return_landmark": (None, "1"),
        "return_attributes": (None, "headpose"),
    }

    # 发送 POST 请求
    response = requests.post(url, files=files)

    # 获取响应状态码
    status_code = response.status_code
    response_json = response.json()

    if status_code == 200:
        face_num = response_json["face_num"]
        if face_num == 1:
            face_rectangle = response_json["faces"][0]["face_rectangle"]

            # 获取人脸关键点
            # landmarks = response_json["faces"][0]["landmark"]
            # print("face++ landmarks", landmarks)

            # headpose 是一个字典,包含俯仰角(pitch)、偏航角(yaw)和滚转角(roll)
            # headpose示例 {'pitch_angle': 6.997899, 'roll_angle': 1.8011835, 'yaw_angle': 5.043002}
            headpose = response_json["faces"][0]["attributes"]["headpose"]
            # 以眼睛为标准,计算的人脸偏转角度,用于人脸矫正
            roll_angle = headpose["roll_angle"] / 2

            ctx.face["rectangle"] = (
                face_rectangle["left"],
                face_rectangle["top"],
                face_rectangle["width"],
                face_rectangle["height"],
            )
            ctx.face["roll_angle"] = roll_angle
        else:
            raise FaceError(
                "Expected 1 face, but got {}".format(face_num), len(face_num)
            )

    elif status_code == 401:
        raise APIError(
            f"Face++ Status code {status_code} Authentication error: API key and secret do not match.",
            status_code,
        )

    elif status_code == 403:
        reason = response_json.get("error_message", "Unknown authorization error.")
        raise APIError(
            f"Authorization error: {reason}",
            status_code,
        )

    elif status_code == 400:
        error_message = response_json.get("error_message", "Bad request.")
        raise APIError(
            f"Bad request error: {error_message}",
            status_code,
        )

    elif status_code == 413:
        raise APIError(
            f"Face++ Status code {status_code} Request entity too large: The image exceeds the 2MB limit.",
            status_code,
        )


def detect_face_retinaface(ctx: Context):
    """
    基于RetinaFace模型的人脸检测处理器,只进行人脸数量的检测
    :param ctx: 上下文,此时已获取到原始图和抠图结果,但是我们只需要原始图
    :raise FaceError: 人脸检测错误,多个人脸或者没有人脸
    """
    from time import time

    global RETINAFCE_SESS

    if RETINAFCE_SESS is None:
        print("首次加载RetinaFace模型...")
        # 计算用时
        tic = time()
        faces_dets, sess = retinaface_detect_faces(
            ctx.origin_image,
            os.path.join(base_dir, "retinaface/weights/retinaface-resnet50.onnx"),
            sess=None,
        )
        RETINAFCE_SESS = sess
        print("首次RetinaFace模型推理用时: {:.4f}s".format(time() - tic))
    else:
        tic = time()
        faces_dets, _ = retinaface_detect_faces(
            ctx.origin_image,
            os.path.join(base_dir, "retinaface/weights/retinaface-resnet50.onnx"),
            sess=RETINAFCE_SESS,
        )
        print("二次RetinaFace模型推理用时: {:.4f}s".format(time() - tic))

    faces_num = len(faces_dets)
    faces_landmarks = []
    for face_det in faces_dets:
        faces_landmarks.append(face_det[5:])

    if faces_num != 1:
        raise FaceError("Expected 1 face, but got {}".format(faces_num), faces_num)
    face_det = faces_dets[0]
    ctx.face["rectangle"] = (
        face_det[0],
        face_det[1],
        face_det[2] - face_det[0] + 1,
        face_det[3] - face_det[1] + 1,
    )

    # 计算roll_angle
    face_landmarks = faces_landmarks[0]
    # print("face_landmarks", face_landmarks)
    left_eye = np.array([face_landmarks[0], face_landmarks[1]])
    right_eye = np.array([face_landmarks[2], face_landmarks[3]])
    dy = right_eye[1] - left_eye[1]
    dx = right_eye[0] - left_eye[0]
    roll_angle = np.degrees(np.arctan2(dy, dx))
    ctx.face["roll_angle"] = roll_angle