Spaces:
Running
Running
TheEeeeLin
commited on
Commit
•
9b2289b
1
Parent(s):
1d213d9
update
Browse files- demo/locales.py +18 -0
- demo/processor.py +4 -0
- demo/ui.py +32 -9
- hivision/creator/__init__.py +27 -5
- hivision/creator/context.py +13 -2
- hivision/creator/face_detector.py +47 -5
- hivision/creator/photo_adjuster.py +1 -1
- hivision/creator/rotation_adjust.py +81 -0
- hivision/creator/utils.py +64 -0
demo/locales.py
CHANGED
@@ -620,4 +620,22 @@ LOCALES = {
|
|
620 |
"label": "포화도 강도",
|
621 |
},
|
622 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
623 |
}
|
|
|
620 |
"label": "포화도 강도",
|
621 |
},
|
622 |
},
|
623 |
+
"face_alignment": {
|
624 |
+
"en": {
|
625 |
+
"label": "Face Alignment",
|
626 |
+
"value": "Open",
|
627 |
+
},
|
628 |
+
"zh": {
|
629 |
+
"label": "人脸旋转对齐",
|
630 |
+
"value": "开启",
|
631 |
+
},
|
632 |
+
"ja": {
|
633 |
+
"label": "顔の整列",
|
634 |
+
"value": "開始",
|
635 |
+
},
|
636 |
+
"ko": {
|
637 |
+
"label": "얼굴 정렬",
|
638 |
+
"value": "시작",
|
639 |
+
},
|
640 |
+
},
|
641 |
}
|
demo/processor.py
CHANGED
@@ -53,6 +53,7 @@ class IDPhotoProcessor:
|
|
53 |
contrast_strength=0,
|
54 |
sharpen_strength=0,
|
55 |
saturation_strength=0,
|
|
|
56 |
):
|
57 |
# 初始化参数
|
58 |
top_distance_min = top_distance_max - 0.02
|
@@ -115,6 +116,7 @@ class IDPhotoProcessor:
|
|
115 |
contrast_strength,
|
116 |
sharpen_strength,
|
117 |
saturation_strength,
|
|
|
118 |
)
|
119 |
except (FaceError, APIError):
|
120 |
return self._handle_photo_generation_error(language)
|
@@ -209,6 +211,7 @@ class IDPhotoProcessor:
|
|
209 |
contrast_strength,
|
210 |
sharpen_strength,
|
211 |
saturation_strength,
|
|
|
212 |
):
|
213 |
"""生成证件照"""
|
214 |
change_bg_only = (
|
@@ -225,6 +228,7 @@ class IDPhotoProcessor:
|
|
225 |
contrast_strength=contrast_strength,
|
226 |
sharpen_strength=sharpen_strength,
|
227 |
saturation_strength=saturation_strength,
|
|
|
228 |
)
|
229 |
|
230 |
def _handle_photo_generation_error(self, language):
|
|
|
53 |
contrast_strength=0,
|
54 |
sharpen_strength=0,
|
55 |
saturation_strength=0,
|
56 |
+
face_alignment_option=False,
|
57 |
):
|
58 |
# 初始化参数
|
59 |
top_distance_min = top_distance_max - 0.02
|
|
|
116 |
contrast_strength,
|
117 |
sharpen_strength,
|
118 |
saturation_strength,
|
119 |
+
face_alignment_option,
|
120 |
)
|
121 |
except (FaceError, APIError):
|
122 |
return self._handle_photo_generation_error(language)
|
|
|
211 |
contrast_strength,
|
212 |
sharpen_strength,
|
213 |
saturation_strength,
|
214 |
+
face_alignment_option,
|
215 |
):
|
216 |
"""生成证件照"""
|
217 |
change_bg_only = (
|
|
|
228 |
contrast_strength=contrast_strength,
|
229 |
sharpen_strength=sharpen_strength,
|
230 |
saturation_strength=saturation_strength,
|
231 |
+
face_alignment=face_alignment_option,
|
232 |
)
|
233 |
|
234 |
def _handle_photo_generation_error(self, language):
|
demo/ui.py
CHANGED
@@ -23,7 +23,13 @@ def create_ui(
|
|
23 |
face_detect_models: list,
|
24 |
language: list,
|
25 |
):
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
DEFAULT_HUMAN_MATTING_MODEL = "modnet_photographic_portrait_matting"
|
28 |
DEFAULT_FACE_DETECT_MODEL = "retinaface-resnet50"
|
29 |
|
@@ -67,12 +73,18 @@ def create_ui(
|
|
67 |
with gr.Tab(
|
68 |
LOCALES["key_param"][DEFAULT_LANG]["label"]
|
69 |
) as key_parameter_tab:
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
with gr.Row(visible=True) as size_list_row:
|
77 |
size_list_options = gr.Dropdown(
|
78 |
choices=LOCALES["size_list"][DEFAULT_LANG]["choices"],
|
@@ -80,7 +92,6 @@ def create_ui(
|
|
80 |
value=LOCALES["size_list"][DEFAULT_LANG]["choices"][0],
|
81 |
elem_id="size_list",
|
82 |
)
|
83 |
-
|
84 |
with gr.Row(visible=False) as custom_size:
|
85 |
custom_size_height = gr.Number(
|
86 |
value=413, label="height", interactive=True
|
@@ -483,6 +494,10 @@ def create_ui(
|
|
483 |
saturation_option: gr.update(
|
484 |
label=LOCALES["saturation_strength"][language]["label"]
|
485 |
),
|
|
|
|
|
|
|
|
|
486 |
}
|
487 |
|
488 |
def change_visibility(option, lang, locales_key, custom_component):
|
@@ -497,20 +512,26 @@ def create_ui(
|
|
497 |
|
498 |
def change_size_mode(size_option_item, lang):
|
499 |
choices = LOCALES["size_mode"][lang]["choices"]
|
|
|
500 |
if size_option_item == choices[2]:
|
501 |
return {
|
502 |
custom_size: gr.update(visible=True),
|
503 |
size_list_row: gr.update(visible=False),
|
|
|
504 |
}
|
|
|
505 |
elif size_option_item == choices[1]:
|
506 |
return {
|
507 |
custom_size: gr.update(visible=False),
|
508 |
size_list_row: gr.update(visible=False),
|
|
|
509 |
}
|
|
|
510 |
else:
|
511 |
return {
|
512 |
custom_size: gr.update(visible=False),
|
513 |
size_list_row: gr.update(visible=True),
|
|
|
514 |
}
|
515 |
|
516 |
def change_image_kb(image_kb_option, lang):
|
@@ -566,6 +587,7 @@ def create_ui(
|
|
566 |
contrast_option,
|
567 |
sharpen_option,
|
568 |
saturation_option,
|
|
|
569 |
],
|
570 |
)
|
571 |
|
@@ -578,7 +600,7 @@ def create_ui(
|
|
578 |
mode_options.input(
|
579 |
change_size_mode,
|
580 |
inputs=[mode_options, language_options],
|
581 |
-
outputs=[custom_size, size_list_row],
|
582 |
)
|
583 |
|
584 |
image_kb_options.input(
|
@@ -627,6 +649,7 @@ def create_ui(
|
|
627 |
contrast_option,
|
628 |
sharpen_option,
|
629 |
saturation_option,
|
|
|
630 |
],
|
631 |
outputs=[
|
632 |
img_output_standard,
|
|
|
23 |
face_detect_models: list,
|
24 |
language: list,
|
25 |
):
|
26 |
+
|
27 |
+
# 加载环境变量DEFAULT_LANG, 如果有且在language中,则将DEFAULT_LANG设置为环境变量
|
28 |
+
if "DEFAULT_LANG" in os.environ and os.environ["DEFAULT_LANG"] in language:
|
29 |
+
DEFAULT_LANG = os.environ["DEFAULT_LANG"]
|
30 |
+
else:
|
31 |
+
DEFAULT_LANG = language[0]
|
32 |
+
|
33 |
DEFAULT_HUMAN_MATTING_MODEL = "modnet_photographic_portrait_matting"
|
34 |
DEFAULT_FACE_DETECT_MODEL = "retinaface-resnet50"
|
35 |
|
|
|
73 |
with gr.Tab(
|
74 |
LOCALES["key_param"][DEFAULT_LANG]["label"]
|
75 |
) as key_parameter_tab:
|
76 |
+
with gr.Row():
|
77 |
+
mode_options = gr.Radio(
|
78 |
+
choices=LOCALES["size_mode"][DEFAULT_LANG]["choices"],
|
79 |
+
label=LOCALES["size_mode"][DEFAULT_LANG]["label"],
|
80 |
+
value=LOCALES["size_mode"][DEFAULT_LANG]["choices"][0],
|
81 |
+
min_width=500,
|
82 |
+
)
|
83 |
+
face_alignment_options = gr.CheckboxGroup(
|
84 |
+
label=LOCALES["face_alignment"][DEFAULT_LANG]["label"],
|
85 |
+
choices=[LOCALES["face_alignment"][DEFAULT_LANG]["value"]],
|
86 |
+
interactive=True,
|
87 |
+
)
|
88 |
with gr.Row(visible=True) as size_list_row:
|
89 |
size_list_options = gr.Dropdown(
|
90 |
choices=LOCALES["size_list"][DEFAULT_LANG]["choices"],
|
|
|
92 |
value=LOCALES["size_list"][DEFAULT_LANG]["choices"][0],
|
93 |
elem_id="size_list",
|
94 |
)
|
|
|
95 |
with gr.Row(visible=False) as custom_size:
|
96 |
custom_size_height = gr.Number(
|
97 |
value=413, label="height", interactive=True
|
|
|
494 |
saturation_option: gr.update(
|
495 |
label=LOCALES["saturation_strength"][language]["label"]
|
496 |
),
|
497 |
+
face_alignment_options: gr.update(
|
498 |
+
label=LOCALES["face_alignment"][language]["label"],
|
499 |
+
value=LOCALES["face_alignment"][language]["value"],
|
500 |
+
),
|
501 |
}
|
502 |
|
503 |
def change_visibility(option, lang, locales_key, custom_component):
|
|
|
512 |
|
513 |
def change_size_mode(size_option_item, lang):
|
514 |
choices = LOCALES["size_mode"][lang]["choices"]
|
515 |
+
# 如果选择自定义尺寸,则隐藏预设尺寸列表
|
516 |
if size_option_item == choices[2]:
|
517 |
return {
|
518 |
custom_size: gr.update(visible=True),
|
519 |
size_list_row: gr.update(visible=False),
|
520 |
+
face_alignment_options: gr.update(visible=True),
|
521 |
}
|
522 |
+
# 如果选择只换底,则隐藏所有尺寸组件
|
523 |
elif size_option_item == choices[1]:
|
524 |
return {
|
525 |
custom_size: gr.update(visible=False),
|
526 |
size_list_row: gr.update(visible=False),
|
527 |
+
face_alignment_options: gr.update(visible=False),
|
528 |
}
|
529 |
+
# 如果选择预设尺寸,则隐藏自定义尺寸组件
|
530 |
else:
|
531 |
return {
|
532 |
custom_size: gr.update(visible=False),
|
533 |
size_list_row: gr.update(visible=True),
|
534 |
+
face_alignment_options: gr.update(visible=True),
|
535 |
}
|
536 |
|
537 |
def change_image_kb(image_kb_option, lang):
|
|
|
587 |
contrast_option,
|
588 |
sharpen_option,
|
589 |
saturation_option,
|
590 |
+
face_alignment_options,
|
591 |
],
|
592 |
)
|
593 |
|
|
|
600 |
mode_options.input(
|
601 |
change_size_mode,
|
602 |
inputs=[mode_options, language_options],
|
603 |
+
outputs=[custom_size, size_list_row, face_alignment_options],
|
604 |
)
|
605 |
|
606 |
image_kb_options.input(
|
|
|
649 |
contrast_option,
|
650 |
sharpen_option,
|
651 |
saturation_option,
|
652 |
+
face_alignment_options,
|
653 |
],
|
654 |
outputs=[
|
655 |
img_output_standard,
|
hivision/creator/__init__.py
CHANGED
@@ -63,6 +63,7 @@ class IDCreator:
|
|
63 |
contrast_strength: int = 0,
|
64 |
sharpen_strength: int = 0,
|
65 |
saturation_strength: int = 0,
|
|
|
66 |
) -> Result:
|
67 |
"""
|
68 |
证件照处理函数
|
@@ -78,6 +79,8 @@ class IDCreator:
|
|
78 |
:param brightness_strength: 亮度强度
|
79 |
:param contrast_strength: 对比度强度
|
80 |
:param sharpen_strength: 锐化强度
|
|
|
|
|
81 |
:return: 返回处理后的证件照和一系列参数
|
82 |
"""
|
83 |
# 0.初始化上下文
|
@@ -94,6 +97,7 @@ class IDCreator:
|
|
94 |
contrast_strength=contrast_strength,
|
95 |
sharpen_strength=sharpen_strength,
|
96 |
saturation_strength=saturation_strength,
|
|
|
97 |
)
|
98 |
|
99 |
self.ctx = Context(params)
|
@@ -105,7 +109,7 @@ class IDCreator:
|
|
105 |
ctx.origin_image = ctx.processing_image.copy()
|
106 |
self.before_all and self.before_all(ctx)
|
107 |
|
108 |
-
# 1.
|
109 |
if not ctx.params.crop_only:
|
110 |
# 调用抠图工作流
|
111 |
self.matting_handler(ctx)
|
@@ -113,7 +117,7 @@ class IDCreator:
|
|
113 |
else:
|
114 |
ctx.matting_image = ctx.processing_image
|
115 |
|
116 |
-
# 2.
|
117 |
self.beauty_handler(ctx)
|
118 |
|
119 |
# 如果仅换底,则直接返回抠图结果
|
@@ -129,16 +133,33 @@ class IDCreator:
|
|
129 |
self.after_all and self.after_all(ctx)
|
130 |
return ctx.result
|
131 |
|
132 |
-
#
|
133 |
self.detection_handler(ctx)
|
134 |
self.after_detect and self.after_detect(ctx)
|
135 |
|
136 |
-
# 3.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
137 |
result_image_hd, result_image_standard, clothing_params, typography_params = (
|
138 |
adjust_photo(ctx)
|
139 |
)
|
140 |
|
141 |
-
#
|
142 |
ctx.result = Result(
|
143 |
standard=result_image_standard,
|
144 |
hd=result_image_hd,
|
@@ -148,4 +169,5 @@ class IDCreator:
|
|
148 |
face=ctx.face,
|
149 |
)
|
150 |
self.after_all and self.after_all(ctx)
|
|
|
151 |
return ctx.result
|
|
|
63 |
contrast_strength: int = 0,
|
64 |
sharpen_strength: int = 0,
|
65 |
saturation_strength: int = 0,
|
66 |
+
face_alignment: bool = False,
|
67 |
) -> Result:
|
68 |
"""
|
69 |
证件照处理函数
|
|
|
79 |
:param brightness_strength: 亮度强度
|
80 |
:param contrast_strength: 对比度强度
|
81 |
:param sharpen_strength: 锐化强度
|
82 |
+
:param align_face: 是否需要人脸矫正
|
83 |
+
|
84 |
:return: 返回处理后的证件照和一系列参数
|
85 |
"""
|
86 |
# 0.初始化上下文
|
|
|
97 |
contrast_strength=contrast_strength,
|
98 |
sharpen_strength=sharpen_strength,
|
99 |
saturation_strength=saturation_strength,
|
100 |
+
face_alignment=face_alignment,
|
101 |
)
|
102 |
|
103 |
self.ctx = Context(params)
|
|
|
109 |
ctx.origin_image = ctx.processing_image.copy()
|
110 |
self.before_all and self.before_all(ctx)
|
111 |
|
112 |
+
# 1. ------------------人像抠图------------------
|
113 |
if not ctx.params.crop_only:
|
114 |
# 调用抠图工作流
|
115 |
self.matting_handler(ctx)
|
|
|
117 |
else:
|
118 |
ctx.matting_image = ctx.processing_image
|
119 |
|
120 |
+
# 2. ------------------美颜------------------
|
121 |
self.beauty_handler(ctx)
|
122 |
|
123 |
# 如果仅换底,则直接返回抠图结果
|
|
|
133 |
self.after_all and self.after_all(ctx)
|
134 |
return ctx.result
|
135 |
|
136 |
+
# 3. ------------------人脸检测------------------
|
137 |
self.detection_handler(ctx)
|
138 |
self.after_detect and self.after_detect(ctx)
|
139 |
|
140 |
+
# 3.1 ------------------人脸对齐------------------
|
141 |
+
if ctx.params.face_alignment and abs(ctx.face["roll_angle"]) > 2:
|
142 |
+
from hivision.creator.rotation_adjust import rotate_bound_4channels
|
143 |
+
|
144 |
+
print("执行人脸对齐")
|
145 |
+
print("旋转角度:", ctx.face["roll_angle"])
|
146 |
+
# 根据角度旋转原图和抠图
|
147 |
+
ctx.origin_image, ctx.matting_image, _, _, _, _ = rotate_bound_4channels(
|
148 |
+
ctx.origin_image,
|
149 |
+
cv2.split(ctx.matting_image)[-1],
|
150 |
+
-1 * ctx.face["roll_angle"],
|
151 |
+
)
|
152 |
+
|
153 |
+
# 旋转后再执行一遍人脸检测
|
154 |
+
self.detection_handler(ctx)
|
155 |
+
self.after_detect and self.after_detect(ctx)
|
156 |
+
|
157 |
+
# 4. ------------------图像调整------------------
|
158 |
result_image_hd, result_image_standard, clothing_params, typography_params = (
|
159 |
adjust_photo(ctx)
|
160 |
)
|
161 |
|
162 |
+
# 5. ------------------返回结果------------------
|
163 |
ctx.result = Result(
|
164 |
standard=result_image_standard,
|
165 |
hd=result_image_hd,
|
|
|
169 |
face=ctx.face,
|
170 |
)
|
171 |
self.after_all and self.after_all(ctx)
|
172 |
+
|
173 |
return ctx.result
|
hivision/creator/context.py
CHANGED
@@ -26,6 +26,7 @@ class Params:
|
|
26 |
contrast_strength: int = 0,
|
27 |
sharpen_strength: int = 0,
|
28 |
saturation_strength: int = 0,
|
|
|
29 |
):
|
30 |
self.__size = size
|
31 |
self.__change_bg_only = change_bg_only
|
@@ -39,6 +40,7 @@ class Params:
|
|
39 |
self.__contrast_strength = contrast_strength
|
40 |
self.__sharpen_strength = sharpen_strength
|
41 |
self.__saturation_strength = saturation_strength
|
|
|
42 |
|
43 |
@property
|
44 |
def size(self):
|
@@ -88,6 +90,10 @@ class Params:
|
|
88 |
def saturation_strength(self):
|
89 |
return self.__saturation_strength
|
90 |
|
|
|
|
|
|
|
|
|
91 |
|
92 |
class Result:
|
93 |
def __init__(
|
@@ -143,15 +149,20 @@ class Context:
|
|
143 |
"""
|
144 |
人像抠图结果
|
145 |
"""
|
146 |
-
self.face:
|
147 |
"""
|
148 |
人脸检测结果,大于一个人脸时已在上层抛出异常
|
149 |
-
|
|
|
150 |
"""
|
151 |
self.result: Optional[Result] = None
|
152 |
"""
|
153 |
证件照处理结果
|
154 |
"""
|
|
|
|
|
|
|
|
|
155 |
|
156 |
|
157 |
ContextHandler = Optional[Callable[[Context], None]]
|
|
|
26 |
contrast_strength: int = 0,
|
27 |
sharpen_strength: int = 0,
|
28 |
saturation_strength: int = 0,
|
29 |
+
face_alignment: bool = False,
|
30 |
):
|
31 |
self.__size = size
|
32 |
self.__change_bg_only = change_bg_only
|
|
|
40 |
self.__contrast_strength = contrast_strength
|
41 |
self.__sharpen_strength = sharpen_strength
|
42 |
self.__saturation_strength = saturation_strength
|
43 |
+
self.__face_alignment = face_alignment
|
44 |
|
45 |
@property
|
46 |
def size(self):
|
|
|
90 |
def saturation_strength(self):
|
91 |
return self.__saturation_strength
|
92 |
|
93 |
+
@property
|
94 |
+
def face_alignment(self):
|
95 |
+
return self.__face_alignment
|
96 |
+
|
97 |
|
98 |
class Result:
|
99 |
def __init__(
|
|
|
149 |
"""
|
150 |
人像抠图结果
|
151 |
"""
|
152 |
+
self.face: dict = dict(rectangle=None, roll_angle=None)
|
153 |
"""
|
154 |
人脸检测结果,大于一个人脸时已在上层抛出异常
|
155 |
+
rectangle: 人脸矩形框,包含 x1, y1, width, height 的坐标, x1, y1 为左上角坐标, width, height 为矩形框的宽度和高度
|
156 |
+
roll_angle: 人脸偏转角度,以眼睛为标准,计算的人脸偏转角度,用于人脸矫正
|
157 |
"""
|
158 |
self.result: Optional[Result] = None
|
159 |
"""
|
160 |
证件照处理结果
|
161 |
"""
|
162 |
+
self.align_info: Optional[dict] = None
|
163 |
+
"""
|
164 |
+
人脸矫正信息,仅当 align_face 为 True 时存在
|
165 |
+
"""
|
166 |
|
167 |
|
168 |
ContextHandler = Optional[Callable[[Context], None]]
|
hivision/creator/face_detector.py
CHANGED
@@ -20,6 +20,7 @@ from hivision.creator.retinaface import retinaface_detect_faces
|
|
20 |
import requests
|
21 |
import cv2
|
22 |
import os
|
|
|
23 |
|
24 |
|
25 |
mtcnn = None
|
@@ -42,11 +43,13 @@ def detect_face_mtcnn(ctx: Context, scale: int = 2):
|
|
42 |
(ctx.origin_image.shape[1] // scale, ctx.origin_image.shape[0] // scale),
|
43 |
interpolation=cv2.INTER_AREA,
|
44 |
)
|
45 |
-
|
|
|
|
|
46 |
# print(len(faces))
|
47 |
if len(faces) != 1:
|
48 |
# 保险措施,如果检测到多个人脸或者没有人脸,用原图再检测一次
|
49 |
-
faces,
|
50 |
else:
|
51 |
# 如果只有一个人脸,将人脸坐标放大
|
52 |
for item, param in enumerate(faces[0]):
|
@@ -54,12 +57,23 @@ def detect_face_mtcnn(ctx: Context, scale: int = 2):
|
|
54 |
if len(faces) != 1:
|
55 |
raise FaceError("Expected 1 face, but got {}".format(len(faces)), len(faces))
|
56 |
|
|
|
57 |
left = faces[0][0]
|
58 |
top = faces[0][1]
|
59 |
width = faces[0][2] - left + 1
|
60 |
height = faces[0][3] - top + 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
|
62 |
-
ctx.face =
|
63 |
|
64 |
|
65 |
def detect_face_face_plusplus(ctx: Context):
|
@@ -83,6 +97,8 @@ def detect_face_face_plusplus(ctx: Context):
|
|
83 |
"api_key": (None, api_key),
|
84 |
"api_secret": (None, api_secret),
|
85 |
"image_base64": (None, image_base64),
|
|
|
|
|
86 |
}
|
87 |
|
88 |
# 发送 POST 请求
|
@@ -96,12 +112,24 @@ def detect_face_face_plusplus(ctx: Context):
|
|
96 |
face_num = response_json["face_num"]
|
97 |
if face_num == 1:
|
98 |
face_rectangle = response_json["faces"][0]["face_rectangle"]
|
99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
face_rectangle["left"],
|
101 |
face_rectangle["top"],
|
102 |
face_rectangle["width"],
|
103 |
face_rectangle["height"],
|
104 |
)
|
|
|
105 |
else:
|
106 |
raise FaceError(
|
107 |
"Expected 1 face, but got {}".format(face_num), len(face_num)
|
@@ -165,12 +193,26 @@ def detect_face_retinaface(ctx: Context):
|
|
165 |
print("二次RetinaFace模型推理用时: {:.4f}s".format(time() - tic))
|
166 |
|
167 |
faces_num = len(faces_dets)
|
|
|
|
|
|
|
|
|
168 |
if faces_num != 1:
|
169 |
raise FaceError("Expected 1 face, but got {}".format(faces_num), faces_num)
|
170 |
face_det = faces_dets[0]
|
171 |
-
ctx.face = (
|
172 |
face_det[0],
|
173 |
face_det[1],
|
174 |
face_det[2] - face_det[0] + 1,
|
175 |
face_det[3] - face_det[1] + 1,
|
176 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
import requests
|
21 |
import cv2
|
22 |
import os
|
23 |
+
import numpy as np
|
24 |
|
25 |
|
26 |
mtcnn = None
|
|
|
43 |
(ctx.origin_image.shape[1] // scale, ctx.origin_image.shape[0] // scale),
|
44 |
interpolation=cv2.INTER_AREA,
|
45 |
)
|
46 |
+
# landmarks 是 5 个关键点,分别是左眼、右眼、鼻子、左嘴角、右嘴角,
|
47 |
+
faces, landmarks = mtcnn.detect(image, thresholds=[0.8, 0.8, 0.8])
|
48 |
+
|
49 |
# print(len(faces))
|
50 |
if len(faces) != 1:
|
51 |
# 保险措施,如果检测到多个人脸或者没有人脸,用原图再检测一次
|
52 |
+
faces, landmarks = mtcnn.detect(ctx.origin_image)
|
53 |
else:
|
54 |
# 如果只有一个人脸,将人脸坐标放大
|
55 |
for item, param in enumerate(faces[0]):
|
|
|
57 |
if len(faces) != 1:
|
58 |
raise FaceError("Expected 1 face, but got {}".format(len(faces)), len(faces))
|
59 |
|
60 |
+
# 计算人脸坐标
|
61 |
left = faces[0][0]
|
62 |
top = faces[0][1]
|
63 |
width = faces[0][2] - left + 1
|
64 |
height = faces[0][3] - top + 1
|
65 |
+
ctx.face["rectangle"] = (left, top, width, height)
|
66 |
+
|
67 |
+
# 根据landmarks计算人脸偏转角度,以眼睛为标准,计算的人脸偏转角度,用于人脸矫正
|
68 |
+
# 示例landmarks [106.37181 150.77415 127.21012 108.369156 144.61522 105.24723 107.45625 133.62355 151.24269 153.34407 ]
|
69 |
+
landmarks = landmarks[0]
|
70 |
+
left_eye = np.array([landmarks[0], landmarks[5]])
|
71 |
+
right_eye = np.array([landmarks[1], landmarks[6]])
|
72 |
+
dy = right_eye[1] - left_eye[1]
|
73 |
+
dx = right_eye[0] - left_eye[0]
|
74 |
+
roll_angle = np.degrees(np.arctan2(dy, dx))
|
75 |
|
76 |
+
ctx.face["roll_angle"] = roll_angle
|
77 |
|
78 |
|
79 |
def detect_face_face_plusplus(ctx: Context):
|
|
|
97 |
"api_key": (None, api_key),
|
98 |
"api_secret": (None, api_secret),
|
99 |
"image_base64": (None, image_base64),
|
100 |
+
"return_landmark": (None, "1"),
|
101 |
+
"return_attributes": (None, "headpose"),
|
102 |
}
|
103 |
|
104 |
# 发送 POST 请求
|
|
|
112 |
face_num = response_json["face_num"]
|
113 |
if face_num == 1:
|
114 |
face_rectangle = response_json["faces"][0]["face_rectangle"]
|
115 |
+
|
116 |
+
# 获取人脸关键点
|
117 |
+
# landmarks = response_json["faces"][0]["landmark"]
|
118 |
+
# print("face++ landmarks", landmarks)
|
119 |
+
|
120 |
+
# headpose 是一个字典,包含俯仰角(pitch)、偏航角(yaw)和滚转角(roll)
|
121 |
+
# headpose示例 {'pitch_angle': 6.997899, 'roll_angle': 1.8011835, 'yaw_angle': 5.043002}
|
122 |
+
headpose = response_json["faces"][0]["attributes"]["headpose"]
|
123 |
+
# 以眼睛为标准,计算的人脸偏转角度,用于人脸矫正
|
124 |
+
roll_angle = headpose["roll_angle"] / 2
|
125 |
+
|
126 |
+
ctx.face["rectangle"] = (
|
127 |
face_rectangle["left"],
|
128 |
face_rectangle["top"],
|
129 |
face_rectangle["width"],
|
130 |
face_rectangle["height"],
|
131 |
)
|
132 |
+
ctx.face["roll_angle"] = roll_angle
|
133 |
else:
|
134 |
raise FaceError(
|
135 |
"Expected 1 face, but got {}".format(face_num), len(face_num)
|
|
|
193 |
print("二次RetinaFace模型推理用时: {:.4f}s".format(time() - tic))
|
194 |
|
195 |
faces_num = len(faces_dets)
|
196 |
+
faces_landmarks = []
|
197 |
+
for face_det in faces_dets:
|
198 |
+
faces_landmarks.append(face_det[5:])
|
199 |
+
|
200 |
if faces_num != 1:
|
201 |
raise FaceError("Expected 1 face, but got {}".format(faces_num), faces_num)
|
202 |
face_det = faces_dets[0]
|
203 |
+
ctx.face["rectangle"] = (
|
204 |
face_det[0],
|
205 |
face_det[1],
|
206 |
face_det[2] - face_det[0] + 1,
|
207 |
face_det[3] - face_det[1] + 1,
|
208 |
)
|
209 |
+
|
210 |
+
# 计算roll_angle
|
211 |
+
face_landmarks = faces_landmarks[0]
|
212 |
+
# print("face_landmarks", face_landmarks)
|
213 |
+
left_eye = np.array([face_landmarks[0], face_landmarks[1]])
|
214 |
+
right_eye = np.array([face_landmarks[2], face_landmarks[3]])
|
215 |
+
dy = right_eye[1] - left_eye[1]
|
216 |
+
dx = right_eye[0] - left_eye[0]
|
217 |
+
roll_angle = np.degrees(np.arctan2(dy, dx))
|
218 |
+
ctx.face["roll_angle"] = roll_angle
|
hivision/creator/photo_adjuster.py
CHANGED
@@ -17,7 +17,7 @@ import cv2
|
|
17 |
|
18 |
def adjust_photo(ctx: Context):
|
19 |
# Step1. 准备人脸参数
|
20 |
-
face_rect = ctx.face
|
21 |
standard_size = ctx.params.size
|
22 |
params = ctx.params
|
23 |
x, y = face_rect[0], face_rect[1]
|
|
|
17 |
|
18 |
def adjust_photo(ctx: Context):
|
19 |
# Step1. 准备人脸参数
|
20 |
+
face_rect = ctx.face["rectangle"]
|
21 |
standard_size = ctx.params.size
|
22 |
params = ctx.params
|
23 |
x, y = face_rect[0], face_rect[1]
|
hivision/creator/rotation_adjust.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
人脸旋转矫正模块
|
3 |
+
|
4 |
+
本模块提供了用于旋转图像的函数,主要用于人脸旋转矫正。
|
5 |
+
包含了处理3通道和4通道图像的旋转函数。
|
6 |
+
"""
|
7 |
+
|
8 |
+
import cv2
|
9 |
+
import numpy as np
|
10 |
+
|
11 |
+
|
12 |
+
def rotate_bound(image: np.ndarray, angle: float, center=None):
|
13 |
+
"""
|
14 |
+
旋转图像而不损失信息的函数
|
15 |
+
|
16 |
+
Args:
|
17 |
+
image (np.ndarray): 输入图像,3通道numpy数组
|
18 |
+
angle (float): 旋转角度(度)
|
19 |
+
center (tuple, optional): 旋转中心坐标,默认为图像中心
|
20 |
+
|
21 |
+
Returns:
|
22 |
+
tuple: 包含以下元素的元组:
|
23 |
+
- rotated (np.ndarray): 旋转后的图像
|
24 |
+
- cos (float): 旋转角度的余弦值
|
25 |
+
- sin (float): 旋转角度的正弦值
|
26 |
+
- dW (int): 宽度变化量
|
27 |
+
- dH (int): 高度变化量
|
28 |
+
"""
|
29 |
+
print("rotate_bound", image.shape)
|
30 |
+
(h, w) = image.shape[:2]
|
31 |
+
if center is None:
|
32 |
+
(cX, cY) = (w / 2, h / 2)
|
33 |
+
else:
|
34 |
+
(cX, cY) = center
|
35 |
+
|
36 |
+
M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
|
37 |
+
cos = np.abs(M[0, 0])
|
38 |
+
sin = np.abs(M[0, 1])
|
39 |
+
|
40 |
+
nW = int((h * sin) + (w * cos))
|
41 |
+
nH = int((h * cos) + (w * sin))
|
42 |
+
|
43 |
+
M[0, 2] += (nW / 2) - cX
|
44 |
+
M[1, 2] += (nH / 2) - cY
|
45 |
+
|
46 |
+
rotated = cv2.warpAffine(image, M, (nW, nH))
|
47 |
+
|
48 |
+
# 计算偏移量
|
49 |
+
dW = nW - w
|
50 |
+
dH = nH - h
|
51 |
+
|
52 |
+
return rotated, cos, sin, dW, dH
|
53 |
+
|
54 |
+
|
55 |
+
def rotate_bound_4channels(image: np.ndarray, a: np.ndarray, angle: float, center=None):
|
56 |
+
"""
|
57 |
+
旋转4通道图像的函数
|
58 |
+
|
59 |
+
这是rotate_bound函数的4通道版本,可以同时处理RGB图像和其对应的alpha通道。
|
60 |
+
|
61 |
+
Args:
|
62 |
+
image (np.ndarray): 输入的3通道RGB图像
|
63 |
+
a (np.ndarray): 输入图像的alpha通道
|
64 |
+
angle (float): 旋转角度(度)
|
65 |
+
center (tuple, optional): 旋转中心坐标,默认为图像中心
|
66 |
+
|
67 |
+
Returns:
|
68 |
+
tuple: 包含以下元素的元组:
|
69 |
+
- input_image (np.ndarray): 旋转后的3通道RGB图像
|
70 |
+
- result_image (np.ndarray): 旋转后的4通道RGBA图像
|
71 |
+
- cos (float): 旋转角度的余弦值
|
72 |
+
- sin (float): 旋转角度的正弦值
|
73 |
+
- dW (int): 宽度变化量
|
74 |
+
- dH (int): 高度变化量
|
75 |
+
"""
|
76 |
+
input_image, cos, sin, dW, dH = rotate_bound(image, angle, center)
|
77 |
+
new_a, _, _, _, _ = rotate_bound(a, angle, center) # 对alpha通道进行旋转
|
78 |
+
b, g, r = cv2.split(input_image)
|
79 |
+
result_image = cv2.merge((b, g, r, new_a)) # 合并旋转后的RGB通道和alpha通道
|
80 |
+
|
81 |
+
return input_image, result_image, cos, sin, dW, dH
|
hivision/creator/utils.py
CHANGED
@@ -140,3 +140,67 @@ def detect_distance(value, crop_height, max=0.06, min=0.04):
|
|
140 |
move_value = int(move_value * crop_height)
|
141 |
# print("下移{}".format(move_value))
|
142 |
return -1, move_value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
move_value = int(move_value * crop_height)
|
141 |
# print("下移{}".format(move_value))
|
142 |
return -1, move_value
|
143 |
+
|
144 |
+
|
145 |
+
def cutting_rect_pan(
|
146 |
+
x1, y1, x2, y2, width, height, L1, L2, L3, clockwise, standard_size
|
147 |
+
):
|
148 |
+
"""
|
149 |
+
本函数的功能是对旋转矫正结果图的裁剪框进行修正 ———— 解决"旋转三角形"现象。
|
150 |
+
Args:
|
151 |
+
- x1: int, 裁剪框左上角的横坐标
|
152 |
+
- y1: int, 裁剪框左上角的纵坐标
|
153 |
+
- x2: int, 裁剪框右下角的横坐标
|
154 |
+
- y2: int, 裁剪框右下角的纵坐标
|
155 |
+
- width: int, 待裁剪图的宽度
|
156 |
+
- height:int, 待裁剪图的高度
|
157 |
+
- L1: CLassObject, 根据旋转点连线所构造函数
|
158 |
+
- L2: CLassObject, 根据旋转点连线所构造函数
|
159 |
+
- L3: ClassObject, 一个特殊裁切点的坐标
|
160 |
+
- clockwise: int, 旋转时针状态
|
161 |
+
- standard_size: tuple, 标准照的尺寸
|
162 |
+
|
163 |
+
Returns:
|
164 |
+
- x1: int, 新的裁剪框左上角的横坐标
|
165 |
+
- y1: int, 新的裁剪框左上角的纵坐标
|
166 |
+
- x2: int, 新的裁剪框右下角的横坐标
|
167 |
+
- y2: int, 新的裁剪框右下角的纵坐标
|
168 |
+
- x_bias: int, 裁剪框横坐标方向上的计算偏置量
|
169 |
+
- y_bias: int, 裁剪框纵坐标方向上的计算偏置量
|
170 |
+
"""
|
171 |
+
# 用于计算的裁剪框坐标x1_cal,x2_cal,y1_cal,y2_cal(如果裁剪框超出了图像范围,则缩小直至在范围内)
|
172 |
+
x1_std = x1 if x1 > 0 else 0
|
173 |
+
x2_std = x2 if x2 < width else width
|
174 |
+
# y1_std = y1 if y1 > 0 else 0
|
175 |
+
y2_std = y2 if y2 < height else height
|
176 |
+
|
177 |
+
# 初始化x和y的计算偏置项x_bias和y_bias
|
178 |
+
x_bias = 0
|
179 |
+
y_bias = 0
|
180 |
+
|
181 |
+
# 如果顺时针偏转
|
182 |
+
if clockwise == 1:
|
183 |
+
if y2 > L1.forward_x(x1_std):
|
184 |
+
y_bias = int(-(y2_std - L1.forward_x(x1_std)))
|
185 |
+
if y2 > L2.forward_x(x2_std):
|
186 |
+
x_bias = int(-(x2_std - L2.forward_y(y2_std)))
|
187 |
+
x2 = x2_std + x_bias
|
188 |
+
if x1 < L3.x:
|
189 |
+
x1 = L3.x
|
190 |
+
# 如果逆时针偏转
|
191 |
+
else:
|
192 |
+
if y2 > L1.forward_x(x1_std):
|
193 |
+
x_bias = int(L1.forward_y(y2_std) - x1_std)
|
194 |
+
if y2 > L2.forward_x(x2_std):
|
195 |
+
y_bias = int(-(y2_std - L2.forward_x(x2_std)))
|
196 |
+
x1 = x1_std + x_bias
|
197 |
+
if x2 > L3.x:
|
198 |
+
x2 = L3.x
|
199 |
+
|
200 |
+
# 计算裁剪框的y的变化
|
201 |
+
y2 = int(y2_std + y_bias)
|
202 |
+
new_cut_width = x2 - x1
|
203 |
+
new_cut_height = int(new_cut_width / standard_size[1] * standard_size[0])
|
204 |
+
y1 = y2 - new_cut_height
|
205 |
+
|
206 |
+
return x1, y1, x2, y2, x_bias, y_bias
|