专栏首页贾志刚-OpenCV学堂轻松学Pytorch – 年龄与性别预测

轻松学Pytorch – 年龄与性别预测

大家好,上周太忙,没有更新Pytorch轻松学系列文章,但是我还是会坚定的继续走下去的,所谓有始有终,这个系列我会一直坚持写下去,希望大家继续支持我,积极给我反馈,当然也感谢大家的信任与点赞支持。

本文主要是基于公开数据集,完成了一个人脸的年龄与性别预测网络模型,以及模型训练与导出使用、本篇主要讲述的知识点有以下:

  • 如何实现卷积神经网络的多任务不同输出
  • 如何同时实现分类跟回归预测
  • 基于人脸年龄与性别的公开数据的数据制作
  • 使用多任务网络实现推理预测

数据集

本文使用的数据集来自这里

https://susanqq.github.io/UTKFace/

我使用的是已经对齐跟剪切之后的人脸数据,超过2W多张的标注数据,标注信息如下:

[age]_[gender]_[race]_[date&time].jpg

文件名称格式就是每张图像的标注信息

  • Age表示年龄,范围在0~116岁之间
  • Gender表示性别,0表示男性,1表示女性
  • Race表示人种,

基于Pytorch的dataset超类,重新完成了我的自定义数据集,代码如下:

class AgeGenderDataset(Dataset):
     def __init__(self, root_dir):
         self.transform = transforms.Compose([transforms.ToTensor()])
         img_files = os.listdir(root_dir)
         nums_ = len(img_files)
         # age: 0 ~116, 0 :male, 1 :female
         self.ages = []
         self.genders = []
         self.images = []
         index = 0
         for file_name in img_files:
             age_gender_group = file_name.split("_")
             age_ = age_gender_group[0]
             gender_ = age_gender_group[1]
             self.genders.append(np.float32(gender_))
             self.ages.append(np.float32(age_)/max_age)
             self.images.append(os.path.join(root_dir, file_name))
             index += 1

     def __len__(self):
         return len(self.images)

     def num_of_samples(self):
         return len(self.images)

     def __getitem__(self, idx):
         if torch.is_tensor(idx):
             idx = idx.tolist()
             image_path = self.images[idx]
         else:
             image_path = self.images[idx]
         img = cv.imread(image_path)  # BGR order
         h, w, c = img.shape
         # rescale
         img = cv.resize(img, (64, 64))
         img = (np.float32(img) /255.0 - 0.5) / 0.5
         # H, W C to C, H, W
         img = img.transpose((2, 0, 1))
         sample = {'image': torch.from_numpy(img), 'age': self.ages[idx], 'gender': self.genders[idx]}
         return sample

网络模型结构

卷积层作为基础模型,在卷积层之后使用最大全局池化,完成降维操作,然后再此基础上分为两路的全链接层,分布预测年龄跟性别分类,模型结构详细信息如下:

MyMulitpleTaskNet(
  (cnn_layers): Sequential(
    (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (4): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (5): ReLU()
    (6): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (7): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (8): Conv2d(64, 96, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (9): ReLU()
    (10): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (11): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (12): Conv2d(96, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU()
    (14): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (15): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (16): Conv2d(128, 196, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (17): ReLU()
    (18): BatchNorm2d(196, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (19): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (global_max_pooling): AdaptiveMaxPool2d(output_size=(1, 1))
  (age_fc_layers): Sequential(
    (0): Linear(in_features=196, out_features=25, bias=True)
    (1): ReLU()
    (2): Linear(in_features=25, out_features=1, bias=True)
    (3): Sigmoid()
  )
  (gender_fc_layers): Sequential(
    (0): Linear(in_features=196, out_features=25, bias=True)
    (1): ReLU()
    (2): Linear(in_features=25, out_features=2, bias=True)
  )
)

其中年龄是一个数值结果,所以通过回归来预测,这里使用sigmoid,需要注意的是在制作数据集的时候需要把年龄归一化到0~1之间。性别是二分类预测,使用softmax,实现预测。损失函数选择,对于年龄预测使用MSE,对于分类预测使用了交叉熵损失,总的损失函数是二值之和、权重相同

模型训练

模型训练的输入图像格式为BGR、NCHW = Nx3x64x64、每个批次N=16个样本,代码实现如下:

if train_on_gpu:
     model.cuda()

 ds = AgeGenderDataset("D:/python/pytorch_tutorial/UTKFace/")
 num_train_samples = ds.num_of_samples()
 bs = 16
 dataloader = DataLoader(ds, batch_size=bs, shuffle=True)

 # 训练模型的次数
 num_epochs = 25
 # optimizer = torch.optim.SGD(model.parameters(), lr=0.001)
 optimizer = torch.optim.Adam(model.parameters(), lr=1e-2)
 model.train()

 # 损失函数
 mse_loss = torch.nn.MSELoss()
 cross_loss = torch.nn.CrossEntropyLoss()
 index = 0
 for epoch in  range(num_epochs):
     train_loss = 0.0
     for i_batch, sample_batched in enumerate(dataloader):
         images_batch, age_batch, gender_batch = \
             sample_batched['image'], sample_batched['age'], sample_batched['gender']
         if train_on_gpu:
             images_batch, age_batch, gender_batch = images_batch.cuda(), age_batch.cuda(), gender_batch.cuda()
         optimizer.zero_grad()

         # forward pass: compute predicted outputs by passing inputs to the model
         m_age_out_, m_gender_out_ = model(images_batch)
         age_batch = age_batch.view(-1, 1)
         gender_batch = gender_batch.long()

         # calculate the batch loss
         loss = mse_loss(m_age_out_, age_batch) + cross_loss(m_gender_out_, gender_batch)

         # backward pass: compute gradient of the loss with respect to model parameters
         loss.backward()

         # perform a single optimization step (parameter update)
         optimizer.step()

         # update training loss
         train_loss += loss.item()
         if index % 100 == 0:
             print('step: {} \tTraining Loss: {:.6f} '.format(index, loss.item()))
         index += 1

         # 计算平均损失
     train_loss = train_loss / num_train_samples

     # 显示训练集与验证集的损失函数
     print('Epoch: {} \tTraining Loss: {:.6f} '.format(epoch, train_loss))

 # save model
 model.eval()
 torch.save(model, 'age_gender_model.pt')

网络模型结构

基于训练模型,实现人脸年龄与性别预测,人脸检测模型使用OpenCV DNN模块,代码实现如下:

 1def video_landmark_demo():
 2    cnn_model = torch.load("./age_gender_model.pt")
 3    print(cnn_model)
 4    # capture = cv.VideoCapture(0)
 5    capture = cv.VideoCapture("D:/images/video/example_dsh.mp4")
 6
 7    # load tensorflow model
 8    net = cv.dnn.readNetFromTensorflow(model_bin, config=config_text)
 9    while True:
10        ret, frame = capture.read()
11        if ret is not True:
12            break
13        frame = cv.flip(frame, 1)
14        h, w, c = frame.shape
15        blobImage = cv.dnn.blobFromImage(frame, 1.0, (300, 300), (104.0, 177.0, 123.0), False, False);
16        net.setInput(blobImage)
17        cvOut = net.forward()
18        # 绘制检测矩形
19        for detection in cvOut[0,0,:,:]:
20            score = float(detection[2])
21            if score > 0.5:
22                left = detection[3]*w
23                top = detection[4]*h
24                right = detection[5]*w
25                bottom = detection[6]*h
26
27                # roi and detect landmark
28                roi = frame[np.int32(top):np.int32(bottom),np.int32(left):np.int32(right),:]
29                rw = right - left
30                rh = bottom - top
31                img = cv.resize(roi, (64, 64))
32                img = (np.float32(img) / 255.0 - 0.5) / 0.5
33                img = img.transpose((2, 0, 1))
34                x_input = torch.from_numpy(img).view(1, 3, 64, 64)
35                age_, gender_ = cnn_model(x_input.cuda())
36                predict_gender = torch.max(gender_, 1)[1].cpu().detach().numpy()[0]
37                gender = "Male"
38                if predict_gender == 1:
39                    gender = "Female"
40                predict_age = age_.cpu().detach().numpy()*116.0
41                print(predict_gender, predict_age)
42
43                # 绘制
44                cv.putText(frame, ("gender: %s, age:%d"%(gender, int(predict_age[0][0]))), (int(left), int(top)-15), cv.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255), 1)
45                cv.rectangle(frame, (int(left), int(top)), (int(right), int(bottom)), (255, 0, 0), thickness=2)
46                # cv.putText(frame, "score:%.2f"%score, (int(left), int(top)), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
47                c = cv.waitKey(10)
48                if c == 27:
49                    break
50                cv.imshow("face detection + landmark", frame)
51
52    cv.waitKey(0)
53    cv.destroyAllWindows()
54
55
56if __name__ == "__main__":
57    video_landmark_demo()

运行结果如下:

本文分享自微信公众号 - OpenCV学堂(CVSCHOOL),作者:gloomyfish

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深度学习AI美颜系列----AI美发算法(美妆相机/天天P图染发特效)

    给照片或者视频中的人物头发换颜色,这个技术已经在手机app诸如天天P图,美图秀秀等应用中使用,并获得了不少用户的青睐。如何给照片或者视频中的人物头发换发色?换发...

    OpenCV学堂
  • Windows下TensorFlow安装与代码测试

    Windows下TensorFlow安装与代码测试 一:Tensorflow介绍 TensorFlow是谷歌的深度学习应用开发框架,其思想基于数据流图与节点图实...

    OpenCV学堂
  • 轻松学Pytorch-自定义数据集制作与使用

    大家好,这是轻松学Pytorch系列的第六篇分享,本篇你将学会如何从头开始制作自己的数据集,并通过DataLoader实现加载。本文以人脸Landmard五点的...

    OpenCV学堂
  • #Python3中字符串的比较

    20.字符串的比较 从第一个字符开始比较谁的ASCII值谁就大 如果前面相同 则比较后一位直到比较出谁大 如果都相同 则相等

    py3study
  • 全局变量的三种声明方法

    显示true FWK未定义,说明隐式声明的全局变量可以被delete,与global对象的parseInt、escape、parseFloat等等类似,可直接被...

    meteoric
  • The `useMongoClient` option is no longer necessary in mongoose 5.x, please remove it.

    mongoose升级到5.X版本就不支持useMongoClient 这种写法了,而是应该写成useNewUrlParser。

    acoolgiser
  • 【SPA大赛】菜鸟在数据挖掘中的体验

    本文从四个方面分享了参加腾讯社交广告比赛的一些想法以及从中吸取的经验和教训。

    谭学仕
  • 【Python3】f-string——s

    f-string,亦称为格式化字符串常量(formatted string literals),是Python3.6新引入的一种字符串格式化方法,主要...

    py3study
  • 3.1 Contour绘制

    首先一起学习利用百度的开源项目绘制contour,百度搜索“echarts heatmap”,找到热力图的一个案例(http://echarts.baidu.c...

    周星星9527
  • 【必读】腾讯企点销售智推上手指南

    ? 企点营销-销售智推 社交场景的销售客户管理工具 筛选消费者需要的精准信息及内容为销售赋能 01 使用前授权准备 ①开通小程序 企业必须申请一个已认证的小...

    腾讯企点

扫码关注云+社区

领取腾讯云代金券