import os
import cv2
import argparse
import numpy as np
from PIL import Image
import skimage.transform
import dlib
def get_standard_landmarks(align_size):
# 加载 Dlib 的 5 点标准模板
# detector = dlib.get_frontal_face_detector()
# predictor = dlib.shape_predictor("shape_predictor_5_face_landmarks.dat")
# Dlib 默认模板
standard_landmarks = np.array([
[30.2946, 51.6963], # 左眼
[65.5318, 51.5014], # 右眼
[48.0252, 71.7366], # 鼻尖
[33.5493, 92.3655], # 左嘴角
[62.7299, 92.2041] # 右嘴角
], dtype=np.float32)
# 将 landmarks 映射到目标尺寸
return standard_landmarks * np.array(align_size) / 112.0
def load_keypoints_from_file(keypoints_file):
"""
:param keypoints_file: dir
:return: keypoints (numpy array, shape=(5, 2))
"""
keypoints = []
with open(keypoints_file, 'r') as f:
for line in f:
x, y = map(float, line.strip().split())
keypoints.append((x, y))
return np.array(keypoints, dtype=np.float32)
def get_aligned_face(image, keypoints, align_size):
"""
:param image: 输入图像 (numpy array, RGB)
:param keypoints: 检测到的人脸关键点 (numpy array, shape=(5, 2))
:param align_size: 对齐后的目标尺寸 (tuple)
:return: 对齐后的图像
"""
# 基准点与目标尺寸匹配
# target_keypoints = np.array([
# [0.34191607, 0.46157411],
# [0.65653393, 0.45983393],
# [0.500225, 0.64050536],
# [0.37097589, 0.82469196],
# [0.63151696, 0.82325089]
# ], dtype=np.float32) * np.array(align_size)
target_keypoints = get_standard_landmarks(align_size)
st = skimage.transform.SimilarityTransform() # 使用相似变换
st.estimate(keypoints, target_keypoints)
aligned_face = cv2.warpAffine( # 仿射变换图像
image, st.params[0:2, :], (align_size[0], align_size[1]),
flags=cv2.INTER_LINEAR, borderValue=(0, 0, 0)
)
# st, _ = cv2.estimateAffinePartial2D(keypoints, target_keypoints)
# # 仿射变换图像
# aligned_face = cv2.warpAffine(
# image, st, (align_size[0], align_size[1]),
# flags=cv2.INTER_LINEAR, borderValue=(0, 0, 0)
# )
return aligned_face
# align and crop images
def process_images(indir, target_size=1024, center_crop_size=700, output_size=512):
"""
:param indir: image folder
:param target_size: 对齐图像的目标尺寸
:param center_crop_size: 中心裁剪尺寸
:param output_size: 输出图像的尺寸
"""
out_dir = os.path.join(indir, "crop")
os.makedirs(out_dir, exist_ok=True)
lm_dir = os.path.join(indir, "detections")
img_files = sorted([x for x in os.listdir(indir) if x.lower().endswith(".png") or x.lower().endswith(".jpg")])
lm_files = sorted([x for x in os.listdir(lm_dir) if x.endswith(".txt")])
for img_file, lm_file in zip(img_files, lm_files):
img_path = os.path.join(indir, img_file)
lm_path = os.path.join(lm_dir, lm_file)
im = Image.open(img_path).convert('RGB')
im_np = np.array(im)
keypoints = np.loadtxt(lm_path).astype(np.float32)
# faces = detector(im_np, 1) # detect faces
# if len(faces) == 0:
# print(f"No faces detected in {img_file}. Skipping.")
# continue
aligned_face = get_aligned_face(im_np, keypoints, (target_size, target_size)) # 对齐人脸
aligned_face = Image.fromarray(aligned_face) # 转换为 PIL 图片
# 中心裁剪
left = max(0, int(aligned_face.size[0] / 2 - center_crop_size / 2))
upper = max(0, int(aligned_face.size[1] / 2 - center_crop_size / 2))
right = min(aligned_face.size[0], left + center_crop_size)
lower = min(aligned_face.size[1], upper + center_crop_size)
im_cropped = aligned_face.crop((left, upper, right, lower))
# 调整为固定输出尺寸
im_cropped = im_cropped.resize((output_size, output_size), resample=Image.LANCZOS)
# 保存结果
out_path = os.path.join(out_dir, img_file.split(".")[0] + ".png")
im_cropped.save(out_path)
print(f"Processed: {out_path}")
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--indir', type=str, required=True, help='input image folder')
args = parser.parse_args()
target_size = 1024
center_crop_size = 900
output_size = 512
process_images(args.indir, target_size, center_crop_size, output_size)
参考代码:
https://huggingface.co/spaces/onnx/ArcFace/raw/651aecbd5380a7e0aef5f3f78bbf7e3fde601ec7/app.py
https://github.com/modelscope/facechain/blob/34d1e9976b80cf99e57c0cf2c9080c37c04958c8/face_adapter/face_preprocess.py#L36 令人叹为观止的StyleGAN - 知乎