# 人脸对齐

注意：这里说的人脸对齐和传统的Face Alignment差别较大，传统的Face Alignment是指从人脸图片上检测人脸关键点，然后用仿射变换将人脸归正。而本文说的人脸对齐只是指仿射变换的部分。

> 参考代码`https://github.com/foamliu/InsightFace-PyTorch/blob/master/align_faces.py`

人脸对齐可分为如下步骤：

1. 由目标人脸大小获得关键点的目标位置
2. 由检测到的人脸关键点和目标关键点位置得到仿射变换的变换矩阵
3. 进行仿射变换

## 获取目标关键点位置

当人脸大小为`(96/w，112/h)`时，目标关键点的位置为(该出处不明)

```python
REFERENCE_FACIAL_POINTS = [
    [30.29459953, 51.69630051],
    [65.53179932, 51.50139999],
    [48.02519989, 71.73660278],
    [33.54930115, 92.3655014],
    [62.72990036, 92.20410156]
]
DEFAULT_CROP_SIZE = (96, 112)
```

然后需要根据需要的人脸shape和内外padding的大小改变关键点的位置。 具体说明可以查看代码及其中说明[align\_faces.py](https://github.com/niyunsheng/note-deeplearning/blob/master/face/codes/align_faces.py)

## 得到变换矩阵

得到变换矩阵共有三种方法：

* 方法1：最小二乘法
* 方法2：由三点对应获得仿射变换矩阵
* 方法3：相似性变换

### 最小二乘法

```python
def get_affine_transform_matrix(src_pts, dst_pts):
    tfm = np.float32([[1, 0, 0], [0, 1, 0]])
    n_pts = src_pts.shape[0]
    ones = np.ones((n_pts, 1), src_pts.dtype)
    src_pts_ = np.hstack([src_pts, ones])
    dst_pts_ = np.hstack([dst_pts, ones])

    A, res, rank, s = np.linalg.lstsq(src_pts_, dst_pts_)

    if rank == 3:
        tfm = np.float32([
            [A[0, 0], A[1, 0], A[2, 0]],
            [A[0, 1], A[1, 1], A[2, 1]]
        ])
    elif rank == 2:
        tfm = np.float32([
            [A[0, 0], A[1, 0], 0],
            [A[0, 1], A[1, 1], 0]
        ])

    return tfm
```

函数的输入`src_pts`和`dst_pts`均为`ndarray:(5,2)` 输出变换矩阵为`ndarray:(3,2)`

`np.linalg`是numpy中的线性代数库， `np.linalg.lstsq(a, b, rcond='warn')`lstsq 是 LeaST SQuare （最小二乘）的意思，即求使得`|| ax - b ||`最小的x的值。函数返回一个包含四个值的元组：

* 第一元素表示所求的最小二乘解x
  * 使得 $a.dot(x) - b$ 的L2模最小
* 第二个元素表示残差总和
* 第三个元素表示x矩阵的秩
* 第四个元素表示x的奇异值

### 通过三个点即可获得仿射变换矩阵

`tfm = cv2.getAffineTransform(src_pts[0:3], ref_pts[0:3])`

### 相似性变换矩阵

```python
tform = trans.SimilarityTransform()
tform.estimate(src_pts, ref_pts)
tfm = tform.params[0:2, :]
```

相似变换的矩阵如下：

$$\left\[\begin{matrix}s \* cos\theta & s \* {(-sin\theta)} & t\_{x} \ s \* sin\theta & cos\theta & t\_{y} \ 0 & 0 & 1\end{matrix}\right]$$

相比仿射变换，相似变换的自由度较小，左上角2×2矩阵为旋转部分，tx和ty为平移因子，它有4个自由度，即旋转，x方向平移，y方向平移和缩放因子s。

## 补充知识：仿射变换

> 本部分内容参考《计算机图形学导论》第五章：几何变换

三维空间的变换矩阵是一个`3*3`的矩阵`W`, $P2 = W\.dot(P1)$ , P1的坐标可以写作 $(x,y,1)$，其中第三维表示bias。

### 平移

将 $(x,y)$ 平移到 $(x+t\_x,y+t\_y)$ , 变换矩阵为：

$$
\left\[\begin{matrix} x & y & 1 \end{matrix}\right] \* \left\[\begin{matrix}1 & 0 & 0 \ 0 & 1 & 0 \ t\_x & t\_y & 1\end{matrix}\right] = \left\[\begin{matrix} x + t\_x & y + t\_y & 1 \end{matrix}\right]
$$

### 缩放

将 $(x,y)$ 缩放到 $(x*s\_x,y*s\_y)$ , 变换矩阵为：

$$
\left\[\begin{matrix} x & y & 1 \end{matrix}\right] \* \left\[\begin{matrix}s\_x & 0 & 0 \ 0 & s\_y & 0 \ 0 & 0 & 1\end{matrix}\right]= \left\[\begin{matrix} x \* s\_x & y \* s\_y & 1 \end{matrix}\right]
$$

### 旋转

将 $(x,y)$ 顺时针旋转 $\theta$ 角度

$$
\left\[\begin{matrix} x & y & 1 \end{matrix}\right] \* \left\[\begin{matrix}cos{\theta} & sin{\theta} & 0 \ -sin{\theta} & cos{\theta} & 0 \ 0 & 0 & 1\end{matrix}\right] = \left\[\begin{matrix} x \* cos{\theta} - y \* sin{\theta} & x \* sin{\theta} + y \* cos{\theta} & 1 \end{matrix}\right]
$$

### 错切

$$
\left\[\begin{matrix} x & y & 1 \end{matrix}\right] \* \left\[\begin{matrix}1 & a & 0 \ 0 & 1 & 0 \ 0 & 0 & 1\end{matrix}\right] = \left\[\begin{matrix} x & a\*x+y & 1 \end{matrix}\right]
$$

### 复合变换

仿射变换公有6个自由度，两个旋转因子，两个缩放因子，x和y的平移因子，共6个。

以 $(t\_x,t\_y)$ 为中心旋转 $\theta$ 度，先平移 $(-x,-y)$，然后以原点为中心旋转，最后再平移 $(x,y)$

$$
\left\[\begin{matrix} x & y & 1 \end{matrix}\right] \* \left\[\begin{matrix}1 & 0 & 0 \ 0 & 1 & 0 \ -t\_x & -t\_y & 1\end{matrix}\right] \* \left\[\begin{matrix}cos{\theta} & sin{\theta} & 0 \ -sin{\theta} & cos{\theta} & 0 \ 0 & 0 & 1\end{matrix}\right] \* \left\[\begin{matrix}1 & 0 & 0 \ 0 & 1 & 0 \ t\_x & t\_y & 1\end{matrix}\right]
$$

### opencv中的仿射变换

`crop_img = cv2.warpAffine(src_img, tfm, (crop_size[0], crop_size[1]))`

其中`tfm`是`shape=(2,3)`的变换矩阵


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://niyunsheng.gitbook.io/deeplearning/ren-lian/face_align.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
