赞
踩
目录
最近遇到个需求,需要将图像中的人物区分为成人和小孩,这是一个典型的二分类问题,打算采用飞桨的图像分类套件PaddleClas来完成算法研发。本文记录相关流程。
本文采用MaGaAge_Asian数据集,该数据集主要由亚洲人图片组成,训练集包含40000张图像,验证集包含3495张图像,每张图像都有对应的年龄真值,所有图像均处理成了统一的大小,宽178像素,高218像素。
数据集地址下载链接。数据集部分示例如下图所示:
该数据集本意是用来做年龄预测的,属于一个数值回归任务,本文将其变成二分类任务,以13岁年龄为界限,小于该年龄的属于小孩,大于该年龄的属于成人。这里之所以选择13岁,因为这个任务是需要筛选出长得很“像”小孩的小孩,13岁以上的青少年很多本身已经长的像成人了,因此,选择13岁作为分界线。
下面首先对该数据集进行处理。
MaGaAge_Asian数据集每张图片对应的人物年龄存放在list文件夹的两个文件中,其中train_age.txt存放训练集对应的年龄真值,test_age.txt存放验证集对应的年龄真值。下面要写一个脚本,将所有小于13岁的图片移动到一个文件夹内,所有大于等于13岁的图片移动到另一个文件夹内。
- #!/usr/bin/env python
- # -*- encoding: utf-8 -*-
- '''
- @文件 :split_asian.py
- @说明 :拆分megaage_asian数据集,将小于13岁的移动到一个文件夹,大于等于13岁的移动到另一个文件夹
- @时间 :2024/07/16 09:11:16
- @作者 :Bin Qian
- @版本 :1.0
- '''
-
-
- import os
- import cv2
-
- thr = 13 # 年龄阈值
-
- # 读取年龄列表
- agefile = 'megaage_asian/list/test_age.txt'
- f=open(agefile)
- ageLst = f.read().splitlines()
- f.close()
-
- # 读取图像
- imgFolder = 'megaage_asian/val'
- imgnames = os.listdir(imgFolder)
- index = 50000
- for imgname in imgnames:
- imgPath = os.path.join(imgFolder,imgname)
- img = cv2.imread(imgPath)
- if img is None:
- continue
- print(imgPath)
- imgindex = int(imgname.split('.')[0])
- age = int(ageLst[imgindex-1])
- if age < thr:
- dstFolder = 'ageclas/child'
- else:
- dstFolder = 'ageclas/adult'
-
- savePath = os.path.join(dstFolder,str(index)+'_asian.jpg')
- cv2.imwrite(savePath,img)
- index += 1
- print('完成')
值得注意的是MaGaAge_Asian数据集中有很多质量较差的图像,这些“脏”图像会影响学习效果,最好手工检查这些数据并将其剔除。
另外,为了能够取得更好的效果,本文从互联网和FFHQ数据集里面再挑选出一些小孩和成人的照片进行补充。部分代码如下:
- import os
- import cv2
-
- # 读取图像
- imgFolder = 'adult'
- imgnames = os.listdir(imgFolder)
- index = 1
- for imgname in imgnames:
- imgPath = os.path.join(imgFolder,imgname)
- img = cv2.imread(imgPath)
- if img is None:
- continue
- print(imgPath)
- dstFolder = 'ageclas/adult'
- savePath = os.path.join(dstFolder,str(index)+'_data.jpg')
- cv2.imwrite(savePath,img)
- index += 1
- print('完成')
补充完整后,最后对整理好的数据集进行拆分,并且获得对应的文件列表:
- # 导入系统库
- import os
- import random
- import cv2
-
-
- # 定义参数
- img_folder = 'ageclas'
- trainlst = 'train_list.txt'
- vallst = 'val_list.txt'
- ratio = 0.95 # 训练集占比
- labellst='label.txt'
-
-
- def writeLst(lstpath,namelst):
- '''
- 保存文件列表
- '''
- print('正在写入 '+lstpath)
- random.shuffle (namelst)
- # 写入训练样本文件
- f=open(lstpath, 'a', encoding='utf-8')
- for i in range(len(namelst)):
- text = namelst[i]+'\n'
- f.write(text)
- f.close()
- print(lstpath+ '已完成写入')
-
-
-
- def main():
- '''
- 主函数
- '''
- # 查找文件夹
- folderlst = os.listdir(img_folder)
- print('共找到 %d 个文件夹' % len(folderlst))
-
- # 循环处理
- trainnamelst = list()
- valnamelst = list()
- labelnamelst = list()
- for i in range(len(folderlst)):
- class_name = folderlst[i]
- class_label = i
- print('开始处理 '+class_name+' 文件夹')
-
- # 获取子文件夹文件列表
- filenamelst = os.listdir(os.path.join(img_folder,class_name))
- totalNum = len(filenamelst)
- print('当前文件夹图片数量为: ' + str(totalNum))
- trainNum = int(ratio*totalNum)
- text = str(class_label)+ ' ' + class_name
- labelnamelst.append(text)
-
- # 检查并校验图像
- for j in range(totalNum):
- imgpath = os.path.join(img_folder,class_name,filenamelst[j])
- img = cv2.imread(imgpath, cv2.IMREAD_COLOR)
- if img is None:
- continue
- text = imgpath + ' ' + str(class_label)
- if j <= trainNum:
- trainnamelst.append(text)
- else:
- valnamelst.append(text)
-
- writeLst(trainlst,trainnamelst)
- writeLst(vallst,valnamelst)
- writeLst(labellst,labelnamelst)
- print('全部完成')
-
-
- if __name__ == '__main__':
- '''程序入口'''
- main()
运行后会生成train_lst.txt、val_lst.txt以及label.txt三个文件,有了这三个文件就可以使用PaddleClas套件进行算法研发了。
- git clone https://gitee.com/paddlepaddle/PaddleClas.git
- cd PaddleClas
- sudo python setup.py install
在PaddleClas目录下新建一个配置文件config_lcnet.yaml,采用PPLCNet_x0_5模型来训练,配置文件代码如下:
# global configs Global: checkpoints: null pretrained_model: null output_dir: ./output/ device: gpu save_interval: 5 eval_during_train: True eval_interval: 5 epochs: 200 print_batch_step: 10 use_visualdl: True # used for static mode and model export image_shape: [3, 224, 224] save_inference_dir: ./output/inference # model architecture Arch: name: PPLCNet_x0_5 class_num: 2 # loss function config for traing/eval process Loss: Train: - CELoss: weight: 1.0 epsilon: 0.1 Eval: - CELoss: weight: 1.0 Optimizer: name: Momentum momentum: 0.9 lr: name: Cosine learning_rate: 0.8 warmup_epoch: 5 regularizer: name: 'L2' coeff: 0.00003 # data loader for train and eval DataLoader: Train: dataset: name: ImageNetDataset image_root: ../process_data/ cls_label_path: ../process_data/train_list.txt transform_ops: - DecodeImage: to_rgb: True channel_first: False - ResizeImage: size: [224,224] - RandFlipImage: flip_code: 1 - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' sampler: name: DistributedBatchSampler batch_size: 64 drop_last: False shuffle: True loader: num_workers: 4 use_shared_memory: True Eval: dataset: name: ImageNetDataset image_root: ../process_data/ cls_label_path: ../process_data/val_list.txt transform_ops: - DecodeImage: to_rgb: True channel_first: False - ResizeImage: size: [224,224] - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' sampler: name: DistributedBatchSampler batch_size: 64 drop_last: False shuffle: False loader: num_workers: 4 use_shared_memory: True Infer: infer_imgs: "../testimgs/10.jpg" batch_size: 1 transforms: - DecodeImage: to_rgb: True channel_first: False - ResizeImage: size: [224,224] - NormalizeImage: scale: 1.0/255.0 mean: [0.485, 0.456, 0.406] std: [0.229, 0.224, 0.225] order: '' - ToCHWImage: PostProcess: name: Topk topk: 1 class_id_map_file: "../process_data/label.txt" Metric: Train: - TopkAcc: topk: [1] Eval: - TopkAcc: topk: [1]
然后使用下面的命令进行训练:
- export CUDA_VISIBLE_DEVICES=0,1
- python3 -m paddle.distributed.launch \
- --gpus="0,1" \
- tools/train.py \
- -c config_lcnet.yaml
训练完成后可以使用下面的命令可视化查看训练结果:
visualdl --logdir results/vdl
运行效果如下:
可以看到,基本在epoch=100以后就收敛了,最高top1准确率达到97.5%,准确率还是比较高的。
下面可以使用动态图对单张图像进行测试,命令如下:
python3 tools/infer.py -c config_lcnet.yaml -o Global.pretrained_model=output/PPLCNet_x0_5/best_model
输出如下:
[{'class_ids': [1], 'scores': [0.93522], 'file_name': '../testimgs/10.jpg', 'label_names': ['adult']}]
为了方便后面进行模型部署,将训练好的最佳模型进行静态图导出。具体命令如下:
- python3 tools/export_model.py \
- -c config_lcnet.yaml \
- -o Global.pretrained_model=output/PPLCNet_x0_5/best_model \
- -o Global.save_inference_dir=output/inference
导出的静态图模型存放在output/inference文件夹下面,整个模型参数加起来不超过3M,因此可以看出这个训练好的PPLCNet_x0_5模型是一个非常轻量级的模型。
下面使用静态图来进行推理。在推理前先使用visualdl工具查看下静态图模型的输入和输出,这将为编写推理脚本奠定基础。
可以看到,输入是[batch,3,224,224]的float型图像数据,输出是[batch,2]的float型数据。尤其是输出的两个值,代表的是两个类别的概率。
有了上面的分析,下面可以用PaddleInference写一个推理脚本infer.py:
- import cv2
- import numpy as np
- from paddle.inference import create_predictor
- from paddle.inference import Config as PredictConfig
-
- # 加载静态图模型
- model_path = "./output/inference/inference.pdmodel"
- params_path = "./output/inference/inference.pdiparams"
- pred_cfg = PredictConfig(model_path, params_path)
- pred_cfg.enable_memory_optim() # 启用内存优化
- pred_cfg.switch_ir_optim(True)
- pred_cfg.enable_use_gpu(500, 0) # 启用GPU推理
- predictor = create_predictor(pred_cfg) # 创建PaddleInference推理器
-
- # 解析模型输入输出
- input_names = predictor.get_input_names()
- input_handle = {}
- for i in range(len(input_names)):
- input_handle[input_names[i]] = predictor.get_input_handle(input_names[i])
- output_names = predictor.get_output_names()
- output_handle = predictor.get_output_handle(output_names[0])
-
- # 图像预处理
- img = cv2.imread("../testimgs/10.jpg", flags=cv2.IMREAD_COLOR)
- img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
- img = cv2.resize(img, (224, 224), interpolation=cv2.INTER_AREA)
- img = img.astype(np.float32)
- PIXEL_MEANS =(0.485, 0.456, 0.406) # RGB格式的均值和方差
- PIXEL_STDS = (0.229, 0.224, 0.225)
- img/=255.0
- img-=np.array(PIXEL_MEANS)
- img/=np.array(PIXEL_STDS)
- img = np.transpose(img[np.newaxis, :, :, :], (0, 3, 1, 2))
-
- # 预测
- input_handle["x"].copy_from_cpu(img)
- predictor.run()
- results = output_handle.copy_to_cpu()
-
- # 后处理
- results = results.squeeze(0)
- if results[0]>results[1]:
- print('小孩'+" "+str(results[0]))
- else:
- print('大人'+" "+str(results[1]))
从网上随便找两张照片,运行效果如下:
输出结果:
小孩 0.7256172
输出结果:
大人 0.9533998
可以看到,推理效果还是比较满意的。
本文以项目为主线,使用了PaddleClas算法套件解决了年龄分类问题。后续读者如果想要深入学习PaddlePaddle(飞桨)及相关算法套件,可以关注我的书籍(链接)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。