基于逆变换的人脸图像变形问题求助(非内置函数实现)
人脸图像逆变换变形问题排查与手动实现方案
核心问题定位
- 顶点顺序不匹配:Delaunay三角网的三角顶点必须在源图和目标图中保持严格一致的顺时针/逆时针顺序,否则变换矩阵会产生镜像或扭曲。
- 行列坐标混淆:图像的像素坐标是
(行,列)对应(y,x),但变换计算通常用(x,y),如果混用会导致整体偏移或变形。 - 未填充像素:逆变换时若未遍历目标图所有像素,或三角区域判断错误,会出现空白区域。
手动逆变换实现步骤
1. 统一三角顶点顺序
遍历所有三角对,将源三角和目标三角统一调整为顺时针顺序:
def is_clockwise(points): # points格式:[(x1,y1), (x2,y2), (x3,y3)] val = (points[1][0]-points[0][0])*(points[1][1]+points[0][1]) + \ (points[2][0]-points[1][0])*(points[2][1]+points[1][1]) + \ (points[0][0]-points[2][0])*(points[0][1]+points[2][1]) return val < 0 # 遍历三角对,统一顺序 for src_tri, dst_tri in zip(source_triangles, dest_triangles): if not is_clockwise(src_tri): src_tri = src_tri[::-1] if not is_clockwise(dst_tri): dst_tri = dst_tri[::-1]
2. 手动计算仿射变换矩阵及逆矩阵
基于3组对应点构建线性方程组求解变换矩阵,再求逆用于逆变换:
import numpy as np def compute_affine_matrix(src_pts, dst_pts): x0, y0 = src_pts[0] x1, y1 = src_pts[1] x2, y2 = src_pts[2] X0, Y0 = dst_pts[0] X1, Y1 = dst_pts[1] X2, Y2 = dst_pts[2] # 构建线性方程组 A = [ [x0, y0, 1, 0, 0, 0], [0, 0, 0, x0, y0, 1], [x1, y1, 1, 0, 0, 0], [0, 0, 0, x1, y1, 1], [x2, y2, 1, 0, 0, 0], [0, 0, 0, x2, y2, 1] ] B = [X0, Y0, X1, Y1, X2, Y2] coeffs = np.linalg.solve(A, B) a, b, c, d, e, f = coeffs # 返回3x3齐次变换矩阵 return np.array([ [a, b, c], [d, e, f], [0, 0, 1] ]) # 对每个三角对计算逆变换矩阵 inv_matrices = [] for src_tri, dst_tri in zip(source_triangles, dest_triangles): affine_mat = compute_affine_matrix(src_tri, dst_tri) inv_matrices.append(np.linalg.inv(affine_mat))
3. 逆变换遍历与像素填充
遍历目标图每个像素,判断所属三角后通过逆矩阵映射回源图取像素:
def inverse_warp(source_img, dest_img, dest_triangles, inv_matrices): h, w = dest_img.shape[:2] # 遍历目标图所有像素 for y in range(h): for x in range(w): # 检查当前像素(x,y)属于哪个目标三角 for idx, dst_tri in enumerate(dest_triangles): if point_in_triangle((x,y), dst_tri): # 用逆矩阵映射回源图坐标 src_x, src_y, _ = inv_matrices[idx] @ np.array([x, y, 1]) src_x = int(round(src_x)) src_y = int(round(src_y)) # 确保坐标在源图范围内 if 0 <= src_x < source_img.shape[1] and 0 <= src_y < source_img.shape[0]: dest_img[y, x] = source_img[src_y, src_x] break return dest_img # 重心坐标法判断点是否在三角形内 def point_in_triangle(p, tri): A, B, C = tri v0 = (C[0]-A[0], C[1]-A[1]) v1 = (B[0]-A[0], B[1]-A[1]) v2 = (p[0]-A[0], p[1]-A[1]) dot00 = v0[0]*v0[0] + v0[1]*v0[1] dot01 = v0[0]*v1[0] + v0[1]*v1[1] dot02 = v0[0]*v2[0] + v0[1]*v2[1] dot11 = v1[0]*v1[0] + v1[1]*v1[1] dot12 = v1[0]*v2[0] + v1[1]*v2[1] denom = dot00 * dot11 - dot01 * dot01 if denom == 0: return False u = (dot11 * dot02 - dot01 * dot12) / denom v = (dot00 * dot12 - dot01 * dot02) / denom return (u >= 0) and (v >= 0) and (u + v <= 1)
关键注意事项
- 所有三角顶点必须统一用
(x,y)格式(对应图像的列、行),禁止与(y,x)混用。 - 逆变换必须遍历目标图的每个像素,而非源图,否则会出现未填充区域。
- 由于Delaunay三角不会出现共线点,变换矩阵必然可逆,无需额外判断。
内容的提问来源于stack exchange,提问作者darryl247
相关产品推荐
相关产品推荐

