Spaces:
Running
Running
import cv2 | |
import math | |
from ..utils import get_box_pro | |
from ..face_tools import face_detect_mtcnn | |
from ..vision import IDphotos_cut, detect_distance, resize_image_esp, draw_picture_dots | |
from ..matting_tools import get_modnet_matting | |
from .move_image import move | |
from src.hivisionai.hyTrain.APIs import aliyun_face_detect_api | |
import numpy as np | |
import json | |
def get_max(height, width, d1, d2, d3, d4, rotation_flag): | |
if rotation_flag: | |
height1 = height | |
height2 = height - int(d1.y) # d2 | |
height3 = int(d4.y) # d3 | |
height4 = int(d4.y) - int(d1.x) | |
width1 = width | |
width2 = width - int(d3.x) | |
width3 = int(d2.x) | |
width4 = int(d2.x) - int(d3.x) | |
else: | |
height1 = height | |
height2 = height - int(d2.y) | |
height3 = int(d3.y) | |
height4 = int(d3.y) - int(d2.y) | |
width1 = width | |
width2 = width - int(d1.x) | |
width3 = int(d4.x) | |
width4 = int(d4.x) - int(d1.x) | |
height_list = [height1, height2, height3, height4] | |
width_list = [width1, width2, width3, width4] | |
background_height = max(height_list) | |
status_height = height_list.index(background_height) | |
background_width = max(width_list) | |
status_width = width_list.index(background_width) | |
height_change = 0 | |
width_change = 0 | |
height_change2 = 0 | |
width_change2 = 0 | |
if status_height == 1 or status_height == 3: | |
if rotation_flag: | |
height_change = abs(d1.y) | |
height_change2 = d1.y | |
else: | |
height_change = abs(d2.y) | |
height_change2 = d2.y | |
if status_width == 1 or status_width == 3: | |
if rotation_flag: | |
width_change = abs(d3.x) | |
width_change2 = d3.x | |
else: | |
width_change = abs(d1.x) | |
width_change2 = d1.x | |
return background_height, status_height, background_width, status_width, height_change, width_change,\ | |
height_change2, width_change2 | |
class LinearFunction_TwoDots(object): | |
""" | |
通过两个坐标点构建线性函数 | |
""" | |
def __init__(self, dot1, dot2): | |
self.d1 = dot1 | |
self.d2 = dot2 | |
self.k = (self.d2.y - self.d1.y) / (self.d2.x - self.d1.x) | |
self.b = self.d2.y - self.k * self.d2.x | |
def forward(self, input, mode="x"): | |
if mode == "x": | |
return self.k * input + self.b | |
elif mode == "y": | |
return (input - self.b) / self.k | |
def forward_x(self, x): | |
return self.k * x + self.b | |
def forward_y(self, y): | |
return (y - self.b) / self.k | |
class Coordinate(object): | |
def __init__(self, x, y): | |
self.x = x | |
self.y = y | |
def __str__(self): | |
return "({}, {})".format(self.x, self.y) | |
def IDphotos_create(input_image, size=(413, 295), head_measure_ratio=0.2, head_height_ratio=0.45, | |
checkpoint_path="checkpoint/ModNet1.0.onnx", align=True): | |
""" | |
input_path: 输入图像路径 | |
output_path: 输出图像路径 | |
size: 裁剪尺寸,格式应该如(413,295),竖直距离在前,水平距离在后 | |
head_measure_ratio: 人头面积占照片面积的head_ratio | |
head_height_ratio: 人头中心处于照片从上到下的head_height | |
align: 是否进行人脸矫正 | |
""" | |
input_image = resize_image_esp(input_image, 2000) # 将输入图片压缩到最大边长为2000 | |
# cv2.imwrite("./temp_input_image.jpg", input_image) | |
origin_png_image = get_modnet_matting(input_image, checkpoint_path) | |
# cv2.imwrite("./test_image/origin_png_image.png", origin_png_image) | |
_, _, _, a = cv2.split(origin_png_image) | |
width_length_ratio = size[0]/size[1] # 长宽比 | |
rotation = aliyun_face_detect_api("./temp_input_image.jpg") | |
# 如果旋转角过小,则不进行矫正 | |
if abs(rotation) < 0.025: | |
align=False | |
if align: | |
print("开始align") | |
if rotation > 0: | |
rotation_flag = 0 # 逆时针旋转 | |
else: | |
rotation_flag = 1 # 顺时针旋转 | |
width, height, channels = input_image.shape | |
p_list = [(0, 0), (0, height), (width, 0), (width, height)] | |
rotate_list = [] | |
rotate = cv2.getRotationMatrix2D((height * 0.5, width * 0.5), rotation, 0.75) | |
for p in p_list: | |
p_m = np.array([[p[1]], [p[0]], [1]]) | |
rotate_list.append(np.dot(rotate[:2], p_m)) | |
# print("旋转角的四个顶点", rotate_list) | |
input_image = cv2.warpAffine(input_image, rotate, (height, width), flags=cv2.INTER_AREA) | |
new_a = cv2.warpAffine(a, rotate, (height, width), flags=cv2.INTER_AREA) | |
# cv2.imwrite("./test_image/rotation.jpg", input_image) | |
# ===================== 开始人脸检测 ===================== # | |
faces, _ = face_detect_mtcnn(input_image, filter=True) | |
face_num = len(faces) | |
print("检测到的人脸数目为:", len(faces)) | |
# ===================== 人脸检测结束 ===================== # | |
if face_num == 1: | |
face_rect = faces[0] | |
x, y = face_rect[0], face_rect[1] | |
w, h = face_rect[2] - x + 1, face_rect[3] - y + 1 | |
elif face_num == 0: | |
print("无人脸,返回0!!!") | |
return 0 | |
else: | |
print("太多人脸,返回2!!!") | |
return 2 | |
d1, d2, d3, d4 = rotate_list[0], rotate_list[1], rotate_list[2], rotate_list[3] | |
d1 = Coordinate(int(d1[0]), int(d1[1])) | |
d2 = Coordinate(int(d2[0]), int(d2[1])) | |
d3 = Coordinate(int(d3[0]), int(d3[1])) | |
d4 = Coordinate(int(d4[0]), int(d4[1])) | |
print("d1:", d1) | |
print("d2:", d2) | |
print("d3:", d3) | |
print("d4:", d4) | |
background_height, status_height, background_width, status_width,\ | |
height_change, width_change, height_change2, width_change2 = get_max(width, height, d1, d2, d3, d4, rotation_flag) | |
print("background_height:", background_height) | |
print("background_width:", background_width) | |
print("status_height:", status_height) | |
print("status_width:", status_width) | |
print("height_change:", height_change) | |
print("width_change:", width_change) | |
background = np.zeros([background_height, background_width, 3]) | |
background_a = np.zeros([background_height, background_width]) | |
background[height_change:height_change+width, width_change:width_change+height] = input_image | |
background_a[height_change:height_change+width, width_change:width_change+height] = new_a | |
d1 = Coordinate(int(d1.x)-width_change2, int(d1.y)-height_change2) | |
d2 = Coordinate(int(d2.x)-width_change2, int(d2.y)-height_change2) | |
d3 = Coordinate(int(d3.x)-width_change2, int(d3.y)-height_change2) | |
d4 = Coordinate(int(d4.x)-width_change2, int(d4.y)-height_change2) | |
print("d1:", d1) | |
print("d2:", d2) | |
print("d3:", d3) | |
print("d4:", d4) | |
if rotation_flag: | |
f13 = LinearFunction_TwoDots(d1, d3) | |
d5 = Coordinate(max(0, d3.x), f13.forward_x(max(0, d3.x))) | |
print("d5:", d5) | |
f42 = LinearFunction_TwoDots(d4, d2) | |
d7 = Coordinate(f42.forward_y(d5.y), d5.y) | |
print("d7", d7) | |
background_draw = draw_picture_dots(background, dots=[(d1.x, d1.y), | |
(d2.x, d2.y), | |
(d3.x, d3.y), | |
(d4.x, d4.y), | |
(d5.x, d5.y), | |
(d7.x, d7.y)]) | |
# cv2.imwrite("./test_image/rotation_background.jpg", background_draw) | |
if x<d5.x or x+w>d7.x: | |
print("return 6") | |
return 6 | |
background_output = background[:int(d5.y), int(d5.x):int(d7.x)] | |
background_a_output = background_a[:int(d5.y), int(d5.x):int(d7.x)] | |
# cv2.imwrite("./test_image/rotation_background_cut.jpg", background_output) | |
else: | |
f34 = LinearFunction_TwoDots(d3, d4) | |
d5 = Coordinate(min(width_change+height, d4.x), f34.forward_x(min(width_change+height, d4.x))) | |
print("d5:", d5) | |
f13 = LinearFunction_TwoDots(d1, d3) | |
d7 = Coordinate(f13.forward_y(d5.y), d5.y) | |
print("d7", d7) | |
if x<d7.x or x+w>d5.x: | |
print("return 6") | |
return 6 | |
background_draw = draw_picture_dots(background, dots=[(d1.x, d1.y), | |
(d2.x, d2.y), | |
(d3.x, d3.y), | |
(d4.x, d4.y), | |
(d5.x, d5.y), | |
(d7.x, d7.y)]) | |
# cv2.imwrite("./test_image/rotation_background.jpg", background_draw) | |
background_output = background[:int(d5.y), int(d7.x):int(d5.x)] | |
background_a_output = background_a[:int(d5.y), int(d7.x):int(d5.x)] | |
# cv2.imwrite("./test_image/rotation_background_cut.jpg", background_output) | |
input_image = np.uint8(background_output) | |
b, g, r = cv2.split(input_image) | |
origin_png_image = cv2.merge((b, g, r, np.uint8(background_a_output))) | |
# ===================== 开始人脸检测 ===================== # | |
width, length = input_image.shape[0], input_image.shape[1] | |
faces, _ = face_detect_mtcnn(input_image, filter=True) | |
face_num = len(faces) | |
print("检测到的人脸数目为:", len(faces)) | |
# ===================== 人脸检测结束 ===================== # | |
if face_num == 1: | |
face_rect = faces[0] | |
x, y = face_rect[0], face_rect[1] | |
w, h = face_rect[2] - x + 1, face_rect[3] - y + 1 | |
# x,y,w,h代表人脸框的左上角坐标和宽高 | |
# 检测头顶下方空隙,如果头顶下方空隙过小,则拒绝 | |
if y+h >= 0.85*width: | |
# print("face bottom too short! y+h={} width={}".format(y+h, width)) | |
print("在人脸下方的空间太少,返回值3!!!") | |
return 3 | |
# 第一次裁剪 | |
# 确定裁剪的基本参数 | |
face_center = (x+w/2, y+h/2) # 面部中心坐标 | |
face_measure = w*h # 面部面积 | |
crop_measure = face_measure/head_measure_ratio # 裁剪框面积:为面部面积的5倍 | |
resize_ratio = crop_measure/(size[0]*size[1]) # 裁剪框缩放率(以输入尺寸为标准) | |
resize_ratio_single = math.sqrt(resize_ratio) | |
crop_size = (int(size[0]*resize_ratio_single), int(size[1]*resize_ratio_single)) # 裁剪框大小 | |
print("crop_size:", crop_size) | |
# 裁剪规则:x1和y1为裁剪的起始坐标,x2和y2为裁剪的最终坐标 | |
# y的确定由人脸中心在照片的45%位置决定 | |
x1 = int(face_center[0]-crop_size[1]/2) | |
y1 = int(face_center[1]-crop_size[0]*head_height_ratio) | |
y2 = y1+crop_size[0] | |
x2 = x1+crop_size[1] | |
# 对原图进行抠图,得到透明图img | |
print("开始进行抠图") | |
# origin_png_image => 对原图的抠图结果 | |
# cut_image => 第一次裁剪后的图片 | |
# result_image => 第二次裁剪后的图片/输出图片 | |
# origin_png_image = get_human_matting(input_image, get_file_dir(checkpoint_path)) | |
cut_image = IDphotos_cut(x1, y1, x2, y2, origin_png_image) | |
# cv2.imwrite("./temp.png", cut_image) | |
# 对裁剪得到的图片temp_path,我们将image=temp_path resize为裁剪框大小,这样方便进行后续计算 | |
cut_image = cv2.resize(cut_image, (crop_size[1], crop_size[0])) | |
y_top, y_bottom, x_left, x_right = get_box_pro(cut_image, model=2) # 得到透明图中人像的上下左右距离信息 | |
print("y_top:{}, y_bottom:{}, x_left:{}, x_right:{}".format(y_top, y_bottom, x_left, x_right)) | |
# 判断左右是否有间隙 | |
if x_left > 0 or x_right > 0: | |
# 左右有空隙, 我们需要减掉它 | |
print("左右有空隙!") | |
status_left_right = 1 | |
cut_value_top = int(((x_left + x_right) * width_length_ratio) / 2) # 减去左右,为了保持比例,上下也要相应减少cut_value_top | |
print("cut_value_top:", cut_value_top) | |
else: | |
# 左右没有空隙, 则不管 | |
status_left_right = 0 | |
cut_value_top = 0 | |
print("cut_value_top:", cut_value_top) | |
# 检测人头顶与照片的顶部是否在合适的距离内 | |
print("y_top:", y_top) | |
status_top, move_value = detect_distance(y_top-int((x_left+x_right)*width_length_ratio/2), crop_size[0]) | |
# status=0 => 距离合适, 无需移动 | |
# status=1 => 距离过大, 人像应向上移动 | |
# status=2 => 距离过小, 人像应向下移动 | |
# move_value => 上下移动的距离 | |
print("status_top:", status_top) | |
print("move_value:", move_value) | |
# 开始第二次裁剪 | |
if status_top == 0: | |
# 如果上下距离合适,则无需移动 | |
if status_left_right: | |
# 如果左右有空隙,则需要用到cut_value_top | |
result_image = IDphotos_cut(x1 + x_left, | |
y1 + cut_value_top, | |
x2 - x_right, | |
y2 - cut_value_top, | |
origin_png_image) | |
else: | |
# 如果左右没有空隙,那么则无需改动 | |
result_image = cut_image | |
elif status_top == 1: | |
# 如果头顶离照片顶部距离过大,需要人像向上移动,则需要用到move_value | |
if status_left_right: | |
# 左右存在距离,则需要cut_value_top | |
result_image = IDphotos_cut(x1 + x_left, | |
y1 + cut_value_top + move_value, | |
x2 - x_right, | |
y2 - cut_value_top + move_value, | |
origin_png_image) | |
else: | |
# 左右不存在距离 | |
result_image = IDphotos_cut(x1 + x_left, | |
y1 + move_value, | |
x2 - x_right, | |
y2 + move_value, | |
origin_png_image) | |
else: | |
# 如果头顶离照片顶部距离过小,则需要人像向下移动,则需要用到move_value | |
if status_left_right: | |
# 左右存在距离,则需要cut_value_top | |
result_image = IDphotos_cut(x1 + x_left, | |
y1 + cut_value_top - move_value, | |
x2 - x_right, | |
y2 - cut_value_top - move_value, | |
origin_png_image) | |
else: | |
# 左右不存在距离 | |
result_image = IDphotos_cut(x1 + x_left, | |
y1 - move_value, | |
x2 - x_right, | |
y2 - move_value, | |
origin_png_image) | |
# 调节头顶位置————防止底部空一块儿 | |
result_image = move(result_image) | |
# 高清保存 | |
# cv2.imwrite(output_path.replace(".png", "_HD.png"), result_image) | |
# 普清保存 | |
result_image2 = cv2.resize(result_image, (size[1], size[0]), interpolation=cv2.INTER_AREA) | |
# cv2.imwrite("./output_image.png", result_image) | |
print("完成.返回1") | |
return 1, result_image, result_image2 | |
elif face_num == 0: | |
print("无人脸,返回0!!!") | |
return 0 | |
else: | |
print("太多人脸,返回2!!!") | |
return 2 | |
if __name__ == "__main__": | |
with open("./Setting.json") as json_file: | |
# file_list = get_filedir_filelist("./input_image") | |
setting = json.load(json_file) | |
filedir = "../IDPhotos/input_image/linzeyi.jpg" | |
file_list = [filedir] | |
for filedir in file_list: | |
print(filedir) | |
# try: | |
status_id, result_image, result_image2 = IDphotos_create(cv2.imread(filedir), | |
size=(setting["size_height"], setting["size_width"]), | |
head_height_ratio=setting["head_height_ratio"], | |
head_measure_ratio=setting["head_measure_ratio"], | |
checkpoint_path=setting["checkpoint_path"], | |
align=True) | |
# cv2.imwrite("./result_image.png", result_image) | |
if status_id == 1: | |
print("处理完毕!") | |
elif status_id == 0: | |
print("没有人脸!请重新上传有人脸的照片.") | |
elif status_id == 2: | |
print("人脸不只一张!请重新上传单独人脸的照片.") | |
elif status_id == 3: | |
print("人头下方空隙不足!") | |
elif status_id == 4: | |
print("此照片不能制作该规格!") | |
# except Exception as e: | |
# print(e) |