You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于68个面部特征点的人脸图像扭曲函数报错求助

人脸变形中cv.getAffineTransform触发断言错误的解决

问题描述

实现人脸变形功能,已获取两张人脸的68个对应特征点并完成三角剖分,但调用OpenCV的cv.getAffineTransform生成变换矩阵时触发断言错误,错误信息:

M = cv.getAffineTransform(np.float32(points1), np.float32(points2))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cv2.error: OpenCV(4.9.0) D:\a\opencv-python\opencv-python\opencv\modules\imgproc\src\imgwarp.cpp:3554: error: (-215:Assertion failed) src.checkVector(2, CV_32F) == 3 && dst.checkVector(2, CV_32F) == 3 in function 'cv::getAffineTransform'

相关代码如下:

Face.py

import numpy as np
import cv2 as cv
import dlib
from warp import triangulate, warp

def crop_faces(image1_path, image2_path):
    cropped_faces_list = []
    for img in [image1_path, image2_path]:
        image = cv.imread(img)

        # convert to grayscale of each frames
        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

        # read the haarcascade to detect the faces in an image
        face_cascade = cv.CascadeClassifier(cv.data.haarcascades + 'haarcascade_frontalface_default.xml')
        
        # detects faces in the input image
        faces = face_cascade.detectMultiScale(gray, 1.3, 4)
        print('Number of detected faces:', len(faces))

        # Crop and save each detected face
        cropped_faces = []
        if len(faces) > 0:
            for (x, y, w, h) in faces:
                cropped_faces.append(image[y: y + h, x:x + w])

        cropped_faces_list.append(cropped_faces)
    
    return cropped_faces_list

def generate_face_correspondeces(theImage1, theImage2):
    # Detect the points of face.
    detector = dlib.get_frontal_face_detector()
    predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')

    imgList = crop_faces(theImage1, theImage2)
    list1 = []
    list2 = []
    feature_points = []
    cropped_images = []
    j = 1

    for m, img_list in enumerate(imgList):
        for img in img_list:

            if (j == 1):
                currList = list1
            else:
                currList = list2

            # Ask the detector to find the bounding boxes of each face. The 1 in the
            # second argument indicates that we should upsample the image 1 time. This
            # will make everything bigger and allow us to detect more faces.

            dets = detector(img, 1)

            try:
                if len(dets) == 0:
                    raise NoFaceFound  # type: ignore
            except NoFaceFound: # type: ignore
                print("Sorry, but I couldn't find a face in the image.")

            j = j + 1

            for k, rect in enumerate(dets):

                # Get landmarks/part for the face in rect
                shape = predictor(img, rect)
                
                for i in range(0, 68):
                    x = shape.part(i).x
                    y = shape.part(i).y
                    currList.append((x, y))
                    cv.circle(img, (x, y), 1, (0, 255, 0), 2)
            
            feature_points.append(currList)
            cropped_images.append(img)

            cv.imwrite(f"test_{m}.png", img)


    return feature_points, cropped_images


img1 = './Images/mulher1.jpg'
img2 = './Images/homem.jpg'

feature_points, cropped_images = generate_face_correspondeces(img1, img2)

for i, image in enumerate(cropped_images):
    triangulate(image, feature_points[i])

warp(img2, feature_points[0], feature_points[1])

Warp.py

import numpy as np
import cv2 as cv

def triangulate(image, points):

    # Create Subdiv2D object
    rect = (0, 0, image.shape[0], image.shape[1])  # Rectangle covering the entire image
    triangulation = cv.Subdiv2D(rect)

      # Insert points into triangulation object
    valid_points = []  # List to store valid points within the image bounds
    for point in points:
        x, y = point
        if 0 <= x < image.shape[1] and 0 <= y < image.shape[0]:  # Check if point is within image bounds
            triangulation.insert((x, y))
            valid_points.append((x, y))
        
    # Get triangles
    triangleList1 = triangulation.getTriangleList()

    # Draw triangles on the image (optional)
    for t in triangleList1:
        pt1 = (int(t[0]), int(t[1]))
        pt2 = (int(t[2]), int(t[3]))
        pt3 = (int(t[4]), int(t[5]))
        cv.line(image, pt1, pt2, (0, 255, 0), 1, cv.LINE_AA)
        cv.line(image, pt2, pt3, (0, 255, 0), 1, cv.LINE_AA)
        cv.line(image, pt3, pt1, (0, 255, 0), 1, cv.LINE_AA)

    # Show or return the triangulated image (optional)
    cv.imshow('Triangulated Image', image)
    cv.waitKey(0)
    cv.destroyAllWindows()

    return valid_points

def warp(image, points1, points2):
    # Compute affine transformation matrix
    M = cv.getAffineTransform(np.float32(points1), np.float32(points2))

    # Warp image1 onto image2
    rows, cols, _ = image.shape
    warped_image = cv.warpAffine(image, M, (cols, rows))

    # Display or save the warped image
    cv.imshow('Warped Image', warped_image)
    cv.waitKey(0)
    cv.destroyAllWindows()

错误原因分析

  1. 参数不匹配:断言错误明确要求src.checkVector(2, CV_32F) == 3 && dst.checkVector(2, CV_32F) == 3,即cv.getAffineTransform仅接受3个2D点作为输入,而你传入了68个特征点的完整列表,直接违反函数参数要求。
  2. 逻辑错误:人脸变形不能用全局仿射变换,需要基于三角剖分的局部仿射变换——对每个三角面片单独计算变换矩阵,再将每个面片的内容扭曲后拼接,才能实现自然的人脸变形效果。
  3. 三角剖分参数错误:原triangulate函数中rect参数顺序错误,应该是(x, y, width, height),即(0, 0, image.shape[1], image.shape[0]),之前写反了宽高,会导致三角剖分异常。

解决方法

1. 修正Warp.py,实现三角面片级局部变形

import numpy as np
import cv2 as cv

def triangulate(image, points):
    # 修正rect参数顺序:(x, y, width, height)
    rect = (0, 0, image.shape[1], image.shape[0])
    triangulation = cv.Subdiv2D(rect)

    valid_points = []
    for point in points:
        x, y = point
        if 0 <= x < image.shape[1] and 0 <= y < image.shape[0]:
            triangulation.insert((x, y))
            valid_points.append((x, y))
    
    # 获取三角面片对应的特征点索引,用于匹配两张图的对应三角
    triangle_indices = []
    triangle_list = triangulation.getTriangleList()
    for t in triangle_list:
        pts = [(t[0], t[1]), (t[2], t[3]), (t[4], t[5])]
        idx = []
        for pt in pts:
            # 匹配最接近的特征点,避免浮点误差
            distances = [np.linalg.norm(np.array(p) - np.array(pt)) for p in valid_points]
            idx.append(np.argmin(distances))
        triangle_indices.append(idx)
    
    # 绘制三角网格(可选)
    for idx in triangle_indices:
        pt1 = valid_points[idx[0]]
        pt2 = valid_points[idx[1]]
        pt3 = valid_points[idx[2]]
        cv.line(image, (int(pt1[0]), int(pt1[1])), (int(pt2[0]), int(pt2[1])), (0,255,0),1,cv.LINE_AA)
        cv.line(image, (int(pt2[0]), int(pt2[1])), (int(pt3[0]), int(pt3[1])), (0,255,0),1,cv.LINE_AA)
        cv.line(image, (int(pt3[0]), int(pt3[1])), (int(pt1[0]), int(pt1[1])), (0,255,0),1,cv.LINE_AA)
    
    cv.imshow('Triangulated Image', image)
    cv.waitKey(0)
    cv.destroyAllWindows()
    
    return valid_points, triangle_indices

def warp(image1, image2, points1, points2, triangle_indices):
    # 初始化输出图像为目标图副本
    output = np.copy(image2)
    rows, cols = image2.shape[:2]
    
    for indices in triangle_indices:
        # 获取两张图中对应三角面片的三个点
        src_pts = np.float32([points1[indices[0]], points1[indices[1]], points1[indices[2]]])
        dst_pts = np.float32([points2[indices[0]], points2[indices[1]], points2[indices[2]]])
        
        # 计算当前三角面片的仿射变换矩阵
        M = cv.getAffineTransform(src_pts, dst_pts)
        
        # 生成源三角区域的掩码
        mask = np.zeros((rows, cols), dtype=np.uint8)
        cv.fillConvexPoly(mask, np.int32(src_pts), 255)
        
        # 对源图像的三角区域进行扭曲
        warped_part = cv.warpAffine(image1, M, (cols, rows))
        
        # 将扭曲后的区域叠加到输出图像
        output[mask == 255] = warped_part[mask == 255]
    
    cv.imshow('Warped Image', output)
    cv.waitKey(0)
    cv.destroyAllWindows()
    return output

2. 修正Face.py的调用逻辑

img1 = './Images/mulher1.jpg'
img2 = './Images/homem.jpg'

feature_points, cropped_images = generate_face_correspondeces(img1, img2)

# 用第一张图的三角剖分索引即可(两张图特征点一一对应)
points1, triangle_indices = triangulate(cropped_images[0].copy(), feature_points[0])
points2, _ = triangulate(cropped_images[1].copy(), feature_points[1])

# 执行局部人脸变形
warp(cropped_images[0], cropped_images[1], points1, points2, triangle_indices)

额外注意事项

  • 确保两张图的68个特征点是严格一一对应的,这是局部变形的核心前提。
  • 如果需要更平滑的过渡效果,可以加入alpha混合,或者实现多帧渐变变形。

内容的提问来源于stack exchange,提问作者Sengeki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:32:32