LFW人脸数据集测试协议及编程实现
1 簡介
LFW人臉數據集主要用于測試模型準確率。數據庫中共有13233張圖像和5749人。 每張圖片都是250x250 jpg。目前LFW數據集不用作訓練,主要用于測試。因此本文主要講解其測試協議。
程序使用pytorch
LFW官方網址:http://vis-www.cs.umass.edu/lfw/
本次實驗測試MobileFaceNet模型在LFW上的準確率。
lfw數據集(已經對齊并且大小為112×112):
鏈接:https://pan.baidu.com/s/1Sy0EkKRfb0xHRgq1iao2qg
提取碼:1rf3
2 數據集準備
2.1 LFW數據集下載
下載地址:http://vis-www.cs.umass.edu/lfw/lfw.tgz
2.2 人臉預處理
下載的LFW數據集是250×250的圖像,我們需要將其進行人臉預處理操作(人臉檢測+人臉對齊),具體方法可以看我另一個博客。
https://blog.csdn.net/qq_41684249/article/details/111302649
三、測試協議
3.1 pair.txt
下載地址:http://vis-www.cs.umass.edu/lfw/pairs.txt
該文件將數據庫隨機分為10組,我們隨機選擇300個匹配對和300個每組中不匹配的對。使用此拆分,可以使用10倍交叉驗證得出數據庫的性能。
pair.txt文件的格式如下:
- 第一行顯示套數后跟每套匹配對的數量(相等到每組不匹配對的數量)。
- 匹配對格式如下:
name n1 n2
表示匹配對name人的第n1和第n2張圖。 - 不匹配對格式如下:
name1 n1 name2 n2
表示不匹配對name1人的第n1張圖和name2人的第n2張圖。
3.2 具體流程
四、程序
4.1 MobileFaceNet
預訓練模型見博客。
博客鏈接:https://blog.csdn.net/qq_41684249/article/details/115357756?spm=1001.2014.3001.5501
4.2 配置文件config.py
只需將這個配置文件對應修改就行。
img_list.txt和pairs.txt文件見第一部分。
# 測試協議txt文件 PAIRS_FILE_PATH = "/home/malidong/workspace/dataset/lfw/pairs.txt" # 預處理后的數據集地址 CROPPED_FACE_FOLDER = "/home/malidong/workspace/dataset/lfw/112×112" # 包含數據集所有相對路徑的txt文件地址 IMAGE_LIST_FILE_PATH = "/home/malidong/workspace/dataset/lfw/img_list.txt" # 模型輸出維度 FEAT_DIM = 512 # 深度卷積層核大小 OUT_H = 7 OUT_W = 7 # 預訓練模型地址 MODEL_PATH = "/home/malidong/workspace/mobilefacenet/Epoch_17.pt" # 設備選擇 DEVICE = "cuda"4.3 數據集加載文件test_dataset.py
import os import cv2 import numpy as np from torch.utils.data import Dataset import torchvision.transforms as transformsclass CommonTestDataset(Dataset):def __init__(self, image_root, image_list_file):'''普通測試數據集加載:param image_root: 數據集根目錄:param image_list_file: txt文件,包含所有圖片相對路徑'''self.image_root = image_rootself.image_list = []image_list_buf = open(image_list_file) # 開始讀取文件line = image_list_buf.readline().strip() # 讀取一行while line:self.image_list.append(line)line = image_list_buf.readline().strip() # 繼續讀取下一行# 預處理self.transform = transforms.Compose([transforms.ToTensor(), # 0-1transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 歸一化])def __len__(self):return len(self.image_list)def __getitem__(self, index):short_image_path = self.image_list[index] # 圖片相對路徑image_path = os.path.join(self.image_root, short_image_path) # 圖片路徑image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), cv2.IMREAD_UNCHANGED) # 讀取圖片#image = cv2.resize(image, (128, 128))image = self.transform(image) # 預處理return image, short_image_path # 圖片 相對路徑(相當于圖片身份,用于查詢)4.4 lfw評估程序lfw_evaluator.py
import torch import numpy as np import torch.nn.functional as Fclass LFWEvaluator(object):def __init__(self, data_loader, pairs_file, device):'''LFW測試協議 先將所有的數據集進行特征提取,然后在測試:param data_loader: 數據集加載:param pairs_file: 測試txt文件地址:param device: 設備'''self.data_loader = data_loaderself.device = deviceself.pair_list = self.pairsParser(pairs_file)def pairsParser(self, pairs_file):'''讀取pair.txt文件內容:param pairs_file: 測試txt文件地址:return: (圖1 圖2 標簽),是一個list列表文件。標簽為0表示不同,標簽為1表示相同。'''test_pair_list = [] # 創建一個listpairs_file_buf = open(pairs_file) # 讀取文件line = pairs_file_buf.readline() # 跳過第一行 因為第一行是無關的內容line = pairs_file_buf.readline().strip() # 讀取一行,去除首尾空格while line: # 只要文件有內容,就會讀取line_strs = line.split('\t') # 按空格分割if len(line_strs) == 3: # 如果是3元素,則表示兩張人臉是同一個人person_name = line_strs[0] # 第一個元素是姓名image_index1 = line_strs[1] # 第二個元素是第一張圖的索引image_index2 = line_strs[2] # 第三個元素是第二張圖的索引image_name1 = person_name + '/' + person_name + '_' + image_index1.zfill(4) + '.jpg' # 得到第一張人臉的地址image_name2 = person_name + '/' + person_name + '_' + image_index2.zfill(4) + '.jpg' # 得到第二張人臉的地址label = 1 # 標簽為1表示是同一個身份elif len(line_strs) == 4: # 表示兩張人臉是不同的人person_name1 = line_strs[0] # 第一個人的姓名image_index1 = line_strs[1] # 第一個人的索引person_name2 = line_strs[2] # 第二個人的姓名image_index2 = line_strs[3] # 第二個人的索引image_name1 = person_name1 + '/' + person_name1 + '_' + image_index1.zfill(4) + '.jpg' # 得到第一張人臉的地址image_name2 = person_name2 + '/' + person_name2 + '_' + image_index2.zfill(4) + '.jpg' # 得到第二張人臉的地址label = 0 # 標簽為0表示不同身份else:raise Exception('Line error: %s.' % line)test_pair_list.append((image_name1, image_name2, label)) # 存入list中line = pairs_file_buf.readline().strip() # 讀取下一行return test_pair_listdef extract_feature(self, model, data_loader):'''提取所有測試集人臉特征:param model: 要測試的模型:param data_loader: 數據集:return: 字典形式 圖片名字(相對路徑):提取的特征'''model.eval() # 測試模式,如果不寫,會對bn層有影響image_name2feature = {} # 字典with torch.no_grad(): # 不進行梯度下降for batch_idx, (images, filenames) in enumerate(data_loader): # 讀取數據images = images.to(self.device) # 圖片features = model(images) # 特征features = F.normalize(features) # 特征歸一化 用于求余弦相似度features = features.cpu().numpy()for filename, feature in zip(filenames, features):image_name2feature[filename] = feature # 存入字典return image_name2featuredef test(self, model):'''測試程序:param model: 測試模型:return: 準確率和方差'''image_name2feature = self.extract_feature(model, self.data_loader)mean, std = self.test_one_model(self.pair_list, image_name2feature)return mean, stddef test_one_model(self, test_pair_list, image_name2feature, is_normalize = True):'''獲取模型的LFW測試準確率:param test_pair_list: 測試pair.txt文件:param image_name2feature: 存入特征的字典:param is_normalize: 是否已經歸一化,如果已經歸一化,就True,無需在進行歸一化。:return: 準確率、方差'''subsets_score_list = np.zeros((10, 600), dtype = np.float32) # 余弦相似度subsets_label_list = np.zeros((10, 600), dtype = np.int8) # 標簽for index, cur_pair in enumerate(test_pair_list): # 圖1 圖2 標簽cur_subset = index // 600 # 對600取整 一共6000對,分成10組cur_id = index % 600 # 對600取余image_name1 = cur_pair[0] # 圖1image_name2 = cur_pair[1] # 圖2label = cur_pair[2] # 標簽 0表示不同 1表示相同subsets_label_list[cur_subset][cur_id] = label # 存入標簽feat1 = image_name2feature[image_name1] # 圖1的特征feat2 = image_name2feature[image_name2] # 圖2的特征if not is_normalize: # 歸一化feat1 = feat1 / np.linalg.norm(feat1)feat2 = feat2 / np.linalg.norm(feat2)cur_score = np.dot(feat1, feat2) # 余弦相似度(即得分)subsets_score_list[cur_subset][cur_id] = cur_score # 存入余弦相似度subset_train = np.array([True] * 10)accu_list = []for subset_idx in range(10): # 一組一組算test_score_list = subsets_score_list[subset_idx] # 一組的余弦相似度test_label_list = subsets_label_list[subset_idx] # 一組的標簽subset_train[subset_idx] = False # 改為Falsetrain_score_list = subsets_score_list[subset_train].flatten() # 其余9組的余弦相似度train_label_list = subsets_label_list[subset_train].flatten() # 其余9組的標簽subset_train[subset_idx] = True # 改為Truebest_thres = self.getThreshold(train_score_list, train_label_list) # 用其余9組得出閾值positive_score_list = test_score_list[test_label_list == 1] # 將標簽為1的放入一個余弦相似度列表negtive_score_list = test_score_list[test_label_list == 0] # 將標簽為0的放入一個余弦相似度列表true_pos_pairs = np.sum(positive_score_list > best_thres) # 計算結果正確的數量true_neg_pairs = np.sum(negtive_score_list < best_thres) # 計算結果正確的數量accu_list.append((true_pos_pairs + true_neg_pairs) / 600) # 計算每組最后結果mean = np.mean(accu_list) # 取平均值std = np.std(accu_list, ddof=1) / np.sqrt(10) # 方差return mean, stddef getThreshold(self, score_list, label_list, num_thresholds=1000):'''得到最佳閾值:param score_list: 余弦相似度list:param label_list: 標簽list:param num_thresholds: 只需n次得到最優roc:return:'''pos_score_list = score_list[label_list == 1] # 將標簽為1的放入一個余弦相似度列表neg_score_list = score_list[label_list == 0] # 將標簽為0的放入一個余弦相似度列表pos_pair_nums = pos_score_list.size # 數量neg_pair_nums = neg_score_list.size # 數量score_max = np.max(score_list) # 最大余弦相似度score_min = np.min(score_list) # 最小余弦相似度score_span = score_max - score_min # 相減step = score_span / num_thresholds # 切分成num_thresholds個threshold_list = score_min + step * np.array(range(1, num_thresholds + 1)) # 得到一個切分后的閾值listfpr_list = []tpr_list = []for threshold in threshold_list: # 一個個試fpr = np.sum(neg_score_list > threshold) / neg_pair_nums # 錯誤/負樣本數tpr = np.sum(pos_score_list > threshold) /pos_pair_nums # 正確/正樣本數fpr_list.append(fpr)tpr_list.append(tpr)fpr = np.array(fpr_list)tpr = np.array(tpr_list)best_index = np.argmax(tpr-fpr) # 取最大值best_thres = threshold_list[best_index]return best_thres # 得到最大閾值4.5 主程序test_lfw.py
import os from prettytable import PrettyTable from torch.utils.data import DataLoader from test_dataset import CommonTestDataset from MobileFaceNets import MobileFaceNet import config from lfw_evaluator import LFWEvaluatorif __name__ == '__main__':# 參數預加載pairs_file_path = config.PAIRS_FILE_PATHcropped_face_folder = config.CROPPED_FACE_FOLDERimage_list_file_path = config.IMAGE_LIST_FILE_PATHfeat_dim = config.FEAT_DIMout_h = config.OUT_Hout_w = config.OUT_Wmodel_path = config.MODEL_PATHdevice = config.DEVICE# 設置gpuos.environ["CUDA_VISIBLE_DEVICES"] = "0, 1"# 測試數據集加載data_loader = DataLoader(CommonTestDataset(cropped_face_folder, image_list_file_path),batch_size=1024, num_workers=16, shuffle=False)# 模型加載model = MobileFaceNet(feat_dim, out_h, out_w)model = model.load_model(model, model_path)# lfw評估類加載lfw_evaluator = LFWEvaluator(data_loader, pairs_file_path, device)# 評估mean, std = lfw_evaluator.test(model)# 顯示accu_list = [(os.path.basename(model_path), mean, std)]pretty_tabel = PrettyTable(["model_name", "mean accuracy", "standard error"])for accu_item in accu_list:pretty_tabel.add_row(accu_item)print(pretty_tabel)五、測試結果
總結
以上是生活随笔為你收集整理的LFW人脸数据集测试协议及编程实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql 5.7 super_MySQ
- 下一篇: 如何确定VS编译器版本--_MSC_VE