人臉對齊中的相似性變換

人臉對齊是大多數人臉分析算法中的一個關鍵模塊,在人臉識別、表情識別、人臉建模等領域有着廣泛的應用。人臉對齊獲取圖像中人臉的幾何結構,基於平移、縮放和旋轉得到對齊後的標準人臉。

在歐式幾何中,如果兩個物體具有相同的形狀,或者其中一個物體的形狀與另一個物體的鏡像相同,那麼這兩個物體是相似的。更準確地說,可以通過均勻縮放(放大或縮小)併疊加必要的平移、旋轉和反射來獲得另一個。這意味着任意物體都可以重新縮放、重新定位和反射,以便與另一物體精確重合。如果兩個物體相似,則每個物體都與另一個物體的特定均勻縮放結果一致。

目前大多數人臉對齊方案在定位關鍵點後採用 Umeyama 對齊算法進行處理。例如 insightface 中的 similarTransform,dlib 中的 get_face_chip_details。相似性變換由平移變換、旋轉變換以及尺度變換組合而成。下面以 skimage.transform.SimilarityTransform 爲例進行介紹。

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是比例因子,齊次變換矩陣爲:
[a0b0a1b0a0b1001] \begin{bmatrix} a_0 & b_0 & a_1 \\ b_0 & a_0 & b_1 \\ 0 & 0 & 1 \end{bmatrix}
除了旋轉和平移參數外,相似性變換還使用單個縮放因子擴展了歐幾里德變換。
參數:

  • matrix:(3,3)數組,可選。齊次變換矩陣。
  • scale:float,可選。比例因子。
  • rotation:float,可選。逆時針旋轉角度(以弧度表示)。
  • translation(tx, ty)作爲數組、列表或元組,可選。x,y 方向平移參數。

屬性:

  • params:(3,3)數組,齊次變換矩陣。

[scos(θ)sin(θ)txsin(θ)scos(θ)ty001] \begin{bmatrix} s \cos(\theta) & \sin(\theta) & t_x\\ \sin(\theta) & s\cos(\theta) & t_y\\ 0 & 0 & 1 \end{bmatrix}

    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)

estimate

從一組對應點估計變換。可以使用總體最小二乘法確定過定、確定和欠定的參數。源座標和目標座標的數量必須匹配。
參數:

  • src:(N,2)數組,源座標。
  • dst:(N,2)數組,目標座標。

返回值:

  • success:布爾型,如果模型估計成功,則返回True
        self.params = _umeyama(src, dst, True)

        return True

scale

    @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

_umeyama

估算是否具有縮放比例的N-D相似度變換。
估計有或無標度的N-D相似變換。
參數:

  • src:(M,N)數組,源座標。
  • dst:(M,N)數組,目標座標。
  • estimate_scale:布爾,是否估計比例因子。

返回值:

  • T:(N + 1,N + 1),齊次相似性變化矩陣。僅當問題條件不完善時,矩陣纔會包含NaN值。

參考文獻

  • [1] “Least-squares estimation of transformation parameters between two point patterns”, Shinji Umeyama, PAMI 1991, :DOI:10.1109/34.88573

μx=1ni=1nxi(34)μy=1ni=1nyi(35)σx2=1ni=1nxiμx2(36)σy2=1ni=1nyiμy2(37)Σxy=1ni=1n(yiμy)(xiμ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}
Σxy\Sigma_{xy}XXYY 的協方差矩陣,μx\mu_xμy\mu_y 分別是 XXYY 的均值向量,σx2\sigma_x^2σy2\sigma_y^2XXYY 的方差。
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

對協方差矩陣 Σxy\Sigma_{xy} 進行奇異值分解 UDVUDV^\top,其中 D=diag(di),d1d2dm0D=\mathrm{diag}(d_i), d_1 \ge d_2 \ge\cdots \ge d_m \ge 0

S={Iif det(Σxy)0diag(1,1,,1,1)if det(Σxy)<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)}
numpy.linalg.det 計算數組的行列式(determinant)。
numpy.linalg.svd 對數組進行奇異值分解。
S對應分解的奇異值 diag(di)\mathrm{diag}(d_i)
d爲行向量,對應公式中的 SS

    # 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(Σxy)=m\text{rank}{(\Sigma_{xy})}= m 時,
S={Iif det(U)det(V)=1diag(1,1,,1,1)if det(U)det(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)}
rank(Σxy)>m\text{rank}{(\Sigma_{xy})}> m 時,最變換參數爲:
R=USV(40)t=μycRμx(41)c=1σx2tr(DS)(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}

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
  • 首先計算尺度參數 cc (公式42);
  • 然後計算平移參數 tt(公式41);
  • 最後旋轉參數 RR 和尺度參數 cc 融合到一起。

T的形式爲:
[scos(θ)sin(θ)txsin(θ)scos(θ)ty001] \begin{bmatrix} s \cos(\theta) & \sin(\theta) & t_x\\ \sin(\theta) & s\cos(\theta) & t_y\\ 0 & 0 & 1 \end{bmatrix}

    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

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章