从零开始搭建人脸识别系统(一)MTCNN 中我们讲了如何在一张图片中定位人脸框的位置。设想这样一种情况,图片中的脸相对于图片是斜的:(下面的图由于人脸关键点比较小可能看不清楚,可以打开原图可以看到标识的关键点)。
import cv2
import matplotlib.pyplot as plt
import numpy as np
img_file = '../tests/asset/images/roate.jpg'
img = cv2.imread(img_file)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
plt.figure(figsize=(10, 10))
plt.imshow(img)
plt.show()
这时我们需要把图片中的脸扶正,再送入人脸识别模块。那我们要怎么做呢,我们结合代码一步一步地来讲。本文的代码同样是基于
faciallab/FaceDetectorgithub.com
如果本文对你有帮助可以帮我点个星星哦。
首先我们加载mtcnn模型
import mtcnn
from mtcnn.utils import draw
# First we create pnet, rnet, onet, and load weights from caffe model.
pnet, rnet, onet = mtcnn.get_net_caffe('../output/converted')
# Then we create a detector
detector = mtcnn.FaceDetector(pnet, rnet, onet, device='cpu')
../mtcnn/network/mtcnn_pytorch.py:9: UserWarning: nn.init.xavier_uniform is now deprecated in favor of nn.init.xavier_uniform_.
nn.init.xavier_uniform(m.weight.data)
../mtcnn/network/mtcnn_pytorch.py:10: UserWarning: nn.init.constant is now deprecated in favor of nn.init.constant_.
nn.init.constant(m.bias, 0.1)
检测人脸并标出关键点
img = cv2.imread(img_file)
boxes, landmarks = detector.detect(img, minsize=24)
face = draw.crop(img, boxes=boxes, landmarks=landmarks)[0]
face = cv2.cvtColor(face, cv2.COLOR_RGB2BGR)
plt.figure(figsize=(5, 5))
plt.imshow(face)
plt.show()
接下来我们就来讲解如何进行人脸对齐
首先假设我们最后要截取一张(112,96)大小的正脸,那么人脸的五个关键点分别在什么位置才算是正脸呢?所以我们需要五个参考点。
# Define the correct points.
REFERENCE_FACIAL_POINTS = np.array([
[30.29459953, 51.69630051],
[65.53179932, 51.50139999],
[48.02519989, 71.73660278],
[33.54930115, 92.3655014],
[62.72990036, 92.20410156]
], np.float32)
# Lets create a empty image|
empty_img = np.zeros((112,96,3), np.uint8)
draw.draw_landmarks(empty_img, REFERENCE_FACIAL_POINTS.astype(int))
plt.figure(figsize=(5, 5))
plt.imshow(empty_img)
plt.show()
那么我们图片中的人脸在什么位置呢
img_copy = img.copy()
landmark = landmarks[0]
img_copy[:112, :96, :] = empty_img
img_copy = cv2.cvtColor(img_copy, cv2.COLOR_RGB2BGR)
draw.draw_landmarks(img_copy, landmark)
plt.figure(figsize=(15, 15))
plt.imshow(img_copy)
plt.show()
所以我们需要一个由蓝色点坐标到红色点坐标的一个仿射变换。那么什么是仿射变换呢,可以参考这篇文章
有了仿射变换的基础知识之后,我们知道通过三个原始点和三个对应的参考点我们就可以确定一个变换矩阵,将图片中人脸移动并对其在上图的黑色区域中。最简单的想法是我们就取眼睛和鼻子和其所对应的三个点计算变换矩阵。
trans_matrix = cv2.getAffineTransform(landmark[:3].cpu().numpy().astype(np.float32), REFERENCE_FACIAL_POINTS[:3])
接下来我们使用这个矩阵对原图像进行变换得到如下结果
aligned_face = cv2.warpAffine(img.copy(), trans_matrix, (112, 112))
aligned_face = cv2.cvtColor(aligned_face, cv2.COLOR_RGB2BGR)
plt.figure(figsize=(5, 5))
plt.imshow(aligned_face)
plt.show()
我们看到人脸已经被大致对其了,但是效果似乎不是很理想,人脸有一些变形。
接下来我们实现一个稍微复杂一点的对其算法
上面这个简单的算法有几个问题。一是我们有5个点的对应关系,却只用了三个。二是上述操作可能会造成对图像的拉伸变换,这样会使图像变形。怎么解决这个问题呢?
首先我们先来回顾一下怎样做一个仿射变换
如果我们想对一个图像在x轴方向平移 tx 个像素,在y轴方向平移ty个像素,我们的变换矩阵长什么样子呢?
如果我们想顺时针旋转一定角度呢
同时平移和旋转呢
所以我们有mtcnn输出的关键点
参考关键点
我们就可以通过下面的等式解出我们的未知参数,a,b,tx,ty
该等式进一步变形就可以写成关于a,b,tx,ty的线性方程
此时,我们可以用最小二乘法来对参数进行估计。(可以用numpy的内置函数 numpy.linalg.lstsq)
下面是具体代码实现
from numpy.linalg import inv, norm, lstsq
from numpy.linalg import matrix_rank as rank
def findNonreflectiveSimilarity(uv, xy, K=2):
M = xy.shape[0]
x = xy[:, 0].reshape((-1, 1)) # use reshape to keep a column vector
y = xy[:, 1].reshape((-1, 1)) # use reshape to keep a column vector
tmp1 = np.hstack((x, y, np.ones((M, 1)), np.zeros((M, 1))))
tmp2 = np.hstack((y, -x, np.zeros((M, 1)), np.ones((M, 1))))
X = np.vstack((tmp1, tmp2))
u = uv[:, 0].reshape((-1, 1)) # use reshape to keep a column vector
v = uv[:, 1].reshape((-1, 1)) # use reshape to keep a column vector
U = np.vstack((u, v))
# We know that X * r = U
if rank(X) >= 2 * K:
r, _, _, _ = lstsq(X, U)
r = np.squeeze(r)
else:
raise Exception('cp2tform:twoUniquePointsReq')
sc = r[0]
ss = r[1]
tx = r[2]
ty = r[3]
Tinv = np.array([
[sc, -ss, 0],
[ss, sc, 0],
[tx, ty, 1]
])
T = inv(Tinv)
T[:, 2] = np.array([0, 0, 1])
T = T[:, 0:2].T
return T
similar_trans_matrix = findNonreflectiveSimilarity(landmark.cpu().numpy().astype(np.float32), REFERENCE_FACIAL_POINTS)
aligned_face = cv2.warpAffine(img.copy(), similar_trans_matrix, (112, 112))
aligned_face = cv2.cvtColor(aligned_face, cv2.COLOR_RGB2BGR)
plt.figure(figsize=(5, 5))
plt.imshow(aligned_face)
plt.show()
这次对其的效果就非常好了。