网上PaddleClass2.2文章很少,都是2.1,但是2.2和2.1的配置还是有些区别的,而且看了网上很多关于paddle lite树莓派相关教程都是修改cc文件,然后./run.sh。但是没有直接调用python api的教程,更有甚至利用python使用os.system('./run.sh')进行调用,实在难受。因此有了这篇文章来记录一下。
本项目主要是基于PaddleClas2.2的奥特曼识别,从数据训练到利用PaddleLite2.9框架将模型部署到树莓派4b 64位,然后在树莓派上利用paddlelite框架的python api进行模型转换和图片&视频流对奥特曼的识别。
目录
1.数据处理
2.模型训练
3.模型评估
4.模型转换
5.树莓派部署
1.数据处理
我们利用百度的aistudio白嫖算力进行数据训练。
首先是数据解压处理
# 数据解压
!unzip -oq /home/aistudio/data/data104203/aoteman.zip
aoteman文件夹结构如下
aoteman
├── dijia
│ ├── 001.jpg
│ ├── 002.jpg
│ ├── 003.jpg
│ ├── ······
├── jieke
│ ├── 001.jpg
│ ├── 002.jpg
│ ├── 003.jpg
│ ├── ······
├── saiwen
│ ├── 001.jpg
│ ├── 002.jpg
│ ├── 003.jpg
│ ├── ······
├── tailuo
│ ├── 001.jpg
│ ├── 002.jpg
│ ├── 003.jpg
│ ├── ······
├──predict_demo.jpg
由于代码执行警告太多很烦,我也先去掉
import warnings
warnings.filterwarnings("ignore")
数据集划分是个很烦的事,之前我也是自己def了n个函数划分然后写入txt,很麻烦,小白甚至觉得训练都没数据处理烦,这里我推荐jikuai库,可以一键划分生成txt,我们先安装一下。
!pip install jikuai -q
那么接下来我们就开始git paddleclas了 ,并选择2.2分支
!git clone https://gitee.com/paddlepaddle/PaddleClas.git -b release/2.2
然后利用jiekuai进行数据划分
%cd ~/
from jikuai.dataset import Dataset
dataset = Dataset("aoteman") # 参数为数据集所在的位置,是分类目录的上一级目录
dataset.paddleclastxt(0.8) # 生成训练集和测试集列表,参数为两者划分的比例值。
!ls
# 2280348.ipynb aoteman data eval.txt PaddleClas train.txt work
我们把数据移到PaddleClas/dataset里,方便打包
!mv eval.txt aoteman/
!mv train.txt aoteman/
!mv aoteman PaddleClas/dataset/
2.模型训练
接下来是最重要的tran前配置了,不得不说PaddleClas比PaddleDetection配置要方便多了,全在一个yaml里
我们对 PaddleClas/ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25.yaml 进行修改设置
主要是以下几点:分类数、训练和验证的路径、图像尺寸、数据预处理、训练和预测的num_workers: 0才可以在aistudio跑通。
# global configs
Global:checkpoints: null# 预训练模型加载,这里不用了pretrained_model: null # 模型保存地址output_dir: ./output/ # 没有gpu就用cpudevice: gpu # 每几个轮次保存一次save_interval: 1eval_during_train: True# 每几个轮次验证一次eval_interval: 1# 训练600轮次epochs: 600# 每10步打印print_batch_step: 10# 可视化,这里不开启use_visualdl: False# 预测模型导出地址image_shape: [3, 224, 224]save_inference_dir: ./inference# 模型结构
Arch:name: ShuffleNetV2_x0_25# 类别class_num: 4# 训练/评估过程的损失函数配置
Loss:Train:- CELoss:weight: 1.0Eval:- CELoss:weight: 1.0Optimizer:name: Momentummomentum: 0.9lr:name: Cosinelearning_rate: 0.0125warmup_epoch: 5regularizer:name: 'L2'coeff: 0.00001# 用于训练和评估的数据加载配置
DataLoader:Train:dataset:name: ImageNetDataset# 文件根目录 因为train.txt格式是“aoteman/tailuo/072.jpg 3” 以便导入image_root: ./dataset# train划分地址cls_label_path: ./dataset/aoteman/train.txt# 数据预处理transform_ops:- DecodeImage:to_rgb: Truechannel_first: False- RandCropImage:size: 224- RandFlipImage:flip_code: 1- NormalizeImage:scale: 1.0/255.0mean: [0.485, 0.456, 0.406]std: [0.229, 0.224, 0.225]order: ''sampler:name: DistributedBatchSampler# 每一步16个数据加载batch_size: 16drop_last: Falseshuffle: Trueloader:num_workers: 0 # 这里一定要0才可以跑通aistudiouse_shared_memory: TrueEval:dataset: name: ImageNetDatasetimage_root: ./datasetcls_label_path: ./dataset/aoteman/eval.txttransform_ops:- DecodeImage:to_rgb: Truechannel_first: False- ResizeImage:resize_short: 256- CropImage:size: 224- NormalizeImage:scale: 1.0/255.0mean: [0.485, 0.456, 0.406]std: [0.229, 0.224, 0.225]order: ''sampler:name: DistributedBatchSamplerbatch_size: 64drop_last: Falseshuffle: Falseloader:num_workers: 0use_shared_memory: TrueInfer:infer_imgs: ./dataset/aoteman/predict_demo.jpgbatch_size: 10transforms:- DecodeImage:to_rgb: Truechannel_first: False- ResizeImage:resize_short: 256- CropImage:size: 224- NormalizeImage:scale: 1.0/255.0mean: [0.485, 0.456, 0.406]std: [0.229, 0.224, 0.225]order: ''- ToCHWImage:PostProcess:name: Topk# 输出的可能性最高的前topk个topk: 4# 标签文件 需要自己新建文件class_id_map_file: ./dataset/aoteman/labels.txt
Metric:Train:- TopkAcc:topk: [1, 4]Eval:- TopkAcc:topk: [1, 4]
自己建个标签文件 ./dataset/label_list.txt
0 迪迦奥特曼
1 杰克奥特曼
2 赛文奥特曼
3 泰罗奥特曼
配置搞定,那么接下来开始训练吧~
# 采用GPU训练
!export CUDA_VISIBLE_DEVICES=0
%cd ~/PaddleClas/
!python tools/train.py \-c ./ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25.yaml
训练结果一窥究竟
[2021/08/13 18:01:49] root INFO: [Train][Epoch 596/600][Iter: 0/28]lr: 0.00001, CELoss: 0.03720, loss: 0.03720, top1: 1.00000, top4: 1.00000, batch_cost: 0.30420s, reader_cost: 0.25090, ips: 52.59725 images/sec, eta: 0:00:42
[2021/08/13 18:01:51] root INFO: [Train][Epoch 596/600][Iter: 10/28]lr: 0.00001, CELoss: 0.13251, loss: 0.13251, top1: 0.96023, top4: 1.00000, batch_cost: 0.22338s, reader_cost: 0.17151, ips: 71.62723 images/sec, eta: 0:00:29
[2021/08/13 18:01:53] root INFO: [Train][Epoch 596/600][Iter: 20/28]lr: 0.00001, CELoss: 0.12071, loss: 0.12071, top1: 0.96131, top4: 1.00000, batch_cost: 0.21830s, reader_cost: 0.16640, ips: 73.29514 images/sec, eta: 0:00:26
[2021/08/13 18:01:55] root INFO: [Train][Epoch 596/600][Avg]CELoss: 0.11507, loss: 0.11507, top1: 0.96591, top4: 1.00000
[2021/08/13 18:01:56] root INFO: [Eval][Epoch 596][Iter: 0/2]CELoss: 0.30446, loss: 0.30446, top1: 0.92188, top4: 1.00000, batch_cost: 1.02158s, reader_cost: 0.99066, ips: 62.64828 images/sec
[2021/08/13 18:01:56] root INFO: [Eval][Epoch 596][Avg]CELoss: 0.31860, loss: 0.31860, top1: 0.90909, top4: 1.00000
[2021/08/13 18:01:56] root INFO: [Eval][Epoch 596][best metric: 0.9454545497894287]
[2021/08/13 18:01:56] root INFO: Already save model in ./output/ShuffleNetV2_x0_25/epoch_596
[2021/08/13 18:01:56] root INFO: Already save model in ./output/ShuffleNetV2_x0_25/latest
训练600epoch,基本上top1能稳定90-96%以上了。大家可以试下加载预训练模型,举个例子。
python3 tools/train.py \-c ./ppcls/configs/quick_start/MobileNetV3_large_x1_0.yaml \-o Arch.pretrained=True \-o Global.device=gpu
其中
Arch.pretrained
设置为True
表示加载ImageNet的预训练模型,此外,Arch.pretrained
也可以指定具体的模型权重文件的地址,使用时需要换成自己的预训练模型权重文件的路径。
3.模型评估
对我们模型评估一下 ,有90以上了,可以用了。
%cd ~/PaddleClas
!python3 tools/eval.py \-c ./ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25.yaml \-o Global.pretrained_model=output/ShuffleNetV2_x0_25/latest
[2021/08/14 10:30:57] root INFO: [Eval][Epoch 0][Iter: 0/2]CELoss: 0.31628, loss: 0.31628, top1: 0.92188, top4: 1.00000, batch_cost: 1.16294s, reader_cost: 1.12821, ips: 55.03274 images/sec
[2021/08/14 10:30:57] root INFO: [Eval][Epoch 0][Avg]CELoss: 0.33670, loss: 0.33670, top1: 0.90909, top4: 1.00000
我们用我们的模型来验证一下
!python3 tools/infer.py \-c ./ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25.yaml \-o Infer.infer_imgs=dataset/aoteman/predict_demo.jpg \-o Global.pretrained_model=output/ShuffleNetV2_x0_25/latest
[{'class_ids': [0, 1, 3, 2], 'scores': [1.0, 0.0, 0.0, 0.0], 'file_name': 'dataset/aoteman/predict_demo.jpg', 'label_names': ['迪迦奥特曼 ', '杰克奥特曼 ', '泰罗奥特曼', '赛文奥特曼 ']}]
验证推定100%是迪迦奥特曼,我们看下是不是迪迦奥特曼。 果然是哈~
4.模型转换
最后要把模型导出inference模型,为什么又要导出新格式模型呢,原来模型不是可以用了吗?因为原来模型是包含正向和反向传播的,而我们部署上线是不需要反向传播的,因此我们导出预测专用的inference模型。
# 导出inference模型
!python3 tools/export_model.py \-c ./ppcls/configs/quick_start/new_user/ShuffleNetV2_x0_25.yaml \-o Global.pretrained_model=./output/ShuffleNetV2_x0_25/latest
模型导出到了我们.yaml里写的 PaddleClas/inference文件夹里里,我们将他下载上传到树莓派里,我们开始下一阶段。
5.树莓派部署
首先进行树莓派PaddleLite2.9源码编译,这大家也可以参考官方文档。
https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_linux.html
我们先写个inference模型转nb模型的python代码
# https://paddlelite.paddlepaddle.org.cn/api_reference/python_api/opt.html
# 引用Paddlelite预测库
from paddlelite.lite import *# 1. 创建opt实例
opt=Opt()
# 2. 指定输入模型地址
# 非combined形式
#opt.set_model_dir("./mobilenet_v1")
# conmbined形式,具体模型和参数名称,请根据实际修改
opt.set_model_file("./aoteman_inference/inference.pdmodel")
opt.set_param_file("./aoteman_inference/inference.pdiparams")
# 3. 指定转化类型: arm、x86、opencl、npu
opt.set_valid_places("arm")
# 4. 指定模型转化类型: naive_buffer、protobuf
opt.set_model_type("naive_buffer")
# 5. 动态离线量化
opt.set_quant_model(True)
opt.set_quant_type("QUANT_INT8")
# 6. 输出模型地址
opt.set_optimize_out("aoteman_model")
# 7. 执行模型优化
opt.run()
生成aoteman_model.nb模型,这样才可以被paddlelite调用。
然后我们建个predict.py利用python api进行模型调用和推断
from paddlelite.lite import *
import cv2
import numpy as np
import sys
import time
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw# 加载模型
def create_predictor(model_dir):config = MobileConfig()config.set_model_from_file(model_dir)predictor = create_paddle_predictor(config)return predictor #图像归一化处理
def process_img(image, input_image_size):origin = imageimg = origin.resize(input_image_size, Image.BILINEAR)resized_img = img.copy()if img.mode != 'RGB':img = img.convert('RGB')img = np.array(img).astype('float32').transpose((2, 0, 1)) # HWC to CHWimg -= 127.5img *= 0.007843img = img[np.newaxis, :]return origin,img# 预测
def predict(image, predictor, input_image_size):#输入数据处理input_tensor = predictor.get_input(0)input_tensor.resize([1, 3, input_image_size[0], input_image_size[1]])image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGRA2RGBA))origin, img = process_img(image, input_image_size)image_data = np.array(img).flatten().tolist()input_tensor.set_float_data(image_data)#执行预测predictor.run()#获取输出output_tensor = predictor.get_output(0)print("output_tensor.float_data()[:] : ", output_tensor.float_data()[:])res = output_tensor.float_data()[:]return res# 展示结果
def post_res(label_dict, res):print(max(res))target_index = res.index(max(res))print("结果是:" + " " + label_dict[target_index])if __name__ == '__main__':# 初始定义label_dict = {0:"dijia", 1:"jieke", 2:"saiwen", 3:"tailuo"}image = "./saiwen2.jpg"model_dir = "./trans/aoteman_model.nb"image_size = (224, 224)# 初始化predictor = create_predictor(model_dir)# 读入图片image = cv2.imread(image)# 预测res = predict(image, predictor, image_size)# 显示结果post_res(label_dict, res)cv2.imshow("image", image)cv2.waitKey()
网上随便找的赛文奥特曼被推断出来了~
除了照片识别还不满足,我们利用摄像头视频流进行识别,建了个predict_camera.py如下
from paddlelite.lite import *
import cv2
import numpy as np
import sys
import time
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw# 加载模型
def create_predictor(model_dir):config = MobileConfig()config.set_model_from_file(model_dir)predictor = create_paddle_predictor(config)return predictor #图像归一化处理
def process_img(image, input_image_size):origin = imageimg = origin.resize(input_image_size, Image.BILINEAR)resized_img = img.copy()if img.mode != 'RGB':img = img.convert('RGB')img = np.array(img).astype('float32').transpose((2, 0, 1)) # HWC to CHWimg -= 127.5img *= 0.007843img = img[np.newaxis, :]return origin,img# 预测
def predict(image, predictor, input_image_size):#输入数据处理input_tensor = predictor.get_input(0)input_tensor.resize([1, 3, input_image_size[0], input_image_size[1]])image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGRA2RGBA))origin, img = process_img(image, input_image_size)image_data = np.array(img).flatten().tolist()input_tensor.set_float_data(image_data)#执行预测predictor.run()#获取输出output_tensor = predictor.get_output(0)# print("output_tensor.float_data()[:] : ", output_tensor.float_data()[:])res = output_tensor.float_data()[:]return res# 展示结果
def post_res(label_dict, res):# print(max(res))target_index = res.index(max(res))# print("结果是:" + " " + label_dict[target_index], "准确率为:", max(res))print(max(res))return max(res), label_dict[target_index]
if __name__ == '__main__':# 初始定义label_dict = {0:"dijia", 1:"jieke", 2:"saiwen", 3:"tailuo"}model_dir = "./trans/aoteman_model.nb"image_size = (224, 224)# 初始化predictor = create_predictor(model_dir)cap = cv2.VideoCapture(0)while True:ret, frame = cap.read()# 预测print('Predict Start')time_start=time.time()res = predict(frame, predictor, image_size)confidence, label_result = post_res(label_dict, res)fps = 1/(time.time()-time_start)# 显示结果post_res(label_dict, res)if confidence > 0.5:cv2.putText(frame, '{}'.format(label_result), (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255), 2)cv2.putText(frame, '{:.2f}'.format(confidence), (20, 100), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255), 2)cv2.putText(frame, 'fps:{:.2f}'.format(fps), (20, 20), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 0, 255), 2)cv2.imshow("frame", frame)if cv2.waitKey(1) & 0xFF == ord('q'):cv2.imwrite('out.png', frame)print("save one image done!")breakprint('Predict End')cap.release()cv2.destroyAllWindows()
迪迦奥特曼被识别出来了!
需要图像数据或者opencv paddlelite编译需求,甚至镜像需要请私聊。
如下配置镜像,并开启jupyter开机自启,插件已经装全。
OpenCV 4.5.1
ncnn 20210124
MNN 1.1.0
Paddle-Lite 2.9
TensorFlow-Lite 2.4.1
TensorFLow 2.4.1
TensorFlow Addons 0.13.0-dev
Pytorch 1.8.0
TorchVision 0.9.0