开源
- 数据集:https://aistudio.baidu.com/aistudio/datasetdetail/110303
- 训练:https://aistudio.baidu.com/aistudio/projectdetail/2408523
一、项目简介
使用PaddleClas和WatchDog实现PC端个人相册分类智慧管理。基于图像识别结果中的多个标签,智能相册可以自定义图像分类,比如“植物”、“美食”、“工作”等类别。方便用户管理相册,带来良好体验。
演示:
二、数据集
-
风景数据集:https://github.com/yuweiming70/Landscape-Dataset
-
食物数据集:https://www.datafountain.cn/datasets/5423
apple_pie,bibimbap,cannoli,edamame,falafel,french_toast,ice_cream,ramen,sushi,tiramisu
-
人物数据集:http://www.seeprettyface.com/mydataset_page2.html
-
文本数据集:https://challenge.xfyun.cn/topic/info?type=ad-2021&ch=dc-wbzmt-05
-
动物数据集:https://blog.csdn.net/sunshine__sun/article/details/115558151
狗,猫,马,大象,鸡,牛,羊
三、模型训练
!pip install paddlepaddle-gpu # --upgrade -i https://mirror.baidu.com/pypi/simple -q
!pip install paddlex -q
!pip install paddlelite -q
# 克隆paddleclas组件代码
!git clone https://gitee.com/paddlepaddle/PaddleClas.git -b develop
# 安装环境
%cd PaddleClas
#!pip install --upgrade -r requirements.txt -i https://mirror.baidu.com/pypi/simple
!rm -rf dataset/flowers102
!unzip /home/aistudio/data/data110303/dataset.zip -d /home/aistudio/PaddleClas/dataset/
# 数据集切分: 训练:测试:验证=8:1:2
!paddlex --split_dataset --format ImageNet --dataset_dir './dataset/' --val_value 0.2 --test_value 0.1
配置文件修改
训练文件配置,修改PaddleClas/ppcls/configs/quick_start/ResNet50_vd.yaml
为(所有要修改的行我都用【】标注了,使用的是时候删除【】就可以):
# global configs
Global:
checkpoints: null
pretrained_model: null
output_dir: ./output/
device: gpu
【save_interval: 5】
eval_during_train: True
【eval_interval: 5】
【epochs: 50】
print_batch_step: 10
use_visualdl: False
# used for static mode and model export
image_shape: [3, 224, 224]
save_inference_dir: ./inference
# model architecture
Arch:
name: MobileNetV3_large_x1_0
【class_num: 99】
# loss function config for traing/eval process
Loss:
Train:
- CELoss:
weight: 1.0
Eval:
- CELoss:
weight: 1.0
Optimizer:
name: Momentum
momentum: 0.9
lr:
name: Cosine
learning_rate: 0.00375
warmup_epoch: 5
last_epoch: -1
regularizer:
name: 'L2'
coeff: 0.000001
# data loader for train and eval
DataLoader:
Train:
dataset:
name: ImageNetDataset
【image_root: ./dataset/】
【cls_label_path: ./dataset/train_list.txt】
transform_ops:
- DecodeImage:
to_rgb: True
channel_first: False
- RandCropImage:
size: 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: 32
drop_last: False
shuffle: True
loader:
【num_workers: 0】
use_shared_memory: True
Eval:
dataset:
name: ImageNetDataset
【image_root: ./dataset/】
【cls_label_path: ./dataset/val_list.txt】
transform_ops:
- DecodeImage:
to_rgb: True
channel_first: False
- ResizeImage:
resize_short: 256
- CropImage:
size: 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: 0】
use_shared_memory: True
Infer:
【infer_imgs: ./dataset/AmsactaLactinea/28.jpg】
batch_size: 10
transforms:
- DecodeImage:
to_rgb: True
channel_first: False
- ResizeImage:
resize_short: 256
- CropImage:
size: 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: 5
【class_id_map_file: ./dataset/labels.txt】
Metric:
Train:
- TopkAcc:
topk: [1, 5]
Eval:
- TopkAcc:
topk: [1, 5]
类别映射文件修改
把dataset
下的label.txt
修改为:
0 animals
1 food
2 people
3 scenery
4 text
!python3 tools/train.py \
-c ./ppcls/configs/quick_start/ResNet50_vd.yaml \
-o Arch.pretrained=True
最终的一个训练结果:
[2021/09/26 17:02:11] root INFO: [Train][Epoch 6/100][Iter: 0/204]lr: 0.01250, CELoss: 0.21896, loss: 0.21896, top1: 0.93750, top5: 1.00000, batch_cost: 0.28717s, reader_cost: 0.23059, ips: 111.43318 images/sec, eta: 1:32:45
[2021/09/26 17:02:14] root INFO: [Train][Epoch 6/100][Iter: 10/204]lr: 0.01250, CELoss: 0.16529, loss: 0.16529, top1: 0.95455, top5: 1.00000, batch_cost: 0.26309s, reader_cost: 0.20698, ips: 121.63060 images/sec, eta: 1:24:56
[2021/09/26 17:02:16] root INFO: [Train][Epoch 6/100][Iter: 20/204]lr: 0.01250, CELoss: 0.14305, loss: 0.14305, top1: 0.95536, top5: 1.00000, batch_cost: 0.25574s, reader_cost: 0.19961, ips: 125.12556 images/sec, eta: 1:22:31
[2021/09/26 17:02:19] root INFO: [Train][Epoch 6/100][Iter: 30/204]lr: 0.01250, CELoss: 0.14826, loss: 0.14826, top1: 0.95565, top5: 1.00000, batch_cost: 0.27352s, reader_cost: 0.21715, ips: 116.99388 images/sec, eta: 1:28:12
[2021/09/26 17:02:22] root INFO: [Train][Epoch 6/100][Iter: 40/204]lr: 0.01250, CELoss: 0.14288, loss: 0.14288, top1: 0.95579, top5: 1.00000, batch_cost: 0.28418s, reader_cost: 0.22783, ips: 112.60358 images/sec, eta: 1:31:36
[2021/09/26 17:02:25] root INFO: [Train][Epoch 6/100][Iter: 50/204]lr: 0.01250, CELoss: 0.14453, loss: 0.14453, top1: 0.95466, top5: 1.00000, batch_cost: 0.28580s, reader_cost: 0.22947, ips: 111.96648 images/sec, eta: 1:32:04
[2021/09/26 17:02:28] root INFO: [Train][Epoch 6/100][Iter: 60/204]lr: 0.01250, CELoss: 0.14173, loss: 0.14173, top1: 0.95543, top5: 1.00000, batch_cost: 0.28339s, reader_cost: 0.22700, ips: 112.91845 images/sec, eta: 1:31:15
[2021/09/26 17:02:31] root INFO: [Train][Epoch 6/100][Iter: 70/204]lr: 0.01250, CELoss: 0.14219, loss: 0.14219, top1: 0.95555, top5: 1.00000, batch_cost: 0.28145s, reader_cost: 0.22503, ips: 113.69719 images/sec, eta: 1:30:34
[2021/09/26 17:02:33] root INFO: [Train][Epoch 6/100][Iter: 80/204]lr: 0.01250, CELoss: 0.13479, loss: 0.13479, top1: 0.95833, top5: 1.00000, batch_cost: 0.28028s, reader_cost: 0.22383, ips: 114.16997 images/sec, eta: 1:30:09
[2021/09/26 17:02:36] root INFO: [Train][Epoch 6/100][Iter: 90/204]lr: 0.01250, CELoss: 0.13717, loss: 0.13717, top1: 0.95776, top5: 1.00000, batch_cost: 0.27936s, reader_cost: 0.22292, ips: 114.54742 images/sec, eta: 1:29:48
[2021/09/26 17:02:39] root INFO: [Train][Epoch 6/100][Iter: 100/204]lr: 0.01250, CELoss: 0.13193, loss: 0.13193, top1: 0.95916, top5: 1.00000, batch_cost: 0.27771s, reader_cost: 0.22126, ips: 115.22808 images/sec, eta: 1:29:14
[2021/09/26 17:02:42] root INFO: [Train][Epoch 6/100][Iter: 110/204]lr: 0.01250, CELoss: 0.12981, loss: 0.12981, top1: 0.95974, top5: 1.00000, batch_cost: 0.27882s, reader_cost: 0.22233, ips: 114.76755 images/sec, eta: 1:29:32
[2021/09/26 17:02:45] root INFO: [Train][Epoch 6/100][Iter: 120/204]lr: 0.01250, CELoss: 0.12835, loss: 0.12835, top1: 0.95997, top5: 1.00000, batch_cost: 0.28274s, reader_cost: 0.22619, ips: 113.17778 images/sec, eta: 1:30:45
[2021/09/26 17:02:47] root INFO: [Train][Epoch 6/100][Iter: 130/204]lr: 0.01250, CELoss: 0.13026, loss: 0.13026, top1: 0.95754, top5: 1.00000, batch_cost: 0.28116s, reader_cost: 0.22461, ips: 113.81383 images/sec, eta: 1:30:12
[2021/09/26 17:02:50] root INFO: [Train][Epoch 6/100][Iter: 140/204]lr: 0.01250, CELoss: 0.12561, loss: 0.12561, top1: 0.95922, top5: 1.00000, batch_cost: 0.28104s, reader_cost: 0.22446, ips: 113.86471 images/sec, eta: 1:30:07
[2021/09/26 17:02:53] root INFO: [Train][Epoch 6/100][Iter: 150/204]lr: 0.01250, CELoss: 0.12391, loss: 0.12391, top1: 0.95944, top5: 1.00000, batch_cost: 0.28117s, reader_cost: 0.22454, ips: 113.80841 images/sec, eta: 1:30:06
[2021/09/26 17:02:56] root INFO: [Train][Epoch 6/100][Iter: 160/204]lr: 0.01250, CELoss: 0.12471, loss: 0.12471, top1: 0.95885, top5: 1.00000, batch_cost: 0.28056s, reader_cost: 0.22392, ips: 114.05585 images/sec, eta: 1:29:52
[2021/09/26 17:02:59] root INFO: [Train][Epoch 6/100][Iter: 170/204]lr: 0.01250, CELoss: 0.12546, loss: 0.12546, top1: 0.95888, top5: 1.00000, batch_cost: 0.28183s, reader_cost: 0.22515, ips: 113.54456 images/sec, eta: 1:30:13
[2021/09/26 17:03:02] root INFO: [Train][Epoch 6/100][Iter: 180/204]lr: 0.01250, CELoss: 0.12573, loss: 0.12573, top1: 0.95943, top5: 1.00000, batch_cost: 0.28235s, reader_cost: 0.22570, ips: 113.33392 images/sec, eta: 1:30:21
[2021/09/26 17:03:05] root INFO: [Train][Epoch 6/100][Iter: 190/204]lr: 0.01250, CELoss: 0.12404, loss: 0.12404, top1: 0.96024, top5: 1.00000, batch_cost: 0.28296s, reader_cost: 0.22634, ips: 113.08878 images/sec, eta: 1:30:30
[2021/09/26 17:03:07] root INFO: [Train][Epoch 6/100][Iter: 200/204]lr: 0.01250, CELoss: 0.12203, loss: 0.12203, top1: 0.96113, top5: 1.00000, batch_cost: 0.28260s, reader_cost: 0.22599, ips: 113.23431 images/sec, eta: 1:30:20
[2021/09/26 17:03:08] root INFO: [Train][Epoch 6/100][Avg]CELoss: 0.12242, loss: 0.12242, top1: 0.96114, top5: 1.00000
[2021/09/26 17:03:08] root INFO: Already save model in ./output/ResNet50_vd/epoch_6
[2021/09/26 17:03:09] root INFO: Already save model in ./output/ResNet50_vd/latest
!mkdir ./output/ResNet50_vd/latest
!mv ./output/ResNet50_vd/latest.pdparams ./output/ResNet50_vd/latest/
!mv ./output/ResNet50_vd/latest.pdopt ./output/ResNet50_vd/latest/
!mv ./output/ResNet50_vd/latest.pdstates ./output/ResNet50_vd/latest/
四、模型测试
!python tools/infer.py -c ./ppcls/configs/quick_start/ResNet50_vd.yaml \
-o Infer.infer_imgs=./dataset/food/1043283.jpg \
-o Global.pretrained_model=./output/ResNet50_vd/latest/latest
输出为:
[{'class_ids': [1, 0, 4, 2, 3], 'scores': [0.99962, 0.00026, 7e-05, 2e-05, 0.0], 'file_name': './dataset/food/1043283.jpg', 'label_names': ['food', 'animals', 'text', 'people', 'scenery']}]
分类正确~
五、本地部署
本地部署需要用到一个很关键的东西-watchdog,那么watchdog是什么呢?
watchdog用来监控指定目录/文件的变化,如添加删除文件或目录、修改文件内容、重命名文件或目录等,每种变化都会产生一个事件,且有一个特定的事件类与之对应,然后再通过事件处理类来处理对应的事件,怎么样处理事件完全可以自定义,只需继承事件处理类的基类并重写对应实例方法。
通俗解释来说,就是一个listener和handler:
- listener:用来监听一个文件夹或者是事件;
- handler:用来触发内部相关条件判断后执行的操作;
由两部分完成一个智慧相册的管理操作。
下载到本地后,需要修改config的use_gpu为cpu!!!
# 一个watchdog的小demo
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import time
import os
folder_to_track = "D:\\workspace\\python\\movingFiles\\src"
folder_destination_pic = "D:\\workspace\\python\\movingFiles\\des\\pic"
folder_destination_music = "D:\\workspace\\python\\movingFiles\\des\\music"
folder_destination_kaoyan = "D:\\workspace\\python\\movingFiles\\des\\kaoyan"
class MyHandler(FileSystemEventHandler):
def on_any_event(self, event):
for filename in os.listdir(folder_to_track) :
src = folder_to_track +"\\"+ filename
if filename.endswith("jpg") or filename.endswith("png"):
new_destination=folder_destination_pic + "\\"+filename
elif filename.endswith("mp3") :
new_destination=folder_destination_music + "\\" + filename
elif filename.__contains__("肖秀荣") :
new_destination = folder_destination_kaoyan + "\\"+filename
os.rename(src, new_destination)
event_handler = MyHandler()
observer = Observer()
observer.schedule(event_handler, folder_to_track, recursive=True)
observer.start()
try:
while True:
time.sleep(10)
except KeyboardInterrupt:
observer.stop()
observer.join()
# 智慧相册的核心watchdog
# coding:utf-8
import os
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import time
# 保持监听的文件
folder_to_track = "./src"
# 图片文件
#folder_destination_picture = "./source/picture"
animals = "./source/picture/animals"
food = "./source/picture/food"
people = "./source/picture/people"
scenery = "./source/picture/scenery"
text = "./source/picture/text"
# 音乐文件
folder_destination_music = "./source/music"
# 视频文件
folder_destination_video = "./source/video"
class MyHandler(FileSystemEventHandler):
def on_any_event(self, event):
for filename in os.listdir(folder_to_track) :
src = folder_to_track +"/"+ filename
if filename.endswith("jpg") or filename.endswith("png"):
#new_destination=folder_destination_picture + "/"+filename
print(filename)
os.system(r"python PaddleClas/tools/infer.py -c ./PaddleClas/ppcls/configs/quick_start/ResNet50_vd.yaml -o Infer.infer_imgs=./src/"+str(filename)+" -o Global.pretrained_model=./PaddleClas/output/ResNet50_vd/latest/latest > ./result.txt")
with open('result.txt', 'r', encoding='utf-8') as f: # 打开文件
lines = f.readlines() # 读取所有行
last_line = lines[-1] # 取最后一行
#print(eval(last_line))
result = eval(last_line)[0]['label_names'][0]
#print(type(eval(last_line)))
if result=='animals':
new_destination=animals + "/" +filename
if result=='people':
new_destination=people + "/" +filename
if result=='food':
new_destination=food + "/" +filename
if result=='scenery':
new_destination=scenery + "/" +filename
if result=='text':
new_destination=text + "/" +filename
elif filename.endswith("mp3") :
new_destination=folder_destination_music + "/" + filename
elif filename.endswith("avi") or filename.endswith("mp4"):
#filename.__contains__("肖秀荣") :
new_destination = folder_destination_video + "/"+filename
os.rename(src, new_destination)
event_handler = MyHandler()
observer = Observer()
observer.schedule(event_handler, folder_to_track, recursive=True)
observer.start()
try:
while True:
time.sleep(10)
print('正在监听...')
except KeyboardInterrupt:
observer.stop()
while True:
time.sleep(10)
print('正在监听...')
except KeyboardInterrupt:
observer.stop()
observer.join()
六、继续开发
这个【智慧相册】还是很简单的一个基于PaddleClas的demo项目,大家如果想要丰富还是可以的,比如将相册种类继续细化划分,加入视频分类等,或者大家有基于此项目的其他想法丰富可以联系我
七、部署使用
- 下载源码
git clone https://github.com/WangRongsheng/smart-photo
- 下载权重
下载lastest
文件夹,放到PaddleClas/output/ResNet50_vd
下
-
启动
命令行启动main.py
-
使用
将任意文件放入src
文件夹完成自动管理分类