HivisionIDPhotos / hivision /creator /photo_adjuster.py
TheEeeeLin's picture
update
9b2289b
raw
history blame
8.42 kB
#!/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_photo
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_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
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