FaceNet人脸识别(二)

共 9503字,需浏览 20分钟

 ·

2022-01-03 19:02

前面我们说到了FacNet的模型结构以及其损失函数,那么这一篇文章我们就来进行数据处理


 人脸ROI提取 

首先,数据集如下,被命名为XXX_XX.jpg,下划线前面是指人的id,下划线后面是指这个人的第几张图片,这个数据集名为CASIA-FaceV5,是亚洲人脸数据集,共有500人,每个人5张图片。不同图片的光照、角度、配饰(眼镜),都有些许不同。


接下来,我们需要进行数据处理,由于我们的任务是进行人脸识别,所以数据处理的第一步就是将人脸ROI区域提取出来。首先新建一个utils.py文件,写入如下代码:

import numpy as npfrom PIL import Imageimport cv2face_detection = cv2.CascadeClassifier('models/haarcascade_frontalface_default.xml')
def get_face_rect(img): img1 = None face_rect = face_detection.detectMultiScale(img,1.1,5) for rect in face_rect: x,y,w,h = rect # cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),1) img1 = img[y:y+h,x:x+w] return img1

然后新建一个change_file.py文件,写入如下代码,用来将数据集中的人脸提取出来,然后分别保存到不同的文件夹中。

import os
import cv2
from utils import get_face_rect
# 数据文件夹,保存文件夹 base_path = r'C:\dataset\all_faces'save=path = r'C:\dataset\face_datasets'
file_names = [os.path.join(base_path ,p ) for p in os.listdir(base_path)]
for file_name in file_names: # print(file_name) floder_name = file_name.split('\\')[-1].split('_')[0] name = file_name.split('\\')[-1].split('_')[-1] try : if not os.path.isdir(os.path.join(save,floder_name)): os.mkdir(os.path.join(save,floder_name)) img = cv2.imread(file_name) print(os.path.join(save,'%s\%s'%(floder_name,name))) face_roi = get_face_rect(img)
cv2.imwrite(os.path.join(save,'%s\%s'%(floder_name,name)),face_roi) except Exception as e: print(e) continue


运行程序,可以看到保存文件夹中多出了很多个目录,存放了不同的人脸,如下所示:



Json 文件生成

下一步,我们进行数据分割,具体流程如下:

  • 读取数据保存路径的所有文件

  • 随机打乱

  • 以9:1分割

  • 根据文件名划分 id与路径并保存成json,如下所示(红圈部分为id后面的数组为该id的所有人脸图片):


代码如下,新建一个gen_json.py文件,写入如下代码,运行程序,train.json 以及 test.json就生成成啦。

import jsonimport osimport randomfrom  tqdm import tqdm
def write_json(list,name='train'): data_dict ={} for floder in tqdm(list): image_paths = [os.path.join(floder,p) for p in os.listdir(floder)] key_ = floder.split('\\')[-1] for image_path in image_paths: if key_ in data_dict.keys(): data_dict[key_].append(image_path) else: data_dict[key_]=[image_path] json_ = json.dumps(data_dict) with open('%s.json'%name, 'w', encoding='utf8') as f: f.writelines(json_)
if __name__ == '__main__':
dataset_paths = r'D:\data\face_datasets' floder_list = [os.path.join(dataset_paths,p) for p in os.listdir(dataset_paths)] random.shuffle(floder_list)
split_ =0.9 train_len = int(len(floder_list)*split_) print(train_len) train_list = floder_list[:train_len] test_list = floder_list[train_len:] write_json(train_list)    write_json(test_list,name='test')


 工具函数编写

下一步,我们进行数据处理,因为目前的数据还只是路径,是无法输入到我们的模型中进行训练的,所以我们需要读取图片,并且通过各种变换,转换成可供训练的数据,首先在utils.py中添加如下代码,用来获得随机数以及不失真的图像缩放:

def rand(a=0, b=1):    return np.random.rand()*(b-a) + a    def letterbox_image(self, image, size):    if self.input_shape[-1] == 1:        image = image.convert("RGB")    iw, ih = image.size    w, h = size    scale = min(w / iw, h / ih)    nw = int(iw * scale)    nh = int(ih * scale)
image = image.resize((nw, nh), Image.BICUBIC) new_image = Image.new('RGB', size, (128, 128, 128)) new_image.paste(image, ((w - nw) // 2, (h - nh) // 2)) if self.input_shape[-1] == 1: new_image = new_image.convert("L")    return new_image


数据加载类 

接着,新建一个load_data.py用来编写加载数据的类,首先导入依赖库:

from  PIL import Imagefrom utils import randimport tensorflow.keras as kimport  cv2import numpy as npimport mathfrom tensorflow.keras import utils as np_utilsimport jsonimport glob


然后,新建一个类叫Face_Dataset,并初始化数据,这里我们必须完成 __len__ 以及 __getitem__ 这两个函数,前者是用来判断总共需要多少次读取才能把数据完全拿到,公式为

读取次数 = 总图片数/batch_size ,而后者是用来获取每一个批次的可供训练的数据,

class Face_Dataset(k.utils.Sequence):    def __init__(self, image_path, batch_size, train=True, input_size=(160, 160, 3)):        self.image_path = image_path        # 一共有多少张图片        self.num_len = len(glob.glob(self.image_path+'/*/*.jpg'))        # 批次大小        self.batch_size = batch_size        # 训练还是测试        self.train = train        # json 数据        self.json_data = self.load_data_path()        # json 的 key        self.json_key = list(self.json_data.keys())        #  输入图片的尺寸        self.image_height, self.image_width, self.channel = input_size  def __len__(self):      return math.ceil(self.num_len / float(self.batch_size))  def __getitem__(self, item):      pass


接着,我们再写入load_data_path函数,用来读取训练,或者测试时的json数据,并返回。

  def load_data_path(self):      if self.train:        with open('train.json', 'r', encoding='utf8') as f:            json_ = json.loads(f.read())        return json_      else:        with open('test.json', 'r', encoding='utf8') as f:            json_ = json.loads(f.read())        return json_


然后,为了使模型更具鲁棒性,所以我们再编写一个数据增强的函数,用来对人脸数据进行随机裁剪、随机翻转、随机缩放等。

# 随机增强数据  def get_random_data(self, image, input_shape, jitter=.1, hue=.1, sat=1.3, val=1.3, flip_signal=True):    '''    :param image: PIL Image    :param input_shape:  输入尺寸    :param jitter: 裁剪    :param hue: h    :param sat: s    :param val: v    :param flip_signal: 翻转    :return:    '''    image = image.convert("RGB")
h, w = input_shape rand_jit1 = rand(1 - jitter, 1 + jitter) rand_jit2 = rand(1 - jitter, 1 + jitter) new_ar = w / h * rand_jit1 / rand_jit2
# 随机裁剪图片 scale = rand(0.9, 1.1) if new_ar < 1: nh = int(scale * h) nw = int(nh * new_ar) else: nw = int(scale * w) nh = int(nw / new_ar) image = image.resize((nw, nh), Image.BICUBIC)
# 随机翻转图片 flip = rand() < .5 if flip and flip_signal: image = image.transpose(Image.FLIP_LEFT_RIGHT)
dx = int(rand(0, w - nw)) dy = int(rand(0, h - nh)) new_image = Image.new('RGB', (w, h), (128, 128, 128))
new_image.paste(image, (dx, dy)) image = new_image
# 随机 rotate = rand() < .5 if rotate: angle = np.random.randint(-10, 10) a, b = w / 2, h / 2 M = cv2.getRotationMatrix2D((a, b), angle, 1) image = cv2.warpAffine(np.array(image), M, (w, h), borderValue=[128, 128, 128]) image_data = image
# 如果是单通道图片 if self.channel == 1:        image_data = Image.fromarray(np.uint8(image)).convert("L")    return image_data


最后就是完成__getitem__函数中的内容啦,我们先使用numpy新创建两个全0的数组,维度分别为(batch_size,3,h,w,c)以及(batch_size,3),用来存放图片数据以及标签数据。

  def __getitem__(self, item):    images = np.zeros((self.batch_size, 3self.image_height, self.image_width, self.channel))    labels = np.zeros((self.batch_size, 3))


然后使用循环读取一个批次的数据并返回,具体流程如下:

  • 在读取的json中随机取得第一个人,并判断这个人拥有的图片数是否大于2,否则重取

  • 在取得的第一个人的图片中随机获取2张图片分别进行图像处理以及标签处理

  • 在取得的json中随机取得第二个人,并判断此人与第一个人是否是同一个人,是则重取

  • 在取得的第二个人的图片中随机获取1张图片并进行图像处理以及标签处理

  • 组合数据与标签

  • 返回

    # 循环获取一个批次的数据    for i in range(self.batch_size):        # 随机在json中获取一个人        c = np.random.choice(self.json_key, 1)        select_path = self.json_data[c[0]]            # 当获取人的图片数量小于2 则重新获取        while len(select_path) < 2:            c = np.random.choice(self.json_key, 1)            select_path = self.json_data[c[0]]                # 在随机获取的人 的图片中 随机取得两张        image_index = np.random.choice(select_path, 2)                # 第一张图片        image1 = Image.open(image_index[0])        # 数据增强        image1 = self.get_random_data(image1, [self.image_height, self.image_width])        image1 = np.asarray(image1).astype(np.float64) / 255.        # 获取当前人的标签        label = self.json_key.index(c[0])            images[i, 0, :, :, :] = image1        labels[i, 0] = label                # 第二张图片        image2 = Image.open(image_index[1])        image2 = self.get_random_data(image2, [self.image_height, self.image_width])        image2 = np.asarray(image2).astype(np.float64) / 255.        images[i, 1, :, :, :] = image2        labels[i, 1] = label                # 随机获取第二个人的图片路径        diff_c = np.random.choice(self.json_key, 1)                # 如果和第一个人 是同一人则重新取        while diff_c[0] == c[0]:            diff_c = np.random.choice(self.json_key, 1)                # 随机取得不同人的一张图片        diff_select_path = self.json_data[diff_c[0]]        diff_c_image_path = np.random.choice(diff_select_path, 1)                # 图片读取        diff_image = Image.open(diff_c_image_path[0])        diff_image = self.get_random_data(diff_image, [self.image_height, self.image_width])        diff_image = np.asarray(diff_image).astype(np.float64) / 255.        diff_label = self.json_key.index(diff_c[0])        images[i, 2, :, :, :] = diff_image        labels[i, 2] = diff_label        # 组合3张图片    images1 = np.array(images)[:, 0, :, :, :]    images2 = np.array(images)[:, 1, :, :, :]    images3 = np.array(images)[:, 2, :, :, :]    images = np.concatenate([images1, images2, images3], 0)        # 组合3个标签    labels1 = np.array(labels)[:, 0]    labels2 = np.array(labels)[:, 1]    labels3 = np.array(labels)[:, 2]    labels = np.concatenate([labels1, labels2, labels3], 0)    # 独热编码    labels = np_utils.to_categorical(np.array(labels), num_classes=len(self.json_key))    return images, {'Embedding': np.zeros_like(labels), 'Softmax': labels}


 测试 

这样一个数据读取的类就完成了,我们可以使用如下的方式进行验证,看看返回的数据是否是我们所需的。

image_dir = r'E:\DataSets\face_datasets'batch_size=1# 实例化数据dataset=Face_Dataset(image_dir,batch_size)
# 获得第一个batch_size 的数据image,dict = dataset.__getitem__(1)embb=dict['Embedding']label = dict['Softmax']
# 风别取得3张图片的索引for i in range(3): image_ = np.array(image[i]*255.,dtype='uint8') cv2.imshow('s%s'%i,image_) print(label[i].argmax())cv2.waitKey(0)


运行结果如下,我们可以看到第一第二张图片很明显就是同一个人,所以他们的索引是一致的,第三张图片是不同人,所以索引是不一样的。


以上就是本次推文的全部内容了,下一章将为大家带来模型的搭建以及训练,喜欢的同学们可以关注一波噢!

浏览 63
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报