Spaces:
Running
Running
TheEeeeLin
commited on
Commit
•
cc3e5bb
1
Parent(s):
72049af
EN demo
Browse files- app.py +250 -62
- size_list_EN.csv +16 -0
- src/face_judgement_align.py +254 -129
app.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import gradio as gr
|
2 |
import onnxruntime
|
3 |
from src.face_judgement_align import IDphotos_create
|
@@ -7,24 +8,33 @@ import pathlib
|
|
7 |
import numpy as np
|
8 |
from image_utils import resize_image_to_kb
|
9 |
from data_utils import csv_to_size_list
|
|
|
|
|
10 |
|
11 |
# 获取尺寸列表
|
12 |
-
|
13 |
-
|
|
|
14 |
|
15 |
-
|
16 |
"蓝色": (86, 140, 212),
|
17 |
"白色": (255, 255, 255),
|
18 |
"红色": (233, 51, 35),
|
19 |
}
|
20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
-
# 设置Gradio examples
|
23 |
def set_example_image(example: list) -> dict:
|
24 |
return gr.Image.update(value=example[0])
|
25 |
|
26 |
|
27 |
-
# 检测RGB是否超出范围,如果超出则约束到0~255之间
|
28 |
def range_check(value, min_value=0, max_value=255):
|
29 |
value = int(value)
|
30 |
if value <= min_value:
|
@@ -47,12 +57,12 @@ def idphoto_inference(
|
|
47 |
custom_size_height,
|
48 |
custom_size_width,
|
49 |
custom_image_kb,
|
|
|
50 |
head_measure_ratio=0.2,
|
51 |
head_height_ratio=0.45,
|
52 |
top_distance_max=0.12,
|
53 |
top_distance_min=0.10,
|
54 |
):
|
55 |
-
|
56 |
idphoto_json = {
|
57 |
"size_mode": mode_option,
|
58 |
"color_mode": color_option,
|
@@ -60,11 +70,45 @@ def idphoto_inference(
|
|
60 |
"image_kb_mode": image_kb_options,
|
61 |
}
|
62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
# 如果尺寸模式选择的是尺寸列表
|
64 |
-
if idphoto_json["size_mode"] == "
|
65 |
-
|
|
|
|
|
|
|
66 |
# 如果尺寸模式选择的是自定义尺寸
|
67 |
-
elif idphoto_json["size_mode"] == "
|
68 |
id_height = int(custom_size_height)
|
69 |
id_width = int(custom_size_width)
|
70 |
if (
|
@@ -76,7 +120,10 @@ def idphoto_inference(
|
|
76 |
img_output_standard: gr.update(value=None),
|
77 |
img_output_standard_hd: gr.update(value=None),
|
78 |
notification: gr.update(
|
79 |
-
value=
|
|
|
|
|
|
|
80 |
),
|
81 |
}
|
82 |
idphoto_json["size"] = (id_height, id_width)
|
@@ -84,17 +131,20 @@ def idphoto_inference(
|
|
84 |
idphoto_json["size"] = (None, None)
|
85 |
|
86 |
# 如果颜色模式选择的是自定义底色
|
87 |
-
if idphoto_json["color_mode"] == "
|
88 |
idphoto_json["color_bgr"] = (
|
89 |
range_check(custom_color_R),
|
90 |
range_check(custom_color_G),
|
91 |
range_check(custom_color_B),
|
92 |
)
|
93 |
else:
|
94 |
-
|
|
|
|
|
|
|
95 |
|
96 |
-
# 如果输出KB大小选择的是自定义
|
97 |
-
if idphoto_json["image_kb_mode"] == "
|
98 |
idphoto_json["custom_image_kb"] = custom_image_kb
|
99 |
else:
|
100 |
idphoto_json["custom_image_kb"] = None
|
@@ -125,24 +175,30 @@ def idphoto_inference(
|
|
125 |
top_distance_min=top_distance_min,
|
126 |
)
|
127 |
|
128 |
-
# 如果检测到人脸数量不等于1
|
129 |
if status == 0:
|
130 |
result_messgae = {
|
131 |
img_output_standard: gr.update(value=None),
|
132 |
img_output_standard_hd: gr.update(value=None),
|
133 |
-
notification: gr.update(
|
|
|
|
|
|
|
134 |
}
|
135 |
|
136 |
-
# 如果检测到人脸数量等于1
|
137 |
else:
|
138 |
-
if idphoto_json["render_mode"] == "
|
139 |
result_image_standard = np.uint8(
|
140 |
add_background(result_image_standard, bgr=idphoto_json["color_bgr"])
|
141 |
)
|
142 |
result_image_hd = np.uint8(
|
143 |
add_background(result_image_hd, bgr=idphoto_json["color_bgr"])
|
144 |
)
|
145 |
-
elif
|
|
|
|
|
|
|
146 |
result_image_standard = np.uint8(
|
147 |
add_background(
|
148 |
result_image_standard,
|
@@ -173,7 +229,10 @@ def idphoto_inference(
|
|
173 |
)
|
174 |
)
|
175 |
|
176 |
-
if
|
|
|
|
|
|
|
177 |
result_layout_image = gr.update(visible=False)
|
178 |
else:
|
179 |
typography_arr, typography_rotate = generate_layout_photo(
|
@@ -189,11 +248,11 @@ def idphoto_inference(
|
|
189 |
width=idphoto_json["size"][1],
|
190 |
)
|
191 |
|
192 |
-
# 如果输出KB大小选择的是自定义
|
193 |
if idphoto_json["custom_image_kb"]:
|
194 |
# 将标准照大小调整至目标大小
|
195 |
-
print("调整kb大小到", idphoto_json["custom_image_kb"], "kb")
|
196 |
-
#
|
197 |
import time
|
198 |
|
199 |
output_image_path = f"./output/{int(time.time())}.jpg"
|
@@ -226,18 +285,28 @@ def idphoto_inference(
|
|
226 |
|
227 |
|
228 |
if __name__ == "__main__":
|
229 |
-
# 预加载ONNX模型
|
230 |
-
HY_HUMAN_MATTING_WEIGHTS_PATH = "
|
231 |
sess = onnxruntime.InferenceSession(HY_HUMAN_MATTING_WEIGHTS_PATH)
|
232 |
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
|
239 |
title = "<h1 id='title'>HivisionIDPhotos</h1>"
|
240 |
-
description = "<h3>😎9.2
|
241 |
css = """
|
242 |
h1#title, h3 {
|
243 |
text-align: center;
|
@@ -250,21 +319,26 @@ if __name__ == "__main__":
|
|
250 |
gr.Markdown(title)
|
251 |
gr.Markdown(description)
|
252 |
with gr.Row():
|
253 |
-
# ------------ 左半边UI ----------------
|
254 |
with gr.Column():
|
255 |
img_input = gr.Image().style(height=350)
|
|
|
|
|
|
|
|
|
256 |
mode_options = gr.Radio(
|
257 |
-
choices=
|
258 |
-
label="
|
259 |
-
value="
|
260 |
elem_id="size",
|
261 |
)
|
|
|
262 |
# 预设尺寸下拉菜单
|
263 |
with gr.Row(visible=True) as size_list_row:
|
264 |
size_list_options = gr.Dropdown(
|
265 |
-
choices=
|
266 |
-
label="
|
267 |
-
value="
|
268 |
elem_id="size_list",
|
269 |
)
|
270 |
|
@@ -278,10 +352,10 @@ if __name__ == "__main__":
|
|
278 |
|
279 |
# 左:背景色选项
|
280 |
color_options = gr.Radio(
|
281 |
-
choices=
|
282 |
)
|
283 |
|
284 |
-
# 左:如果选择「自定义底色」,显示RGB输入框
|
285 |
with gr.Row(visible=False) as custom_color:
|
286 |
custom_color_R = gr.Number(value=0, label="R", interactive=True)
|
287 |
custom_color_G = gr.Number(value=0, label="G", interactive=True)
|
@@ -289,64 +363,149 @@ if __name__ == "__main__":
|
|
289 |
|
290 |
# 左:渲染方式选项
|
291 |
render_options = gr.Radio(
|
292 |
-
choices=
|
293 |
-
label="
|
294 |
-
value="
|
295 |
elem_id="render",
|
296 |
)
|
297 |
|
298 |
-
# 左:输出KB大小选项
|
299 |
image_kb_options = gr.Radio(
|
300 |
-
choices=
|
301 |
-
label="
|
302 |
-
value="
|
303 |
elem_id="image_kb",
|
304 |
)
|
305 |
|
306 |
-
# 自定义KB
|
307 |
with gr.Row(visible=False) as custom_image_kb:
|
308 |
custom_image_kb_size = gr.Slider(
|
309 |
minimum=10,
|
310 |
maximum=1000,
|
311 |
value=50,
|
312 |
-
label="KB
|
313 |
interactive=True,
|
314 |
)
|
315 |
|
316 |
-
img_but = gr.Button("
|
317 |
|
318 |
# 案例图片
|
319 |
example_images = gr.Dataset(
|
320 |
components=[img_input],
|
321 |
samples=[
|
322 |
[path.as_posix()]
|
323 |
-
for path in sorted(
|
|
|
|
|
|
|
|
|
324 |
],
|
325 |
)
|
326 |
|
327 |
-
# ---------------- 右半边UI ----------------
|
328 |
with gr.Column():
|
329 |
-
notification = gr.Text(label="
|
330 |
with gr.Row():
|
331 |
-
img_output_standard = gr.Image(label="
|
332 |
-
img_output_standard_hd = gr.Image(label="
|
333 |
-
img_output_layout = gr.Image(label="
|
334 |
-
file_download = gr.File(label="
|
335 |
|
336 |
# ---------------- 设置隐藏/显示组件 ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
337 |
def change_color(colors):
|
338 |
-
if colors == "自定义底色":
|
339 |
return {custom_color: gr.update(visible=True)}
|
340 |
else:
|
341 |
return {custom_color: gr.update(visible=False)}
|
342 |
|
343 |
def change_size_mode(size_option_item):
|
344 |
-
if
|
|
|
|
|
|
|
345 |
return {
|
346 |
custom_size: gr.update(visible=True),
|
347 |
size_list_row: gr.update(visible=False),
|
348 |
}
|
349 |
-
elif
|
|
|
|
|
|
|
350 |
return {
|
351 |
custom_size: gr.update(visible=False),
|
352 |
size_list_row: gr.update(visible=False),
|
@@ -358,12 +517,31 @@ if __name__ == "__main__":
|
|
358 |
}
|
359 |
|
360 |
def change_image_kb(image_kb_option):
|
361 |
-
if image_kb_option == "自定义":
|
362 |
return {custom_image_kb: gr.update(visible=True)}
|
363 |
else:
|
364 |
return {custom_image_kb: gr.update(visible=False)}
|
365 |
|
366 |
# ---------------- 绑定事件 ----------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
367 |
color_options.input(
|
368 |
change_color, inputs=[color_options], outputs=[custom_color]
|
369 |
)
|
@@ -393,6 +571,7 @@ if __name__ == "__main__":
|
|
393 |
custom_size_height,
|
394 |
custom_size_wdith,
|
395 |
custom_image_kb_size,
|
|
|
396 |
],
|
397 |
outputs=[
|
398 |
img_output_standard,
|
@@ -407,4 +586,13 @@ if __name__ == "__main__":
|
|
407 |
fn=set_example_image, inputs=[example_images], outputs=[img_input]
|
408 |
)
|
409 |
|
410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
import gradio as gr
|
3 |
import onnxruntime
|
4 |
from src.face_judgement_align import IDphotos_create
|
|
|
8 |
import numpy as np
|
9 |
from image_utils import resize_image_to_kb
|
10 |
from data_utils import csv_to_size_list
|
11 |
+
import argparse
|
12 |
+
|
13 |
|
14 |
# 获取尺寸列表
|
15 |
+
root_dir = os.path.dirname(os.path.abspath(__file__))
|
16 |
+
size_list_dict_CN = csv_to_size_list(os.path.join(root_dir, "size_list_CN.csv"))
|
17 |
+
size_list_dict_EN = csv_to_size_list(os.path.join(root_dir, "size_list_EN.csv"))
|
18 |
|
19 |
+
color_list_dict_CN = {
|
20 |
"蓝色": (86, 140, 212),
|
21 |
"白色": (255, 255, 255),
|
22 |
"红色": (233, 51, 35),
|
23 |
}
|
24 |
|
25 |
+
color_list_dict_EN = {
|
26 |
+
"Blue": (86, 140, 212),
|
27 |
+
"White": (255, 255, 255),
|
28 |
+
"Red": (233, 51, 35),
|
29 |
+
}
|
30 |
+
|
31 |
|
32 |
+
# 设置 Gradio examples
|
33 |
def set_example_image(example: list) -> dict:
|
34 |
return gr.Image.update(value=example[0])
|
35 |
|
36 |
|
37 |
+
# 检测 RGB 是否超出范围,如果超出则约束到 0~255 之间
|
38 |
def range_check(value, min_value=0, max_value=255):
|
39 |
value = int(value)
|
40 |
if value <= min_value:
|
|
|
57 |
custom_size_height,
|
58 |
custom_size_width,
|
59 |
custom_image_kb,
|
60 |
+
language,
|
61 |
head_measure_ratio=0.2,
|
62 |
head_height_ratio=0.45,
|
63 |
top_distance_max=0.12,
|
64 |
top_distance_min=0.10,
|
65 |
):
|
|
|
66 |
idphoto_json = {
|
67 |
"size_mode": mode_option,
|
68 |
"color_mode": color_option,
|
|
|
70 |
"image_kb_mode": image_kb_options,
|
71 |
}
|
72 |
|
73 |
+
text_lang_map = {
|
74 |
+
"中文": {
|
75 |
+
"Size List": "尺寸列表",
|
76 |
+
"Custom Size": "自定义尺寸",
|
77 |
+
"The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.": "宽度应不大于长度;长宽不应小于 100,大于 1800",
|
78 |
+
"Custom Color": "自定义底色",
|
79 |
+
"Custom": "自定义",
|
80 |
+
"The number of faces is not equal to 1": "人脸数量不等于 1",
|
81 |
+
"Solid Color": "纯色",
|
82 |
+
"Up-Down Gradient (White)": "上下渐变 (白)",
|
83 |
+
"Center Gradient (White)": "中心渐变 (白)",
|
84 |
+
"Set KB size (Download in the bottom right)": "设置 KB 大小(结果在右边最底的组件下载)",
|
85 |
+
"Not Set": "不设置",
|
86 |
+
"Only Change Background": "只换底",
|
87 |
+
},
|
88 |
+
"English": {
|
89 |
+
"Size List": "Size List",
|
90 |
+
"Custom Size": "Custom Size",
|
91 |
+
"The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.": "The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800.",
|
92 |
+
"Custom Color": "Custom Color",
|
93 |
+
"Custom": "Custom",
|
94 |
+
"The number of faces is not equal to 1": "The number of faces is not equal to 1",
|
95 |
+
"Solid Color": "Solid Color",
|
96 |
+
"Up-Down Gradient (White)": "Up-Down Gradient (White)",
|
97 |
+
"Center Gradient (White)": "Center Gradient (White)",
|
98 |
+
"Set KB size (Download in the bottom right)": "Set KB size (Download in the bottom right)",
|
99 |
+
"Not Set": "Not Set",
|
100 |
+
"Only Change Background": "Only Change Background",
|
101 |
+
},
|
102 |
+
}
|
103 |
+
|
104 |
# 如果尺寸模式选择的是尺寸列表
|
105 |
+
if idphoto_json["size_mode"] == text_lang_map[language]["Size List"]:
|
106 |
+
if language == "中文":
|
107 |
+
idphoto_json["size"] = size_list_dict_CN[size_list_option]
|
108 |
+
else:
|
109 |
+
idphoto_json["size"] = size_list_dict_EN[size_list_option]
|
110 |
# 如果尺寸模式选择的是自定义尺寸
|
111 |
+
elif idphoto_json["size_mode"] == text_lang_map[language]["Custom Size"]:
|
112 |
id_height = int(custom_size_height)
|
113 |
id_width = int(custom_size_width)
|
114 |
if (
|
|
|
120 |
img_output_standard: gr.update(value=None),
|
121 |
img_output_standard_hd: gr.update(value=None),
|
122 |
notification: gr.update(
|
123 |
+
value=text_lang_map[language][
|
124 |
+
"The width should not be greater than the length; the length and width should not be less than 100, and no more than 1800."
|
125 |
+
],
|
126 |
+
visible=True,
|
127 |
),
|
128 |
}
|
129 |
idphoto_json["size"] = (id_height, id_width)
|
|
|
131 |
idphoto_json["size"] = (None, None)
|
132 |
|
133 |
# 如果颜色模式选择的是自定义底色
|
134 |
+
if idphoto_json["color_mode"] == text_lang_map[language]["Custom Color"]:
|
135 |
idphoto_json["color_bgr"] = (
|
136 |
range_check(custom_color_R),
|
137 |
range_check(custom_color_G),
|
138 |
range_check(custom_color_B),
|
139 |
)
|
140 |
else:
|
141 |
+
if language == "中文":
|
142 |
+
idphoto_json["color_bgr"] = color_list_dict_CN[color_option]
|
143 |
+
else:
|
144 |
+
idphoto_json["color_bgr"] = color_list_dict_EN[color_option]
|
145 |
|
146 |
+
# 如果输出 KB 大小选择的是自定义
|
147 |
+
if idphoto_json["image_kb_mode"] == text_lang_map[language]["Custom"]:
|
148 |
idphoto_json["custom_image_kb"] = custom_image_kb
|
149 |
else:
|
150 |
idphoto_json["custom_image_kb"] = None
|
|
|
175 |
top_distance_min=top_distance_min,
|
176 |
)
|
177 |
|
178 |
+
# 如果检测到人脸数量不等于 1
|
179 |
if status == 0:
|
180 |
result_messgae = {
|
181 |
img_output_standard: gr.update(value=None),
|
182 |
img_output_standard_hd: gr.update(value=None),
|
183 |
+
notification: gr.update(
|
184 |
+
value=text_lang_map[language]["The number of faces is not equal to 1"],
|
185 |
+
visible=True,
|
186 |
+
),
|
187 |
}
|
188 |
|
189 |
+
# 如果检测到人脸数量等于 1
|
190 |
else:
|
191 |
+
if idphoto_json["render_mode"] == text_lang_map[language]["Solid Color"]:
|
192 |
result_image_standard = np.uint8(
|
193 |
add_background(result_image_standard, bgr=idphoto_json["color_bgr"])
|
194 |
)
|
195 |
result_image_hd = np.uint8(
|
196 |
add_background(result_image_hd, bgr=idphoto_json["color_bgr"])
|
197 |
)
|
198 |
+
elif (
|
199 |
+
idphoto_json["render_mode"]
|
200 |
+
== text_lang_map[language]["Up-Down Gradient (White)"]
|
201 |
+
):
|
202 |
result_image_standard = np.uint8(
|
203 |
add_background(
|
204 |
result_image_standard,
|
|
|
229 |
)
|
230 |
)
|
231 |
|
232 |
+
if (
|
233 |
+
idphoto_json["size_mode"]
|
234 |
+
== text_lang_map[language]["Only Change Background"]
|
235 |
+
):
|
236 |
result_layout_image = gr.update(visible=False)
|
237 |
else:
|
238 |
typography_arr, typography_rotate = generate_layout_photo(
|
|
|
248 |
width=idphoto_json["size"][1],
|
249 |
)
|
250 |
|
251 |
+
# 如果输出 KB 大小选择的是自定义
|
252 |
if idphoto_json["custom_image_kb"]:
|
253 |
# 将标准照大小调整至目标大小
|
254 |
+
print("调整 kb 大小到", idphoto_json["custom_image_kb"], "kb")
|
255 |
+
# 输出路径为一个根据时间戳 + 哈希值生成的随机文件名
|
256 |
import time
|
257 |
|
258 |
output_image_path = f"./output/{int(time.time())}.jpg"
|
|
|
285 |
|
286 |
|
287 |
if __name__ == "__main__":
|
288 |
+
# 预加载 ONNX 模型
|
289 |
+
HY_HUMAN_MATTING_WEIGHTS_PATH = os.path.join(root_dir, "hivision_modnet.onnx")
|
290 |
sess = onnxruntime.InferenceSession(HY_HUMAN_MATTING_WEIGHTS_PATH)
|
291 |
|
292 |
+
language = ["中文", "English"]
|
293 |
+
size_mode_CN = ["尺寸列表", "只换底", "自定义尺寸"]
|
294 |
+
size_mode_EN = ["Size List", "Only Change Background", "Custom Size"]
|
295 |
+
|
296 |
+
size_list_CN = list(size_list_dict_CN.keys())
|
297 |
+
size_list_EN = list(size_list_dict_EN.keys())
|
298 |
+
|
299 |
+
colors_CN = ["蓝色", "白色", "红色", "自定义底色"]
|
300 |
+
colors_EN = ["Blue", "White", "Red", "Custom Color"]
|
301 |
+
|
302 |
+
render_CN = ["纯色", "上下渐变 (白)", "中心渐变 (白)"]
|
303 |
+
render_EN = ["Solid Color", "Up-Down Gradient (White)", "Center Gradient (White)"]
|
304 |
+
|
305 |
+
image_kb_CN = ["不设置", "自定义"]
|
306 |
+
image_kb_EN = ["Not Set", "Custom"]
|
307 |
|
308 |
title = "<h1 id='title'>HivisionIDPhotos</h1>"
|
309 |
+
description = "<h3>😎9.2 Update: Add photo size KB adjustment</h3>"
|
310 |
css = """
|
311 |
h1#title, h3 {
|
312 |
text-align: center;
|
|
|
319 |
gr.Markdown(title)
|
320 |
gr.Markdown(description)
|
321 |
with gr.Row():
|
322 |
+
# ------------ 左半边 UI ----------------
|
323 |
with gr.Column():
|
324 |
img_input = gr.Image().style(height=350)
|
325 |
+
language_options = gr.Dropdown(
|
326 |
+
choices=language, label="Language", value="English", elem_id="language"
|
327 |
+
)
|
328 |
+
|
329 |
mode_options = gr.Radio(
|
330 |
+
choices=size_mode_EN,
|
331 |
+
label="ID photo size options",
|
332 |
+
value="Size List",
|
333 |
elem_id="size",
|
334 |
)
|
335 |
+
|
336 |
# 预设尺寸下拉菜单
|
337 |
with gr.Row(visible=True) as size_list_row:
|
338 |
size_list_options = gr.Dropdown(
|
339 |
+
choices=size_list_EN,
|
340 |
+
label="Default size",
|
341 |
+
value="One inch",
|
342 |
elem_id="size_list",
|
343 |
)
|
344 |
|
|
|
352 |
|
353 |
# 左:背景色选项
|
354 |
color_options = gr.Radio(
|
355 |
+
choices=colors_EN, label="Background color", value="Blue", elem_id="color"
|
356 |
)
|
357 |
|
358 |
+
# 左:如果选择「自定义底色」,显示 RGB 输入框
|
359 |
with gr.Row(visible=False) as custom_color:
|
360 |
custom_color_R = gr.Number(value=0, label="R", interactive=True)
|
361 |
custom_color_G = gr.Number(value=0, label="G", interactive=True)
|
|
|
363 |
|
364 |
# 左:渲染方式选项
|
365 |
render_options = gr.Radio(
|
366 |
+
choices=render_EN,
|
367 |
+
label="Rendering mode",
|
368 |
+
value="Solid Color",
|
369 |
elem_id="render",
|
370 |
)
|
371 |
|
372 |
+
# 左:输出 KB 大小选项
|
373 |
image_kb_options = gr.Radio(
|
374 |
+
choices=image_kb_EN,
|
375 |
+
label="Set KB size (Download in the bottom right)",
|
376 |
+
value="Not Set",
|
377 |
elem_id="image_kb",
|
378 |
)
|
379 |
|
380 |
+
# 自定义 KB 大小,滑动条,最小 10KB,最大 200KB
|
381 |
with gr.Row(visible=False) as custom_image_kb:
|
382 |
custom_image_kb_size = gr.Slider(
|
383 |
minimum=10,
|
384 |
maximum=1000,
|
385 |
value=50,
|
386 |
+
label="KB size",
|
387 |
interactive=True,
|
388 |
)
|
389 |
|
390 |
+
img_but = gr.Button("Start", elem_id="start")
|
391 |
|
392 |
# 案例图片
|
393 |
example_images = gr.Dataset(
|
394 |
components=[img_input],
|
395 |
samples=[
|
396 |
[path.as_posix()]
|
397 |
+
for path in sorted(
|
398 |
+
pathlib.Path(os.path.join(root_dir, "images")).rglob(
|
399 |
+
"*.jpg"
|
400 |
+
)
|
401 |
+
)
|
402 |
],
|
403 |
)
|
404 |
|
405 |
+
# ---------------- 右半边 UI ----------------
|
406 |
with gr.Column():
|
407 |
+
notification = gr.Text(label="Status", visible=False)
|
408 |
with gr.Row():
|
409 |
+
img_output_standard = gr.Image(label="Standard photo").style(height=350)
|
410 |
+
img_output_standard_hd = gr.Image(label="HD photo").style(height=350)
|
411 |
+
img_output_layout = gr.Image(label="Layout photo").style(height=350)
|
412 |
+
file_download = gr.File(label="Download the photo after adjusting the KB size", visible=False)
|
413 |
|
414 |
# ---------------- 设置隐藏/显示组件 ----------------
|
415 |
+
def change_language(language):
|
416 |
+
# 将Gradio组件中的内容改为中文或英文
|
417 |
+
if language == "中文":
|
418 |
+
return {
|
419 |
+
size_list_options: gr.update(
|
420 |
+
label="预设尺寸",
|
421 |
+
choices=size_list_CN,
|
422 |
+
value="一寸",
|
423 |
+
),
|
424 |
+
mode_options: gr.update(
|
425 |
+
label="证件照尺寸选项",
|
426 |
+
choices=size_mode_CN,
|
427 |
+
value="尺寸列表",
|
428 |
+
),
|
429 |
+
color_options: gr.update(
|
430 |
+
label="背景色",
|
431 |
+
choices=colors_CN,
|
432 |
+
value="蓝色",
|
433 |
+
),
|
434 |
+
img_but: gr.update(value="开始制作"),
|
435 |
+
render_options: gr.update(
|
436 |
+
label="渲染方式",
|
437 |
+
choices=render_CN,
|
438 |
+
value="纯色",
|
439 |
+
),
|
440 |
+
image_kb_options: gr.update(
|
441 |
+
label="设置 KB 大小(结果在右边最底的组件下载)",
|
442 |
+
choices=image_kb_CN,
|
443 |
+
value="不设置",
|
444 |
+
),
|
445 |
+
custom_image_kb_size: gr.update(label="KB 大小"),
|
446 |
+
notification: gr.update(label="状态"),
|
447 |
+
img_output_standard: gr.update(label="标准照"),
|
448 |
+
img_output_standard_hd: gr.update(label="高清照"),
|
449 |
+
img_output_layout: gr.update(label="六寸排版照"),
|
450 |
+
file_download: gr.update(label="下载调整 KB 大小后的照片"),
|
451 |
+
}
|
452 |
+
elif language == "English":
|
453 |
+
return {
|
454 |
+
size_list_options: gr.update(
|
455 |
+
label="Default size",
|
456 |
+
choices=size_list_EN,
|
457 |
+
value="One inch",
|
458 |
+
),
|
459 |
+
mode_options: gr.update(
|
460 |
+
label="ID photo size options",
|
461 |
+
choices=size_mode_EN,
|
462 |
+
value="Size List",
|
463 |
+
),
|
464 |
+
color_options: gr.update(
|
465 |
+
label="Background color",
|
466 |
+
choices=colors_EN,
|
467 |
+
value="Blue",
|
468 |
+
),
|
469 |
+
img_but: gr.update(value="Start"),
|
470 |
+
render_options: gr.update(
|
471 |
+
label="Rendering mode",
|
472 |
+
choices=render_EN,
|
473 |
+
value="Solid Color",
|
474 |
+
),
|
475 |
+
image_kb_options: gr.update(
|
476 |
+
label="Set KB size (Download in the bottom right)",
|
477 |
+
choices=image_kb_EN,
|
478 |
+
value="Not Set",
|
479 |
+
),
|
480 |
+
custom_image_kb_size: gr.update(label="KB size"),
|
481 |
+
notification: gr.update(label="Status"),
|
482 |
+
img_output_standard: gr.update(label="Standard photo"),
|
483 |
+
img_output_standard_hd: gr.update(label="HD photo"),
|
484 |
+
img_output_layout: gr.update(label="Layout photo"),
|
485 |
+
file_download: gr.update(
|
486 |
+
label="Download the photo after adjusting the KB size"
|
487 |
+
),
|
488 |
+
}
|
489 |
+
|
490 |
def change_color(colors):
|
491 |
+
if colors == "自定义底色" or colors == "Custom Color":
|
492 |
return {custom_color: gr.update(visible=True)}
|
493 |
else:
|
494 |
return {custom_color: gr.update(visible=False)}
|
495 |
|
496 |
def change_size_mode(size_option_item):
|
497 |
+
if (
|
498 |
+
size_option_item == "自定义尺寸"
|
499 |
+
or size_option_item == "Custom Size"
|
500 |
+
):
|
501 |
return {
|
502 |
custom_size: gr.update(visible=True),
|
503 |
size_list_row: gr.update(visible=False),
|
504 |
}
|
505 |
+
elif (
|
506 |
+
size_option_item == "只换底"
|
507 |
+
or size_option_item == "Only Change Background"
|
508 |
+
):
|
509 |
return {
|
510 |
custom_size: gr.update(visible=False),
|
511 |
size_list_row: gr.update(visible=False),
|
|
|
517 |
}
|
518 |
|
519 |
def change_image_kb(image_kb_option):
|
520 |
+
if image_kb_option == "自定义" or image_kb_option == "Custom":
|
521 |
return {custom_image_kb: gr.update(visible=True)}
|
522 |
else:
|
523 |
return {custom_image_kb: gr.update(visible=False)}
|
524 |
|
525 |
# ---------------- 绑定事件 ----------------
|
526 |
+
language_options.input(
|
527 |
+
change_language,
|
528 |
+
inputs=[language_options],
|
529 |
+
outputs=[
|
530 |
+
size_list_options,
|
531 |
+
mode_options,
|
532 |
+
color_options,
|
533 |
+
img_but,
|
534 |
+
render_options,
|
535 |
+
image_kb_options,
|
536 |
+
custom_image_kb_size,
|
537 |
+
notification,
|
538 |
+
img_output_standard,
|
539 |
+
img_output_standard_hd,
|
540 |
+
img_output_layout,
|
541 |
+
file_download,
|
542 |
+
],
|
543 |
+
)
|
544 |
+
|
545 |
color_options.input(
|
546 |
change_color, inputs=[color_options], outputs=[custom_color]
|
547 |
)
|
|
|
571 |
custom_size_height,
|
572 |
custom_size_wdith,
|
573 |
custom_image_kb_size,
|
574 |
+
language_options,
|
575 |
],
|
576 |
outputs=[
|
577 |
img_output_standard,
|
|
|
586 |
fn=set_example_image, inputs=[example_images], outputs=[img_input]
|
587 |
)
|
588 |
|
589 |
+
argparser = argparse.ArgumentParser()
|
590 |
+
argparser.add_argument(
|
591 |
+
"--port", type=int, default=7860, help="The port number of the server"
|
592 |
+
)
|
593 |
+
argparser.add_argument(
|
594 |
+
"--host", type=str, default="127.0.0.1", help="The host of the server"
|
595 |
+
)
|
596 |
+
args = argparser.parse_args()
|
597 |
+
|
598 |
+
demo.launch(server_name=args.host, server_port=args.port)
|
size_list_EN.csv
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Name,Height,Width
|
2 |
+
One inch, 413, 295
|
3 |
+
Two inches, 626, 413
|
4 |
+
Small one inch, 378, 260
|
5 |
+
Small two inches, 531, 413
|
6 |
+
Large one inch, 567 ,390
|
7 |
+
Large two inches,626,413
|
8 |
+
Five inches,1499,1050
|
9 |
+
Teacher qualification certificate,413,295
|
10 |
+
National civil service exam ,413 ,295
|
11 |
+
Primary accounting exam ,413 ,295
|
12 |
+
English CET-4 and CET-6 exams ,192 ,144
|
13 |
+
Computer level exam ,567 ,390
|
14 |
+
Graduate entrance exam ,709 ,531
|
15 |
+
Social security card ,441 ,358
|
16 |
+
Electronic driver's license ,378 ,260
|
src/face_judgement_align.py
CHANGED
@@ -3,11 +3,23 @@ import cv2
|
|
3 |
import numpy as np
|
4 |
from hivisionai.hycv.face_tools import face_detect_mtcnn
|
5 |
from hivisionai.hycv.utils import get_box_pro
|
6 |
-
from hivisionai.hycv.vision import
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
import onnxruntime
|
9 |
from src.error import IDError
|
10 |
-
from src.imageTransform import
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
from src.layoutCreate import generate_layout_photo
|
12 |
from src.move_image import move
|
13 |
|
@@ -67,17 +79,17 @@ class Coordinate(object):
|
|
67 |
def face_number_and_angle_detection(input_image):
|
68 |
"""
|
69 |
本函数的功能是利用机器学习算法计算图像中人脸的数目与关键点,并通过关键点信息来计算人脸在平面上的旋转角度。
|
70 |
-
当前人脸数目!=1时,将raise一个错误信息并终止全部程序。
|
71 |
Args:
|
72 |
-
input_image: numpy.array(3 channels),用户上传的原图(经过了一些简单的resize)
|
73 |
|
74 |
Returns:
|
75 |
-
- dets: list,人脸定位信息(x1, y1, x2, y2)
|
76 |
- rotation: int,旋转角度,正数代表逆时针偏离,负数代表顺时针偏离
|
77 |
- landmark: list,人脸关键点信息
|
78 |
"""
|
79 |
|
80 |
-
# face
|
81 |
# input_image_bytes = CV2Bytes.cv2_byte(input_image, ".jpg")
|
82 |
# face_num, face_rectangle, landmarks, headpose = megvii_face_detector(input_image_bytes)
|
83 |
# print(face_rectangle)
|
@@ -85,19 +97,25 @@ def face_number_and_angle_detection(input_image):
|
|
85 |
faces, landmarks = face_detect_mtcnn(input_image)
|
86 |
face_num = len(faces)
|
87 |
|
88 |
-
# 排除不合人脸数目要求(必须是1)的照片
|
89 |
if face_num == 0 or face_num >= 2:
|
90 |
if face_num == 0:
|
91 |
status_id_ = "1101"
|
92 |
else:
|
93 |
status_id_ = "1102"
|
94 |
-
raise IDError(
|
|
|
|
|
|
|
|
|
95 |
|
96 |
# 获得人脸定位坐标
|
97 |
face_rectangle = []
|
98 |
for iter, (x1, y1, x2, y2, _) in enumerate(faces):
|
99 |
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
100 |
-
face_rectangle.append(
|
|
|
|
|
101 |
|
102 |
# 获取人脸定位坐标与关键点信息
|
103 |
dets = face_rectangle[0]
|
@@ -108,6 +126,7 @@ def face_number_and_angle_detection(input_image):
|
|
108 |
# return dets, rotation, landmark
|
109 |
return dets
|
110 |
|
|
|
111 |
@calTime
|
112 |
def image_matting(input_image, params):
|
113 |
"""
|
@@ -116,11 +135,13 @@ def image_matting(input_image, params):
|
|
116 |
- input_image: numpy.array(3 channels),用户原图
|
117 |
|
118 |
Returns:
|
119 |
-
- origin_png_image: numpy.array(4 channels)
|
120 |
"""
|
121 |
|
122 |
print("抠图采用本地模型")
|
123 |
-
origin_png_image = get_modnet_matting(
|
|
|
|
|
124 |
|
125 |
origin_png_image = hollowOutFix(origin_png_image) # 抠图洞洞修补
|
126 |
return origin_png_image
|
@@ -131,31 +152,35 @@ def rotation_ajust(input_image, rotation, a, IS_DEBUG=False):
|
|
131 |
"""
|
132 |
本函数的功能是根据旋转角对原图进行无损旋转,并返回结果图与附带信息。
|
133 |
Args:
|
134 |
-
- input_image: numpy.array(3 channels), 用户上传的原图(经过了一些简单的resize、美颜)
|
135 |
- rotation: float, 人的五官偏离"端正"形态的旋转角
|
136 |
-
- a: numpy.array(1 channel), matting图的matte
|
137 |
-
- IS_DEBUG: DEBUG模式开关
|
138 |
|
139 |
Returns:
|
140 |
- result_jpg_image: numpy.array(3 channels), 原图旋转的结果图
|
141 |
-
- result_png_image: numpy.array(4 channels), matting图旋转的结果图
|
142 |
- L1: CLassObject, 根据��转点连线所构造函数
|
143 |
- L2: ClassObject, 根据旋转点连线所构造函数
|
144 |
- dotL3: ClassObject, 一个特殊裁切点的坐标
|
145 |
- clockwise: int, 表示照片是顺时针偏离还是逆时针偏离
|
146 |
-
- drawed_dots_image: numpy.array(3 channels), 在result_jpg_image上标定了4个旋转点的结果图,用于DEBUG模式
|
147 |
"""
|
148 |
|
149 |
# Step1. 数据准备
|
150 |
-
rotation = -1 * rotation # rotation为正数->原图顺时针偏离,为负数->逆时针偏离
|
151 |
h, w = input_image.copy().shape[:2]
|
152 |
|
153 |
# Step2. 无损旋转
|
154 |
-
result_jpg_image, result_png_image, cos, sin = rotate_bound_4channels(
|
|
|
|
|
155 |
|
156 |
# Step3. 附带信息计算
|
157 |
nh, nw = result_jpg_image.shape[:2] # 旋转后的新的长宽
|
158 |
-
clockwise =
|
|
|
|
|
159 |
# 如果逆时针偏离:
|
160 |
if rotation < 0:
|
161 |
p1 = Coordinate(0, int(w * sin))
|
@@ -164,7 +189,9 @@ def rotation_ajust(input_image, rotation, a, IS_DEBUG=False):
|
|
164 |
p4 = Coordinate(int(h * sin), nh)
|
165 |
L1 = LinearFunction_TwoDots(p1, p4)
|
166 |
L2 = LinearFunction_TwoDots(p4, p3)
|
167 |
-
dotL3 = Coordinate(
|
|
|
|
|
168 |
|
169 |
# 如果顺时针偏离:
|
170 |
else:
|
@@ -174,41 +201,55 @@ def rotation_ajust(input_image, rotation, a, IS_DEBUG=False):
|
|
174 |
p4 = Coordinate(0, int(h * cos))
|
175 |
L1 = LinearFunction_TwoDots(p4, p3)
|
176 |
L2 = LinearFunction_TwoDots(p3, p2)
|
177 |
-
dotL3 = Coordinate(
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
|
|
|
|
|
|
|
|
182 |
if IS_DEBUG:
|
183 |
testImages.append(["drawed_dots_image", drawed_dots_image])
|
184 |
|
185 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
|
187 |
|
188 |
@calTime
|
189 |
def face_number_detection_mtcnn(input_image):
|
190 |
"""
|
191 |
-
本函数的功能是对旋转矫正的结果图进行基于MTCNN模型的人脸检测。
|
192 |
Args:
|
193 |
-
- input_image: numpy.array(3 channels), 旋转矫正(rotation_adjust)的3通道结果图
|
194 |
|
195 |
Returns:
|
196 |
- faces: list, 人脸检测的结果,包含人脸位置信息
|
197 |
"""
|
198 |
-
# 如果图像的长或宽>1500px,则对图像进行1/2的resize再做MTCNN人脸检测,以加快处理速度
|
199 |
if max(input_image.shape[0], input_image.shape[1]) >= 1500:
|
200 |
-
input_image_resize = cv2.resize(
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
|
|
|
|
205 |
if len(faces) == 1:
|
206 |
for item, param in enumerate(faces[0]):
|
207 |
faces[0][item] = param * 2
|
208 |
-
# 如果两次人脸检测结果有偏差,则默认缩放后图像的MTCNN检测存在误差,则将原图输入再做一次MTCNN(保险措施)
|
209 |
else:
|
210 |
faces, _ = face_detect_mtcnn(input_image, filter=True)
|
211 |
-
# 如果图像的长或宽<1500px,则直接进行MTCNN检测
|
212 |
else:
|
213 |
faces, _ = face_detect_mtcnn(input_image, filter=True)
|
214 |
|
@@ -216,7 +257,9 @@ def face_number_detection_mtcnn(input_image):
|
|
216 |
|
217 |
|
218 |
@calTime
|
219 |
-
def cutting_rect_pan(
|
|
|
|
|
220 |
"""
|
221 |
本函数的功能是对旋转矫正结果图的裁剪框进行修正 ———— 解决"旋转三角形"现象。
|
222 |
Args:
|
@@ -240,13 +283,13 @@ def cutting_rect_pan(x1, y1, x2, y2, width, height, L1, L2, L3, clockwise, stand
|
|
240 |
- x_bias: int, 裁剪框横坐标方向上的计算偏置量
|
241 |
- y_bias: int, 裁剪框纵坐标方向上的计算偏置量
|
242 |
"""
|
243 |
-
# 用于计算的裁剪框坐标x1_cal,x2_cal,y1_cal,y2_cal(如果裁剪框超出了图像范围,则缩小直至在范围内)
|
244 |
x1_std = x1 if x1 > 0 else 0
|
245 |
x2_std = x2 if x2 < width else width
|
246 |
# y1_std = y1 if y1 > 0 else 0
|
247 |
y2_std = y2 if y2 < height else height
|
248 |
|
249 |
-
# 初始化x和y的计算偏置项x_bias和y_bias
|
250 |
x_bias = 0
|
251 |
y_bias = 0
|
252 |
|
@@ -269,7 +312,7 @@ def cutting_rect_pan(x1, y1, x2, y2, width, height, L1, L2, L3, clockwise, stand
|
|
269 |
if x2 > L3.x:
|
270 |
x2 = L3.x
|
271 |
|
272 |
-
# 计算裁剪框的y的变化
|
273 |
y2 = int(y2_std + y_bias)
|
274 |
new_cut_width = x2 - x1
|
275 |
new_cut_height = int(new_cut_width / standard_size[1] * standard_size[0])
|
@@ -279,25 +322,36 @@ def cutting_rect_pan(x1, y1, x2, y2, width, height, L1, L2, L3, clockwise, stand
|
|
279 |
|
280 |
|
281 |
@calTime
|
282 |
-
def idphoto_cutting(
|
283 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
284 |
"""
|
285 |
-
本函数的功能为进行证件照的自适应裁剪,自适应依据Setting.json的控制参数,以及输入图像的自身情况。
|
286 |
Args:
|
287 |
- faces: list, 人脸位置信息
|
288 |
- head_measure_ratio: float, 人脸面积与全图面积的期望比值
|
289 |
-
- standard_size: tuple,
|
290 |
- head_height_ratio: float, 人脸中心处在全图高度的比例期望值
|
291 |
- origin_png_image: numpy.array(4 channels), 经过一系列转换后的用户输入图
|
292 |
- origin_png_image_pre:numpy.array(4 channels),经过一系列转换(但没有做旋转矫正)的用户输入图
|
293 |
- rotation_params:旋转参数字典
|
294 |
-
- L1: classObject, 来自rotation_ajust的L1线性函数
|
295 |
-
- L2: classObject, 来自rotation_ajust的L2线性函数
|
296 |
-
- L3: classObject, 来自rotation_ajust的dotL3点
|
297 |
-
- clockwise: int, (顺/逆)时针偏差
|
298 |
-
- drawed_image: numpy.array, 红点标定4个旋转点的图像
|
299 |
- align: bool, 是否图像做过旋转矫正
|
300 |
-
- IS_DEBUG: DEBUG模式开关
|
301 |
- top_distance_max: float, 头距离顶部的最大比例
|
302 |
- top_distance_min: float, 头距离顶部的最小比例
|
303 |
|
@@ -324,11 +378,15 @@ def idphoto_cutting(faces, head_measure_ratio, standard_size, head_height_ratio,
|
|
324 |
# Step2. 计算高级参数
|
325 |
face_center = (x + w / 2, y + h / 2) # 面部中心坐标
|
326 |
face_measure = w * h # 面部面积
|
327 |
-
crop_measure = face_measure / head_measure_ratio # 裁剪框面积:为面部面积的5倍
|
328 |
resize_ratio = crop_measure / (standard_size[0] * standard_size[1]) # 裁剪框缩放率
|
329 |
-
resize_ratio_single = math.sqrt(
|
330 |
-
|
331 |
-
|
|
|
|
|
|
|
|
|
332 |
|
333 |
# 裁剪框的定位信息
|
334 |
x1 = int(face_center[0] - crop_size[1] / 2)
|
@@ -339,7 +397,7 @@ def idphoto_cutting(faces, head_measure_ratio, standard_size, head_height_ratio,
|
|
339 |
# Step3. 对于旋转矫正图片的裁切处理
|
340 |
# if align:
|
341 |
# y_top_pre, _, _, _ = get_box_pro(origin_png_image.astype(np.uint8), model=2,
|
342 |
-
# correction_factor=0) # 获取matting结果图的顶距
|
343 |
# # 裁剪参数重新计算,目标是以最小的图像损失来消除"旋转三角形"
|
344 |
# x1, y1, x2, y2, x_bias, y_bias = cutting_rect_pan(x1, y1, x2, y2, width, height, L1, L2, L3, clockwise,
|
345 |
# standard_size)
|
@@ -374,38 +432,44 @@ def idphoto_cutting(faces, head_measure_ratio, standard_size, head_height_ratio,
|
|
374 |
# Step4. 对照片的第一轮裁剪
|
375 |
cut_image = IDphotos_cut(x1, y1, x2, y2, origin_png_image)
|
376 |
cut_image = cv2.resize(cut_image, (crop_size[1], crop_size[0]))
|
377 |
-
y_top, y_bottom, x_left, x_right = get_box_pro(
|
378 |
-
|
|
|
379 |
if IS_DEBUG:
|
380 |
testImages.append(["firstCut", cut_image])
|
381 |
|
382 |
-
# Step5. 判定cut_image中的人像是否处于合理的位置,若不合理,则处理数据以便之后调整位置
|
383 |
# 检测人像与裁剪框左边或右边是否存在空隙
|
384 |
if x_left > 0 or x_right > 0:
|
385 |
status_left_right = 1
|
386 |
-
cut_value_top = int(
|
|
|
|
|
387 |
else:
|
388 |
status_left_right = 0
|
389 |
cut_value_top = 0
|
390 |
|
391 |
"""
|
392 |
检测人头顶与照片的顶部是否在合适的距离内:
|
393 |
-
- status==0:
|
394 |
-
- status=1:
|
395 |
-
- status=2:
|
396 |
"""
|
397 |
-
status_top, move_value = detect_distance(
|
398 |
-
|
|
|
399 |
|
400 |
# Step6. 对照片的第二轮裁剪
|
401 |
if status_left_right == 0 and status_top == 0:
|
402 |
result_image = cut_image
|
403 |
else:
|
404 |
-
result_image = IDphotos_cut(
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
|
|
|
|
409 |
if IS_DEBUG:
|
410 |
testImages.append(["result_image_pre", result_image])
|
411 |
|
@@ -421,14 +485,16 @@ def idphoto_cutting(faces, head_measure_ratio, standard_size, head_height_ratio,
|
|
421 |
|
422 |
# Step8. 标准照与高清照转换
|
423 |
result_image_standard = standard_photo_resize(result_image, standard_size)
|
424 |
-
result_image_hd, resize_ratio_max = resize_image_by_min(
|
|
|
|
|
425 |
|
426 |
-
# Step9.
|
427 |
clothing_params = {
|
428 |
"relative_x": relative_x * resize_ratio_max,
|
429 |
"relative_y": relative_y * resize_ratio_max,
|
430 |
"w": w * resize_ratio_max,
|
431 |
-
"h": h * resize_ratio_max
|
432 |
}
|
433 |
|
434 |
return result_image_hd, result_image_standard, clothing_params
|
@@ -445,30 +511,48 @@ def debug_mode_process(testImages):
|
|
445 |
if item == 0:
|
446 |
testHeight = height
|
447 |
result_image_test = imageItem
|
448 |
-
result_image_test = cv2.putText(
|
449 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
450 |
else:
|
451 |
-
imageItem = cv2.resize(
|
452 |
-
|
453 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
result_image_test = cv2.hconcat([result_image_test, imageItem])
|
455 |
if item == len(testImages) - 1:
|
456 |
return result_image_test
|
457 |
|
458 |
|
459 |
@calTime("主函数")
|
460 |
-
def IDphotos_create(
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
|
|
|
|
472 |
"""
|
473 |
证件照制作主函数
|
474 |
Args:
|
@@ -476,26 +560,34 @@ def IDphotos_create(input_image,
|
|
476 |
size: (h, w)
|
477 |
head_measure_ratio: 头部占比?
|
478 |
head_height_ratio: 头部高度占比?
|
479 |
-
align: 是否进行人脸矫正(roll),默认为True(是)
|
480 |
-
fd68: 人脸68关键点检测类,详情参见hycv.FaceDetection68.
|
481 |
-
human_sess: 人像抠图模型类,由onnx
|
482 |
-
oss_image_name: 阿里云api需要的参数,实际上是上传到oss的路径
|
483 |
-
user: 阿里云api的accessKey配置对象
|
484 |
top_distance_max: float, 头距离顶部的最大比例
|
485 |
top_distance_min: float, 头距离顶部的最小比例
|
486 |
Returns:
|
487 |
-
result_image(高清版), result_image(普清版), api请求日志,
|
488 |
-
|
489 |
在函数不出错的情况下,函数会因为一些原因主动抛出异常:
|
490 |
-
1. 无人脸(或者只有半张,dlib无法检测出来),抛出IDError异常,内部参数face_num为0
|
491 |
-
2. 人脸数量超过1,抛出IDError异常,内部参数face_num为2
|
492 |
-
3. 抠图api请求失败,抛出IDError异常,内部参数face_num
|
493 |
"""
|
494 |
|
495 |
# Step0. 数据准备/图像预处理
|
496 |
matting_params = {"modnet": {"human_sess": human_sess}}
|
497 |
-
rotation_params = {
|
498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
499 |
|
500 |
# Step1. 人脸检测
|
501 |
# dets, rotation, landmark = face_number_and_angle_detection(input_image)
|
@@ -507,13 +599,15 @@ def IDphotos_create(input_image,
|
|
507 |
|
508 |
# Step3. 抠图
|
509 |
origin_png_image = image_matting(input_image, matting_params)
|
510 |
-
if mode == "只换底":
|
511 |
return origin_png_image, origin_png_image, None, None, None, None, None, None, 1
|
512 |
|
513 |
-
origin_png_image_pre =
|
|
|
|
|
514 |
|
515 |
# Step4. 旋转矫正
|
516 |
-
# 如果旋转角不大于2, 则不做旋转
|
517 |
# if abs(rotation) <= 2:
|
518 |
# align = False
|
519 |
# # 否则,进行旋转矫正
|
@@ -531,26 +625,46 @@ def IDphotos_create(input_image,
|
|
531 |
# rotation_params["clockwise"] = clockwise
|
532 |
# rotation_params["drawed_image"] = drawed_image
|
533 |
|
534 |
-
# Step5. MTCNN人脸检测
|
535 |
faces = face_number_detection_mtcnn(input_image)
|
536 |
|
537 |
# Step6. 证件照自适应裁剪
|
538 |
face_num = len(faces)
|
539 |
-
# 报错MTCNN检测结果不等于1的图片
|
540 |
if face_num != 1:
|
541 |
return None, None, None, None, None, None, None, None, 0
|
542 |
# 符合条件的进入下一环
|
543 |
else:
|
544 |
-
result_image_hd, result_image_standard, clothing_params =
|
545 |
-
|
546 |
-
|
547 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
548 |
|
549 |
# Step7. 排版照参数获取
|
550 |
-
typography_arr, typography_rotate = generate_layout_photo(
|
551 |
-
|
552 |
-
|
553 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
554 |
|
555 |
|
556 |
if __name__ == "__main__":
|
@@ -559,18 +673,29 @@ if __name__ == "__main__":
|
|
559 |
|
560 |
input_image = cv2.imread("test.jpg")
|
561 |
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
576 |
cv2.imwrite("result_image_hd.png", result_image_hd)
|
|
|
3 |
import numpy as np
|
4 |
from hivisionai.hycv.face_tools import face_detect_mtcnn
|
5 |
from hivisionai.hycv.utils import get_box_pro
|
6 |
+
from hivisionai.hycv.vision import (
|
7 |
+
resize_image_esp,
|
8 |
+
IDphotos_cut,
|
9 |
+
add_background,
|
10 |
+
calTime,
|
11 |
+
resize_image_by_min,
|
12 |
+
rotate_bound_4channels,
|
13 |
+
)
|
14 |
import onnxruntime
|
15 |
from src.error import IDError
|
16 |
+
from src.imageTransform import (
|
17 |
+
standard_photo_resize,
|
18 |
+
hollowOutFix,
|
19 |
+
get_modnet_matting,
|
20 |
+
draw_picture_dots,
|
21 |
+
detect_distance,
|
22 |
+
)
|
23 |
from src.layoutCreate import generate_layout_photo
|
24 |
from src.move_image import move
|
25 |
|
|
|
79 |
def face_number_and_angle_detection(input_image):
|
80 |
"""
|
81 |
本函数的功能是利用机器学习算法计算图像中人脸的数目与关键点,并通过关键点信息来计算人脸在平面上的旋转角度。
|
82 |
+
当前人脸数目!=1 时,将 raise 一个错误信息并终止全部程序。
|
83 |
Args:
|
84 |
+
input_image: numpy.array(3 channels),用户上传的原图(经过了一些简单的 resize)
|
85 |
|
86 |
Returns:
|
87 |
+
- dets: list,人脸定位信息 (x1, y1, x2, y2)
|
88 |
- rotation: int,旋转角度,正数代表逆时针偏离,负数代表顺时针偏离
|
89 |
- landmark: list,人脸关键点信息
|
90 |
"""
|
91 |
|
92 |
+
# face++ 人脸检测
|
93 |
# input_image_bytes = CV2Bytes.cv2_byte(input_image, ".jpg")
|
94 |
# face_num, face_rectangle, landmarks, headpose = megvii_face_detector(input_image_bytes)
|
95 |
# print(face_rectangle)
|
|
|
97 |
faces, landmarks = face_detect_mtcnn(input_image)
|
98 |
face_num = len(faces)
|
99 |
|
100 |
+
# 排除不合人脸数目要求(必须是 1)的照片
|
101 |
if face_num == 0 or face_num >= 2:
|
102 |
if face_num == 0:
|
103 |
status_id_ = "1101"
|
104 |
else:
|
105 |
status_id_ = "1102"
|
106 |
+
raise IDError(
|
107 |
+
f"人脸检测出错!检测出了{face_num}张人脸",
|
108 |
+
face_num=face_num,
|
109 |
+
status_id=status_id_,
|
110 |
+
)
|
111 |
|
112 |
# 获得人脸定位坐标
|
113 |
face_rectangle = []
|
114 |
for iter, (x1, y1, x2, y2, _) in enumerate(faces):
|
115 |
x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)
|
116 |
+
face_rectangle.append(
|
117 |
+
{"top": x1, "left": y1, "width": x2 - x1, "height": y2 - y1}
|
118 |
+
)
|
119 |
|
120 |
# 获取人脸定位坐标与关键点信息
|
121 |
dets = face_rectangle[0]
|
|
|
126 |
# return dets, rotation, landmark
|
127 |
return dets
|
128 |
|
129 |
+
|
130 |
@calTime
|
131 |
def image_matting(input_image, params):
|
132 |
"""
|
|
|
135 |
- input_image: numpy.array(3 channels),用户原图
|
136 |
|
137 |
Returns:
|
138 |
+
- origin_png_image: numpy.array(4 channels),抠好的图
|
139 |
"""
|
140 |
|
141 |
print("抠图采用本地模型")
|
142 |
+
origin_png_image = get_modnet_matting(
|
143 |
+
input_image, sess=params["modnet"]["human_sess"]
|
144 |
+
)
|
145 |
|
146 |
origin_png_image = hollowOutFix(origin_png_image) # 抠图洞洞修补
|
147 |
return origin_png_image
|
|
|
152 |
"""
|
153 |
本函数的功能是根据旋转角对原图进行无损旋转,并返回结果图与附带信息。
|
154 |
Args:
|
155 |
+
- input_image: numpy.array(3 channels), 用户上传的原图(经过了一些简单的 resize、美颜)
|
156 |
- rotation: float, 人的五官偏离"端正"形态的旋转角
|
157 |
+
- a: numpy.array(1 channel), matting 图的 matte
|
158 |
+
- IS_DEBUG: DEBUG 模式开关
|
159 |
|
160 |
Returns:
|
161 |
- result_jpg_image: numpy.array(3 channels), 原图旋转的结果图
|
162 |
+
- result_png_image: numpy.array(4 channels), matting 图旋转的结果图
|
163 |
- L1: CLassObject, 根据��转点连线所构造函数
|
164 |
- L2: ClassObject, 根据旋转点连线所构造函数
|
165 |
- dotL3: ClassObject, 一个特殊裁切点的坐标
|
166 |
- clockwise: int, 表示照片是顺时针偏离还是逆时针偏离
|
167 |
+
- drawed_dots_image: numpy.array(3 channels), 在 result_jpg_image 上标定了 4 个旋转点的结果图,用于 DEBUG 模式
|
168 |
"""
|
169 |
|
170 |
# Step1. 数据准备
|
171 |
+
rotation = -1 * rotation # rotation 为正数->原图顺时针偏离,为负数->逆时针偏离
|
172 |
h, w = input_image.copy().shape[:2]
|
173 |
|
174 |
# Step2. 无损旋转
|
175 |
+
result_jpg_image, result_png_image, cos, sin = rotate_bound_4channels(
|
176 |
+
input_image, a, rotation
|
177 |
+
)
|
178 |
|
179 |
# Step3. 附带信息计算
|
180 |
nh, nw = result_jpg_image.shape[:2] # 旋转后的新的长宽
|
181 |
+
clockwise = (
|
182 |
+
-1 if rotation < 0 else 1
|
183 |
+
) # clockwise 代表时针,即 1 为顺时针,-1 为逆时针
|
184 |
# 如果逆时针偏离:
|
185 |
if rotation < 0:
|
186 |
p1 = Coordinate(0, int(w * sin))
|
|
|
189 |
p4 = Coordinate(int(h * sin), nh)
|
190 |
L1 = LinearFunction_TwoDots(p1, p4)
|
191 |
L2 = LinearFunction_TwoDots(p4, p3)
|
192 |
+
dotL3 = Coordinate(
|
193 |
+
int(0.25 * p2.x + 0.75 * p3.x), int(0.25 * p2.y + 0.75 * p3.y)
|
194 |
+
)
|
195 |
|
196 |
# 如果顺时针偏离:
|
197 |
else:
|
|
|
201 |
p4 = Coordinate(0, int(h * cos))
|
202 |
L1 = LinearFunction_TwoDots(p4, p3)
|
203 |
L2 = LinearFunction_TwoDots(p3, p2)
|
204 |
+
dotL3 = Coordinate(
|
205 |
+
int(0.75 * p4.x + 0.25 * p1.x), int(0.75 * p4.y + 0.25 * p1.y)
|
206 |
+
)
|
207 |
+
|
208 |
+
# Step4. 根据附带信息进行图像绘制(4 个旋转点),便于 DEBUG 模式验证
|
209 |
+
drawed_dots_image = draw_picture_dots(
|
210 |
+
result_jpg_image,
|
211 |
+
[(p1.x, p1.y), (p2.x, p2.y), (p3.x, p3.y), (p4.x, p4.y), (dotL3.x, dotL3.y)],
|
212 |
+
)
|
213 |
if IS_DEBUG:
|
214 |
testImages.append(["drawed_dots_image", drawed_dots_image])
|
215 |
|
216 |
+
return (
|
217 |
+
result_jpg_image,
|
218 |
+
result_png_image,
|
219 |
+
L1,
|
220 |
+
L2,
|
221 |
+
dotL3,
|
222 |
+
clockwise,
|
223 |
+
drawed_dots_image,
|
224 |
+
)
|
225 |
|
226 |
|
227 |
@calTime
|
228 |
def face_number_detection_mtcnn(input_image):
|
229 |
"""
|
230 |
+
本函数的功能是对旋转矫正的结果图进行基于 MTCNN 模型的人脸检测。
|
231 |
Args:
|
232 |
+
- input_image: numpy.array(3 channels), 旋转矫正 (rotation_adjust) 的 3 通道结果图
|
233 |
|
234 |
Returns:
|
235 |
- faces: list, 人脸检测的结果,包含人脸位置信息
|
236 |
"""
|
237 |
+
# 如果图像的长或宽>1500px,则对图像进行 1/2 的 resize 再做 MTCNN 人脸检测,以加快处理速度
|
238 |
if max(input_image.shape[0], input_image.shape[1]) >= 1500:
|
239 |
+
input_image_resize = cv2.resize(
|
240 |
+
input_image,
|
241 |
+
(input_image.shape[1] // 2, input_image.shape[0] // 2),
|
242 |
+
interpolation=cv2.INTER_AREA,
|
243 |
+
)
|
244 |
+
faces, _ = face_detect_mtcnn(input_image_resize, filter=True) # MTCNN 人脸检测
|
245 |
+
# 如果缩放后图像的 MTCNN 人脸数目检测结果等于 1->两次人脸检测结果没有偏差,则对定位数据 x2
|
246 |
if len(faces) == 1:
|
247 |
for item, param in enumerate(faces[0]):
|
248 |
faces[0][item] = param * 2
|
249 |
+
# 如果两次人脸检测结果有偏差,则默认缩放后图像的 MTCNN 检测存在误差,则将原图输入再做一次 MTCNN(保险措施)
|
250 |
else:
|
251 |
faces, _ = face_detect_mtcnn(input_image, filter=True)
|
252 |
+
# 如果图像的长或宽<1500px,则直接进行 MTCNN 检测
|
253 |
else:
|
254 |
faces, _ = face_detect_mtcnn(input_image, filter=True)
|
255 |
|
|
|
257 |
|
258 |
|
259 |
@calTime
|
260 |
+
def cutting_rect_pan(
|
261 |
+
x1, y1, x2, y2, width, height, L1, L2, L3, clockwise, standard_size
|
262 |
+
):
|
263 |
"""
|
264 |
本函数的功能是对旋转矫正结果图的裁剪框进行修正 ———— 解决"旋转三角形"现象。
|
265 |
Args:
|
|
|
283 |
- x_bias: int, 裁剪框横坐标方向上的计算偏置量
|
284 |
- y_bias: int, 裁剪框纵坐标方向上的计算偏置量
|
285 |
"""
|
286 |
+
# 用于计算的裁剪框坐标 x1_cal,x2_cal,y1_cal,y2_cal(如果裁剪框超出了图像范围,则缩小直至在范围内)
|
287 |
x1_std = x1 if x1 > 0 else 0
|
288 |
x2_std = x2 if x2 < width else width
|
289 |
# y1_std = y1 if y1 > 0 else 0
|
290 |
y2_std = y2 if y2 < height else height
|
291 |
|
292 |
+
# 初始化 x 和 y 的计算偏置项 x_bias 和 y_bias
|
293 |
x_bias = 0
|
294 |
y_bias = 0
|
295 |
|
|
|
312 |
if x2 > L3.x:
|
313 |
x2 = L3.x
|
314 |
|
315 |
+
# 计算裁剪框的 y 的变化
|
316 |
y2 = int(y2_std + y_bias)
|
317 |
new_cut_width = x2 - x1
|
318 |
new_cut_height = int(new_cut_width / standard_size[1] * standard_size[0])
|
|
|
322 |
|
323 |
|
324 |
@calTime
|
325 |
+
def idphoto_cutting(
|
326 |
+
faces,
|
327 |
+
head_measure_ratio,
|
328 |
+
standard_size,
|
329 |
+
head_height_ratio,
|
330 |
+
origin_png_image,
|
331 |
+
origin_png_image_pre,
|
332 |
+
rotation_params,
|
333 |
+
align=False,
|
334 |
+
IS_DEBUG=False,
|
335 |
+
top_distance_max=0.12,
|
336 |
+
top_distance_min=0.10,
|
337 |
+
):
|
338 |
"""
|
339 |
+
本函数的功能为进行证件照的自适应裁剪,自适应依据 Setting.json 的控制参数,以及输入图像的自身情况。
|
340 |
Args:
|
341 |
- faces: list, 人脸位置信息
|
342 |
- head_measure_ratio: float, 人脸面积与全图面积的期望比值
|
343 |
+
- standard_size: tuple, 标准照尺寸,如 (413, 295)
|
344 |
- head_height_ratio: float, 人脸中心处在全图高度的比例期望值
|
345 |
- origin_png_image: numpy.array(4 channels), 经过一系列转换后的用户输入图
|
346 |
- origin_png_image_pre:numpy.array(4 channels),经过一系列转换(但没有做旋转矫正)的用户输入图
|
347 |
- rotation_params:旋转参数字典
|
348 |
+
- L1: classObject, 来自 rotation_ajust 的 L1 线性函数
|
349 |
+
- L2: classObject, 来自 rotation_ajust 的 L2 线性函数
|
350 |
+
- L3: classObject, 来自 rotation_ajust 的 dotL3 点
|
351 |
+
- clockwise: int, (顺/逆) 时针偏差
|
352 |
+
- drawed_image: numpy.array, 红点标定 4 个旋转点的图像
|
353 |
- align: bool, 是否图像做过旋转矫正
|
354 |
+
- IS_DEBUG: DEBUG 模式开关
|
355 |
- top_distance_max: float, 头距离顶部的最大比例
|
356 |
- top_distance_min: float, 头距离顶部的最小比例
|
357 |
|
|
|
378 |
# Step2. 计算高级参数
|
379 |
face_center = (x + w / 2, y + h / 2) # 面部中心坐标
|
380 |
face_measure = w * h # 面部面积
|
381 |
+
crop_measure = face_measure / head_measure_ratio # 裁剪框面积:为面部面积的 5 倍
|
382 |
resize_ratio = crop_measure / (standard_size[0] * standard_size[1]) # 裁剪框缩放率
|
383 |
+
resize_ratio_single = math.sqrt(
|
384 |
+
resize_ratio
|
385 |
+
) # 长和宽的缩放率(resize_ratio 的开方)
|
386 |
+
crop_size = (
|
387 |
+
int(standard_size[0] * resize_ratio_single),
|
388 |
+
int(standard_size[1] * resize_ratio_single),
|
389 |
+
) # 裁剪框大小
|
390 |
|
391 |
# 裁剪框的定位信息
|
392 |
x1 = int(face_center[0] - crop_size[1] / 2)
|
|
|
397 |
# Step3. 对于旋转矫正图片的裁切处理
|
398 |
# if align:
|
399 |
# y_top_pre, _, _, _ = get_box_pro(origin_png_image.astype(np.uint8), model=2,
|
400 |
+
# correction_factor=0) # 获取 matting 结果图的顶距
|
401 |
# # 裁剪参数重新计算,目标是以最小的图像损失来消除"旋转三角形"
|
402 |
# x1, y1, x2, y2, x_bias, y_bias = cutting_rect_pan(x1, y1, x2, y2, width, height, L1, L2, L3, clockwise,
|
403 |
# standard_size)
|
|
|
432 |
# Step4. 对照片的第一轮裁剪
|
433 |
cut_image = IDphotos_cut(x1, y1, x2, y2, origin_png_image)
|
434 |
cut_image = cv2.resize(cut_image, (crop_size[1], crop_size[0]))
|
435 |
+
y_top, y_bottom, x_left, x_right = get_box_pro(
|
436 |
+
cut_image.astype(np.uint8), model=2, correction_factor=0
|
437 |
+
) # 得到 cut_image 中人像的上下左右距离信息
|
438 |
if IS_DEBUG:
|
439 |
testImages.append(["firstCut", cut_image])
|
440 |
|
441 |
+
# Step5. 判定 cut_image 中的人像是否处于合理的位置,若不合理,则处理数据以便之后调整位置
|
442 |
# 检测人像与裁剪框左边或右边是否存在空隙
|
443 |
if x_left > 0 or x_right > 0:
|
444 |
status_left_right = 1
|
445 |
+
cut_value_top = int(
|
446 |
+
((x_left + x_right) * width_height_ratio) / 2
|
447 |
+
) # 减去左右,为了保持比例,上下也要相应减少 cut_value_top
|
448 |
else:
|
449 |
status_left_right = 0
|
450 |
cut_value_top = 0
|
451 |
|
452 |
"""
|
453 |
检测人头顶与照片的顶部是否在合适的距离内:
|
454 |
+
- status==0: 距离合适,无需移动
|
455 |
+
- status=1: 距离过大,人像应向上移动
|
456 |
+
- status=2: 距离过小,人像应向下移动
|
457 |
"""
|
458 |
+
status_top, move_value = detect_distance(
|
459 |
+
y_top - cut_value_top, crop_size[0], max=top_distance_max, min=top_distance_min
|
460 |
+
)
|
461 |
|
462 |
# Step6. 对照片的第二轮裁剪
|
463 |
if status_left_right == 0 and status_top == 0:
|
464 |
result_image = cut_image
|
465 |
else:
|
466 |
+
result_image = IDphotos_cut(
|
467 |
+
x1 + x_left,
|
468 |
+
y1 + cut_value_top + status_top * move_value,
|
469 |
+
x2 - x_right,
|
470 |
+
y2 - cut_value_top + status_top * move_value,
|
471 |
+
origin_png_image,
|
472 |
+
)
|
473 |
if IS_DEBUG:
|
474 |
testImages.append(["result_image_pre", result_image])
|
475 |
|
|
|
485 |
|
486 |
# Step8. 标准照与高清照转换
|
487 |
result_image_standard = standard_photo_resize(result_image, standard_size)
|
488 |
+
result_image_hd, resize_ratio_max = resize_image_by_min(
|
489 |
+
result_image, esp=max(600, standard_size[1])
|
490 |
+
)
|
491 |
|
492 |
+
# Step9. 参数准备 - 为换装服务
|
493 |
clothing_params = {
|
494 |
"relative_x": relative_x * resize_ratio_max,
|
495 |
"relative_y": relative_y * resize_ratio_max,
|
496 |
"w": w * resize_ratio_max,
|
497 |
+
"h": h * resize_ratio_max,
|
498 |
}
|
499 |
|
500 |
return result_image_hd, result_image_standard, clothing_params
|
|
|
511 |
if item == 0:
|
512 |
testHeight = height
|
513 |
result_image_test = imageItem
|
514 |
+
result_image_test = cv2.putText(
|
515 |
+
result_image_test,
|
516 |
+
text,
|
517 |
+
(50, 50),
|
518 |
+
cv2.FONT_HERSHEY_COMPLEX,
|
519 |
+
1.0,
|
520 |
+
(200, 100, 100),
|
521 |
+
3,
|
522 |
+
)
|
523 |
else:
|
524 |
+
imageItem = cv2.resize(
|
525 |
+
imageItem, (int(width * testHeight / height), testHeight)
|
526 |
+
)
|
527 |
+
imageItem = cv2.putText(
|
528 |
+
imageItem,
|
529 |
+
text,
|
530 |
+
(50, 50),
|
531 |
+
cv2.FONT_HERSHEY_COMPLEX,
|
532 |
+
1.0,
|
533 |
+
(200, 100, 100),
|
534 |
+
3,
|
535 |
+
)
|
536 |
result_image_test = cv2.hconcat([result_image_test, imageItem])
|
537 |
if item == len(testImages) - 1:
|
538 |
return result_image_test
|
539 |
|
540 |
|
541 |
@calTime("主函数")
|
542 |
+
def IDphotos_create(
|
543 |
+
input_image,
|
544 |
+
mode="ID",
|
545 |
+
size=(413, 295),
|
546 |
+
head_measure_ratio=0.2,
|
547 |
+
head_height_ratio=0.45,
|
548 |
+
align=False,
|
549 |
+
beauty=True,
|
550 |
+
fd68=None,
|
551 |
+
human_sess=None,
|
552 |
+
IS_DEBUG=False,
|
553 |
+
top_distance_max=0.12,
|
554 |
+
top_distance_min=0.10,
|
555 |
+
):
|
556 |
"""
|
557 |
证件照制作主函数
|
558 |
Args:
|
|
|
560 |
size: (h, w)
|
561 |
head_measure_ratio: 头部占比?
|
562 |
head_height_ratio: 头部高度占比?
|
563 |
+
align: 是否进行人脸矫正(roll),默认为 True(是)
|
564 |
+
fd68: 人脸 68 关键点检测类,详情参见 hycv.FaceDetection68.faceDetection688
|
565 |
+
human_sess: 人像抠图模型类,由 onnx 载入(不与下面两个参数连用)连用)
|
566 |
+
oss_image_name: 阿里云 api 需要的参数,实际上是上传到 oss 的路径
|
567 |
+
user: 阿里云 api 的 accessKey 配置对象
|
568 |
top_distance_max: float, 头距离顶部的最大比例
|
569 |
top_distance_min: float, 头距离顶部的最小比例
|
570 |
Returns:
|
571 |
+
result_image(高清版), result_image(普清版), api 请求日志,
|
572 |
+
排版照参数 (list),排版照是否旋转参数,照片尺寸(x,y)
|
573 |
在函数不出错的情况下,函数会因为一些原因主动抛出异常:
|
574 |
+
1. 无人脸(或者只有半张,dlib 无法检测出来),抛出 IDError 异常,内部参数 face_num 为 0
|
575 |
+
2. 人脸数量超过 1,抛出 IDError 异常,内部参数 face_num 为 2
|
576 |
+
3. 抠图 api 请求失败,抛出 IDError 异常,内部参数 face_num 为 -1num 为 -1
|
577 |
"""
|
578 |
|
579 |
# Step0. 数据准备/图像预处理
|
580 |
matting_params = {"modnet": {"human_sess": human_sess}}
|
581 |
+
rotation_params = {
|
582 |
+
"L1": None,
|
583 |
+
"L2": None,
|
584 |
+
"L3": None,
|
585 |
+
"clockwise": None,
|
586 |
+
"drawed_image": None,
|
587 |
+
}
|
588 |
+
input_image = resize_image_esp(
|
589 |
+
input_image, 2000
|
590 |
+
) # 将输入图片 resize 到最大边长为 2000
|
591 |
|
592 |
# Step1. 人脸检测
|
593 |
# dets, rotation, landmark = face_number_and_angle_detection(input_image)
|
|
|
599 |
|
600 |
# Step3. 抠图
|
601 |
origin_png_image = image_matting(input_image, matting_params)
|
602 |
+
if mode == "只换底" or mode == "Only Change Background":
|
603 |
return origin_png_image, origin_png_image, None, None, None, None, None, None, 1
|
604 |
|
605 |
+
origin_png_image_pre = (
|
606 |
+
origin_png_image.copy()
|
607 |
+
) # 备份一下现在抠图结果图,之后在 iphoto_cutting 函数有用
|
608 |
|
609 |
# Step4. 旋转矫正
|
610 |
+
# 如果旋转角不大于 2, 则不做旋转
|
611 |
# if abs(rotation) <= 2:
|
612 |
# align = False
|
613 |
# # 否则,进行旋转矫正
|
|
|
625 |
# rotation_params["clockwise"] = clockwise
|
626 |
# rotation_params["drawed_image"] = drawed_image
|
627 |
|
628 |
+
# Step5. MTCNN 人脸检测
|
629 |
faces = face_number_detection_mtcnn(input_image)
|
630 |
|
631 |
# Step6. 证件照自适应裁剪
|
632 |
face_num = len(faces)
|
633 |
+
# 报错 MTCNN 检测结果不等于 1 的图片
|
634 |
if face_num != 1:
|
635 |
return None, None, None, None, None, None, None, None, 0
|
636 |
# 符合条件的进入下一环
|
637 |
else:
|
638 |
+
result_image_hd, result_image_standard, clothing_params = idphoto_cutting(
|
639 |
+
faces,
|
640 |
+
head_measure_ratio,
|
641 |
+
size,
|
642 |
+
head_height_ratio,
|
643 |
+
origin_png_image,
|
644 |
+
origin_png_image_pre,
|
645 |
+
rotation_params,
|
646 |
+
align=align,
|
647 |
+
IS_DEBUG=IS_DEBUG,
|
648 |
+
top_distance_max=top_distance_max,
|
649 |
+
top_distance_min=top_distance_min,
|
650 |
+
)
|
651 |
|
652 |
# Step7. 排版照参数获取
|
653 |
+
typography_arr, typography_rotate = generate_layout_photo(
|
654 |
+
input_height=size[0], input_width=size[1]
|
655 |
+
)
|
656 |
+
|
657 |
+
return (
|
658 |
+
result_image_hd,
|
659 |
+
result_image_standard,
|
660 |
+
typography_arr,
|
661 |
+
typography_rotate,
|
662 |
+
clothing_params["relative_x"],
|
663 |
+
clothing_params["relative_y"],
|
664 |
+
clothing_params["w"],
|
665 |
+
clothing_params["h"],
|
666 |
+
1,
|
667 |
+
)
|
668 |
|
669 |
|
670 |
if __name__ == "__main__":
|
|
|
673 |
|
674 |
input_image = cv2.imread("test.jpg")
|
675 |
|
676 |
+
(
|
677 |
+
result_image_hd,
|
678 |
+
result_image_standard,
|
679 |
+
typography_arr,
|
680 |
+
typography_rotate,
|
681 |
+
_,
|
682 |
+
_,
|
683 |
+
_,
|
684 |
+
_,
|
685 |
+
_,
|
686 |
+
) = IDphotos_create(
|
687 |
+
input_image,
|
688 |
+
size=(413, 295),
|
689 |
+
head_measure_ratio=0.2,
|
690 |
+
head_height_ratio=0.45,
|
691 |
+
align=True,
|
692 |
+
beauty=True,
|
693 |
+
fd68=None,
|
694 |
+
human_sess=sess,
|
695 |
+
oss_image_name="test_tmping.jpg",
|
696 |
+
user=None,
|
697 |
+
IS_DEBUG=False,
|
698 |
+
top_distance_max=0.12,
|
699 |
+
top_distance_min=0.10,
|
700 |
+
)
|
701 |
cv2.imwrite("result_image_hd.png", result_image_hd)
|