人脸对齐是大多数人脸分析算法中的一个关键模块,在人脸识别、表情识别、人脸建模等领域有着广泛的应用。人脸对齐获取图像中人脸的几何结构,基于平移、缩放和旋转得到对齐后的标准人脸。
在欧式几何中,如果两个物体具有相同的形状,或者其中一个物体的形状与另一个物体的镜像相同,那么这两个物体是相似的。更准确地说,可以通过均匀缩放(放大或缩小)并叠加必要的平移、旋转和反射来获得另一个。这意味着任意物体都可以重新缩放、重新定位和反射,以便与另一物体精确重合。如果两个物体相似,则每个物体都与另一个物体的特定均匀缩放结果一致。
目前大多数人脸对齐方案在定位关键点后采用 Umeyama 对齐算法进行处理。例如 insightface 中的 similarTransform,dlib 中的 get_face_chip_details。相似性变换由平移变换、旋转变换以及尺度变换组合而成。下面以 skimage.transform.SimilarityTransform 为例进行介绍。
2D 相似性变换。具有以下形式:
X = a0 * x - b0 * y + a1 =
= s * x * cos(rotation) - s * y * sin(rotation) + a1
Y = b0 * x + a0 * y + b1 =
= s * x * sin(rotation) + s * y * cos(rotation) + b1
其中s
是比例因子,齐次变换矩阵为:
[ a 0 b 0 a 1 b 0 a 0 b 1 0 0 1 ] \begin{bmatrix} a_0 & b_0 & a_1 \\ b_0 & a_0 & b_1 \\ 0 & 0 & 1 \end{bmatrix} ⎣⎡a0b00b0a00a1b11⎦⎤
除了旋转和平移参数外,相似性变换还使用单个缩放因子扩展了欧几里德变换。
参数:
matrix
:(3,3)数组,可选。齐次变换矩阵。scale
:float,可选。比例因子。rotation
:float,可选。逆时针旋转角度(以弧度表示)。translation
:(tx, ty)
作为数组、列表或元组,可选。x,y 方向平移参数。属性:
params
:(3,3)数组,齐次变换矩阵。[ s cos ( θ ) sin ( θ ) t x sin ( θ ) s cos ( θ ) t y 0 0 1 ] \begin{bmatrix} s \cos(\theta) & \sin(\theta) & t_x\\ \sin(\theta) & s\cos(\theta) & t_y\\ 0 & 0 & 1 \end{bmatrix} ⎣⎡scos(θ)sin(θ)0sin(θ)scos(θ)0txty1⎦⎤
def __init__(self, matrix=None, scale=None, rotation=None,
translation=None):
params = any(param is not None
for param in (scale, rotation, translation))
if params and matrix is not None:
raise ValueError("You cannot specify the transformation matrix and"
" the implicit parameters at the same time.")
elif matrix is not None:
if matrix.shape != (3, 3):
raise ValueError("Invalid shape of transformation matrix.")
self.params = matrix
elif params:
if scale is None:
scale = 1
if rotation is None:
rotation = 0
if translation is None:
translation = (0, 0)
self.params = np.array([
[math.cos(rotation), - math.sin(rotation), 0],
[math.sin(rotation), math.cos(rotation), 0],
[ 0, 0, 1]
])
self.params[0:2, 0:2] *= scale
self.params[0:2, 2] = translation
else:
# default to an identity transform
self.params = np.eye(3)
从一组对应点估计变换。可以使用总体最小二乘法确定过定、确定和欠定的参数。源坐标和目标坐标的数量必须匹配。
参数:
src
:(N,2)数组,源坐标。dst
:(N,2)数组,目标坐标。返回值:
success
:布尔型,如果模型估计成功,则返回True
。 self.params = _umeyama(src, dst, True)
return True
@property
def scale(self):
if abs(math.cos(self.rotation)) < np.spacing(1):
# sin(self.rotation) == 1
scale = self.params[1, 0]
else:
scale = self.params[0, 0] / math.cos(self.rotation)
return scale
估算是否具有缩放比例的N-D相似度变换。
估计有或无标度的N-D相似变换。
参数:
src
:(M,N)数组,源坐标。dst
:(M,N)数组,目标坐标。estimate_scale
:布尔,是否估计比例因子。返回值:
T
:(N + 1,N + 1),齐次相似性变化矩阵。仅当问题条件不完善时,矩阵才会包含NaN
值。参考文献
10.1109/34.88573
μ x = 1 n ∑ i = 1 n x i (34) μ y = 1 n ∑ i = 1 n y i (35) σ x 2 = 1 n ∑ i = 1 n ∥ x i − μ x ∥ 2 (36) σ y 2 = 1 n ∑ i = 1 n ∥ y i − μ y ∥ 2 (37) Σ x y = 1 n ∑ i = 1 n ( y i − μ y ) ( x i − μ x ) ⊤ (38) \begin{aligned} \mu_x &= \frac{1}{n}\sum_{i=1}^n x_i \qquad\qquad\qquad \text{(34)}\\ \mu_y &= \frac{1}{n}\sum_{i=1}^n y_i \qquad\qquad\qquad \text{(35)}\\ \sigma_x^2 &= \frac{1}{n}\sum_{i=1}^n \| x_i-\mu_x\|^2 \qquad\qquad \text{(36)}\\ \sigma_y^2 &= \frac{1}{n}\sum_{i=1}^n \| y_i-\mu_y\|^2 \qquad\qquad \text{(37)}\\ \Sigma_{xy} &= \frac{1}{n}\sum_{i=1}^n (y_i-\mu_y)(x_i-\mu_x)^\top \quad \text{(38)} \end{aligned} μxμyσx2σy2Σxy=n1i=1∑nxi(34)=n1i=1∑nyi(35)=n1i=1∑n∥xi−μx∥2(36)=n1i=1∑n∥yi−μy∥2(37)=n1i=1∑n(yi−μy)(xi−μx)⊤(38)
Σ x y \Sigma_{xy} Σxy 是 X X X 和 Y Y Y 的协方差矩阵, μ x \mu_x μx 和 μ y \mu_y μy 分别是 X X X 和 Y Y Y 的均值向量, σ x 2 \sigma_x^2 σx2 和 σ y 2 \sigma_y^2 σy2 是 X X X 和 Y Y Y 的方差。
num
是点的数量,dim
为点的坐标维度。
num = src.shape[0]
dim = src.shape[1]
# Compute mean of src and dst.
src_mean = src.mean(axis=0)
dst_mean = dst.mean(axis=0)
# Subtract mean from src and dst.
src_demean = src - src_mean
dst_demean = dst - dst_mean
# Eq. (38).
A = dst_demean.T @ src_demean / num
对协方差矩阵 Σ x y \Sigma_{xy} Σxy 进行奇异值分解 U D V ⊤ UDV^\top UDV⊤,其中 D = d i a g ( d i ) , d 1 ≥ d 2 ≥ ⋯ ≥ d m ≥ 0 D=\mathrm{diag}(d_i), d_1 \ge d_2 \ge\cdots \ge d_m \ge 0 D=diag(di),d1≥d2≥⋯≥dm≥0。
S = { I if d e t ( Σ x y ) ≥ 0 d i a g ( 1 , 1 , … , 1 , − 1 ) if d e t ( Σ x y ) < 0 (39) S = \begin{cases} I &\text{if } \mathrm{det}(\Sigma_{xy}) \ge 0 \\ \mathrm{diag}(1,1,\dots,1,-1) &\text{if } \mathrm{det}(\Sigma_{xy}) < 0 \end{cases}\quad \text{(39)} S={Idiag(1,1,…,1,−1)if det(Σxy)≥0if det(Σxy)<0(39)
numpy.linalg.det 计算数组的行列式(determinant)。
numpy.linalg.svd 对数组进行奇异值分解。
S
对应分解的奇异值 d i a g ( d i ) \mathrm{diag}(d_i) diag(di)。
d
为行向量,对应公式中的 S S S。
# Eq. (39).
d = np.ones((dim,), dtype=np.double)
if np.linalg.det(A) < 0:
d[dim - 1] = -1
T = np.eye(dim + 1, dtype=np.double)
U, S, V = np.linalg.svd(A)
numpy.linalg.matrix_rank 使用 SVD 方法返回数组的矩阵秩。
当 rank ( Σ x y ) = m \text{rank}{(\Sigma_{xy})}= m rank(Σxy)=m 时,
S = { I if d e t ( U ) d e t ( V ) = 1 d i a g ( 1 , 1 , … , 1 , − 1 ) if d e t ( U ) d e t ( V ) = − 1 (43) S = \begin{cases} I &\text{if } \mathrm{det}(U)\mathrm{det}(V)=1 \\ \mathrm{diag}(1,1,\dots,1,-1) &\text{if } \mathrm{det}(U)\mathrm{det}(V)=-1 \end{cases} \quad \text{(43)} S={Idiag(1,1,…,1,−1)if det(U)det(V)=1if det(U)det(V)=−1(43)
当 rank ( Σ x y ) > m \text{rank}{(\Sigma_{xy})}> m rank(Σxy)>m 时,最变换参数为:
R = U S V ⊤ (40) t = μ y − c R μ x (41) c = 1 σ x 2 t r ( D S ) (42) \begin{aligned} R &= USV^\top \qquad \text{(40)}\\ t &= \mu_y - cR\mu_x \quad \text{(41)}\\ c &= \frac{1}{\sigma_x^2}\mathrm{tr}(DS) \quad \text{(42)} \end{aligned} Rtc=USV⊤(40)=μy−cRμx(41)=σx21tr(DS)(42)
s
临时保存。
# Eq. (40) and (43).
rank = np.linalg.matrix_rank(A)
if rank == 0:
return np.nan * T
elif rank == dim - 1:
if np.linalg.det(U) * np.linalg.det(V) > 0:
T[:dim, :dim] = U @ V
else:
s = d[dim - 1]
d[dim - 1] = -1
T[:dim, :dim] = U @ np.diag(d) @ V
d[dim - 1] = s
else:
T[:dim, :dim] = U @ np.diag(d) @ V
T
的形式为:
[ s cos ( θ ) sin ( θ ) t x sin ( θ ) s cos ( θ ) t y 0 0 1 ] \begin{bmatrix} s \cos(\theta) & \sin(\theta) & t_x\\ \sin(\theta) & s\cos(\theta) & t_y\\ 0 & 0 & 1 \end{bmatrix} ⎣⎡scos(θ)sin(θ)0sin(θ)scos(θ)0txty1⎦⎤
if estimate_scale:
# Eq. (41) and (42).
scale = 1.0 / src_demean.var(axis=0).sum() * (S @ d)
else:
scale = 1.0
T[:dim, dim] = dst_mean - scale * (T[:dim, :dim] @ src_mean.T)
T[:dim, :dim] *= scale
return T