当前位置:   article > 正文

Paddle实现人脸对比

Paddle实现人脸对比

人脸对比

人脸对比,顾名思义,就是对比两个人脸的相似度。本文将用Paddle实现这一功能。

PS:作者肝了整整3天才稍微搞明白实现方法

数据集准备

这里使用百度AI Studio的开源数据集:

人脸数据_数据集-飞桨AI Studio星河社区 (baidu.com)

这个数据集提供了500个人的人脸各100张,比较适合我们的项目。

根据这个数据集,很容易写出数据集类,文件名为face_dataset.py:

  1. import numpy as np
  2. from PIL import Image
  3. import paddle
  4. from random import shuffle
  5. class FaceData(paddle.io.Dataset):
  6. def __init__(self, mode, num):
  7. super().__init__()
  8. # 训练集/测试集
  9. file = 'facecap/train_list.txt' if mode == 'train' else 'facecap/test_list.txt'
  10. self.imgs1 = []
  11. self.imgs2 = []
  12. self.labels = []
  13. # 控制相同人脸个数与不同人脸个数各占一半
  14. _1_count = 0
  15. with open(file) as f:
  16. # 读取数据集文件信息数据并洗牌
  17. lines = f.readlines()
  18. shuffle(lines)
  19. lines = lines[:num]
  20. print('read down')
  21. # 加载数据集
  22. for line1 in lines:
  23. line1 = line1.strip()
  24. img1, label1 = line1.split(' ')
  25. pil_img1 = Image.open(f'facecap\\{img1}').convert('RGB').resize((96, 96))
  26. for line2 in lines:
  27. line2 = line2.strip()
  28. img2, label2 = line2.split(' ')
  29. if label1 == label2:
  30. _1_count += 1
  31. pil_img2 = Image.open(f'facecap\\{img2}').convert('RGB').resize((96, 96))
  32. self.imgs1.append(np.array(pil_img1).transpose((2, 0, 1)) / 255.0)
  33. self.imgs2.append(np.array(pil_img2).transpose((2, 0, 1)) / 255.0)
  34. self.labels.append(1)
  35. elif _1_count > 0:
  36. _1_count -= 1
  37. pil_img2 = Image.open(f'facecap\\{img2}').convert('RGB').resize((96, 96))
  38. self.imgs1.append(np.array(pil_img1).transpose((2, 0, 1)) / 255.0)
  39. self.imgs2.append(np.array(pil_img2).transpose((2, 0, 1)) / 255.0)
  40. self.labels.append(0)
  41. self.imgs1 = np.array(self.imgs1, dtype=np.float32)
  42. self.imgs2 = np.array(self.imgs2, dtype=np.float32)
  43. self.labels = np.array(self.labels, dtype=np.float32)
  44. print('load down')
  45. def __getitem__(self, idx):
  46. return self.imgs1[idx], self.imgs2[idx], self.labels[idx]
  47. def __len__(self):
  48. return len(self.labels)

需要注意的是,PIL的图片维度与paddle CNN的维度不一样,需要使用transpose改变 

当然,使用这个数据集类读取数据是非常漫长的,因此我们创建了一个face_create_dataset.py,创建数据集对象并保存到本地:

  1. from face_dataset import FaceData
  2. import pickle
  3. train_dataset = FaceData(mode='train', num=2000)
  4. test_dataset = FaceData(mode='test', num=200)
  5. pickle.dump(train_dataset, open('./database/train.data', 'wb'), protocol=4)
  6. pickle.dump(test_dataset, open('./database/test.data', 'wb'), protocol=4)

 这里我们使用pickle保存对象,注意这里要指定protocol=4,以保证可以存储超过4G的大文件

最后,这个脚本会在本地的database文件夹下生成两个data文件,使用时只需要加载即可

孪生网络

既然要输入两张图片,就自然需要使用两张卷积网络,分别处理两张图片。但是人脸对比与输入顺序无关,这就要求两个网络对于同一张图片的输出是相同的,也就是这两个网络是相同的。即共享权重的网络。因此我们可以定义网络如下:

  1. class FaceNet(paddle.nn.Layer):
  2. def __init__(self):
  3. super().__init__()
  4. # 共享权重的cnn网络
  5. self.cnn = paddle.nn.Sequential(
  6. paddle.nn.Conv2D(3, 16, 3, padding=1),
  7. paddle.nn.ReLU(),
  8. paddle.nn.MaxPool2D(2, 2),
  9. paddle.nn.Conv2D(16, 32, 3, padding=1),
  10. paddle.nn.ReLU(),
  11. paddle.nn.MaxPool2D(2, 2),
  12. paddle.nn.Conv2D(32, 64, 3, padding=1),
  13. paddle.nn.ReLU(),
  14. paddle.nn.MaxPool2D(2, 2),
  15. paddle.nn.Conv2D(64, 128, 3, padding=1),
  16. paddle.nn.ReLU(),
  17. paddle.nn.MaxPool2D(2, 2),
  18. paddle.nn.Flatten(),
  19. paddle.nn.Linear(4608, 5)
  20. )
  21. def forward(self, face1, face2):
  22. # 前向传播:使用cnn网络分别输出两个结果并返回
  23. n1r = self.cnn(face1)
  24. n2r = self.cnn(face2)
  25. return n1r, n2r

这个网络还需要有特殊的损失函数。这个损失函数将会使相同的人脸距离相近,不同的人脸距离更远。我们采用勾股定理计算距离,这样的距离也叫欧氏距离。

因此,对于一个在n维空间上的两个点(x1, x2, x3, ..., xn), (y1, y2, y3, ..., yn),就有:

d = \sqrt{(x_1-y_1)^2+(x_2-y_2)^2+...+(x_n-y_n)^2}

因此,如果人脸相同,损失函数将会输出的损失值是:

\left | 0-d \right |

这样的话,如果距离过远,损失值就会偏大 ,从而使输出更接近0

如果人脸不同,输出的损失值是:

max(m-d, 0)

这样的话,只有当距离比设定阈值远时,损失才为0.

另外,损失最好使用乘方而不是绝对值,这样的话就可以增加远离时的梯度。

我们定义其损失函数如下:

  1. # 损失函数定义
  2. class FaceLoss(paddle.nn.Layer):
  3. def __init__(self, margin=(512 ** 0.5)):
  4. super(FaceLoss, self).__init__()
  5. self.margin = margin
  6. def forward(self, output1, output2, label):
  7. # 计算欧式距离(勾股定理)
  8. euclidean_distance = paddle.norm(output1 - output2, axis=1)
  9. # 损失值
  10. # 在数据集中,1为相同,0为不同。但是输出要求相似的图片距离更近
  11. loss_contrastive = label * paddle.square(euclidean_distance) + \
  12. (1 - label) * paddle.square(
  13. paddle.maximum(self.margin - euclidean_distance, paddle.to_tensor(0.0)))
  14. # 损失函数应对同一批次取一个损失值
  15. return paddle.mean(loss_contrastive)

在paddle中,可以使用paddle.norm计算距离。axis=1表示只对第1维度计算距离,因为第0维度是数据批次。

在数据集中,我们定义1为相同,0为不同。根据我们之前的分析,很容易算出损失值的公式。

接下来就可以把这两个整合在一个py文件中,起名face_layers.py:

  1. import paddle
  2. class FaceNet(paddle.nn.Layer):
  3. def __init__(self):
  4. super().__init__()
  5. # 共享权重的cnn网络
  6. self.cnn = paddle.nn.Sequential(
  7. paddle.nn.Conv2D(3, 16, 3, padding=1),
  8. paddle.nn.ReLU(),
  9. paddle.nn.MaxPool2D(2, 2),
  10. paddle.nn.Conv2D(16, 32, 3, padding=1),
  11. paddle.nn.ReLU(),
  12. paddle.nn.MaxPool2D(2, 2),
  13. paddle.nn.Conv2D(32, 64, 3, padding=1),
  14. paddle.nn.ReLU(),
  15. paddle.nn.MaxPool2D(2, 2),
  16. paddle.nn.Conv2D(64, 128, 3, padding=1),
  17. paddle.nn.ReLU(),
  18. paddle.nn.MaxPool2D(2, 2),
  19. paddle.nn.Flatten(),
  20. paddle.nn.Linear(4608, 5)
  21. )
  22. def forward(self, face1, face2):
  23. # 前向传播:使用cnn网络分别输出两个结果并返回
  24. n1r = self.cnn(face1)
  25. n2r = self.cnn(face2)
  26. return n1r, n2r
  27. # 损失函数定义
  28. class FaceLoss(paddle.nn.Layer):
  29. def __init__(self, margin=(512 ** 0.5)):
  30. super(FaceLoss, self).__init__()
  31. self.margin = margin
  32. def forward(self, output1, output2, label):
  33. # 计算欧式距离(勾股定理)
  34. euclidean_distance = paddle.norm(output1 - output2, axis=1)
  35. # 损失值
  36. # 在数据集中,1为相同,0为不同。但是输出要求相似的图片距离更近
  37. loss_contrastive = label * paddle.square(euclidean_distance) + \
  38. (1 - label) * paddle.square(
  39. paddle.maximum(self.margin - euclidean_distance, paddle.to_tensor(0.0)))
  40. # 损失函数应对同一批次取一个损失值
  41. return paddle.mean(loss_contrastive)

训练

接下来我们需要编写训练脚本face.py:

  1. import paddle
  2. from face_dataset import FaceData
  3. from face_layers import FaceNet, FaceLoss
  4. import pickle
  5. # 加载数据集
  6. train_dataset = pickle.load(open('./database/train.data', 'rb'))
  7. test_dataset = pickle.load(open('./database/test.data', 'rb'))
  8. # 输出数据集信息
  9. print(f'加载数据完毕,训练集数据个数:{len(train_dataset)};测试集数据个数:{len(test_dataset)}')
  10. count = 0
  11. for context1, context2, label in train_dataset:
  12. if label == 1:
  13. count += 1
  14. print(f'训练集相同人脸个数{count}')
  15. count = 0
  16. for context1, context2, label in test_dataset:
  17. if label == 1:
  18. count += 1
  19. print(f'测试集相同人脸个数{count}')
  20. # 指定设备
  21. paddle.device.set_device('gpu')
  22. # 创建模型
  23. model = paddle.Model(FaceNet())
  24. # 打印模型信息
  25. print(model.summary(((1, 3, 96, 96), (1, 3, 96, 96))))
  26. # 模型训练的配置准备,准备损失函数,优化器和评价指标
  27. model.prepare(paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=0.00001),
  28. FaceLoss())
  29. # 模型训练
  30. model.fit(train_dataset, epochs=50, batch_size=64, verbose=1)
  31. # 模型评估
  32. model.evaluate(test_dataset, batch_size=64, verbose=1)
  33. # 保存模型
  34. model.save('./output/face-compare')

这里需要注意,我们需要使用FaceLoss作为损失函数

训练完毕后,训练数据将被存储在本地的output文件夹下,使用时加载即可

接下来我们可以编写face_use.py使用这个模型:

  1. import paddle
  2. from face_dataset import FaceData
  3. from face_layers import FaceNet
  4. from PIL import Image
  5. import numpy as np
  6. # 加载模型
  7. model = paddle.Model(FaceNet())
  8. model.load('./output/face-compare')
  9. print('加载模型完毕')
  10. # 打开图片
  11. pil_img1 = Image.open(f'facecap\\003\\30.jpg').convert('RGB').resize((96, 96))
  12. pil_img2 = Image.open(f'facecap\\003\\27.jpg').convert('RGB').resize((96, 96))
  13. # 转np数组
  14. np_img1 = np.array(pil_img1, dtype=np.float32).transpose((2, 0, 1)) / 255.0
  15. np_img2 = np.array(pil_img2, dtype=np.float32).transpose((2, 0, 1)) / 255.0
  16. # 预测
  17. pred = model.predict_batch((np.array([np_img1], dtype=np.float32), np.array([np_img2], dtype=np.float32)))
  18. # 计算距离
  19. euclidean_distance = paddle.norm(paddle.to_tensor([pred[0]]) - paddle.to_tensor([pred[1]]))
  20. print(euclidean_distance.numpy())

这里只以两张相同人的人脸的图片做测试,最后输出:

  1. 加载模型完毕
  2. [0.1978856]

改用两张不同人的人脸做测试,最后输出:

  1. 加载模型完毕
  2. [1.1059165]

可以看到,这个模型的效果还不错。但是经过我的多次测试,发现这个模型还有一定的提升空间。这需要更大的数据集、更深的模型和更多的训练次数

总结

我们使用孪生网络技术,成功实现了人脸对比模型,并有一定的准确性,可以应用于人脸比对等场景。但是,由于数据集、模型和训练次数有限,还难以实现更准确的人脸对比

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/377161
推荐阅读
  

闽ICP备14008679号