咨询一个照片裁剪的算法问题,客户的要求为两眼间距100到120,头顶间距40到80,分辨率540*700,下面是我初步写的代码,我的思路是先通过dlib对人像照片进行检测,然后获取到两眼中间的位置,然后通过
horizontal_padding_width = round((original_image_resolution.width - two_eyes_middle.x) * 0.85)
vertical_padding_top = round(head_top.y * 0.3)
vertical_padding_bottom = round((original_image_resolution.height - chin.y) * 0.6)
这些值从两眼中间往上下左右偏移出间距,这样做可以最大化保留细节,然后再通过resize函数缩放成540700保留的细节完整,图片不会模糊,人物形变的也不多,但是不满足两眼间距和头顶间距,因为这里是固定的值,我不知道怎么通过计算让裁剪区域再缩放成540700刚好就是我要求的两眼间距
def cop(
target_resolution: Resolution,
two_eyes_target_distance: DistanceRange,
head_to_top_margin_distance: DistanceRange,
file_size: DistanceRange | None,
img_path: str,
save_path: str,
bg_color: Color = (255, 255, 255, 255),
) -> None:
mat = from_path(img_path)
if mat is None:
logger.error("无法读取图片文件")
raise Exception("无法读取图片文件")
original_image = remove_bg(mat, bg_color)
if original_image is None:
logger.error("未获取移除背景的图片")
raise Exception("未获取移除背景的图片")
gray_image = to_gray_image(original_image)
if gray_image is None:
logger.error("未获取到灰度图")
raise Exception("未获取到灰度图")
faces = get_faces(gray_image)
if not faces:
logger.error("未检测到人脸")
raise Exception("未检测到人脸")
if len(faces) > 1:
logger.error("一张照片同时有多个人员, 请重新拍照")
raise Exception("一张照片同时有多个人员, 请重新拍照")
face = faces[0]
landmarks = get_face_landmarks(gray_image, face)
left_ear = get_left_ear(landmarks) # 左耳
chin = get_chin(landmarks) # 下巴
right_ear = get_right_ear(landmarks) # 右耳
head_top = get_highest_hair_point(gray_image) # 头顶
left_pupil = get_left_pupil(landmarks) # 左瞳孔
right_pupil = get_right_pupil(landmarks) # 右瞳孔
two_eyes_middle = get_two_eyes_middle_by_xy(left_pupil, right_pupil) # 两眼中间坐标
original_image_resolution = get_image_resolution(original_image) # 原图分辨率
two_eyes_distance = get_two_eyes_distance_by_xy(left_pupil, right_pupil) # 两眼间距 461
highest_hair_point = get_highest_hair_point(gray_image) # 获取图片的最高点
for target_eye in range(two_eyes_target_distance.min, two_eyes_target_distance.max + 1):
horizontal_padding_width = round((original_image_resolution.width - two_eyes_middle.x) * 0.85) # 水平填充宽度
vertical_padding_top = round(head_top.y * 0.3) # 垂直填充高度
vertical_padding_bottom = round((original_image_resolution.height - chin.y) * 0.6) # 垂直填充高度
print(horizontal_padding_width)
print(vertical_padding_top)
print(vertical_padding_bottom)
crop_box_left = two_eyes_middle.x - horizontal_padding_width # 裁剪区域的左边
crop_box_top = head_top.y - vertical_padding_top # 裁剪区域的上边
crop_box_right = two_eyes_middle.x + horizontal_padding_width # 裁剪区域的右边
crop_box_bottom = chin.y + vertical_padding_bottom # 裁剪区域的下边
crop_box_width = crop_box_right - crop_box_left # 裁剪后的宽
crop_box_height = crop_box_bottom - crop_box_top # 裁剪后的高
# 裁剪后图像中的瞳孔坐标
left_eye_pupil_x_cropped = left_pupil.x - crop_box_left
left_eye_pupil_y_cropped = left_pupil.y - crop_box_top
right_eye_pupil_x_cropped = right_pupil.x - crop_box_left
right_eye_pupil_y_cropped = right_pupil.y - crop_box_top
scale_left_pupil = Point2D(x=left_eye_pupil_x_cropped, y=left_eye_pupil_y_cropped)
scale_right_pupil = Point2D(x=right_eye_pupil_x_cropped, y=right_eye_pupil_y_cropped)
scale_two_eye_distance = round(get_two_eyes_distance_by_xy(scale_left_pupil, scale_right_pupil)) # 两眼间距
scale_head_top_distance = round(highest_hair_point.y - crop_box_top)
print(f"--------------------{target_eye}----------------------")
print(f"左耳:{left_ear}")
print(f"右耳:{right_ear}")
print(f"下巴:{chin}")
print(f"头顶:{head_top}")
print(f"左瞳孔:{left_pupil}")
print(f"右瞳孔:{right_pupil}")
print(f"原图:{(original_image_resolution.width, original_image_resolution.height)}")
print(f"原图两眼间距:{two_eyes_distance}")
print(f"原图头顶间距:{highest_hair_point.y}")
print(f"左:{crop_box_left}")
print(f"右:{crop_box_right}")
print(f"上:{crop_box_top}")
print(f"下:{crop_box_bottom}")
print(f"裁剪后的宽:{crop_box_width}")
print(f"裁剪后的高:{crop_box_height}")
print(f"缩放后的两眼间距:{scale_two_eye_distance}")
print(f"缩放后的头顶间距:{scale_head_top_distance}")
scale_x = target_resolution.width / crop_box_width
scale_y = target_resolution.height / crop_box_height
scale_two_eye_distance = scale_two_eye_distance * scale_x
scale_head_top_distance = scale_head_top_distance * scale_y
print(f"scale_x:{scale_x}")
print(f"scale_y:{scale_y}")
print(f"裁剪后宽度:{crop_box_width}")
print(f"裁剪后高度:{crop_box_height}")
print(f"两眼间距:{scale_two_eye_distance}")
print(f"头顶间距:{scale_head_top_distance}")
print(f"--------------------{target_eye}----------------------")
# eyes_result = two_eyes_target_distance.min < scale_two_eye_distance < two_eyes_target_distance.max
# head_top_result = head_to_top_margin_distance.min < scale_head_top_distance < head_to_top_margin_distance.max
# if not eyes_result or not head_top_result:
# continue
new_img = original_image[crop_box_top:crop_box_bottom, crop_box_left:crop_box_right]
new_img = cv2.resize(src=new_img, dsize=(target_resolution.width, target_resolution.height), interpolation=cv2.INTER_LANCZOS4)
new_img = Image.fromarray(cv2.cvtColor(new_img, cv2.COLOR_BGR2RGB))
save_img_path = save_path + "_" + str(target_eye) + ".jpg"
if file_size is None:
new_img.save(save_img_path, format="jpeg", quality=100, dpi=(350, 350))
else:
save_file(save_img_name=save_img_path, img=new_img, target_size_kb=file_size)
我尝试了不少方式,但是都失败了,所以我一点一点的改比例值调试,发现如果要两眼间距为110,
horizontal_padding_width = round((original_image_resolution.width - two_eyes_middle.x) * 0.85)这里就改成0.85左右就接近了,但是这个值怎么得来我不知道怎么去计算。
在循环中我添加了动态调整
horizontal_padding_width
和vertical_padding_top
,根据眼距和头顶间距与目标值间的差进行调整。我添加了退出循环的逻辑,当眼距和头顶间距满足要求时退出。
我还添加了
max_attempts
变量限制尝试次数。