目录

  • 一、概述
    • 1.1 多任务人脸属性分析
    • 1.2 本文任务
  • 二、环境配置
    • 2.1 安装PaddlePaddle
    • 2.2 安装PaddleClas
  • 三、算法模型开发
    • 3.1 算法原理
    • 3.2 数据集准备
      • 3.2.1 使用labelme标注原始数据
      • 3.2.2 数据集格式转化
    • 3.3 训练和评估
  • 四、部署
    • 4.1 静态图导出
    • 4.2 paddle serving高性能部署
    • 4.3 Python客户端请求
  • 五、小结
  • 参考文献

一、概述

1.1 多任务人脸属性分析

多任务人脸分析在多媒体、社交网络、计算机视觉等领域有着非常广泛的应用,不同于我们所熟知的“人脸检测”、“人脸验证”、“人脸比对”等技术,多任务人脸分析可以理解为:给定一张肖像图,算法自动给出对应的属性分析值,例如“是否戴了口罩”、“是否戴了墨镜”、“人脸是否模糊”、“性别”、“年龄”、“肤色”、“表情“等等。这里不同属性的预测代表不同的子任务,因此,多任务人脸分析是一个典型的多任务学习任务。

多任务学习可以简单理解为模型一次性可以同时处理两个以上的任务,而传统的模型通常只能解决一个特定的任务(例如性别识别、表情识别等)。进入Deep Learning时代,尤其是在CV相关的很多任务上,特征是可以共享的,此处“共享”可以理解为各个子任务上的特征存在一定的复用性,这种多任务训练出来的特征本身考虑了多任务之间的相关性,可以有效的去除各个子任务之间的特征冗余。也就是说通过共享网络设计,可以训练出来最精炼的特征,给多个子任务提取鉴别特征。

使用多任务学习模型可以大大减少系统执行流程的复杂度,使得原本需要串联的多个模型只需要在统一的模型下进行推理即可完成。

1.2 本文任务

本文基于飞桨PaddleClas算法套件,全流程记录多任务人脸属性分析的数据预处理、模型训练、评估、测试、部署等步骤,能够按照自己的定制化需求完成人脸相关属性的训练、推理和部署一条龙任务,能够打造出真正高性能的多任务人脸属性分析系统。同时,本文相关内容也可以无缝迁移到其它类似的多任务属性分析系统开发中。

由于需要使用英伟达GPU进行训练和推理,因此需要提前GPU服务器上装好配套的GPU显卡驱动、CUDA、CUDNN、TensorRT。具体安装教程可以请参考我的另一篇博客。本文服务器上开发环境如下:

  • 操作系统:ubuntu18.04
  • 深度学习框架:PaddlePaddle 2.3
  • CUDA:10.2
  • CUDNN:7.6.5
  • TensorRT:6.0.1.8

二、环境配置

本文核心算法使用PaddlePaddle实现,因此首先需要安装PaddlePaddle。

2.1 安装PaddlePaddle

参考官网进行安装。本文系统为ubuntu(linux),cuda为10.2,使用Paddle2.3版本,对应的pip安装命令如下:

python -m pip install paddlepaddle-gpu==2.3.1 -i https://mirror.baidu.com/pypi/simple

2.2 安装PaddleClas

PaddleClas是PaddlePaddle框架配套的开源图像分类算法库,涵盖了众多开箱即用的图像分类算法,只需要做简单的配置就可以上手训练和推理,使用非常方便,本文将使用PaddleClas来训练多任务人脸属性分析算法。

为了加速下载,可以选择我的gitee镜像源:

git clone https://gitee.com/binghai228/PaddleClas.git

然后进行安装:

cd PaddleClas
sudo python setup.py install

在安装时可能会有个别的依赖库因为网络原因下载不下来,可以使用百度镜像源加速安装,例如:

pip install opencv-python==4.4.0.46 -i https://mirror.baidu.com/pypi/simple

另外在安装时可能会出现下面的错误:

error: importlib-metadata 4.8.3 is installed but importlib-metadata<4.3;

解决方法:

pip install flake8

三、算法模型开发

3.1 算法原理

本文使用PaddleClas提供的超轻量图像分类方案(PULC,Practical Ultra Lightweight image Classification)来开发和完成多任务人脸属性识别模型。从整个架构上看,它的基本原理跟一般的图像分类应用差别不大,都是先用基于卷积神经网络的backbone网络提取共性特征,然后在此基础上根据任务数提取对应的任务独有特征,然后使用多标签损失函数一起迭代计算。

这里值得说明的是PULC使用了轻量级PPLCNet作为backbone(也可以替换成其他模型),然后级联多标签损失函数来完成多任务分类。

在计算机视觉领域中,骨干网络的好坏直接影响到整个视觉任务的结果。近年来,有很多轻量级的骨干网络问世,尤其最近两年,各种 NAS 搜索出的网络层出不穷,这些网络要么主打 FLOPs 或者 Params 上的优势,要么主打 ARM 设备上的推理速度的优势,很少有网络专门针对 Intel CPU 做特定的优化,导致这些网络在 Intel CPU 端的推理速度并不是很完美。基于此,针对 Intel CPU 设备以及其加速库 MKLDNN ,飞桨团队设计了特定的骨干网络 PP-LCNet,比起其他的轻量级的 SOTA 模型,该骨干网络可以在不增加推理时间的情况下,进一步提升模型的性能,最终大幅度超越现有的 SOTA 模型。

PPLCNet网络结构整体如下图所示:

为了取得最佳的推理速度和推理精度,PPLCNet在四个方面做了改进:

  • 更好的激活函数
    自从卷积神经网络使用了 ReLU 激活函数后,网络性能得到了大幅度提升,近些年 ReLU 激活函数的变体也相继出现,如 Leaky-ReLU、P-ReLU、ELU 等。2017 年,谷歌大脑团队通过搜索的方式得到了 swish 激活函数,该激活函数在轻量级网络上表现优异,在 2019 年,MobileNetV3 的作者将该激活函数进一步优化为 h-swish,该激活函数去除了指数运算,速度更快,网络精度几乎不受影响,在轻量级网络上表现尤其优异。所以在 PP-LCNet 中,使用了该激活函数。
  • 合适的位置添加 SE 模块
    SE 模块是 SENet 提出的一种通道注意力机制,可以有效提升模型的精度。但是该模块同样会带来较大的延时,如何平衡精度和速度是一个难题。虽然在 MobileNetV3 等基于 NAS 搜索的网络中对 SE 模块的位置进行了搜索,但是并没有得出一般的结论。通过实验发现,SE 模块越靠近网络的尾部对模型精度的提升越大,因此,PP-LCNet 中的 SE 模块的位置选用了上面模型结构图中的方案。
  • 合适的位置添加更大的卷积核
    在 MixNet 的论文中,作者分析了卷积核大小对模型性能的影响,结论是在一定范围内大的卷积核可以提升模型的性能,但是超过这个范围会有损模型的性能,所以作者组合了一种 split-concat 范式的 MixConv,这种组合虽然可以提升模型的性能,但是不利于推理。飞桨研究员总结了一些更大的卷积核在不同位置的作用,类似 SE 模块的位置,更大的卷积核在网络的中后部作用更明显,与此同时,可以获得更快的推理速度。PP-LCNet 最终采取了这样的方案。
  • GAP 后使用更大的 1x1 卷积层
    在 GoogLeNet 之后,GAP(Global-Average-Pooling)后往往直接接分类层,但是在轻量级网络中,这样会导致 GAP 后提取的特征没有得到进一步的融合和加工。如果在此后使用一个更大的 1x1 卷积层(等同于 FC 层),GAP 后的特征便不会直接经过分类层,而是先进行了融合,并将融合的特征进行分类。这样可以在不影响模型推理速度的同时大大提升准确率。

3.2 数据集准备

3.2.1 使用labelme标注原始数据

对于多标签分类数据集我们可以使用labelme进行标注,具体安装方法请参考官网。

sudo pip3 install labelme

安装完成以后我们就可以开始进行标注了。

注意,由于我们这里是多标签标注,因此,为了方便我们创建标签,我们可以先用一个txt来把所有标签名写进去,然后再导入到labelme工具中,这样可以省去每次导入都要创建标签的麻烦,尤其是在标签比较多的时候。

先创建一个名为label.txt的标签文件,内容样式如下所示:

blur
mask
glass
hat
expression

每行表示一种类别。注意标签绝对不能用中文!!!

然后我们启动labelme,启动方式如下:

 labelme --flags label.txt

注意,当前路径下必须有label.txt文件才能执行成功。启动后效果如下:

可以看到右侧已经将label.txt中的标签自动导入到Flags列表中了。

接下来我们单击菜单栏File-Open Dir,打开指定的图片文件夹目录。然后我们关闭掉File-Dave With Image Data复选菜单,这样我们保存的标注文件就不会保存原始图像数据了(否则标注文件太大了)。到这里,我们设置就全部结束了。

具体标注只需要根据每张图片自行在右侧Flags窗口中打上标记就可以。如下图所示:

标注的文件自动以json文件保存,内容如下所示:

{"version": "4.6.0","flags": {"naked": false,"strange": false,"mask": false,"hair": false,"hat": false,"glass": false,"blur": false,"light": false,"hand": false,"scarf": false,"necklace": false,"earrings": false,"smoke": false,"uniform": false,"ps": false,"unrealperson": true},"shapes": [],"imagePath": "1.jpg","imageData": null,"imageHeight": 1717,"imageWidth": 1280
}

其中flag字段中的每一项就代码标注的每一个属性。imagePath字段表示当前标注的图片名称。

标注完后所有的json标注文件都存放在每张图片对应的同目录下,其名称与图片名称相同。如下图所示:

3.2.2 数据集格式转化

数据集标注完以后我们需要对其进行转换,转换成适合PaddleClas处理的数据集形式。
数据集基本形式如下

dataset
├── images1
│   ├── 000001.jpg
│   ├── 000002.jpg
...
├── images2
│   ├── 080001.jpg
│   ├── 080002.jpg
...
├── train_list.txt
├── val_list.txt
├── test_list.txt

其中train_list.txt、val_list.txt、test_list.txt分别表示训练、验证、测试文件列表,用于后续算法训练、验证和测试。一个典型的train_list.txt内容如下所示:

images1/080001.jpg   0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,0,1,1,0,0
images1/080002.jpg  0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,0,0,1,0,0
images2/080003.jpg  0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,1,1,0,1,0
images1/080004.jpg  0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,1,0,1,0

每一行表示一个样本,前面images1/080001.jpg表示图片路径,后面01序列串表示每个属性是否包含,例如对于戴口罩这个属性,约定如果这张图片包含口罩就为1,不包含就为0。各个属性之间用逗号分隔。图片路径和序列串之间用’\t’分隔符隔开。

下面的代码用于执行转换操作,对于我的任务来说共需要标注16个属性。因此,下面的转换代码需要各位读者结合自己的实际任务进行微改(注意,单独将名为good的文件夹放在了face_attribute_dataset目录下面,这个子文件夹用于存放没有任何属性的照片,这个子文件夹不需要标注,默认所有属性都赋值为0):

# 导入系统库
import cv2
import os
import json
import random# 导入自定义库
from tools import getFileList, writeFileLstdef main():'''主函数'''# 检索文件夹org_img_folder = 'face_attribute_dataset'# 训练集占比ratio = 0.9# 属性个数class_num = 16# 检索good文件夹图片imgList = getFileList(os.path.join(org_img_folder,'good'), [])print('本次执行检索到 ' + str(len(imgList)) + ' 个文件\n')# 循环处理每个文件fileList = []for imgFile in imgList:# 读取json文件print(imgFile)# 解析图像路径img = cv2.imread(imgFile, cv2.IMREAD_COLOR)if img is None:continue# 解析属性attList = [0] * class_num# 拼接写入的字符串img_path = imgFileimg_path = img_path.replace(org_img_folder + '\\', "")img_path = img_path.replace('\\', '/') + '\t'for i in range(len(attList)):img_path += str(attList[i]) + ','file_path = img_path[:-1] + '\n'fileList.append(file_path)# 检索json文件jsonList = getFileList(org_img_folder, [], ext='json')print('本次执行检索到 ' + str(len(jsonList)) + ' 个文件\n')# 循环处理每个文件for jsonFile in jsonList:# 读取json文件print(jsonFile)current_dir = os.path.dirname(jsonFile)  # 当前文件所在的目录# 解析图像路径jsondata = json.load(open(jsonFile))if not 'imagePath' in jsondata:continueimg_path = os.path.join(current_dir, jsondata['imagePath'])img = cv2.imread(img_path, cv2.IMREAD_COLOR)if img is None:continue# 解析属性flags = jsondata['flags']attList = [0] * len(flags)i = 0for key in flags:attList[i] = flags[key] + 0i += 1if sum(attList) == 0:continue# 拼接写入的字符串img_path = img_path.replace(org_img_folder + '\\', "")img_path = img_path.replace('\\', '/') + '\t'for i in range(len(attList)):img_path += str(attList[i]) + ','file_path = img_path[:-1] + '\n'fileList.append(file_path)# 拆分数据random.shuffle(fileList)train_num = int(ratio * len(fileList))train_list = fileList[0:train_num]val_list = fileList[train_num:]# 写入数据train_list_path = os.path.join(org_img_folder, 'train_list.txt')val_list_path = os.path.join(org_img_folder, 'val_list.txt')writeFileLst(train_list_path, train_list)writeFileLst(val_list_path, val_list)print('处理完毕')if __name__ == "__main__":'''程序入口'''main()

其中tools.py用于存放相关工具函数,完整内容如下:

import os
import base64# 循环读取图像
def getFileList(dir,Filelist, ext=None):"""获取文件夹及其子文件夹中文件列表输入 dir:文件夹根目录输入 ext: 扩展名返回: 文件路径列表"""newDir = dirif os.path.isfile(dir):if ext is None:Filelist.append(dir)else:if ext in dir[-4:]:Filelist.append(dir)elif os.path.isdir(dir):for s in os.listdir(dir):newDir=os.path.join(dir,s)getFileList(newDir, Filelist, ext)return Filelistdef cv2_to_base64(image):'''图像二进制数据转base64'''return base64.b64encode(image).decode('utf8')def writeFileLst(list_path,filelist):'''写入文件列表'''if os.path.exists(list_path):os.remove(list_path) with open(os.path.join(list_path), 'w') as f:for filename in filelist:        f.write(filename)  # 写入list.txt

到这里,整个数据集需要的train_list.txt和val_list.txt就制作完了。接下来就进入正式的模型训练和验证阶段了。

3.3 训练和评估

PaddleClas提供好了非常方便的训练接口,只需要按照官方文档说明进行操作即可。具体操作时只需要修改对应的配置文件,对照自己的数据集修改相关参数,然后启动即可。

进入PaddleClas目录下,然后创建一个config.yaml配置文件,内容如下:

# global configs
Global:checkpoints: null   # 断点继续训练的模型pretrained_model: null  # 预训练模型output_dir: "./output/"  # 输出结果目录device: "gpu"  # 使用GPU进行训练save_interval: 1  # 保存间隔eval_during_train: Trueeval_interval: 1epochs: 100  # 总的训练迭代次数print_batch_step: 10use_visualdl: True  # 是否开启visualdl监控# used for static mode and model exportimage_shape: [3, 256, 192]  # 静态图导出时固定的图像尺寸,此处为3通道,高度256,宽度192save_inference_dir: "./inference"  # 静态图导出目录use_multilabel: True  # 是否使用多标签分类模式# model architecture
Arch:name: "PPLCNet_x1_0"  # 定义backbone模型# name: "ResNet101_vd"# name: "PPHGNet_small"pretrained: True  # 是否使用预训练的backbone模型use_ssld: Trueclass_num: 16   #  多标签分类总的类别数# loss function config for traing/eval process
Loss:Train:- MultiLabelLoss:weight: 1.0weight_ratio: Truesize_sum: TrueEval:- MultiLabelLoss:weight: 1.0weight_ratio: Truesize_sum: TrueOptimizer:name: Momentummomentum: 0.9lr:name: Cosinelearning_rate: 0.01warmup_epoch: 5regularizer:name: 'L2'coeff: 0.0005# data loader for train and eval
DataLoader:Train:dataset:name: MultiLabelDataset   # 多标签分类数据集格式image_root: "dataset/face_attribute_dataset/"   # 数据集根目录cls_label_path: "dataset/face_attribute_dataset/train_list.txt"  # 训练文件列表label_ratio: Truetransform_ops:- DecodeImage:to_rgb: Truechannel_first: False- ResizeImage:size: [192, 256]- TimmAutoAugment:prob: 0.8config_str: rand-m9-mstd0.5-inc1interpolation: bicubicimg_size: [192, 256] # 图像尺寸,宽度192,高度256- Padv2:size: [212, 276]pad_mode: 1fill_value: 0- RandomCropImage:size: [192, 256] # 图像随机裁剪尺寸,宽度192,高度256- RandFlipImage:flip_code: 1- NormalizeImage:scale: 1.0/255.0mean: [0.485, 0.456, 0.406]std: [0.229, 0.224, 0.225]order: ''- RandomErasing:EPSILON: 0.4sl: 0.02sh: 1.0/3.0r1: 0.3attempt: 10use_log_aspect: Truemode: pixelsampler:name: DistributedBatchSamplerbatch_size: 64   # 训练时batchsizedrop_last: Trueshuffle: Trueloader:num_workers: 4use_shared_memory: TrueEval:dataset:name: MultiLabelDatasetimage_root: "dataset/face_attribute_dataset/"  # 数据集目录cls_label_path: "dataset/face_attribute_dataset/val_list.txt"  #验证集列表label_ratio: Truetransform_ops:- DecodeImage:to_rgb: Truechannel_first: False- ResizeImage:size: [192, 256]  # 验证时图像缩放尺寸,宽度192,高度256- NormalizeImage:scale: 1.0/255.0mean: [0.485, 0.456, 0.406]std: [0.229, 0.224, 0.225]order: ''sampler:name: DistributedBatchSamplerbatch_size: 64  # 验证时batchsizedrop_last: Falseshuffle: Falseloader:num_workers: 4use_shared_memory: TrueInfer:infer_imgs: dataset/face_attribute_dataset/test/090004.jpgbatch_size: 10transforms:- DecodeImage:to_rgb: Truechannel_first: False- ResizeImage:size: [192, 256]  # 推理时图像缩放尺寸,宽度192,高度256- NormalizeImage:scale: 1.0/255.0mean: [0.485, 0.456, 0.406]std: [0.229, 0.224, 0.225]order: ''- ToCHWImage:PostProcess:name: PersonAttributethreshold: 0.5  #default thresholdglasses_threshold: 0.3  #threshold only for glasseshold_threshold: 0.6 #threshold only for holdMetric:Eval:- ATTRMetric:

上面的各参数需要结合自己的数据集路径进行修改。对于关键参数已经给了中文注释。将上述文件配置好以后我们就可以开始训练了。

使用下面的命令进行单机四卡分布式训练:

export CUDA_VISIBLE_DEVICES=0,1,2,3
python3 -m paddle.distributed.launch \--gpus="0,1,2,3" \tools/train.py \-c ./config.yaml

在训练的时候会给出当前最佳best_model的评估指标。

想要快速验证不同的算法模型性能,可以通过修改config.yaml文件实现基准模型替换,具体如下所示:

Arch:name: "PPHGNet_small"

所有PaddleClas支持的模型可以从pplc/arch/backbone/init.py文件中查看。

下面是各个模型评估性能对比:

backbone 精度(MA) 模型大小(M)
ResNet101_vd 0.903 166
PPLCNet_x1_0 0.869 6
PPLCNetV2_base 0.889 21
PPHGNet_small 0.902 87
HRNet_W48_C 0.899 294
PPLCNet_x2_5 0.897 30
PPHGNet_base 0.897 271

由于我们采用的backbone模型都使用了预训练模型,因此整个训练收敛速度是比较快的。可以使用下面的命令查看训练过程:

visualdl --logdir vdl

如下图所示:


对于我自己的项目来说,由于对精度要求较高,因此综合精度和推理速度,最后选择PPHGNet_small。

四、部署

4.1 静态图导出

为了方便后面工业环境部署,我们需要将训练好的动态图模型转换成静态图模型,转换代码如下所示:

python3 tools/export_model.py \-c config.yaml \-o Global.pretrained_model=output/PPHGNet_small/epoch_45 \-o Global.save_inference_dir=output/static_models/PPHGNet_small

转换完成后在output/static_models/PPHGNet_small目录下会生成具体的静态图模型文件:

  • inference.pdiparams
  • inference.pdiparams.info
  • inference.pdmodel

4.2 paddle serving高性能部署

PaddleServing的相关介绍和使用请参考我的另一篇博客。这里需要注意,对于我的项目来说精度要求较高,为了能有效提高人脸属性分析的精度,我在具体执行人脸属性分析前会先进行抠图操作,即先从复杂的环境中将人提取出来,去除背景图像的干扰,这样再进行人脸属性评估精度会提高不少。当然,这就要求我们的训练数据也需要先去除背景,这个我们可以采用PaddleSeg套件中的人像抠图算法实现。在官网也提供了训练好的人像抠图静态图模型,我们只需要直接使用就行。

由于我们一共使用了两个模型:人像抠图、人脸属性分析,因此,在部署PaddleServing时我们就需要采用Pipeline级联方式。

首先转换抠图模型:

python -m paddle_serving_client.convert \--dirname ./models/matting \--model_filename model.pdmodel \--params_filename model.pdiparams

转换完成后会生成serving_server和serving_client,这就是Paddle Serving部署需要的模型文件。我们将其改名为matting_serving_server和matting_serving_client。

接下来转换人脸属性分析模型:

python -m paddle_serving_client.convert \--dirname ./models/eval \--model_filename inference.pdmodel \--params_filename inference.pdiparams

同样的,我们将生成的serving_server和serving_client改名为eval_serving_server和eval_serving_client。

接下来,我们创建一个名为config.yml的部署参数文件,内容如下:

dag:is_thread_op: true  # True为线程模型;False为进程模型use_profile: false  # 是否开启性能分析retry: 1 # 重试次数http_port: 27006 # http端口build_dag_each_worker: false  #False表示框架在进程内创建一条DAG;True表示框架会在每个进程内创建多个独立的DAGworker_num: 1    # 最大并发数。当build_dag_each_worker=True时, 框架会创建worker_num个进程,每个进程内构建grpcSever和DAGop:# 抠图模块matting:   concurrency: 1   # 并发数,is_thread_op=True时,为线程并发;否则为进程并发local_service_conf:client_type: local_predictor  # client类型,包括brpc, grpc和local_predictor.local_predictordevice_type: 1  # device_type, 0=cpu, 1=gpu, 2=tensorRT, 3=arm cpu, 4=kunlun xpudevices: '0'  # 当devices为""或不写时为CPU预测;当devices为"0", "0,1,2"时为GPU预测,表示使用的GPU卡fetch_list:  #Fetch结果列表,model中fetch_var的alias_name为准, 如果没有设置则全部返回- tmp_75ir_optim: True   # 开启内存优化batch_size: 1    model_config: matting_serving_server/    # 服务器模型存放路径# 合规性评估模块eval:   concurrency: 1   # 并发数,is_thread_op=True时,为线程并发;否则为进程并发local_service_conf:client_type: local_predictor  # client类型,包括brpc, grpc和local_predictor.local_predictordevice_type: 1  # device_type, 0=cpu, 1=gpu, 2=tensorRT, 3=arm cpu, 4=kunlun xpudevices: '0'  # 当devices为""或不写时为CPU预测;当devices为"0", "0,1,2"时为GPU预测,表示使用的GPU卡fetch_list:  #Fetch结果列表,model中fetch_var的alias_name为准, 如果没有设置则全部返回- sigmoid_12.tmp_0ir_optim: True   # 开启内存优化batch_size: 1    model_config: eval_serving_server/    # 服务器模型存放路径

最后我们编写服务启动脚本web_service.py,内容如下:

# 导入依赖库
import numpy as np
import cv2
from paddle_serving_app.reader import *
import base64
from paddle_serving_server.web_service import WebService, Op
from io import BytesIOclass MattingOp(Op):'''定义抠图算子'''def init_op(self):'''初始化'''self.ref_size = 512self.img_width = self.ref_sizeself.img_height = self.ref_size  self.img_preprocess = Sequential([#BGR2RGB(), Div(255.0),#Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], False),#Resize(self.ref_size), Transpose((2, 0, 1))])def preprocess(self, input_dicts, data_id, log_id):'''预处理'''(_, input_dict), = input_dicts.items()imgs = []for key in input_dict.keys():# 解码图像data = base64.b64decode(input_dict[key].encode('utf8'))data = np.fromstring(data, np.uint8)im = cv2.imdecode(data, cv2.IMREAD_COLOR)self.img = imself.img_height,self.img_width,_ = im.shape# 短边对齐,长边设置为32整数倍(根据算法模型要求)im_h, im_w, _ = im.shape  if im_w >= im_h:im_rh = self.ref_sizeim_rw = int(im_w *1.0 / im_h * self.ref_size)elif im_w < im_h:im_rw = self.ref_sizeim_rh = int(im_h *1.0 / im_w * self.ref_size)      im_rw = im_rw - im_rw % 32im_rh = im_rh - im_rh % 32im = cv2.resize(im,(im_rw,im_rh))# cv2转tensorim = self.img_preprocess(im)imgs.append({"img": im[np.newaxis, :],})# 准备输入数据feed_dict = {"img": np.concatenate([x["img"] for x in imgs], axis=0),}return feed_dict, False, None, ""def postprocess(self, input_dicts, fetch_dict, data_id, log_id):'''后处理'''# 取出掩码图alpha = fetch_dict["tmp_75"]alpha = alpha.squeeze(0).squeeze(0)alpha = (alpha * 255).astype('uint8')  alpha = cv2.resize(alpha, (self.img_width, self.img_height), interpolation=cv2.INTER_NEAREST)print(alpha.shape)# 与白背景合成bg = np.ones((self.img_height,self.img_width), np.uint8) * 255bg = cv2.cvtColor(bg, cv2.COLOR_GRAY2BGR)alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2BGR)alpha = alpha.astype(np.float32)/255.0bg = bg.astype(np.float32)img = self.img.astype(np.float32) * alpha + (1 - alpha) * bgimg = img.astype(np.uint8)# 封装返回out_dict = {"img": img}return out_dict, None, ""class EvalOp(Op):'''定义不合规识别工序'''def init_op(self):'''初始化'''self.target_width = 192self.target_height = 256self.img_preprocess = Sequential([BGR2RGB(), Div(255.0),Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], False),Resize((self.target_width, self.target_height)),Transpose((2, 0, 1))])def preprocess(self, input_dicts, data_id, log_id):'''预处理'''(_, input_dict), = input_dicts.items()img = input_dict["img"]feed_dict = {}# 预处理img = self.img_preprocess(img)# 封装输入数据feed_dict["x"] = img[np.newaxis, :, :, :]return feed_dict, False, None, ""def postprocess(self, input_dicts, fetch_dict, data_id, log_id):'''后处理'''# 取出检测结果scores = fetch_dict["sigmoid_12.tmp_0"].squeeze(0)print(scores.shape)#封装成字典返回# bytesio = BytesIO()# np.savetxt(bytesio, scores)# content = bytesio.getvalue()# b64_code = base64.b64encode(content)# scores = str(b64_code, encoding='utf-8')res = ''for i in range(len(scores)):res += (str("%.2f" % scores[i])+',')# 返回out_dict = {"scores": res}return out_dict, None, ""class FaceEvalService(WebService):'''定义服务(级联抠图和人脸属性评估模型)'''def get_pipeline_response(self, read_op):matting_op = MattingOp(name="matting", input_ops=[read_op])eval_op = EvalOp(name="eval", input_ops=[matting_op])return eval_op# 创建服务
face_service = FaceEvalService(name="faceeval")
# 加载配置文件
face_service.prepare_pipeline_config("config.yml")
# 启动服务
face_service.run_service()

到这里我们就可以使用命令启动服务了:

python web_service.py

4.3 Python客户端请求

客户端请求代码如下:

# 导入依赖库
import requests
import json
import base64def cv2_to_base64(image):return base64.b64encode(image).decode('utf8')# 定义http接口
url = "http://172.19.17.101:27006/faceeval/prediction"# 打开待预测的图像文件
img_path = 'test/48.png'
with open(img_path, 'rb') as file:image_data1 = file.read()# 采用base64编码图像文件
image = cv2_to_base64(image_data1)# 按照特定格式封装成字典
data = {"key": ["image"], "value": [image]}# 发送请求
r = requests.post(url=url, data=json.dumps(data))# 解析返回值
r = r.json()
scores = r["value"][0]
print(scores)

如下图所示:

最后返回的结果就是每个属性的score值。如下所示:

0.00,0.00,0.00,0.00,1.00,0.98,0.03,0.12,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,

可以看到有两个属性值特别大,这两个属性值分别对应戴帽子、戴墨镜。应该说检测是比较准的。

五、小结

本文基于PaddleClas开源库实现了完整的人脸多标签属性分析功能。本文更偏重应用,对于亟需开发上线人脸属性分析功能的相关读者可以参考本文快速完成开发任务。对于有较高精度要求的读者,也可以在本文基础上,继续深入研究算法,结合本文部署流程,开发出商业级别的人脸属性分析应用。

想要继续深入学习Paddle的读者,今年年末我会出版一本书籍《深度学习与图像处理—基于PaddlePaddle》,欢迎关注和支持。

由于水平有限,文中如果有错误或更优的解决方案也请读者在评论区指正。欢迎大家一起探讨。

参考文献

【1】J. Cao, Y. Li and Z. Zhang, “Partially Shared Multi-task Convolutional Neural Network with Local Constraint for Face Attribute Learning,” IEEE Conference on Computer Vision and Pattern Recognition, 2018, pp. 4290-4299.

基于深度学习的多任务人脸属性分析(基于飞桨PaddlePaddle)相关推荐

  1. 蚂蚁金服张洁:基于深度学习的支付宝人脸识别技术解秘-1

    蚂蚁金服张洁:基于深度学习的支付宝人脸识别技术解秘(1) 2015-08-13 10:22 于雪 51CTO 字号:T | T 用户身份认证是互联网金融发展的基石.今年三月,在德国汉诺威举办的IT展览 ...

  2. 基于深度学习的图像隐写分析综述 阅读

    背景 隐写术英文为Steganography. 现有的通信安全保障主要分为加密和信息隐藏:加密主要对秘密信息本身进行操作,但经过特殊处理后的明文更加容易受到第三方的怀疑;而信息隐藏则隐藏秘密数据的存在 ...

  3. 蚂蚁金服张洁:基于深度学习的支付宝人脸识别技术解秘

    用户身份认证是互联网金融发展的基石.今年三月,在德国汉诺威举办的IT展览会上,马云向德国总理默克尔和中国副总理马凯展示支付宝的"刷脸"支付,引起了人们对人脸识别技术的热议.在WOT ...

  4. 基于深度学习的人脸识别闸机开发(基于飞桨PaddlePaddle)

    目录 一.概述 1.1 人脸识别背景 1.2 实现 1.2.1 算法说明 1.2.2 环境设置 1.2.3 实现思路 二.示例脚本 2.1 安装PaddlePaddle和PLSC 2.2 下载人脸检测 ...

  5. 基于深度学习的可疑活动视频分析

    导 语 学习如何构建可以将视频分为三类的AI系统:犯罪或暴力活动视频,潜在可疑的活动视频.安全活动视频. 本文介绍了一种视频分类技术的实现方法.我们的目标是解释我们如何实现的以及我们获得的结果,以便大 ...

  6. 毕业设计-基于深度学习的图像隐写分析

    目录 前言 课题背景和意义 实现技术思路 一.隐写术介绍 二.半学习隐写分析 三.全学习隐写分析 实现效果图样例 最后 前言

  7. 综述|基于深度学习的低光图像增强

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 标题&作者团队 本文是南开大学程明明与南洋理工大学Chen ChangeLoy等人关于深度学习 ...

  8. 深度学习下的人脸识别技术:从“后真相”到“无隐私”

    2019-06-17 14:27:08 图片来源@视觉中国 文|五矩研究社,作者|劫镖 2018年7月,<大西洋月刊>曾发表过一篇人脸识别的文章,名字叫做<开启假视频时代>,文 ...

  9. 【每周CV论文推荐】 初学深度学习人脸属性分析必读的文章

    欢迎来到<每周CV论文推荐>.在这个专栏里,还是本着有三AI一贯的原则,专注于让大家能够系统性完成学习,所以我们推荐的文章也必定是同一主题的. 人脸属性分析在社交娱乐,人机交互等领域有重要 ...

最新文章

  1. 吴甘沙:天外飞“厕”、红绿灯消失,未来无人驾驶将被重新定义
  2. mysql 1045 登录失败
  3. 什么是latex科技排版系统,有对比word有何不同?
  4. linux桌面服务器系统下载,Ubuntu 14.10 中文桌面版/服务器正式版下载
  5. 本地jupyter notebook远程连接服务器
  6. LeetCode之长度最小的子数组
  7. Atitti.数字证书体系cer pfx attilax总结
  8. 自动化生产线认知_什么是认知自动化?
  9. 【首发】上亿用户QQ号码泄露,腾讯WEB产品漏洞
  10. 软件测试行业用mac好还是win好,为了在Mac上也能用好Windows,我做了这些调整
  11. 深入mysql语言_深入简出mysql--第一部分
  12. css 三栏布局 圣杯布局 双飞翼 flex
  13. git报错unsafe repository xxx is owned by someone else
  14. 费马小定理(详细证明)
  15. 如何进入华为P40 debug 模式/开发者选项模式
  16. 由内而外全面造就自己(一)
  17. 11部中考常考的名著思维导图汇总
  18. matlab 绘制高斯(Gaussan)函数图像
  19. 如何找到土蜂蜜销售渠道?
  20. 循序渐进:用python做金融量化分析(五)MACD策略系统

热门文章

  1. windows 远程桌面无法连接的问题
  2. MVC5_Identity登录原理 - Claims-based认证和OWIN
  3. 分享72个商务商城PHP源码,总有一款适合你
  4. Collections。API
  5. 数据库的事务ACID特性以及MySQL如何保持事物特性
  6. 4.基于NIO的群聊系统
  7. java通过输入的方式给int数组赋值并输出数组
  8. padStart()和padEnd()使用
  9. 外科手术器械设计 超声刀设计思路和原理 - 02
  10. 拼多多开店不做推广能行吗?