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