From 14fc91919c62b9b9a7348aaa31aba2cec0e59e62 Mon Sep 17 00:00:00 2001 From: KAAANG <79990647+SAKURA-CAT@users.noreply.github.com> Date: Thu, 5 Sep 2024 21:20:17 +0800 Subject: [PATCH] refactor:main --- app/web.py | 4 +- hivision/creater/__init__.py | 10 - {src => hivision/creator}/EulerZ.py | 0 hivision/creator/__init__.py | 105 +++++++++ hivision/creator/context.py | 93 ++++++++ {src => hivision/creator}/cuny_tools.py | 0 hivision/creator/face_detector.py | 42 ++++ .../creator}/face_judgement_align.py | 64 +----- {src => hivision/creator}/imageTransform.py | 0 {src => hivision/creator}/layoutCreate.py | 0 {src => hivision/creator}/move_image.py | 0 hivision/creator/photo_adjuster.py | 207 ++++++++++++++++++ hivision/creator/utils.py | 142 ++++++++++++ hivision/error.py | 21 ++ requirements.txt | 1 + scripts/api/deploy_api.py | 5 +- scripts/inference.py | 4 +- src/error.py | 27 --- 18 files changed, 622 insertions(+), 103 deletions(-) delete mode 100644 hivision/creater/__init__.py rename {src => hivision/creator}/EulerZ.py (100%) create mode 100644 hivision/creator/__init__.py create mode 100644 hivision/creator/context.py rename {src => hivision/creator}/cuny_tools.py (100%) create mode 100644 hivision/creator/face_detector.py rename {src => hivision/creator}/face_judgement_align.py (93%) rename {src => hivision/creator}/imageTransform.py (100%) rename {src => hivision/creator}/layoutCreate.py (100%) rename {src => hivision/creator}/move_image.py (100%) create mode 100644 hivision/creator/photo_adjuster.py create mode 100644 hivision/creator/utils.py create mode 100644 hivision/error.py delete mode 100644 src/error.py diff --git a/app/web.py b/app/web.py index 3ceb971..c7ee5af 100644 --- a/app/web.py +++ b/app/web.py @@ -1,9 +1,9 @@ import os import gradio as gr import onnxruntime -from src.face_judgement_align import IDphotos_create +from hivision.creator.face_judgement_align import IDphotos_create from hivisionai.hycv.vision import add_background -from src.layoutCreate import generate_layout_photo, generate_layout_image +from hivision.creator.layoutCreate import generate_layout_photo, generate_layout_image import pathlib import numpy as np from utils.image_utils import resize_image_to_kb diff --git a/hivision/creater/__init__.py b/hivision/creater/__init__.py deleted file mode 100644 index c024d4f..0000000 --- a/hivision/creater/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -r""" -@DATE: 2024/9/5 16:45 -@File: __init__.py -@IDE: pycharm -@Description: - 创建证件照 -""" - diff --git a/src/EulerZ.py b/hivision/creator/EulerZ.py similarity index 100% rename from src/EulerZ.py rename to hivision/creator/EulerZ.py diff --git a/hivision/creator/__init__.py b/hivision/creator/__init__.py new file mode 100644 index 0000000..862c7d4 --- /dev/null +++ b/hivision/creator/__init__.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +r""" +@DATE: 2024/9/5 16:45 +@File: __init__.py +@IDE: pycharm +@Description: + 创建证件照 +""" +import numpy as np +from typing import Tuple +import hivision.creator.utils as U +from .context import Context, ContextHandler, Params, Result +from .face_detector import detect_face +from .photo_adjuster import adjust_photo + + +class IDCreator: + """ + 证件照创建类,包含完整的证件照流程 + """ + + def __init__(self): + # 回调时机 + self.before_all: ContextHandler = None + """ + 在所有处理之前,此时图像已经被 resize 到最大边长为 2000 + """ + self.after_matting: ContextHandler = None + """ + 在抠图之后,ctx.matting_image 被赋值 + """ + self.after_detect: ContextHandler = None + """ + 在人脸检测之后,ctx.face 被赋值,如果为仅换底,则不会执行此回调 + """ + self.after_adjust: ContextHandler = None + """ + 在人脸调整之后,ctx.face 被赋值,如果为仅换底,则不会执行此回调 + """ + self.after_all: ContextHandler = None + """ + 在所有处理之后,此时 ctx.result被赋值 + """ + + # 处理者 + self.matting_handler: ContextHandler = None + self.detection_handler: ContextHandler = None + # 上下文 + self.ctx = None + + def __call__( + self, + image: np.ndarray, + size: Tuple[int, int] = (413, 295), + change_bg_only: bool = False, + head_measure_ratio: float = 0.2, + head_height_ratio: float = 0.45, + head_top_range: float = (0.12, 0.1), + ) -> Result: + """ + 证件照处理函数 + :param image: 输入图像 + :param change_bg_only: 是否只需要换底 + :param size: 输出的图像大小(h,w) + :param head_measure_ratio: 人脸面积与全图面积的期望比值 + :param head_height_ratio: 人脸中心处在全图高度的比例期望值 + :param head_top_range: 头距离顶部的比例(max,min) + + :return: 返回处理后的证件照和一系列参数 + """ + # 0.初始化上下文 + params = Params( + size=size, + change_bg_only=change_bg_only, + head_measure_ratio=head_measure_ratio, + head_height_ratio=head_height_ratio, + head_top_range=head_top_range, + ) + self.ctx = Context(params) + ctx = self.ctx + ctx.processing_image = image + ctx.processing_image = U.resize_image_esp( + ctx.processing_image, 2000 + ) # 将输入图片 resize 到最大边长为 2000 + self.before_all and self.before_all(ctx) + # 1. 人像抠图 + self.matting_handler(ctx) + ctx.matting_image = ctx.processing_image.copy() + self.after_matting and self.after_matting(ctx) + # 2. 人脸检测 + ctx.face = detect_face(ctx) + self.after_detect and self.after_detect(ctx) + # 3. 图像调整 + result_image_hd, result_image_standard, clothing_params, typography_params = ( + adjust_photo(ctx) + ) + ctx.result = Result( + standard=result_image_standard, + hd=result_image_hd, + clothing_params=clothing_params, + typography_params=typography_params, + ) + self.after_adjust and self.after_adjust(ctx) + return ctx.result diff --git a/hivision/creator/context.py b/hivision/creator/context.py new file mode 100644 index 0000000..8445bb7 --- /dev/null +++ b/hivision/creator/context.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +r""" +@DATE: 2024/9/5 19:20 +@File: context.py +@IDE: pycharm +@Description: + 证件照创建上下文类,用于同步信息 +""" +from typing import Optional, Callable, Tuple +import numpy as np + + +class Params: + def __init__( + self, + size: Tuple[int, int] = (413, 295), + change_bg_only: bool = False, + head_measure_ratio: float = 0.2, + head_height_ratio: float = 0.45, + head_top_range: float = (0.12, 0.1), + ): + self.__size = size + self.__change_bg_only = change_bg_only + self.__head_measure_ratio = head_measure_ratio + self.__head_height_ratio = head_height_ratio + self.__head_top_range = head_top_range + + @property + def size(self): + return self.__size + + @property + def change_bg_only(self): + return self.__change_bg_only + + @property + def head_measure_ratio(self): + return self.__head_measure_ratio + + @property + def head_height_ratio(self): + return self.__head_height_ratio + + @property + def head_top_range(self): + return self.__head_top_range + + +class Result: + def __init__( + self, + standard: np.ndarray, + hd: np.ndarray, + clothing_params: dict, + typography_params: dict, + ): + self.standard = standard + self.hd = hd + self.clothing_params = clothing_params + self.typography_params = typography_params + + +class Context: + def __init__(self, params: Params): + self.params: Params = params + """ + 证件照处理参数 + """ + self.origin_image: Optional[np.ndarray] = None + """ + 输入的原始图像,处理时会进行resize,长宽不一定等于输入图像 + """ + self.processing_image: Optional[np.ndarray] = None + """ + 当前正在处理的图像 + """ + self.matting_image: Optional[np.ndarray] = None + """ + 人像抠图结果 + """ + self.face: Optional[Tuple[int, int, int, int, float]] = None + """ + 人脸检测结果,大于一个人脸时已在上层抛出异常 + 元组长度为5,包含 x1, y1, x2, y2, score 的坐标, (x1, y1)为左上角坐标,(x2, y2)为右下角坐标, score为置信度, 最大值为1 + """ + self.result: Optional[Result] = None + """ + 证件照处理结果 + """ + + +ContextHandler = Optional[Callable[[Context], None]] diff --git a/src/cuny_tools.py b/hivision/creator/cuny_tools.py similarity index 100% rename from src/cuny_tools.py rename to hivision/creator/cuny_tools.py diff --git a/hivision/creator/face_detector.py b/hivision/creator/face_detector.py new file mode 100644 index 0000000..aac3b2c --- /dev/null +++ b/hivision/creator/face_detector.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +r""" +@DATE: 2024/9/5 19:32 +@File: face_detector.py +@IDE: pycharm +@Description: + 人脸检测器 +""" +from mtcnnruntime import MTCNN +from .context import Context +from hivision.error import FaceError +import cv2 + +mtcnn = None + + +def detect_face(ctx: Context, scale: int = 2): + """ + 人脸检测处理者,只进行人脸数量的检测 + :param ctx: 上下文,此时已获取到原始图和抠图结果,但是我们只需要原始图 + :param scale: 最大边长缩放比例,原图:缩放图 = 1:scale + :raise FaceError: 人脸检测错误,多个人脸或者没有人脸 + """ + global mtcnn + if mtcnn is None: + mtcnn = MTCNN() + image = cv2.resize( + ctx.origin_image, + (ctx.origin_image.shape[1] // scale, ctx.origin_image.shape[0] // scale), + interpolation=cv2.INTER_AREA, + ) + faces, _ = mtcnn.detect(image) + if len(faces) != 1: + # 保险措施,如果检测到多个人脸或者没有人脸,用原图再检测一次 + faces, _ = mtcnn.detect(ctx.origin_image) + else: + for item, param in enumerate(faces[0]): + faces[0][item] = param * 2 + if len(faces) != 1: + raise FaceError("Expected 1 face, but got {}".format(len(faces)), len(faces)) + ctx.face = (faces[0][0], faces[0][1], faces[0][2], faces[0][3], faces[0][4]) diff --git a/src/face_judgement_align.py b/hivision/creator/face_judgement_align.py similarity index 93% rename from src/face_judgement_align.py rename to hivision/creator/face_judgement_align.py index 3ccf3bf..2c078dc 100644 --- a/src/face_judgement_align.py +++ b/hivision/creator/face_judgement_align.py @@ -12,16 +12,16 @@ from hivisionai.hycv.vision import ( rotate_bound_4channels, ) import onnxruntime -from src.error import IDError -from src.imageTransform import ( +from hivision.error import FaceError +from hivision.creator.imageTransform import ( standard_photo_resize, hollowOutFix, get_modnet_matting, draw_picture_dots, detect_distance, ) -from src.layoutCreate import generate_layout_photo -from src.move_image import move +from hivision.creator.layoutCreate import generate_layout_photo +from hivision.creator.move_image import move testImages = [] @@ -89,25 +89,12 @@ def face_number_and_angle_detection(input_image): - landmark: list,人脸关键点信息 """ - # face++ 人脸检测 - # input_image_bytes = CV2Bytes.cv2_byte(input_image, ".jpg") - # face_num, face_rectangle, landmarks, headpose = megvii_face_detector(input_image_bytes) - # print(face_rectangle) - faces, landmarks = face_detect_mtcnn(input_image) face_num = len(faces) # 排除不合人脸数目要求(必须是 1)的照片 if face_num == 0 or face_num >= 2: - if face_num == 0: - status_id_ = "1101" - else: - status_id_ = "1102" - raise IDError( - f"人脸检测出错!检测出了{face_num}张人脸", - face_num=face_num, - status_id=status_id_, - ) + raise FaceError(f"人脸检测出错!检测出了{face_num}张人脸", face_num=face_num) # 获得人脸定位坐标 face_rectangle = [] @@ -328,10 +315,7 @@ def idphoto_cutting( standard_size, head_height_ratio, origin_png_image, - origin_png_image_pre, rotation_params, - align=False, - IS_DEBUG=False, top_distance_max=0.12, top_distance_min=0.10, ): @@ -435,8 +419,6 @@ def idphoto_cutting( y_top, y_bottom, x_left, x_right = get_box_pro( cut_image.astype(np.uint8), model=2, correction_factor=0 ) # 得到 cut_image 中人像的上下左右距离信息 - if IS_DEBUG: - testImages.append(["firstCut", cut_image]) # Step5. 判定 cut_image 中的人像是否处于合理的位置,若不合理,则处理数据以便之后调整位置 # 检测人像与裁剪框左边或右边是否存在空隙 @@ -470,8 +452,6 @@ def idphoto_cutting( y2 - cut_value_top + status_top * move_value, origin_png_image, ) - if IS_DEBUG: - testImages.append(["result_image_pre", result_image]) # 换装参数准备 relative_x = x - (x1 + x_left) @@ -665,37 +645,3 @@ def IDphotos_create( clothing_params["h"], 1, ) - - -if __name__ == "__main__": - HY_HUMAN_MATTING_WEIGHTS_PATH = "./hivision_modnet.onnx" - sess = onnxruntime.InferenceSession(HY_HUMAN_MATTING_WEIGHTS_PATH) - - input_image = cv2.imread("test.jpg") - - ( - result_image_hd, - result_image_standard, - typography_arr, - typography_rotate, - _, - _, - _, - _, - _, - ) = IDphotos_create( - input_image, - size=(413, 295), - head_measure_ratio=0.2, - head_height_ratio=0.45, - align=True, - beauty=True, - fd68=None, - human_sess=sess, - oss_image_name="test_tmping.jpg", - user=None, - IS_DEBUG=False, - top_distance_max=0.12, - top_distance_min=0.10, - ) - cv2.imwrite("result_image_hd.png", result_image_hd) diff --git a/src/imageTransform.py b/hivision/creator/imageTransform.py similarity index 100% rename from src/imageTransform.py rename to hivision/creator/imageTransform.py diff --git a/src/layoutCreate.py b/hivision/creator/layoutCreate.py similarity index 100% rename from src/layoutCreate.py rename to hivision/creator/layoutCreate.py diff --git a/src/move_image.py b/hivision/creator/move_image.py similarity index 100% rename from src/move_image.py rename to hivision/creator/move_image.py diff --git a/hivision/creator/photo_adjuster.py b/hivision/creator/photo_adjuster.py new file mode 100644 index 0000000..8e6e890 --- /dev/null +++ b/hivision/creator/photo_adjuster.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +r""" +@DATE: 2024/9/5 20:02 +@File: photo_adjuster.py +@IDE: pycharm +@Description: + 证件照调整 +""" +from .context import Context +import hivision.creator.utils as U +from hivision.creator.imageTransform import ( + standard_photo_resize, +) +from hivisionai.hycv.vision import ( + resize_image_by_min, +) +import numpy as np +import math +import cv2 +from hivision.creator.layoutCreate import generate_layout_photo + + +def adjust_photo(ctx: Context): + # Step1. 准备人脸参数 + face_rect = ctx.face + standard_size = ctx.params.size + params = ctx.params + x, y = face_rect[0], face_rect[1] + w, h = face_rect[2] - x + 1, face_rect[3] - y + 1 + height, width = ctx.processing_image.shape[:2] + width_height_ratio = standard_size[0] / standard_size[1] + # Step2. 计算高级参数 + face_center = (x + w / 2, y + h / 2) # 面部中心坐标 + face_measure = w * h # 面部面积 + crop_measure = ( + face_measure / params.head_measure_ratio + ) # 裁剪框面积:为面部面积的 5 倍 + resize_ratio = crop_measure / (standard_size[0] * standard_size[1]) # 裁剪框缩放率 + resize_ratio_single = math.sqrt( + resize_ratio + ) # 长和宽的缩放率(resize_ratio 的开方) + crop_size = ( + int(standard_size[0] * resize_ratio_single), + int(standard_size[1] * resize_ratio_single), + ) # 裁剪框大小 + + # 裁剪框的定位信息 + x1 = int(face_center[0] - crop_size[1] / 2) + y1 = int(face_center[1] - crop_size[0] * params.head_height_ratio) + y2 = y1 + crop_size[0] + x2 = x1 + crop_size[1] + + # Step3, 裁剪框的调整 + cut_image = IDphotos_cut(x1, y1, x2, y2, ctx.processing_image) + cut_image = cv2.resize(cut_image, (crop_size[1], crop_size[0])) + y_top, y_bottom, x_left, x_right = U.get_box( + cut_image.astype(np.uint8), model=2, correction_factor=0 + ) # 得到 cut_image 中人像的上下左右距离信息 + + # Step5. 判定 cut_image 中的人像是否处于合理的位置,若不合理,则处理数据以便之后调整位置 + # 检测人像与裁剪框左边或右边是否存在空隙 + if x_left > 0 or x_right > 0: + status_left_right = 1 + cut_value_top = int( + ((x_left + x_right) * width_height_ratio) / 2 + ) # 减去左右,为了保持比例,上下也要相应减少 cut_value_top + else: + status_left_right = 0 + cut_value_top = 0 + + """ + 检测人头顶与照片的顶部是否在合适的距离内: + - status==0: 距离合适,无需移动 + - status=1: 距离过大,人像应向上移动 + - status=2: 距离过小,人像应向下移动 + """ + status_top, move_value = U.detect_distance( + y_top - cut_value_top, + crop_size[0], + max=params.head_top_range[0], + min=params.head_top_range[1], + ) + + # Step6. 对照片的第二轮裁剪 + if status_left_right == 0 and status_top == 0: + result_image = cut_image + else: + result_image = IDphotos_cut( + x1 + x_left, + y1 + cut_value_top + status_top * move_value, + x2 - x_right, + y2 - cut_value_top + status_top * move_value, + ctx.processing_image, + ) + + # 换装参数准备 + relative_x = x - (x1 + x_left) + relative_y = y - (y1 + cut_value_top + status_top * move_value) + + # Step7. 当照片底部存在空隙时,下拉至底部 + result_image, y_high = move(result_image.astype(np.uint8)) + relative_y = relative_y + y_high # 更新换装参数 + + # Step8. 标准照与高清照转换 + result_image_standard = standard_photo_resize(result_image, standard_size) + result_image_hd, resize_ratio_max = resize_image_by_min( + result_image, esp=max(600, standard_size[1]) + ) + + # Step9. 参数准备 - 为换装服务 + clothing_params = { + "relative_x": relative_x * resize_ratio_max, + "relative_y": relative_y * resize_ratio_max, + "w": w * resize_ratio_max, + "h": h * resize_ratio_max, + } + + # Step7. 排版照参数获取 + typography_arr, typography_rotate = generate_layout_photo( + input_height=standard_size[0], input_width=standard_size[1] + ) + + return ( + result_image_hd, + result_image_standard, + clothing_params, + { + "arr": typography_arr, + "rotate": typography_rotate, + }, + ) + + +def IDphotos_cut(x1, y1, x2, y2, img): + """ + 在图片上进行滑动裁剪,输入输出为 + 输入:一张图片 img,和裁剪框信息 (x1,x2,y1,y2) + 输出:裁剪好的图片,然后裁剪框超出了图像范围,那么将用 0 矩阵补位 + ------------------------------------ + x:裁剪框左上的横坐标 + y:裁剪框左上的纵坐标 + x2:裁剪框右下的横坐标 + y2:裁剪框右下的纵坐标 + crop_size:裁剪框大小 + img:裁剪图像(numpy.array) + output_path:裁剪图片的输出路径 + ------------------------------------ + """ + + crop_size = (y2 - y1, x2 - x1) + """ + ------------------------------------ + temp_x_1:裁剪框左边超出图像部分 + temp_y_1:裁剪框上边超出图像部分 + temp_x_2:裁剪框右边超出图像部分 + temp_y_2:裁剪框下边超出图像部分 + ------------------------------------ + """ + temp_x_1 = 0 + temp_y_1 = 0 + temp_x_2 = 0 + temp_y_2 = 0 + + if y1 < 0: + temp_y_1 = abs(y1) + y1 = 0 + if y2 > img.shape[0]: + temp_y_2 = y2 + y2 = img.shape[0] + temp_y_2 = temp_y_2 - y2 + + if x1 < 0: + temp_x_1 = abs(x1) + x1 = 0 + if x2 > img.shape[1]: + temp_x_2 = x2 + x2 = img.shape[1] + temp_x_2 = temp_x_2 - x2 + + # 生成一张全透明背景 + print("crop_size:", crop_size) + background_bgr = np.full((crop_size[0], crop_size[1]), 255, dtype=np.uint8) + background_a = np.full((crop_size[0], crop_size[1]), 0, dtype=np.uint8) + background = cv2.merge( + (background_bgr, background_bgr, background_bgr, background_a) + ) + + background[ + temp_y_1 : crop_size[0] - temp_y_2, temp_x_1 : crop_size[1] - temp_x_2 + ] = img[y1:y2, x1:x2] + + return background + + +def move(input_image): + """ + 裁剪主函数,输入一张 png 图像,该图像周围是透明的 + """ + png_img = input_image # 获取图像 + + height, width, channels = png_img.shape # 高 y、宽 x + y_low, y_high, _, _ = U.get_box(png_img, model=2) # for 循环 + base = np.zeros((y_high, width, channels), dtype=np.uint8) # for 循环 + png_img = png_img[0 : height - y_high, :, :] # for 循环 + png_img = np.concatenate((base, png_img), axis=0) + return png_img, y_high diff --git a/hivision/creator/utils.py b/hivision/creator/utils.py new file mode 100644 index 0000000..f11cf4f --- /dev/null +++ b/hivision/creator/utils.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +r""" +@DATE: 2024/9/5 19:25 +@File: utils.py +@IDE: pycharm +@Description: + 通用图像处理工具 +""" +import cv2 +import numpy as np + + +def resize_image_esp(input_image, esp=2000): + """ + 输入: + input_path:numpy 图片 + esp:限制的最大边长 + """ + # resize 函数=>可以让原图压缩到最大边为 esp 的尺寸 (不改变比例) + width = input_image.shape[0] + + length = input_image.shape[1] + max_num = max(width, length) + + if max_num > esp: + print("Image resizing...") + if width == max_num: + length = int((esp / width) * length) + width = esp + + else: + width = int((esp / length) * width) + length = esp + print(length, width) + im_resize = cv2.resize( + input_image, (length, width), interpolation=cv2.INTER_AREA + ) + return im_resize + else: + return input_image + + +def get_box( + image: np.ndarray, + model: int = 1, + correction_factor=None, + thresh: int = 127, +): + """ + 本函数能够实现输入一张四通道图像,返回图像中最大连续非透明面积的区域的矩形坐标 + 本函数将采用 opencv 内置函数来解析整个图像的 mask,并提供一些参数,用于读取图像的位置信息 + Args: + image: 四通道矩阵图像 + model: 返回值模式 + correction_factor: 提供一些边缘扩张接口,输入格式为 list 或者 int:[up, down, left, right]。 + 举个例子,假设我们希望剪切出的矩形框左边能够偏左 1 个像素,则输入 [0, 0, 1, 0]; + 如果希望右边偏右 1 个像素,则输入 [0, 0, 0, 1] + 如果输入为 int,则默认只会对左右两边做拓展,比如输入 2,则和 [0, 0, 2, 2] 是等效的 + thresh: 二值化阈值,为了保持一些羽化效果,thresh 必须要小 + Returns: + model 为 1 时,将会返回切割出的矩形框的四个坐标点信息 + model 为 2 时,将会返回矩形框四边相距于原图四边的距离 + """ + # ------------ 数据格式规范部分 -------------- # + # 输入必须为四通道 + if correction_factor is None: + correction_factor = [0, 0, 0, 0] + if not isinstance(image, np.ndarray) or len(cv2.split(image)) != 4: + raise TypeError("输入的图像必须为四通道 np.ndarray 类型矩阵!") + # correction_factor 规范化 + if isinstance(correction_factor, int): + correction_factor = [0, 0, correction_factor, correction_factor] + elif not isinstance(correction_factor, list): + raise TypeError("correction_factor 必须为 int 或者 list 类型!") + # ------------ 数据格式规范完毕 -------------- # + # 分离 mask + _, _, _, mask = cv2.split(image) + # mask 二值化处理 + _, mask = cv2.threshold(mask, thresh=thresh, maxval=255, type=0) + contours, hierarchy = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) + temp = np.ones(image.shape, np.uint8) * 255 + cv2.drawContours(temp, contours, -1, (0, 0, 255), -1) + contours_area = [] + for cnt in contours: + contours_area.append(cv2.contourArea(cnt)) + idx = contours_area.index(max(contours_area)) + x, y, w, h = cv2.boundingRect(contours[idx]) # 框出图像 + # ------------ 开始输出数据 -------------- # + height, width, _ = image.shape + y_up = y - correction_factor[0] if y - correction_factor[0] >= 0 else 0 + y_down = ( + y + h + correction_factor[1] + if y + h + correction_factor[1] < height + else height - 1 + ) + x_left = x - correction_factor[2] if x - correction_factor[2] >= 0 else 0 + x_right = ( + x + w + correction_factor[3] + if x + w + correction_factor[3] < width + else width - 1 + ) + if model == 1: + # model=1,将会返回切割出的矩形框的四个坐标点信息 + return [y_up, y_down, x_left, x_right] + elif model == 2: + # model=2, 将会返回矩形框四边相距于原图四边的距离 + return [y_up, height - y_down, x_left, width - x_right] + else: + raise EOFError("请选择正确的模式!") + + +def detect_distance(value, crop_height, max=0.06, min=0.04): + """ + 检测人头顶与照片顶部的距离是否在适当范围内。 + 输入:与顶部的差值 + 输出:(status, move_value) + status=0 不动 + status=1 人脸应向上移动(裁剪框向下移动) + status-2 人脸应向下移动(裁剪框向上移动) + --------------------------------------- + value:头顶与照片顶部的距离 + crop_height: 裁剪框的高度 + max: 距离的最大值 + min: 距离的最小值 + --------------------------------------- + """ + value = value / crop_height # 头顶往上的像素占图像的比例 + if min <= value <= max: + return 0, 0 + elif value > max: + # 头顶往上的像素比例高于 max + move_value = value - max + move_value = int(move_value * crop_height) + # print("上移{}".format(move_value)) + return 1, move_value + else: + # 头顶往上的像素比例低于 min + move_value = min - value + move_value = int(move_value * crop_height) + # print("下移{}".format(move_value)) + return -1, move_value diff --git a/hivision/error.py b/hivision/error.py new file mode 100644 index 0000000..dd1ae85 --- /dev/null +++ b/hivision/error.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +r""" +@DATE: 2024/9/5 18:32 +@File: error.py +@IDE: pycharm +@Description: + 错误处理 +""" + + +class FaceError(Exception): + def __init__(self, err, face_num): + """ + 证件照人脸错误,此时人脸检测失败,可能是没有检测到人脸或者检测到多个人脸 + Args: + err: 错误描述 + face_num: 告诉此时识别到的人像个数 + """ + super().__init__(err) + self.face_num = face_num diff --git a/requirements.txt b/requirements.txt index d39b76e..e1104f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ opencv-python>=4.8.1.78 onnxruntime==1.15.0 numpy==1.24.3 +mtcnn-runtime \ No newline at end of file diff --git a/scripts/api/deploy_api.py b/scripts/api/deploy_api.py index 6cbb599..7785a23 100644 --- a/scripts/api/deploy_api.py +++ b/scripts/api/deploy_api.py @@ -1,13 +1,12 @@ from fastapi import FastAPI, UploadFile, Form import onnxruntime -from src.face_judgement_align import IDphotos_create -from src.layoutCreate import generate_layout_photo, generate_layout_image +from hivision.creator.face_judgement_align import IDphotos_create +from hivision.creator.layoutCreate import generate_layout_photo, generate_layout_image from hivisionai.hycv.vision import add_background from utils import resize_image_to_kb_base64, hex_to_rgb import base64 import numpy as np import cv2 -import ast app = FastAPI() diff --git a/scripts/inference.py b/scripts/inference.py index 7b6a132..a1ae0ce 100644 --- a/scripts/inference.py +++ b/scripts/inference.py @@ -4,9 +4,9 @@ import argparse import numpy as np import onnxruntime from utils import resize_image_to_kb, hex_to_rgb -from src.face_judgement_align import IDphotos_create +from hivision.creator.face_judgement_align import IDphotos_create from hivisionai.hycv.vision import add_background -from src.layoutCreate import generate_layout_photo, generate_layout_image +from hivision.creator.layoutCreate import generate_layout_photo, generate_layout_image parser = argparse.ArgumentParser(description="HivisionIDPhotos 证件照制作推理程序。") diff --git a/src/error.py b/src/error.py deleted file mode 100644 index 916b2b4..0000000 --- a/src/error.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -@author: cuny -@file: error.py -@time: 2022/4/7 15:50 -@description: -定义证件照制作的错误类 -""" -from hivisionai.hyService.error import ProcessError - - -class IDError(ProcessError): - def __init__(self, err, diary=None, face_num=-1, status_id: str = "1500"): - """ - 用于报错 - Args: - err: 错误描述 - diary: 函数运行日志,默认为 None - face_num: 告诉此时识别到的人像个数,如果为 -1 则说明为未知错误 - """ - super().__init__(err) - if diary is None: - diary = {} - self.err = err - self.diary = diary - self.face_num = face_num - self.status_id = status_id -