Spaces:
Running
Running
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)
|