Spaces:
Running
Running
#!/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 | |
from .layout_calculator import generate_layout_array | |
import hivision.creator.utils as U | |
import numpy as np | |
import math | |
import cv2 | |
def adjust_photo(ctx: Context): | |
# Step1. 准备人脸参数 | |
face_rect = ctx.face["rectangle"] | |
standard_size = ctx.params.size | |
params = ctx.params | |
x, y = face_rect[0], face_rect[1] | |
w, h = face_rect[2], face_rect[3] | |
height, width = ctx.matting_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.matting_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.matting_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_array( | |
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 | |
# 生成一张全透明背景 | |
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 | |
def standard_photo_resize(input_image: np.array, size): | |
""" | |
input_image: 输入图像,即高清照 | |
size: 标准照的尺寸 | |
""" | |
resize_ratio = input_image.shape[0] / size[0] | |
resize_item = int(round(input_image.shape[0] / size[0])) | |
if resize_ratio >= 2: | |
for i in range(resize_item - 1): | |
if i == 0: | |
result_image = cv2.resize( | |
input_image, | |
(size[1] * (resize_item - i - 1), size[0] * (resize_item - i - 1)), | |
interpolation=cv2.INTER_AREA, | |
) | |
else: | |
result_image = cv2.resize( | |
result_image, | |
(size[1] * (resize_item - i - 1), size[0] * (resize_item - i - 1)), | |
interpolation=cv2.INTER_AREA, | |
) | |
else: | |
result_image = cv2.resize( | |
input_image, (size[1], size[0]), interpolation=cv2.INTER_AREA | |
) | |
return result_image | |
def resize_image_by_min(input_image, esp=600): | |
""" | |
将图像缩放为最短边至少为 esp 的图像。 | |
:param input_image: 输入图像(OpenCV 矩阵) | |
:param esp: 缩放后的最短边长 | |
:return: 缩放后的图像,缩放倍率 | |
""" | |
height, width = input_image.shape[0], input_image.shape[1] | |
min_border = min(height, width) | |
if min_border < esp: | |
if height >= width: | |
new_width = esp | |
new_height = height * esp // width | |
else: | |
new_height = esp | |
new_width = width * esp // height | |
return ( | |
cv2.resize( | |
input_image, (new_width, new_height), interpolation=cv2.INTER_AREA | |
), | |
new_height / height, | |
) | |
else: | |
return input_image, 1 | |