185 lines
7.0 KiB
Python
185 lines
7.0 KiB
Python
|
|
"""
|
|||
|
|
这部分输入face5点位置和原图
|
|||
|
|
输出aligned cropped face
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import numpy as np
|
|||
|
|
import cv2
|
|||
|
|
import time
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
# image_data: src image
|
|||
|
|
# image_width, image_height,image_channels: width and height, channels of src image
|
|||
|
|
# src_x, src_y: 输出image每个像素对应的src image 中的像素位置.
|
|||
|
|
def sampling(image_data, image_width, image_height, image_channels, src_x, src_y):
|
|||
|
|
ux = np.floor(src_x).astype(int)
|
|||
|
|
uy = np.floor(src_y).astype(int)
|
|||
|
|
|
|||
|
|
# 创建一个与src_x形状相同的空数组,用于存储最终的像素值
|
|||
|
|
pixel = np.zeros((*src_x.shape, image_channels), dtype=np.uint8)
|
|||
|
|
|
|||
|
|
# 创建一个掩码数组,标记有效的采样点
|
|||
|
|
valid_mask = (ux >= 0) & (ux < image_height - 1) & (uy >= 0) & (uy < image_width - 1)
|
|||
|
|
|
|||
|
|
# 计算插值
|
|||
|
|
x = src_x - ux
|
|||
|
|
y = src_y - uy
|
|||
|
|
|
|||
|
|
# 提取图像数据的各个通道
|
|||
|
|
image_data_reshape = image_data.reshape(-1, image_channels) # (height * width, channels)
|
|||
|
|
ux_uy = ux * image_width + uy # (height * width)
|
|||
|
|
ux_uy_next = ux_uy + 1
|
|||
|
|
ux_next = (ux + 1) * image_width + uy
|
|||
|
|
ux_next_next = ux_next + 1
|
|||
|
|
|
|||
|
|
ux_uy[~valid_mask] = 0
|
|||
|
|
ux_uy_next[~valid_mask] = 0
|
|||
|
|
ux_next[~valid_mask] = 0
|
|||
|
|
ux_next_next[~valid_mask] = 0
|
|||
|
|
|
|||
|
|
# 使用广播计算各个通道的插值
|
|||
|
|
top_left = image_data_reshape[ux_uy]
|
|||
|
|
top_right = image_data_reshape[ux_uy_next]
|
|||
|
|
bottom_left = image_data_reshape[ux_next]
|
|||
|
|
bottom_right = image_data_reshape[ux_next_next]
|
|||
|
|
|
|||
|
|
# 计算插值
|
|||
|
|
interpolated_top = (1 - y[:, :, np.newaxis]) * top_left + y[:, :, np.newaxis] * top_right
|
|||
|
|
interpolated_bottom = (1 - y[:, :, np.newaxis]) * bottom_left + y[:, :, np.newaxis] * bottom_right
|
|||
|
|
interpolated_pixel = (1 - x[:, :, np.newaxis]) * interpolated_top + x[:, :, np.newaxis] * interpolated_bottom
|
|||
|
|
|
|||
|
|
# 填充最终的像素值
|
|||
|
|
pixel[valid_mask] = np.clip(interpolated_pixel[valid_mask], 0, 255).astype(np.uint8)
|
|||
|
|
|
|||
|
|
return pixel
|
|||
|
|
|
|||
|
|
def spatial_transform(image_data, image_width, image_height, image_channels,
|
|||
|
|
crop_data, crop_width, crop_height, transformation,
|
|||
|
|
pad_top=0, pad_bottom=0, pad_left=0, pad_right=0,
|
|||
|
|
type='LINEAR', dtype='ZERO_PADDING', N=1):
|
|||
|
|
channels = image_channels
|
|||
|
|
dst_h = crop_height + pad_top + pad_bottom
|
|||
|
|
dst_w = crop_width + pad_left + pad_right
|
|||
|
|
|
|||
|
|
for n in range(N):
|
|||
|
|
theta_data = transformation.reshape(-1)
|
|||
|
|
scale = np.sqrt(theta_data[0] ** 2 + theta_data[3] ** 2)
|
|||
|
|
|
|||
|
|
bx, by = np.meshgrid(np.arange(dst_w) - pad_left, np.arange(dst_h) - pad_top)
|
|||
|
|
bx = bx.T
|
|||
|
|
by = by.T
|
|||
|
|
src_y = theta_data[0] * by + theta_data[1] * bx + theta_data[2]
|
|||
|
|
src_x = theta_data[3] * by + theta_data[4] * bx + theta_data[5]
|
|||
|
|
|
|||
|
|
crop_data[:] = sampling(image_data, image_width, image_height, image_channels, src_x, src_y,)
|
|||
|
|
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def transformation_maker(crop_width, crop_height, points, mean_shape, mean_shape_width, mean_shape_height):
|
|||
|
|
points_num = len(points) # point 个数 5
|
|||
|
|
std_points = np.zeros((points_num, 2), dtype=np.float32) # 标准点
|
|||
|
|
|
|||
|
|
# 生成标准点的坐标
|
|||
|
|
for i in range(points_num):
|
|||
|
|
std_points[i, 0] = mean_shape[i * 2] * crop_width / mean_shape_width
|
|||
|
|
std_points[i, 1] = mean_shape[i * 2 + 1] * crop_height / mean_shape_height
|
|||
|
|
|
|||
|
|
feat_points = np.array(points, dtype=np.float32).reshape(points_num, 2)
|
|||
|
|
|
|||
|
|
# 初始化
|
|||
|
|
sum_x = 0.0
|
|||
|
|
sum_y = 0.0
|
|||
|
|
sum_u = 0.0
|
|||
|
|
sum_v = 0.0
|
|||
|
|
sum_xx_yy = 0.0
|
|||
|
|
sum_ux_vy = 0.0
|
|||
|
|
sum_vx_uy = 0.0
|
|||
|
|
|
|||
|
|
for c in range(points_num):
|
|||
|
|
sum_x += std_points[c, 0]
|
|||
|
|
sum_y += std_points[c, 1]
|
|||
|
|
sum_u += feat_points[c, 0]
|
|||
|
|
sum_v += feat_points[c, 1]
|
|||
|
|
sum_xx_yy += std_points[c, 0] ** 2 + std_points[c, 1] ** 2
|
|||
|
|
sum_ux_vy += std_points[c, 0] * feat_points[c, 0] + std_points[c, 1] * feat_points[c, 1]
|
|||
|
|
sum_vx_uy += feat_points[c, 1] * std_points[c, 0] - feat_points[c, 0] * std_points[c, 1]
|
|||
|
|
|
|||
|
|
if sum_xx_yy <= np.finfo(np.float32).eps:
|
|||
|
|
return False, None
|
|||
|
|
|
|||
|
|
q = sum_u - sum_x * sum_ux_vy / sum_xx_yy + sum_y * sum_vx_uy / sum_xx_yy
|
|||
|
|
p = sum_v - sum_y * sum_ux_vy / sum_xx_yy - sum_x * sum_vx_uy / sum_xx_yy
|
|||
|
|
r = points_num - (sum_x ** 2 + sum_y ** 2) / sum_xx_yy
|
|||
|
|
|
|||
|
|
if np.abs(r) <= np.finfo(np.float32).eps:
|
|||
|
|
return False, None
|
|||
|
|
|
|||
|
|
a = (sum_ux_vy - sum_x * q / r - sum_y * p / r) / sum_xx_yy
|
|||
|
|
b = (sum_vx_uy + sum_y * q / r - sum_x * p / r) / sum_xx_yy
|
|||
|
|
c = q / r
|
|||
|
|
d = p / r
|
|||
|
|
|
|||
|
|
transformation = np.zeros((2, 3), dtype=np.float64)
|
|||
|
|
transformation[0, 0] = transformation[1, 1] = a
|
|||
|
|
transformation[0, 1] = -b
|
|||
|
|
transformation[1, 0] = b
|
|||
|
|
transformation[0, 2] = c
|
|||
|
|
transformation[1, 2] = d
|
|||
|
|
|
|||
|
|
return True, transformation
|
|||
|
|
|
|||
|
|
|
|||
|
|
class FaceAlign:
|
|||
|
|
def __init__(self) -> None:
|
|||
|
|
self.crop_width, self.crop_height = 256, 256
|
|||
|
|
self.mean_shape_width, self.mean_shape_height = 256, 256
|
|||
|
|
self.mean_face = [ # 标准人脸的特征点的位置
|
|||
|
|
89.3095, 72.9025,
|
|||
|
|
169.3095, 72.9025,
|
|||
|
|
127.8949, 127.0441,
|
|||
|
|
96.8796, 184.8907,
|
|||
|
|
159.1065, 184.7601
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
|
|||
|
|
# landmarks5 = [
|
|||
|
|
# [268.99814285714285, 166.26619999999997],
|
|||
|
|
# [342.636625, 164.43359999999998],
|
|||
|
|
# [311.5448214285714, 221.24419999999998],
|
|||
|
|
# [272.2709642857143, 243.23539999999997],
|
|||
|
|
# [344.2730357142857, 241.40279999999996]
|
|||
|
|
# ]
|
|||
|
|
def align(self, image, landmarks5): # 原图image landmarks5
|
|||
|
|
success, transformation = transformation_maker(self.crop_width, self.crop_height, landmarks5, self.mean_face, self.mean_shape_width, self.mean_shape_height)
|
|||
|
|
if not success:
|
|||
|
|
print("Failed to compute transformation matrix.")
|
|||
|
|
|
|||
|
|
img_height, img_width, img_channels = image.shape
|
|||
|
|
|
|||
|
|
crop_data = np.zeros((self.crop_height, self.crop_width, 3), dtype=np.uint8)
|
|||
|
|
success = spatial_transform(image, img_width, img_height, img_channels,
|
|||
|
|
crop_data, self.crop_width, self.crop_height,
|
|||
|
|
transformation,
|
|||
|
|
)
|
|||
|
|
if success:
|
|||
|
|
if os.path.exists("./images/result1.jpg"):
|
|||
|
|
cv2.imwrite("./images/result2.jpg", crop_data, [cv2.IMWRITE_JPEG_QUALITY, 100])
|
|||
|
|
else:
|
|||
|
|
cv2.imwrite("./images/result1.jpg", crop_data, [cv2.IMWRITE_JPEG_QUALITY, 100])
|
|||
|
|
else:
|
|||
|
|
print("error when spatial_transform...")
|
|||
|
|
|
|||
|
|
return crop_data
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
fa = FaceAlign()
|
|||
|
|
landmarks5 = [(240.56920098163752, 111.91879640513824),
|
|||
|
|
(283.7146242409017, 93.30582481805237),
|
|||
|
|
(268.9820406889578, 129.202270021718),
|
|||
|
|
(259.51109411985107, 155.79222943184064),
|
|||
|
|
(296.34255299971073, 137.17925784475477)]
|
|||
|
|
landmarks5 = [ [ld5[0],ld5[1]] for ld5 in landmarks5]
|
|||
|
|
image = cv2.imread("/home/bns/seetaface6Python/seetaFace6Python/asserts/1.jpg")
|
|||
|
|
fa.align(image = image, landmarks5=landmarks5)
|
|||
|
|
|