基于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()
错误原因分析
- 参数不匹配:断言错误明确要求
src.checkVector(2, CV_32F) == 3 && dst.checkVector(2, CV_32F) == 3,即cv.getAffineTransform仅接受3个2D点作为输入,而你传入了68个特征点的完整列表,直接违反函数参数要求。 - 逻辑错误:人脸变形不能用全局仿射变换,需要基于三角剖分的局部仿射变换——对每个三角面片单独计算变换矩阵,再将每个面片的内容扭曲后拼接,才能实现自然的人脸变形效果。
- 三角剖分参数错误:原
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
相关产品推荐
相关产品推荐

