AdaFace(CVPR(2022)):通过AdaFace实现低质量面部数据集的人脸识别

对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》

写在前面


  • 工作中遇到,简单整理
  • 理解不足小伙伴帮忙指正

对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》


低质量人脸数据集中的识别具有挑战性,因为人脸属性被模糊和降级。基于裕量的损失函数的进步提高了嵌入空间中人脸的可辨别性。

此外,以前的研究已经研究了适应性损失的影响,以更加重视错误分类的(硬)例子。在这项工作中,我们介绍了损失函数自适应性的另一个方面,即图像质量。我们认为,强调错误分类样本的策略应根据其图像质量进行调整。具体来说,简单和硬样品的相对重要性应基于样品的图像质量。

我们提出了一种新的损失函数,该函数根据图像质量强调不同难度的样本。我们的方法通过用特征范数近似图像质量,以自适应裕量函数的形式实现这一点。大量的实验表明,我们的方法AdaFace在四个数据集(IJB-B,IJB-C,IJB-S和TinyFace)上提高了最先进的(SoTA)的人脸识别性能。

1
2
3
4
5
6
@inproceedings{kim2022adaface,
title={AdaFace: Quality Adaptive Margin for Face Recognition},
author={Kim, Minchul and Jain, Anil K and Liu, Xiaoming},
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
year={2022}
}

实际测试中发现,AdaFace 确实很强大,特别适合远距离,小目标,图片质量低的人脸识别。

https://github.com/mk-minchul/AdaFace

克隆项目,环境搭建

1
2
3
4
5
6
7
8
9
10
11
(base) C:\Users\liruilong>conda create -n AdaFace python==3.8
Solving environment: done
(base) C:\Users\liruilong>conda activate AdaFace

(AdaFace) C:\Users\liruilong>cd Documents\GitHub\AdaFace_demo

(AdaFace) C:\Users\liruilong\Documents\GitHub\AdaFace_demo>conda install scikit-image matplotlib pandas scikit-learn
Solving environment: done
。。。
(AdaFace) C:\Users\liruilong\Documents\GitHub\AdaFace_demo>pip install -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
Looking in indexes: http://pypi.douban.com/simple/

没有GPU,所以我们需要修改原代码中部分为 CPU 可以执行

修改位置:\GitHub\AdaFace_demo\face_alignment\align.py

1
2
3
mtcnn_model = mtcnn.MTCNN(device='cuda:0', crop_size=(112, 112))
# 修改为
mtcnn_model = mtcnn.MTCNN(device='cpu', crop_size=(112, 112))

修改位置:\GitHub\AdaFace_demo\inference.py

1
2
3
statedict = torch.load(adaface_models[architecture])['state_dict']
# 修改为
statedict = torch.load(adaface_models[architecture], map_location=torch.device('cpu'))['state_dict']

之后需要下载对应的模型文件,可以做 github 看到。放到指定位置pretrained/adaface_ir101_webface12m.ckpt就可以执行了,这里不多讲

运行 Demo,3 张图片比较

1
2
3
4
5
6
7
8
9
(AdaFace) C:\Users\liruilong\Documents\GitHub\AdaFace_demo> python inference.py
C:\Users\liruilong\Documents\GitHub\AdaFace_demo\face_alignment\mtcnn_pytorch\src\matlab_cp2tform.py:90: FutureWarning: `rcond` parameter will change to the default of machine precision times ``max(M, N)`` where M and N are the input matrix dimensions.
To use the future default and silence this warning we advise to pass `rcond=None`, to keep using the old, explicitly pass `rcond=-1`.
r, _, _, _ = lstsq(X, U)
inference.py:25: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at C:\actions-runner\_work\pytorch\pytorch\builder\windows\pytorch\torch\csrc\utils\tensor_new.cpp:248.)
tensor = torch.tensor([brg_img.transpose(2,0,1)]).float()
tensor([[ 1.0000, 0.7329, -0.0794],
[ 0.7329, 1.0000, -0.0087],
[-0.0794, -0.0087, 1.0000]], grad_fn=<MmBackward0>)

这里的矩阵表示,每张图片相互比较,矩阵为3*3,三行三列,第一张图片跟第一张图片的相似度为 1 (自己和自己比),然后第一张图片跟第二张图片对比的相似度为 0.7329,第一张图片跟第三张图片对比的相似度为 -0.0794,对角都为自己和自己比较所以是1.

我们通过上面余弦相似度得分可以区分是否是一个人,在具体的人脸识别应用中。

  1. 需要预先通过上面的模型把人脸库每张照片的特征向量保存到文本里,生成特征向量集 Ax={A1,A2,A3,....Ai}(i=n)
  2. 需要识别的时候,通过模型获取要识别照片的特征向量 B1,用特征向量集 Ax 中的每个向量Ai和识别照片的特征向量B1获取余弦相似度得分
  3. 对于获取的相似得分最大值,和阈值判断,或者取大于阈值内的数据,判断是否为一个人,从而实现人脸识别。
  4. 相似度得分是一个接近 小于等一,大于等于 -1 的值,越大相识度越高,等于1 即是确定同一个人,-1 即完全不是一个人, 实际识别中,给相似得分一个阈值,在这个范围内我们确定为一个人。

下面为一个 Demo

  • build_vector_pkl 用于生成特征文件(需要准备一个照片数据集,通过照片名字标注人)
  • read_vector_pkl 用于加载特征文件到内存
  • find 用于需要识别的人脸和人脸库比对,返回识别结果
  • 这里默认我们已经通过检测,每张识别照片只有一个人脸。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import net
import torch
import os
from face_alignment import align
import numpy as np
import pandas as pd

adaface_models = {'ir_101': "pretrained/adaface_ir101_webface12m.ckpt",}

def load_pretrained_model(architecture='ir_101'):
# load model and pretrained statedict
assert architecture in adaface_models.keys()
model = net.build_model(architecture)
statedict = torch.load(
adaface_models[architecture], map_location=torch.device('cpu'))['state_dict']
model_statedict = {key[6:]: val for key,
val in statedict.items() if key.startswith('model.')}
model.load_state_dict(model_statedict)
model.eval()
return model

def to_input(pil_rgb_image):
tensor = None
try:
np_img = np.array(pil_rgb_image)
brg_img = ((np_img[:, :, ::-1] / 255.) - 0.5) / 0.5
tensor = torch.tensor([brg_img.transpose(2, 0,1)]).float()
except Exception :
return tensor
return tensor

def read_vector_pkl(db_path, adaface_model_name):
"""
@Time : 2023/06/16 12:10:47
@Author : liruilonger@gmail.com
@Version : 1.0
@Desc : 读取特征向量文件
Args:

Returns:
df
"""
import pickle
file_name = f"representations_adaface_{adaface_model_name}.pkl"
file_name = file_name.replace("-", "_").lower()
with open(f"{db_path}/{file_name}", "rb") as f:
representations = pickle.load(f)
df = pd.DataFrame(representations, columns=["identity", f"{adaface_model_name}_representation"])
return df


def build_vector_pkl(db_path, adaface_model_name='adaface_model'):
"""
@Time : 2023/06/16 11:40:23
@Author : liruilonger@gmail.com
@Version : 1.0
@Desc : 构建特征向量文件
Args:

Returns:
void
"""
import time
from os import path
from tqdm import tqdm
import pickle

if os.path.isdir(db_path) is not True:
raise ValueError("Passed db_path does not exist!")

file_name = f"representations_adaface_{adaface_model_name}.pkl"
file_name = file_name.replace("-", "_").lower()
if path.exists(db_path + "/" + file_name):
pass
else:
employees = []
for r, _, f in os.walk(db_path):
for file in f:
if (
(".jpg" in file.lower())
or (".jpeg" in file.lower())
or (".png" in file.lower())
):
exact_path = r + "/" + file
employees.append(exact_path)

if len(employees) == 0:
raise ValueError(
"没有任何图像在 ",
db_path,
" 文件夹! 验证此路径中是否存在.jpg或.png文件。",
)
representations = []

pbar = tqdm(
range(0, len(employees)),
desc="生成向量特征文件中:😍😊🔬🔬🔬⚒️⚒️⚒️🎢🎢🎢🎢🎢",
)
for index in pbar:
employee = employees[index]

img_representation = get_represent(employee)
instance = []
instance.append(employee)
instance.append(img_representation)
representations.append(instance)
with open(f"{db_path}/{file_name}", "wb") as f:
pickle.dump(representations, f)


def get_represent(path):
"""
@Time : 2023/06/16 11:54:11
@Author : liruilonger@gmail.com
@Version : 1.0
@Desc : 获取脸部特征向量
Args:

Returns:
void
"""
feature = None
aligned_rgb_img = align.get_aligned_face(path)
bgr_tensor_input = to_input(aligned_rgb_img)
if bgr_tensor_input is not None:
feature, _ = model(bgr_tensor_input)
else:
print(f"无法提取脸部特征向量{path}")
return feature

def findCosineDistance(source_representation, test_representation):
"""
@Time : 2023/06/16 12:19:27
@Author : liruilonger@gmail.com
@Version : 1.0
@Desc : 计算两个向量之间的余弦相似度得分
Args:

Returns:
void
"""
import torch.nn.functional as F
return F.cosine_similarity(source_representation, test_representation)


def demo1():
model_name = "test"
build_vector_pkl(test_image_path,model_name)
df = read_vector_pkl(test_image_path, model_name)
for index, instance in df.iterrows():
source_representation = instance[f"{model_name}_representation"]
#distance = findCosineDistance(source_representation, target_representation)
print(source_representation)
features.append(source_representation)
similarity_scores = torch.cat(features) @ torch.cat(features).T
print(similarity_scores)


def find(test_image_path,threshold=0.5):
"""
@Time : 2023/06/16 14:02:52
@Author : liruilonger@gmail.com
@Version : 1.0
@Desc : 根据图片在人脸库比对找人
Args:

Returns:
void
"""

test_representation = get_represent(test_image_path)
if test_representation is not None:
reset = {}
for index, instance in df.iterrows():
source_representation = instance[f"{model_name}_representation"]
ten = findCosineDistance(source_representation,test_representation)
reset[ten.item()]= instance["identity"]
cosine_similarity = max(reset.keys())
print(f"💝💝💝💝💝💝💝💝💝💝 {cosine_similarity} 💝💝💝💝💝{threshold}")
return cosine_similarity > threshold ,reset[cosine_similarity]
else:
return False,0

def marge(m1,m2):
from PIL import Image
import uuid
# 打开第一张图片
image1 = Image.open(m1)
# 打开第二张图片
image2 = Image.open(m2)
# 获取第一张图片的大小
width1, height1 = image1.size
# 获取第二张图片的大小
width2, height2 = image2.size
# 创建一个新的画布,大小为两张图片的宽度之和和高度的最大值
new_image = Image.new("RGB", (width1 + width2, max(height1, height2)))
# 将第一张图片粘贴到画布的左侧
new_image.paste(image1, (0, 0))
# 将第二张图片粘贴到画布的右侧
new_image.paste(image2, (width1, 0))
# 保存拼接后的图片
new_image.save(str(uuid.uuid4()).replace('-', '')+"new_image.jpg")



if __name__ == '__main__':

import imutils
from imutils import paths
import cv2
import uuid
model = load_pretrained_model('ir_101')
# 需要识别的图片位置
test_image_path = 'face_alignment/ser'
features = set()
model_name = "test_img"

build_vector_pkl("face_alignment/test",model_name)
df = read_vector_pkl("face_alignment/test", model_name)

for path in paths.list_images(test_image_path):
b, r = find(path,0.25)
if b:
if r not in features:
features.add(r)
marge(r,path)
else:
img = cv2.imread(path)
cv2.imwrite('__not' + str(uuid.uuid4()).replace('-', '')+".jpg", img)

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知,这是一个开源项目,如果你认可它,不要吝啬星星哦 :)


AdaFace: Quality Adaptive Margin for Face Recognition(AdaFace:用于人脸识别的质量自适应裕量): https://arxiv.org/abs/2204.00964

https://github.com/mk-minchul/AdaFace


© 2018-2023 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2023-06-17

更新于

2023-06-21

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×