TheEeeeLin commited on
Commit
cc3e5bb
1 Parent(s): 72049af
Files changed (3) hide show
  1. app.py +250 -62
  2. size_list_EN.csv +16 -0
  3. 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
- size_list_dict = csv_to_size_list("size_list_CN.csv")
13
- print(size_list_dict)
 
14
 
15
- color_list_dict = {
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
- idphoto_json["size"] = size_list_dict[size_list_option]
 
 
 
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="宽度应不大于长度;长宽不应小于100,大于1800", visible=True
 
 
 
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
- idphoto_json["color_bgr"] = color_list_dict[color_option]
 
 
 
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(value="人脸数量不等于1", visible=True),
 
 
 
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 idphoto_json["render_mode"] == "上下渐变(白)":
 
 
 
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 idphoto_json["size_mode"] == "只换底":
 
 
 
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 = "./hivision_modnet.onnx"
231
  sess = onnxruntime.InferenceSession(HY_HUMAN_MATTING_WEIGHTS_PATH)
232
 
233
- size_mode = ["尺寸列表", "只换底", "自定义尺寸"]
234
- size_list = list(size_list_dict.keys())
235
- colors = ["蓝色", "白色", "红色", "自定义底色"]
236
- render = ["纯色", "上下渐变(白)", "中心渐变(白)"]
237
- image_kb = ["不设置", "自定义"]
 
 
 
 
 
 
 
 
 
 
238
 
239
  title = "<h1 id='title'>HivisionIDPhotos</h1>"
240
- description = "<h3>😎9.2更新:新增照片大小KB调整</h3>"
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=size_mode,
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=size_list,
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=colors, label="背景色", value="蓝色", elem_id="color"
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=render,
293
- label="渲染方式",
294
- value="纯色",
295
  elem_id="render",
296
  )
297
 
298
- # 左:输出KB大小选项
299
  image_kb_options = gr.Radio(
300
- choices=image_kb,
301
- label="设置KB大小(结果在右边最底的组件下载)",
302
- value="不设置",
303
  elem_id="image_kb",
304
  )
305
 
306
- # 自定义KB大小, 滑动条,最小10KB,最大200KB
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(pathlib.Path("images").rglob("*.jpg"))
 
 
 
 
324
  ],
325
  )
326
 
327
- # ---------------- 右半边UI ----------------
328
  with gr.Column():
329
- notification = gr.Text(label="状态", visible=False)
330
  with gr.Row():
331
- img_output_standard = gr.Image(label="标准照").style(height=350)
332
- img_output_standard_hd = gr.Image(label="高清照").style(height=350)
333
- img_output_layout = gr.Image(label="六寸排版照").style(height=350)
334
- file_download = gr.File(label="下载调整KB大小后的照片", visible=False)
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 size_option_item == "自定义尺寸":
 
 
 
345
  return {
346
  custom_size: gr.update(visible=True),
347
  size_list_row: gr.update(visible=False),
348
  }
349
- elif size_option_item == "只换底":
 
 
 
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
- demo.launch()
 
 
 
 
 
 
 
 
 
 
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 resize_image_esp, IDphotos_cut, add_background, calTime, resize_image_by_min, \
7
- rotate_bound_4channels
 
 
 
 
 
 
8
  import onnxruntime
9
  from src.error import IDError
10
- from src.imageTransform import standard_photo_resize, hollowOutFix, get_modnet_matting, draw_picture_dots, detect_distance
 
 
 
 
 
 
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(f"人脸检测出错!检测出了{face_num}张人脸", face_num=face_num, status_id=status_id_)
 
 
 
 
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({'top': x1, 'left': y1, 'width': x2 - x1, 'height': y2 - y1})
 
 
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(input_image, sess=params["modnet"]["human_sess"])
 
 
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(input_image, a, rotation)
 
 
155
 
156
  # Step3. 附带信息计算
157
  nh, nw = result_jpg_image.shape[:2] # 旋转后的新的长宽
158
- clockwise = -1 if rotation < 0 else 1 # clockwise代表时针,即1为顺时针,-1为逆时针
 
 
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(int(0.25 * p2.x + 0.75 * p3.x), int(0.25 * p2.y + 0.75 * p3.y))
 
 
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(int(0.75 * p4.x + 0.25 * p1.x), int(0.75 * p4.y + 0.25 * p1.y))
178
-
179
- # Step4. 根据附带信息进行图像绘制(4个旋转点),便于DEBUG模式验证
180
- drawed_dots_image = draw_picture_dots(result_jpg_image, [(p1.x, p1.y), (p2.x, p2.y), (p3.x, p3.y),
181
- (p4.x, p4.y), (dotL3.x, dotL3.y)])
 
 
 
 
182
  if IS_DEBUG:
183
  testImages.append(["drawed_dots_image", drawed_dots_image])
184
 
185
- return result_jpg_image, result_png_image, L1, L2, dotL3, clockwise, drawed_dots_image
 
 
 
 
 
 
 
 
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(input_image,
201
- (input_image.shape[1] // 2, input_image.shape[0] // 2),
202
- interpolation=cv2.INTER_AREA)
203
- faces, _ = face_detect_mtcnn(input_image_resize, filter=True) # MTCNN人脸检测
204
- # 如果缩放后图像的MTCNN人脸数目检测结果等于1->两次人脸检测结果没有偏差,则对定位数据x2
 
 
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(x1, y1, x2, y2, width, height, L1, L2, L3, clockwise, standard_size):
 
 
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(faces, head_measure_ratio, standard_size, head_height_ratio, origin_png_image, origin_png_image_pre,
283
- rotation_params, align=False, IS_DEBUG=False, top_distance_max=0.12, top_distance_min=0.10):
 
 
 
 
 
 
 
 
 
 
 
284
  """
285
- 本函数的功能为进行证件照的自适应裁剪,自适应依据Setting.json的控制参数,以及输入图像的自身情况。
286
  Args:
287
  - faces: list, 人脸位置信息
288
  - head_measure_ratio: float, 人脸面积与全图面积的期望比值
289
- - standard_size: tuple, 标准照尺寸, (413, 295)
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(resize_ratio) # 长和宽的缩放率(resize_ratio的开方)
330
- crop_size = (int(standard_size[0] * resize_ratio_single),
331
- int(standard_size[1] * resize_ratio_single)) # 裁剪框大小
 
 
 
 
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(cut_image.astype(np.uint8), model=2,
378
- correction_factor=0) # 得到cut_image中人像的上下左右距离信息
 
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(((x_left + x_right) * width_height_ratio) / 2) # 减去左右,为了保持比例,上下也要相应减少cut_value_top
 
 
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(y_top - cut_value_top, crop_size[0], max=top_distance_max,
398
- min=top_distance_min)
 
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(x1 + x_left,
405
- y1 + cut_value_top + status_top * move_value,
406
- x2 - x_right,
407
- y2 - cut_value_top + status_top * move_value,
408
- origin_png_image)
 
 
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(result_image, esp=max(600, standard_size[1]))
 
 
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(result_image_test, text, (50, 50), cv2.FONT_HERSHEY_COMPLEX, 1.0,
449
- (200, 100, 100), 3)
 
 
 
 
 
 
 
450
  else:
451
- imageItem = cv2.resize(imageItem, (int(width * testHeight / height), testHeight))
452
- imageItem = cv2.putText(imageItem, text, (50, 50), cv2.FONT_HERSHEY_COMPLEX, 1.0, (200, 100, 100),
453
- 3)
 
 
 
 
 
 
 
 
 
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(input_image,
461
- mode="ID",
462
- size=(413, 295),
463
- head_measure_ratio=0.2,
464
- head_height_ratio=0.45,
465
- align=False,
466
- beauty=True,
467
- fd68=None,
468
- human_sess=None,
469
- IS_DEBUG=False,
470
- top_distance_max=0.12,
471
- top_distance_min=0.10):
 
 
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.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
- 排版照参数(list),排版照是否旋转参数,照片尺寸(x, y)
489
  在函数不出错的情况下,函数会因为一些原因主动抛出异常:
490
- 1. 无人脸(或者只有半张,dlib无法检测出来),抛出IDError异常,内部参数face_num为0
491
- 2. 人脸数量超过1,抛出IDError异常,内部参数face_num为2
492
- 3. 抠图api请求失败,抛出IDError异常,内部参数face_num为-1
493
  """
494
 
495
  # Step0. 数据准备/图像预处理
496
  matting_params = {"modnet": {"human_sess": human_sess}}
497
- rotation_params = {"L1": None, "L2": None, "L3": None, "clockwise": None, "drawed_image": None}
498
- input_image = resize_image_esp(input_image, 2000) # 将输入图片resize到最大边长为2000
 
 
 
 
 
 
 
 
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 = origin_png_image.copy() # 备份一下现在抠图结果图,之后在iphoto_cutting函数有用
 
 
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
- idphoto_cutting(faces, head_measure_ratio, size, head_height_ratio, origin_png_image,
546
- origin_png_image_pre, rotation_params, align=align, IS_DEBUG=IS_DEBUG,
547
- top_distance_max=top_distance_max, top_distance_min=top_distance_min)
 
 
 
 
 
 
 
 
 
548
 
549
  # Step7. 排版照参数获取
550
- typography_arr, typography_rotate = generate_layout_photo(input_height=size[0], input_width=size[1])
551
-
552
- return result_image_hd, result_image_standard, typography_arr, typography_rotate, \
553
- clothing_params["relative_x"], clothing_params["relative_y"], clothing_params["w"], clothing_params["h"], 1
 
 
 
 
 
 
 
 
 
 
 
554
 
555
 
556
  if __name__ == "__main__":
@@ -559,18 +673,29 @@ if __name__ == "__main__":
559
 
560
  input_image = cv2.imread("test.jpg")
561
 
562
- result_image_hd, result_image_standard, typography_arr, typography_rotate, \
563
- _, _, _, _, _ = IDphotos_create(input_image,
564
- size=(413, 295),
565
- head_measure_ratio=0.2,
566
- head_height_ratio=0.45,
567
- align=True,
568
- beauty=True,
569
- fd68=None,
570
- human_sess=sess,
571
- oss_image_name="test_tmping.jpg",
572
- user=None,
573
- IS_DEBUG=False,
574
- top_distance_max=0.12,
575
- top_distance_min=0.10)
 
 
 
 
 
 
 
 
 
 
 
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)