mirror of
https://github.com/Zeyi-Lin/HivisionIDPhotos.git
synced 2026-08-28 19:45:12 +08:00
refactor:main
This commit is contained in:
+2
-2
@@ -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
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
@DATE: 2024/9/5 16:45
|
||||
@File: __init__.py
|
||||
@IDE: pycharm
|
||||
@Description:
|
||||
创建证件照
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
@@ -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]]
|
||||
@@ -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])
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,3 +1,4 @@
|
||||
opencv-python>=4.8.1.78
|
||||
onnxruntime==1.15.0
|
||||
numpy==1.24.3
|
||||
mtcnn-runtime
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 证件照制作推理程序。")
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user