下载地址:http://m.pan38.com/download.php?code=MDADTF 访问密码(可选):6666
图片眨眼动画生成功能,包含人脸检测、关键点定位、图像变形和GIF生成。使用时需要先下载dlib的68点人脸形状预测器。代码通过Delaunay三角剖分实现自然的眼部变形效果,可以生成平滑的眨眼动画。
import cv2
import dlib
import numpy as np
from PIL import Image
from scipy.spatial import Delaunay
from matplotlib import pyplot as plt
import os
import time
from tqdm import tqdm
import argparse
class FaceBlinkGenerator:
def __init__(self, predictor_path="shape_predictor_68_face_landmarks.dat"):
self.detector = dlib.get_frontal_face_detector()
self.predictor = dlib.shape_predictor(predictor_path)
self.eye_indices = {
'left': list(range(36, 42)),
'right': list(range(42, 48))
}
self.blink_frames = 5
def load_image(self, image_path):
image = cv2.imread(image_path)
if image is None:
raise ValueError(f"无法加载图像: {image_path}")
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
def detect_face(self, image):
gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
faces = self.detector(gray)
if len(faces) == 0:
raise ValueError("未检测到人脸")
return faces[0]
def get_landmarks(self, image, face):
landmarks = self.predictor(image, face)
return np.array([(landmarks.part(i).x, landmarks.part(i).y)
for i in range(68)], dtype=np.int32)
def create_eye_mask(self, landmarks, eye_type):
indices = self.eye_indices[eye_type]
eye_points = landmarks[indices]
hull = cv2.convexHull(eye_points)
mask = np.zeros_like(image[:, :, 0])
cv2.fillConvexPoly(mask, hull, 255)
return mask, eye_points
def interpolate_eye(self, upper, lower, progress):
middle = (1 - progress) * upper + progress * lower
return middle.astype(np.int32)
def generate_blink_sequence(self, image, landmarks):
height, width = image.shape[:2]
frames = []
# 获取左右眼关键点
left_mask, left_points = self.create_eye_mask(landmarks, 'left')
right_mask, right_points = self.create_eye_mask(landmarks, 'right')
# 计算闭眼时的位置(上下眼睑合并)
left_upper = left_points[:3]
left_lower = np.vstack([left_points[4], left_points[3], left_points[5]])
right_upper = right_points[:3]
right_lower = np.vstack([right_points[4], right_points[3], right_points[5]])
# 生成眨眼动画序列
for i in range(self.blink_frames):
progress = i / (self.blink_frames - 1)
if i < self.blink_frames // 2:
# 闭眼过程
progress = 2 * progress
else:
# 睁眼过程
progress = 2 * (1 - progress)
# 插值计算中间状态
left_inter = self.interpolate_eye(left_upper, left_lower, progress)
right_inter = self.interpolate_eye(right_upper, right_lower, progress)
# 创建新关键点数组
new_landmarks = landmarks.copy()
new_landmarks[36:42] = left_inter
new_landmarks[42:48] = right_inter
# 生成新图像
warped = self.warp_image(image, landmarks, new_landmarks)
frames.append(warped)
return frames
def warp_image(self, img, src_points, dst_points):
h, w = img.shape[:2]
# 计算Delaunay三角剖分
tri = Delaunay(src_points)
triangles = src_points[tri.simplices]
# 创建空白图像
warped = np.zeros_like(img)
for triangle in triangles:
# 获取源三角形和目标三角形
src_tri = np.float32([src_points[i] for i in tri.simplices[0]])
dst_tri = np.float32([dst_points[i] for i in tri.simplices[0]])
# 计算仿射变换
transform = cv2.getAffineTransform(src_tri, dst_tri)
# 应用变换
warped_triangle = cv2.warpAffine(img, transform, (w, h),
flags=cv2.INTER_LINEAR,
borderMode=cv2.BORDER_REFLECT_101)
# 创建三角形mask
mask = np.zeros((h, w), dtype=np.uint8)
cv2.fillConvexPoly(mask, np.int32(dst_tri), 255)
# 将三角形合并到结果图像
warped = cv2.bitwise_or(warped, cv2.bitwise_and(warped_triangle,
warped_triangle,
mask=mask))
# 混合原始图像和变形图像
alpha = 0.7
result = cv2.addWeighted(img, 1-alpha, warped, alpha, 0)
return result
def save_gif(self, frames, output_path, duration=100):
pil_images = [Image.fromarray(frame) for frame in frames]
pil_images[0].save(output_path, save_all=True,
append_images=pil_images[1:],
duration=duration, loop=0)
def process_image(self, image_path, output_path):
try:
image = self.load_image(image_path)
face = self.detect_face(image)
landmarks = self.get_landmarks(image, face)
frames = self.generate_blink_sequence(image, landmarks)
self.save_gif(frames, output_path)
return True
except Exception as e:
print(f"处理图像时出错: {str(e)}")
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='生成眨眼动画')
parser.add_argument('input', help='输入图片路径')
parser.add_argument('output', help='输出GIF路径')
parser.add_argument('--predictor', default='shape_predictor_68_face_landmarks.dat',
help='dlib形状预测器路径')
args = parser.parse_args()
generator = FaceBlinkGenerator(args.predictor)
if generator.process_image(args.input, args.output):
print(f"成功生成眨眼动画: {args.output}")
else:
print("生成失败")
umpy==1.23.5
opencv-python==4.7.0.72
dlib==19.24.2
pillow==9.4.0
scipy==1.10.1
matplotlib==3.7.1
tqdm==4.65.0
argparse==1.4.0
import unittest
import os
import tempfile
from blink_generator import FaceBlinkGenerator
class TestBlinkGenerator(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.test_image = "test_face.jpg"
cls.predictor = "shape_predictor_68_face_landmarks.dat"
cls.generator = FaceBlinkGenerator(cls.predictor)
def test_load_image(self):
image = self.generator.load_image(self.test_image)
self.assertIsNotNone(image)
def test_detect_face(self):
image = self.generator.load_image(self.test_image)
face = self.generator.detect_face(image)
self.assertIsNotNone(face)
def test_generate_blink(self):
with tempfile.NamedTemporaryFile(suffix='.gif', delete=False) as tmp:
output_path = tmp.name
success = self.generator.process_image(self.test_image, output_path)
self.assertTrue(success)
self.assertTrue(os.path.exists(output_path))
self.assertGreater(os.path.getsize(output_path), 0)
os.unlink(output_path)
if __name__ == '__main__':
unittest.main()
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。