人脸年龄识别
人脸识别背景
现今生活中主要采用号码、磁卡、口令等识别方法,这些都存在着易丢失、易伪造、易遗忘等诸多问题。随着技术的不断发展,传统的身份识别方法已经受到越来越多的挑战,可靠性大为降低,势必出现新的信息识别和检测技术。人们逐渐的把目光转向了生物体征,这些都是人类遗传的DNA所决定的,并且每个人都拥有自己独一无二的生物体征。生物识别技术大致可以分为两大类,一类是物体体征,例如指纹、虹膜、人脸、体味等。另一类是行为体征,例如书写、步频、惯性动作等,这些都可以通过现在的计算机图像处理技术进行识别。与其他人类的生理特征相比,人脸存在易采集、非接触、静态等优点,比较容易被大众所接受。 据调查,人与人接触时,90%的人是通过观察一个人的脸部特征来了解对方,获取对方的基本信息,这就是所谓的第一印象。虽然外部条件例如年龄、表情、光照等发生巨大变化,是一个人的面部特征发生巨大变化,但是人类仍然可以识别,这一现象说明人的脸部存有大量特征信息,通过提出人脸部的特征信息,就可以判断一个人。
人脸识别研究概况
2014年是我国人脸识别技术的转折点,使人脸识别技术从理论走向了应用,2018年则是人脸识别技术全面应用的重要节点,"刷脸"时代正式到来。
目前,从我国人脸识别技术应用来看,主要集中在三大领域:考勤门禁、安防以及金融。从具体应用来看,主要包含了公共安全领域的刑侦追逃、罪犯识别以及边防安全等;信息安全领域的政府职能领域的电子政务、户籍管理、社会福利和保险;商业企业领域的电子商务、电子货币和支付、考勤、市场营销;场所进出领域的军事机要部门、金融机构的门禁控制和进出管理等。
人脸识别算法分类
整体来看,人脸识别算法分为三类:
- 人脸检测(Face Detection) ,重点处理人脸定位问题,定位图像中的人脸;
- 人脸对齐(Face Alignment) ,重点处理人脸变换问题,得到同一角度与姿态的人脸;
- 人脸特征表征(Feature Representation) ,重点对人脸细节进行识别和处理,比如本文的人脸年龄检测;
人脸识别的意义
人脸识别的目标是确定一张人脸图像的身份,这是机器学习和模式识别中的分类问题。它不但可以用在身份识别和身份验证中,帮助找寻失踪人口、追踪嫌疑人、智能交互身份识别等场景;而且在证件查询、出入考勤查验、身份验证解锁、支付等场景中,也有广泛的应用,并为我们的生活带来极大的便利。
一张有趣的人脸年龄识别结果图
究竟是什么,让原本只有一岁之差的两个人被 AI 误会为「形同父子」?是 AI 的「良知泯灭」,还是人类的「自食其果」?
本文目的
通过训练模型推测人脸年龄
数据集
1到70岁人脸面部图像,数量不等
数据集下载地址: https://static.leiphone.com/face_age_dataset.zip.gz.
代码下载地址:https://www.leiphone.com/uploads/new/aihub/matchCode/zip/5e539045aa017.zip
dataloader.py
from PIL import Image
from torch.utils.data.dataset import Dataset
from torchvision import transforms
#定义人脸数据集类
class FADataset(Dataset):
def __init__(self, filelist, isTrain=False):
self.images = [item.strip().split(' ')[0] for item in open(filelist, 'r').readlines()]
self.labels = [int(item.strip().split(' ')[1]) for item in open(filelist, 'r').readlines()]
#对训练集进行水平翻转及规范化操作
if isTrain:
self.transform = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5]),
])
#对测试集不需要数据增强操作
else:
self.transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5]),
])
#返回数据集中图片的个数
def __len__(self):
return len(self.images)
#读取图片文件,返回其label
def __getitem__(self, index):
# image = Image.open(self.images[index])
image = Image.open(self.images[index]).convert('L')
img = self.transform(image)
label = self.labels[index]
return img, label
generate_train_val_file.py
import os
#定义训练集路径并列出文件列表
root = '../../data/train/'
A = os.listdir(root)
#为训练集图片加上label
for item in A:
if len(item) != 3:
continue
flag = 0
for i in range(len(item)):
if item[i] != '0':
flag = i
break
label = int(item[flag:]) - 1
B = os.listdir(root + item)
for idx in B:
with open('data/train.txt', 'a+') as f:
f.write(root + item + '/' + idx + ' ' + str(label) +'\n')
img_aug.py
# coding:utf-8
import numpy as np
import cv2
import os
import random
from tqdm import tqdm
#进行图像处理,数据增强
def gamma(img, gamma):
image_gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
image_gamma = np.uint8(np.power((np.array(img) / 255.0), gamma) * 255.0)
cv2.normalize(image_gamma, image_gamma, 0, 255, cv2.NORM_MINMAX)
cv2.convertScaleAbs(image_gamma, image_gamma)
return image_gamma
def resize(img, size):
img_resize = cv2.resize(img, size)
img_resize = cv2.resize(img_resize, (200, 200))
return img_resize
def img_process(img):
if random.random() > 0.5:
img = gamma(img, random.uniform(0.5, 1.9))
if random.random() > 0.5:
img = resize(img, (100, 100))
if random.random() > 0.5:
img = resize(img, (400, 400))
if random.random() > 0.5:
img = img[:, ::-1, :]
return img
if __name__ == '__main__':
root = 'data/train/'
save = 'data/aug/'
if not os.path.exists(save):
os.makedirs(save)
A = os.listdir(root)
for item in tqdm(A):
if len(item) > 3:
continue
B = os.listdir(root + item)
if len(B) > 200:
continue
if not os.path.exists(save + item):
os.makedirs(save + item)
flag = 0
for i in range(len(item)):
if item[i] != '0':
flag = i
break
label = int(item[flag:]) - 1
for idx in range(len(B)):
img = cv2.imread(root + item + '/' + B[idx])
if len(B) < 100:
img_a = img_process(img)
cv2.imwrite(save + item + '/' + B[idx].split('.')[0] + 'a.jpg', img_a)
with open('data/aug.txt', 'a+') as f:
f.write(save + item + '/' + B[idx].split('.')[0] + 'a.jpg ' + str(label) + '\n')
img_p = img_process(img)
cv2.imwrite(save + item + '/' + B[idx].split('.')[0] + 'p.jpg', img_p)
with open('data/aug.txt', 'a+') as f:
f.write(save + item + '/' + B[idx].split('.')[0] + 'p.jpg ' + str(label) + '\n')
# cv2.imshow('1', img)
# cv2.imshow('3', img)
# cv2.imshow('2', cv2.imread(root + item + '/' + B[idx]))
# cv2.waitKey(0)
net.py
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision.models import vgg19_bn
#使用VGG19模型以及预训练
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
model = vgg19_bn(pretrained=True)
# model = vgg19_bn(pretrained=False)
weight = model.features[0].weight
model.features[0] = nn.Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
model.features[0].weight = torch.nn.Parameter(weight[:, :1, :, :])
self.features = nn.Sequential(*list(model.features.children())[:-4])
self.fc = nn.Sequential(
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(512, 70),
)
def forward(self, input):
x = self.features(input)
x = nn.AdaptiveAvgPool2d(1)(x)
x = x.view(x.size(0), -1)
output = self.fc(x)
return output
train.py
# -*- coding: utf-8 -*
import torch
import torch.nn as nn
from torch.optim import Adam, SGD, lr_scheduler
import torch.nn.functional as F
from net import Net
from dataloader import FADataset
#使用定义好的网络进行训练
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
net = Net()
# net.load_state_dict(torch.load("15.pkl"))
net.to(device)
loss_fn = nn.CrossEntropyLoss()
optimizer = Adam(net.parameters(), lr=1e-3, betas=(0.9, 0.999))
# optimizer = SGD(net.parameters(), lr=1e-4)
scheduler = lr_scheduler.MultiStepLR(optimizer, [8, 13], gamma=0.1, last_epoch=-1)
# scheduler = lr_scheduler.MultiStepLR(optimizer, [13, 19, 27, 33], gamma=0.1, last_epoch=-1)
best_accuracy = 0
best_loss = 10
dataset_train = FADataset('data/train.txt', True)
# dataset_train = FADataset('data/new_aug.txt', True)
train_loader = torch.utils.data.DataLoader(dataset_train, batch_size=128, shuffle=True)
EPOCHS = 15
# EPOCHS = 39
for epoch in range(EPOCHS):
train_loss = 0.0
net.train()
for i, (imgs, target) in enumerate(train_loader):
imgs = imgs.to(device)
target = target.to(device)
optimizer.zero_grad()
outputs = net(imgs)
loss = loss_fn(outputs, target)
loss.backward()
optimizer.step()
train_loss += loss.item()
train_loss /= len(train_loader)
for param_group in optimizer.param_groups:
print('lr: %s' % param_group['lr'])
print('Epoch: {}/{} - Average loss: {:.4f}'.format(epoch + 1, EPOCHS, train_loss))
torch.save(net.state_dict(), 'checkpoints/' + str(epoch + 1) + '.pkl')
scheduler.step(epoch + 1)
test.py
import argparse
import os
from PIL import Image
import torch
import torch.nn.functional as F
from torchvision import transforms
from net import Net
from tqdm import tqdm
# parser = argparse.ArgumentParser()
# parser.add_argument("epoch", type=str, help="test epoch")
# args = parser.parse_args()
#调用测试集进行模型测试
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
Transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5]), ])
net = Net().to(device)
epoch = 32
net.load_state_dict(torch.load('./checkpoints/' + epoch + '.pkl'))
net.eval()
root = 'data/test/'
for item in tqdm(os.listdir(root)):
img = Image.open(root + item).convert('L')
x_data = Transform(img).float().unsqueeze(0).to(device)
outputs = net(x_data)
prediction = torch.max(F.softmax(outputs, dim=1), dim=1)[1].cpu().numpy()[0]
age = prediction + 1
if len(str(age)) == 1:
result = '00' + str(age)
else:
result = '0' + str(age)
with open('submission.csv', 'a+') as f:
f.write(item.split('.')[0] + ',' + result + '\n')