提交 079ad695 编写于 作者: H HydrogenSulfate

Merge branch 'develop' into add_ShiTuV2_tipc

...@@ -4,64 +4,75 @@ ...@@ -4,64 +4,75 @@
## 简介 ## 简介
飞桨图像识别套件PaddleClas是飞桨为工业界和学术界所准备的一个图像识别和图像分类任务的工具集,助力使用者训练出更好的视觉模型和应用落地。 飞桨图像识别套件PaddleClas是飞桨为工业界和学术界所准备的一个图像识别和图像分类任务的工具集,助力使用者训练出更好的视觉模型和应用落地。 
<div align="center">
<img src="./docs/images/class_simple.gif" width = "600" />
<p>PULC实用图像分类模型效果展示</p>
</div>
&nbsp;
<div align="center"> <div align="center">
<img src="./docs/images/recognition.gif" width = "400" /> <img src="./docs/images/recognition.gif" width = "400" />
<p>PP-ShiTu图像识别系统效果展示</p> <p>PP-ShiTuV2图像识别系统效果展示</p>
</div> </div>
## 近期更新
- 📢将于**6月15-6月17日晚20:30** 进行为期三天的课程直播,详细介绍超轻量图像分类方案,对各场景模型优化原理及使用方式进行拆解,之后还有产业案例全流程实操,对各类痛难点解决方案进行手把手教学,加上现场互动答疑,抓紧扫码上车吧!
<div align="center"> <div align="center">
<img src="https://user-images.githubusercontent.com/45199522/173483779-2332f990-4941-4f8d-baee-69b62035fc31.png" width = "200" height = "200"/> <img src="./docs/images/class_simple.gif" width = "600" />
<p>PULC实用图像分类模型效果展示</p>
</div> </div>
- 🔥️ 2022.6.15 发布[PULC超轻量图像分类实用方案](docs/zh_CN/PULC/PULC_train.md),CPU推理3ms,精度比肩SwinTransformer,覆盖人、车、OCR场景九大常见任务。
- 2022.5.26 [飞桨产业实践范例直播课](http://aglc.cn/v-c4FAR),解读**超轻量重点区域人员出入管理方案**
- 2022.5.23 新增[人员出入管理范例库](https://aistudio.baidu.com/aistudio/projectdetail/4094475),具体内容可以在 AI Studio 上体验。 ## 近期更新
- 🔥️ 发布[PP-ShiTuV2](./docs/zh_CN/PPShiTu/PPShiTuV2_introduction.md),recall1精度提升10个点,覆盖20+识别场景,新增库管理工具,Android Demo全新体验。
- 2022.9.4 新增[生鲜产品自主结算范例库](https://aistudio.baidu.com/aistudio/projectdetail/4486158),具体内容可以在AI Studio上体验。
- 2022.6.15 发布[PULC超轻量图像分类实用方案](docs/zh_CN/PULC/PULC_train.md),CPU推理3ms,精度比肩SwinTransformer,覆盖人、车、OCR场景九大常见任务。
- 2022.5.23 新增[人员出入管理范例库](https://aistudio.baidu.com/aistudio/projectdetail/4094475),具体内容可以在 AI Studio 上体验。
- 2022.5.20 上线[PP-HGNet](./docs/zh_CN/models/PP-HGNet.md), [PP-LCNetv2](./docs/zh_CN/models/PP-LCNetV2.md) - 2022.5.20 上线[PP-HGNet](./docs/zh_CN/models/PP-HGNet.md), [PP-LCNetv2](./docs/zh_CN/models/PP-LCNetV2.md)
- 2022.4.21 新增 CVPR2022 oral论文 [MixFormer](https://arxiv.org/pdf/2204.02557.pdf) 相关[代码](https://github.com/PaddlePaddle/PaddleClas/pull/1820/files)
- [more](./docs/zh_CN/others/update_history.md) - [more](./docs/zh_CN/others/update_history.md)
## 特性 ## 特性
PaddleClas发布了[PP-HGNet](docs/zh_CN/models/PP-HGNet.md)[PP-LCNetv2](docs/zh_CN/models/PP-LCNetV2.md)[PP-LCNet](docs/zh_CN/models/PP-LCNet.md)[SSLD半监督知识蒸馏方案](docs/zh_CN/advanced_tutorials/ssld.md)等算法, PaddleClas发布了[PP-HGNet](docs/zh_CN/models/PP-HGNet.md)[PP-LCNetv2](docs/zh_CN/models/PP-LCNetV2.md)[PP-LCNet](docs/zh_CN/models/PP-LCNet.md)[SSLD半监督知识蒸馏方案](docs/zh_CN/advanced_tutorials/ssld.md)等算法,
并支持多种图像分类、识别相关算法,在此基础上打造[PULC超轻量图像分类方案](docs/zh_CN/PULC/PULC_quickstart.md)[PP-ShiTu图像识别系统](./docs/zh_CN/quick_start/quick_start_recognition.md) 并支持多种图像分类、识别相关算法,在此基础上打造[PULC超轻量图像分类方案](docs/zh_CN/PULC/PULC_quickstart.md)[PP-ShiTu图像识别系统](./docs/zh_CN/quick_start/quick_start_recognition.md)
![](https://user-images.githubusercontent.com/19523330/173273046-239a42da-c88d-4c2c-94b1-2134557afa49.png) ![](https://user-images.githubusercontent.com/11568925/188055076-7b0e3492-55cb-420f-b61a-160d4dd0bb0d.png)
## 欢迎加入技术交流群 ## 欢迎加入技术交流群
* 您可以扫描下面的微信/QQ二维码(添加小助手微信并回复“C”),加入PaddleClas微信交流群,获得更高效的问题答疑,与各行各业开发者充分交流,期待您的加入。 * 欢迎加入PaddleClas 微信用户群(扫码填写问卷即可入群)
<div align="center"> <div align="center">
<img src="https://user-images.githubusercontent.com/48054808/160531099-9811bbe6-cfbb-47d5-8bdb-c2b40684d7dd.png" width="200"/> <img src="https://user-images.githubusercontent.com/45199522/173483779-2332f990-4941-4f8d-baee-69b62035fc31.png" width = "200" height = "200"/>
<img src="https://user-images.githubusercontent.com/80816848/164383225-e375eb86-716e-41b4-a9e0-4b8a3976c1aa.jpg" width="200"/>
</div> </div>
## 快速体验 ## 快速体验
PULC超轻量图像分类方案快速体验:[点击这里](docs/zh_CN/PULC/PULC_quickstart.md) PULC超轻量图像分类方案快速体验:[点击这里](docs/zh_CN/PULC/PULC_quickstart.md)
PP-ShiTu图像识别快速体验:[点击这里](./docs/zh_CN/quick_start/quick_start_recognition.md) PP-ShiTu图像识别快速体验:[点击这里](./docs/zh_CN/quick_start/quick_start_recognition.md)
<div align="center">
<img src="./docs/images/quick_start/android_demo/PPShiTu_qrcode.png" width = "400" />
<p>PP-ShiTuV2 Android Demo</p>
</div>
## 文档教程 ## 文档教程
- [环境准备](docs/zh_CN/installation/install_paddleclas.md) - [环境准备](docs/zh_CN/installation/install_paddleclas.md)
- [PP-ShiTuV2图像识别系统介绍](./docs/zh_CN/PPShiTu/PPShiTuV2_introduction.md)
- [图像识别快速体验](docs/zh_CN/quick_start/quick_start_recognition.md)
- 模块介绍
- [主体检测](./docs/zh_CN/image_recognition_pipeline/mainbody_detection.md)
- [特征提取模型](./docs/zh_CN/image_recognition_pipeline/feature_extraction.md)
- [向量检索](./docs/zh_CN/image_recognition_pipeline/vector_search.md)
- [哈希编码](docs/zh_CN/image_recognition_pipeline/)
- [模型训练](docs/zh_CN/models_training/recognition.md)
- 推理部署
- [基于python预测引擎推理](docs/zh_CN/inference_deployment/python_deploy.md#2)
- [基于C++预测引擎推理](deploy/cpp_shitu/readme.md)
- [服务化部署](docs/zh_CN/inference_deployment/recognition_serving_deploy.md)
- [端侧部署](docs/zh_CN/inference_deployment/lite_shitu.md)
- [库管理工具](docs/zh_CN/inference_deployment/shitu_gallery_manager.md)
- [20+应用场景库](docs/zh_CN/introduction/ppshitu_application_scenarios.md)
- [PULC超轻量图像分类实用方案](docs/zh_CN/PULC/PULC_train.md) - [PULC超轻量图像分类实用方案](docs/zh_CN/PULC/PULC_train.md)
- [超轻量图像分类快速体验](docs/zh_CN/PULC/PULC_quickstart.md) - [超轻量图像分类快速体验](docs/zh_CN/PULC/PULC_quickstart.md)
- [超轻量图像分类模型库](docs/zh_CN/PULC/PULC_model_list.md) - [超轻量图像分类模型库](docs/zh_CN/PULC/PULC_model_list.md)
...@@ -82,19 +93,6 @@ PP-ShiTu图像识别快速体验:[点击这里](./docs/zh_CN/quick_start/quick ...@@ -82,19 +93,6 @@ PP-ShiTu图像识别快速体验:[点击这里](./docs/zh_CN/quick_start/quick
- [端侧部署](docs/zh_CN/inference_deployment/paddle_lite_deploy.md) - [端侧部署](docs/zh_CN/inference_deployment/paddle_lite_deploy.md)
- [Paddle2ONNX模型转化与预测](deploy/paddle2onnx/readme.md) - [Paddle2ONNX模型转化与预测](deploy/paddle2onnx/readme.md)
- [模型压缩](deploy/slim/README.md) - [模型压缩](deploy/slim/README.md)
- [PP-ShiTu图像识别系统介绍](#图像识别系统介绍)
- [图像识别快速体验](docs/zh_CN/quick_start/quick_start_recognition.md)
- 模块介绍
- [主体检测](./docs/zh_CN/image_recognition_pipeline/mainbody_detection.md)
- [特征提取模型](./docs/zh_CN/image_recognition_pipeline/feature_extraction.md)
- [向量检索](./docs/zh_CN/image_recognition_pipeline/vector_search.md)
- [哈希编码](docs/zh_CN/image_recognition_pipeline/)
- [模型训练](docs/zh_CN/models_training/recognition.md)
- 推理部署
- [基于python预测引擎推理](docs/zh_CN/inference_deployment/python_deploy.md#2)
- [基于C++预测引擎推理](deploy/cpp_shitu/readme.md)
- [服务化部署](docs/zh_CN/inference_deployment/recognition_serving_deploy.md)
- [端侧部署](deploy/lite_shitu/README.md)
- PP系列骨干网络模型 - PP系列骨干网络模型
- [PP-HGNet](docs/zh_CN/models/PP-HGNet.md) - [PP-HGNet](docs/zh_CN/models/PP-HGNet.md)
- [PP-LCNetv2](docs/zh_CN/models/PP-LCNetV2.md) - [PP-LCNetv2](docs/zh_CN/models/PP-LCNetV2.md)
...@@ -117,59 +115,75 @@ PP-ShiTu图像识别快速体验:[点击这里](./docs/zh_CN/quick_start/quick ...@@ -117,59 +115,75 @@ PP-ShiTu图像识别快速体验:[点击这里](./docs/zh_CN/quick_start/quick
- [许可证书](#许可证书) - [许可证书](#许可证书)
- [贡献代码](#贡献代码) - [贡献代码](#贡献代码)
<a name="PULC超轻量图像分类方案"></a>
## PULC超轻量图像分类方案
<div align="center">
<img src="https://user-images.githubusercontent.com/19523330/173011854-b10fcd7a-b799-4dfd-a1cf-9504952a3c44.png" width = "800" />
</div>
PULC融合了骨干网络、数据增广、蒸馏等多种前沿算法,可以自动训练得到轻量且高精度的图像分类模型。
PaddleClas提供了覆盖人、车、OCR场景九大常见任务的分类模型,CPU推理3ms,精度比肩SwinTransformer。
<a name="图像识别系统介绍"></a> <a name="图像识别系统介绍"></a>
## PP-ShiTu图像识别系统
## PP-ShiTuV2图像识别系统
<div align="center"> <div align="center">
<img src="./docs/images/structure.jpg" width = "800" /> <img src="./docs/images/structure.jpg" width = "800" />
</div> </div>
PP-ShiTu是一个实用的轻量级通用图像识别系统,主要由主体检测、特征学习和向量检索三个模块组成。该系统从骨干网络选择和调整、损失函数的选择、数据增强、学习率变换策略、正则化参数选择、预训练模型使用以及模型裁剪量化8个方面,采用多种策略,对各个模块的模型进行优化,最终得到在CPU上仅0.2s即可完成10w+库的图像识别的系统。更多细节请参考[PP-ShiTu技术方案](https://arxiv.org/pdf/2111.00775.pdf)
<a name="分类效果展示"></a> PP-ShiTuV2是一个实用的轻量级通用图像识别系统,主要由主体检测、特征学习和向量检索三个模块组成。该系统从骨干网络选择和调整、损失函数的选择、数据增强、学习率变换策略、正则化参数选择、预训练模型使用以及模型裁剪量化多个方面,采用多种策略,对各个模块的模型进行优化,PP-ShiTuV2相比V1而已,Recall1提升10+个点。更多细节请参考[PP-ShiTuV2详细介绍](./docs/zh_CN/PPShiTu/PPShiTuV2_introduction.md)
## PULC实用图像分类模型效果展示
<div align="center">
<img src="docs/images/classification.gif">
</div>
<a name="识别效果展示"></a> <a name="识别效果展示"></a>
## PP-ShiTu图像识别系统效果展示
## PP-ShiTuV2图像识别系统效果展示
- 瓶装饮料识别 - 瓶装饮料识别
<div align="center"> <div align="center">
<img src="docs/images/drink_demo.gif"> <img src="docs/images/drink_demo.gif">
</div> </div>
- 商品识别 - 商品识别
<div align="center"> <div align="center">
<img src="https://user-images.githubusercontent.com/18028216/122769644-51604f80-d2d7-11eb-8290-c53b12a5c1f6.gif" width = "400" /> <img src="https://user-images.githubusercontent.com/18028216/122769644-51604f80-d2d7-11eb-8290-c53b12a5c1f6.gif" width = "400" />
</div> </div>
- 动漫人物识别 - 动漫人物识别
<div align="center"> <div align="center">
<img src="https://user-images.githubusercontent.com/18028216/122769746-6b019700-d2d7-11eb-86df-f1d710999ba6.gif" width = "400" /> <img src="https://user-images.githubusercontent.com/18028216/122769746-6b019700-d2d7-11eb-86df-f1d710999ba6.gif" width = "400" />
</div> </div>
- logo识别 - logo识别
<div align="center"> <div align="center">
<img src="https://user-images.githubusercontent.com/18028216/122769837-7fde2a80-d2d7-11eb-9b69-04140e9d785f.gif" width = "400" /> <img src="https://user-images.githubusercontent.com/18028216/122769837-7fde2a80-d2d7-11eb-9b69-04140e9d785f.gif" width = "400" />
</div> </div>
- 车辆识别 - 车辆识别
<div align="center"> <div align="center">
<img src="https://user-images.githubusercontent.com/18028216/122769916-8ec4dd00-d2d7-11eb-8c60-42d89e25030c.gif" width = "400" /> <img src="https://user-images.githubusercontent.com/18028216/122769916-8ec4dd00-d2d7-11eb-8c60-42d89e25030c.gif" width = "400" />
</div> </div>
<a name="PULC超轻量图像分类方案"></a>
## PULC超轻量图像分类方案
<div align="center">
<img src="https://user-images.githubusercontent.com/19523330/173011854-b10fcd7a-b799-4dfd-a1cf-9504952a3c44.png" width = "800" />
</div>
PULC融合了骨干网络、数据增广、蒸馏等多种前沿算法,可以自动训练得到轻量且高精度的图像分类模型。
PaddleClas提供了覆盖人、车、OCR场景九大常见任务的分类模型,CPU推理3ms,精度比肩SwinTransformer。
<a name="分类效果展示"></a>
## PULC实用图像分类模型效果展示
<div align="center">
<img src="docs/images/classification.gif">
</div>
<a name="许可证书"></a> <a name="许可证书"></a>
## 许可证书 ## 许可证书
......
...@@ -22,7 +22,7 @@ PreProcess: ...@@ -22,7 +22,7 @@ PreProcess:
scale: 0.00392157 scale: 0.00392157
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
channel_num: 3 channel_num: 3
- ToCHWImage: - ToCHWImage:
......
...@@ -6,7 +6,7 @@ Global: ...@@ -6,7 +6,7 @@ Global:
threshold: 0.2 threshold: 0.2
max_det_results: 1 max_det_results: 1
label_list: label_list:
- foreground - foreground
# inference engine config # inference engine config
use_gpu: True use_gpu: True
...@@ -30,5 +30,5 @@ DetPreProcess: ...@@ -30,5 +30,5 @@ DetPreProcess:
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
- DetPermute: {} - DetPermute: {}
DetPostProcess: {} DetPostProcess: {}
\ No newline at end of file
Global: Global:
infer_imgs: "./drink_dataset_v1.0/test_images/hongniu_1.jpg" infer_imgs: "./drink_dataset_v2.0/test_images/100.jpeg"
det_inference_model_dir: "./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer" det_inference_model_dir: "./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer"
rec_inference_model_dir: "./models/general_PPLCNet_x2_5_lite_v1.0_infer" rec_inference_model_dir: "./models/general_PPLCNetV2_base_pretrained_v1.0_infer"
rec_nms_thresold: 0.05 rec_nms_thresold: 0.05
batch_size: 1 batch_size: 1
...@@ -9,7 +9,7 @@ Global: ...@@ -9,7 +9,7 @@ Global:
threshold: 0.2 threshold: 0.2
max_det_results: 5 max_det_results: 5
label_list: label_list:
- foreground - foreground
use_gpu: True use_gpu: True
enable_mkldnn: False enable_mkldnn: False
...@@ -43,7 +43,7 @@ RecPreProcess: ...@@ -43,7 +43,7 @@ RecPreProcess:
scale: 0.00392157 scale: 0.00392157
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
- ToCHWImage: - ToCHWImage:
RecPostProcess: null RecPostProcess: null
...@@ -51,13 +51,13 @@ RecPostProcess: null ...@@ -51,13 +51,13 @@ RecPostProcess: null
# indexing engine config # indexing engine config
IndexProcess: IndexProcess:
index_method: "HNSW32" # supported: HNSW32, IVF, Flat index_method: "HNSW32" # supported: HNSW32, IVF, Flat
image_root: "./drink_dataset_v1.0/gallery" image_root: "./drink_dataset_v2.0/gallery"
index_dir: "./drink_dataset_v1.0/index" index_dir: "./drink_dataset_v2.0/index"
data_file: "./drink_dataset_v1.0/gallery/drink_label.txt" data_file: "./drink_dataset_v2.0/gallery/drink_label.txt"
index_operation: "new" # suported: "append", "remove", "new" index_operation: "new" # suported: "append", "remove", "new"
delimiter: "\t" delimiter: "\t"
dist_type: "IP" dist_type: "IP"
embedding_size: 512 embedding_size: 512
batch_size: 32 batch_size: 32
return_k: 5 return_k: 5
score_thres: 0.4 score_thres: 0.4
\ No newline at end of file
Global: Global:
infer_imgs: "./drink_dataset_v1.0/test_images/nongfu_spring.jpeg" infer_imgs: "./drink_dataset_v2.0/test_images/100.jpeg"
det_inference_model_dir: "./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer" det_inference_model_dir: "./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer"
rec_inference_model_dir: "./models/general_PPLCNet_x2_5_lite_v1.0_infer" rec_inference_model_dir: "./models/general_PPLCNetV2_base_pretrained_v1.0_infer"
rec_nms_thresold: 0.05 rec_nms_thresold: 0.05
batch_size: 1 batch_size: 1
...@@ -9,7 +9,7 @@ Global: ...@@ -9,7 +9,7 @@ Global:
threshold: 0.2 threshold: 0.2
max_det_results: 5 max_det_results: 5
label_list: label_list:
- foreground - foreground
use_gpu: True use_gpu: True
enable_mkldnn: True enable_mkldnn: True
...@@ -38,12 +38,15 @@ DetPostProcess: {} ...@@ -38,12 +38,15 @@ DetPostProcess: {}
RecPreProcess: RecPreProcess:
transform_ops: transform_ops:
- ResizeImage: - ResizeImage:
size: 224 size: [224, 224]
return_numpy: False
interpolation: bilinear
backend: cv2
- NormalizeImage: - NormalizeImage:
scale: 0.00392157 scale: 1.0/255.0
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: hwc
- ToCHWImage: - ToCHWImage:
RecPostProcess: null RecPostProcess: null
...@@ -51,13 +54,13 @@ RecPostProcess: null ...@@ -51,13 +54,13 @@ RecPostProcess: null
# indexing engine config # indexing engine config
IndexProcess: IndexProcess:
index_method: "HNSW32" # supported: HNSW32, IVF, Flat index_method: "HNSW32" # supported: HNSW32, IVF, Flat
image_root: "./drink_dataset_v1.0/gallery/" image_root: "./drink_dataset_v2.0/gallery/"
index_dir: "./drink_dataset_v1.0/index" index_dir: "./drink_dataset_v2.0/index"
data_file: "./drink_dataset_v1.0/gallery/drink_label.txt" data_file: "./drink_dataset_v2.0/gallery/drink_label.txt"
index_operation: "new" # suported: "append", "remove", "new" index_operation: "new" # suported: "append", "remove", "new"
delimiter: "\t" delimiter: "\t"
dist_type: "IP" dist_type: "IP"
embedding_size: 512 embedding_size: 512
batch_size: 32 batch_size: 32
return_k: 5 return_k: 5
score_thres: 0.5 score_thres: 0.5
\ No newline at end of file
Global: Global:
infer_imgs: "./images/wangzai.jpg" infer_imgs: "./images/wangzai.jpg"
rec_inference_model_dir: "./models/product_ResNet50_vd_aliproduct_v1.0_infer" rec_inference_model_dir: "./models/general_PPLCNetV2_base_pretrained_v1.0_infer"
batch_size: 1 batch_size: 1
use_gpu: False use_gpu: False
enable_mkldnn: True enable_mkldnn: True
...@@ -22,7 +22,7 @@ RecPreProcess: ...@@ -22,7 +22,7 @@ RecPreProcess:
scale: 0.00392157 scale: 0.00392157
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
- ToCHWImage: - ToCHWImage:
RecPostProcess: null RecPostProcess: null
\ No newline at end of file
# 服务器端C++预测 # 服务器端C++预测
本教程将介绍在服务器端部署PP-ShiTU的详细步骤。 本教程将介绍在服务器端部署PP-ShiTu的详细步骤。
## 目录 ## 目录
...@@ -30,39 +30,39 @@ ...@@ -30,39 +30,39 @@
- 下载最新版本cmake - 下载最新版本cmake
```shell ```shell
# 当前版本最新为3.22.0,根据实际情况自行下载,建议最新版本 # 当前版本最新为3.22.0,根据实际情况自行下载,建议最新版本
wget https://github.com/Kitware/CMake/releases/download/v3.22.0/cmake-3.22.0.tar.gz wget https://github.com/Kitware/CMake/releases/download/v3.22.0/cmake-3.22.0.tar.gz
tar xf cmake-3.22.0.tar.gz tar -xf cmake-3.22.0.tar.gz
``` ```
最终可以在当前目录下看到`cmake-3.22.0/`的文件夹。 最终可以在当前目录下看到`cmake-3.22.0/`的文件夹。
- 编译cmake,首先设置came源码路径(`root_path`)以及安装路径(`install_path`),`root_path`为下载的came源码路径,`install_path`为came的安装路径。在本例中,源码路径即为当前目录下的`cmake-3.22.0/` - 编译cmake,首先设置cmake源码路径(`root_path`)以及安装路径(`install_path`),`root_path`为下载的cmake源码路径,`install_path`为cmake的安装路径。在本例中,源码路径即为当前目录下的`cmake-3.22.0/`
```shell ```shell
cd ./cmake-3.22.0 cd ./cmake-3.22.0
export root_path=$PWD export root_path=$PWD
export install_path=${root_path}/cmake export install_path=${root_path}/cmake
``` ```
- 然后在cmake源码路径下,按照下面的方式进行编译 - 然后在cmake源码路径下,执行以下命令进行编译
```shell ```shell
./bootstrap --prefix=${install_path} ./bootstrap --prefix=${install_path}
make -j make -j
make install make install
``` ```
- 设置环境变量 - 编译安装cmake完成后,设置cmake的环境变量供后续程序使用
```shell ```shell
export PATH=${install_path}/bin:$PATH export PATH=${install_path}/bin:$PATH
#检查是否正常使用 #检查是否正常使用
cmake --version cmake --version
``` ```
此时,cmake就可以使用了 此时cmake就可以正常使用了
<a name="1.2"></a> <a name="1.2"></a>
...@@ -70,61 +70,66 @@ cmake --version ...@@ -70,61 +70,66 @@ cmake --version
* 首先需要从opencv官网上下载在Linux环境下源码编译的包,以3.4.7版本为例,下载及解压缩命令如下: * 首先需要从opencv官网上下载在Linux环境下源码编译的包,以3.4.7版本为例,下载及解压缩命令如下:
``` ```shell
wget https://github.com/opencv/opencv/archive/3.4.7.tar.gz wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/opencv-3.4.7.tar.gz
tar -xvf 3.4.7.tar.gz tar -xvf 3.4.7.tar.gz
``` ```
最终可以在当前目录下看到`opencv-3.4.7/`的文件夹。 最终可以在当前目录下看到`opencv-3.4.7/`的文件夹。
* 编译opencv,首先设置opencv源码路径(`root_path`)以及安装路径(`install_path`),`root_path`为下载的opencv源码路径,`install_path`为opencv的安装路径。在本例中,源码路径即为当前目录下的`opencv-3.4.7/` * 编译opencv,首先设置opencv源码路径(`root_path`)以及安装路径(`install_path`),`root_path`为下载的opencv源码路径,`install_path`为opencv的安装路径。在本例中,源码路径即为当前目录下的`opencv-3.4.7/`
```shell ```shell
cd ./opencv-3.4.7 # 进入deploy/cpp_shitu目录
export root_path=$PWD cd deploy/cpp_shitu
export install_path=${root_path}/opencv3
```
* 然后在opencv源码路径下,按照下面的方式进行编译。
```shell # 安装opencv
rm -rf build cd ./opencv-3.4.7
mkdir build export root_path=$PWD
cd build export install_path=${root_path}/opencv3
```
cmake .. \ * 然后在opencv源码路径下,按照下面的方式进行编译。
-DCMAKE_INSTALL_PREFIX=${install_path} \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DWITH_IPP=OFF \
-DBUILD_IPP_IW=OFF \
-DWITH_LAPACK=OFF \
-DWITH_EIGEN=OFF \
-DCMAKE_INSTALL_LIBDIR=lib64 \
-DWITH_ZLIB=ON \
-DBUILD_ZLIB=ON \
-DWITH_JPEG=ON \
-DBUILD_JPEG=ON \
-DWITH_PNG=ON \
-DBUILD_PNG=ON \
-DWITH_TIFF=ON \
-DBUILD_TIFF=ON
make -j ```shell
make install rm -rf build
``` mkdir build
cd build
cmake .. \
-DCMAKE_INSTALL_PREFIX=${install_path} \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DWITH_IPP=OFF \
-DBUILD_IPP_IW=OFF \
-DWITH_LAPACK=OFF \
-DWITH_EIGEN=OFF \
-DCMAKE_INSTALL_LIBDIR=lib64 \
-DWITH_ZLIB=ON \
-DBUILD_ZLIB=ON \
-DWITH_JPEG=ON \
-DBUILD_JPEG=ON \
-DWITH_PNG=ON \
-DBUILD_PNG=ON \
-DWITH_TIFF=ON \
-DBUILD_TIFF=ON
make -j
make install
```
* `make install`完成之后,会在该文件夹下生成opencv头文件和库文件,用于后面的PaddleClas代码编译。 * `make install`完成之后,会在该文件夹下生成opencv头文件和库文件,用于后面的PaddleClas代码编译。
以opencv3.4.7版本为例,最终在安装路径下的文件结构如下所示。**注意**:不同的opencv版本,下述的文件结构可能不同。 以opencv3.4.7版本为例,最终在安装路径下的文件结构如下所示。**注意**:不同的opencv版本,下述的文件结构可能不同。
``` ```log
opencv3/ opencv3/
|-- bin ├── bin
|-- include ├── include
|-- lib64 ├── lib
|-- share ├── lib64
``` └── share
```
<a name="1.3"></a> <a name="1.3"></a>
...@@ -139,44 +144,48 @@ opencv3/ ...@@ -139,44 +144,48 @@ opencv3/
* 如果希望获取最新预测库特性,可以从Paddle github上克隆最新代码,源码编译预测库。 * 如果希望获取最新预测库特性,可以从Paddle github上克隆最新代码,源码编译预测库。
* 可以参考[Paddle预测库官网](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16)的说明,从github上获取Paddle代码,然后进行编译,生成最新的预测库。使用git获取代码方法如下。 * 可以参考[Paddle预测库官网](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16)的说明,从github上获取Paddle代码,然后进行编译,生成最新的预测库。使用git获取代码方法如下。
```shell ```shell
git clone https://github.com/PaddlePaddle/Paddle.git # 进入deploy/cpp_shitu目录
``` cd deploy/cpp_shitu
* 进入Paddle目录后,使用如下方法编译。 git clone https://github.com/PaddlePaddle/Paddle.git
```
```shell * 进入Paddle目录后,使用如下方法编译。
rm -rf build
mkdir build
cd build
cmake .. \ ```shell
-DWITH_CONTRIB=OFF \ rm -rf build
-DWITH_MKL=ON \ mkdir build
-DWITH_MKLDNN=ON \ cd build
-DWITH_TESTING=OFF \
-DCMAKE_BUILD_TYPE=Release \ cmake .. \
-DWITH_INFERENCE_API_TEST=OFF \ -DWITH_CONTRIB=OFF \
-DON_INFER=ON \ -DWITH_MKL=ON \
-DWITH_PYTHON=ON -DWITH_MKLDNN=ON \
make -j -DWITH_TESTING=OFF \
make inference_lib_dist -DCMAKE_BUILD_TYPE=Release \
``` -DWITH_INFERENCE_API_TEST=OFF \
-DON_INFER=ON \
-DWITH_PYTHON=ON
make -j
make inference_lib_dist
```
更多编译参数选项可以参考[Paddle C++预测库官网](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16) 更多编译参数选项可以参考[Paddle C++预测库官网](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/05_inference_deployment/inference/build_and_install_lib_cn.html#id16)
* 编译完成之后,可以在`build/paddle_inference_install_dir/`文件下看到生成了以下文件及文件夹。 * 编译完成之后,可以在`build/paddle_inference_install_dir/`文件下看到生成了以下文件及文件夹。
``` ```log
build/paddle_inference_install_dir/ build/paddle_inference_install_dir/
|-- CMakeCache.txt ├── CMakeCache.txt
|-- paddle ├── paddle
|-- third_party ├── third_party
|-- version.txt └── version.txt
``` ```
其中`paddle`就是之后进行C++预测时所需的Paddle库,`version.txt`中包含当前预测库的版本信息。 其中`paddle`就是之后进行C++预测时所需的Paddle库,`version.txt`中包含当前预测库的版本信息。
<a name="1.3.2"></a> <a name="1.3.2"></a>
...@@ -187,33 +196,41 @@ build/paddle_inference_install_dir/ ...@@ -187,33 +196,41 @@ build/paddle_inference_install_dir/
`https://paddle-inference-lib.bj.bcebos.com/2.1.1-gpu-cuda10.2-cudnn8.1-mkl-gcc8.2/paddle_inference.tgz``develop`版本为例,使用下述命令下载并解压: `https://paddle-inference-lib.bj.bcebos.com/2.1.1-gpu-cuda10.2-cudnn8.1-mkl-gcc8.2/paddle_inference.tgz``develop`版本为例,使用下述命令下载并解压:
```shell ```shell
wget https://paddle-inference-lib.bj.bcebos.com/2.1.1-gpu-cuda10.2-cudnn8.1-mkl-gcc8.2/paddle_inference.tgz # 进入deploy/cpp_shitu目录
cd deploy/cpp_shitu
tar -xvf paddle_inference.tgz wget https://paddle-inference-lib.bj.bcebos.com/2.1.1-gpu-cuda10.2-cudnn8.1-mkl-gcc8.2/paddle_inference.tgz
```
tar -xvf paddle_inference.tgz
```
最终会在当前的文件夹中生成`paddle_inference/`的子文件夹。 最终会在当前的文件夹中生成`paddle_inference/`的子文件夹。
<a name="1.4"></a> <a name="1.4"></a>
### 1.4 安装faiss库 ### 1.4 安装faiss库
在安装`faiss`前,请安装`openblas``ubuntu`系统中安装命令如下:
```shell ```shell
# 下载 faiss apt-get install libopenblas-dev
git clone https://github.com/facebookresearch/faiss.git
cd faiss
export faiss_install_path=$PWD/faiss_install
cmake -B build . -DFAISS_ENABLE_PYTHON=OFF -DCMAKE_INSTALL_PREFIX=${faiss_install_path}
make -C build -j faiss
make -C build install
``` ```
在安装`faiss`前,请安装`openblas``ubuntu`系统中安装命令如下: 然后按照以下命令编译并安装faiss
```shell ```shell
apt-get install libopenblas-dev # 进入deploy/cpp_shitu目录
cd deploy/cpp_shitu
# 下载 faiss
git clone https://github.com/facebookresearch/faiss.git
cd faiss
export faiss_install_path=$PWD/faiss_install
cmake -B build . -DFAISS_ENABLE_PYTHON=OFF -DCMAKE_INSTALL_PREFIX=${faiss_install_path}
make -C build -j faiss
make -C build install
``` ```
注意本教程以安装faiss cpu版本为例,安装时请参考[faiss](https://github.com/facebookresearch/faiss)官网文档,根据需求自行安装。 注意本教程以安装faiss cpu版本为例,安装时请参考[faiss](https://github.com/facebookresearch/faiss)官网文档,根据需求自行安装。
...@@ -224,12 +241,14 @@ apt-get install libopenblas-dev ...@@ -224,12 +241,14 @@ apt-get install libopenblas-dev
编译命令如下,其中Paddle C++预测库、opencv等其他依赖库的地址需要换成自己机器上的实际地址。同时,编译过程中需要下载编译`yaml-cpp`等C++库,请保持联网环境。 编译命令如下,其中Paddle C++预测库、opencv等其他依赖库的地址需要换成自己机器上的实际地址。同时,编译过程中需要下载编译`yaml-cpp`等C++库,请保持联网环境。
```shell ```shell
# 进入deploy/cpp_shitu目录
cd deploy/cpp_shitu
sh tools/build.sh sh tools/build.sh
``` ```
具体地,`tools/build.sh`中内容如下,请根据具体路径修改。 具体地,`tools/build.sh`中内容如下,请根据具体路径和配置情况进行修改。
```shell ```shell
OPENCV_DIR=${opencv_install_dir} OPENCV_DIR=${opencv_install_dir}
...@@ -261,14 +280,13 @@ cd .. ...@@ -261,14 +280,13 @@ cd ..
上述命令中, 上述命令中,
* `OPENCV_DIR`为opencv编译安装的地址(本例中为`opencv-3.4.7/opencv3`文件夹的路径); * `OPENCV_DIR`:opencv编译安装的地址(本例中为`opencv-3.4.7/opencv3`文件夹的路径);
* `LIB_DIR`为下载的Paddle预测库(`paddle_inference`文件夹),或编译生成的Paddle预测库(`build/paddle_inference_install_dir`文件夹)的路径; * `LIB_DIR`:下载的Paddle预测库(`paddle_inference`文件夹),或编译生成的Paddle预测库(`build/paddle_inference_install_dir`文件夹)的路径;
* `CUDA_LIB_DIR`为cuda库文件地址,在docker中为`/usr/local/cuda/lib64` * `CUDA_LIB_DIR`:cuda库文件地址,在docker中为`/usr/local/cuda/lib64`
* `CUDNN_LIB_DIR`为cudnn库文件地址,在docker中为`/usr/lib/x86_64-linux-gnu/` * `CUDNN_LIB_DIR`:cudnn库文件地址,在docker中为`/usr/lib/x86_64-linux-gnu/`
* `TENSORRT_DIR`是tensorrt库文件地址,在dokcer中为`/usr/local/TensorRT6-cuda10.0-cudnn7/`,TensorRT需要结合GPU使用。 * `TENSORRT_DIR`:tensorrt库文件地址,在dokcer中为`/usr/local/TensorRT6-cuda10.0-cudnn7/`,TensorRT需要结合GPU使用。
* `FAISS_DIR`是faiss的安装地址 * `FAISS_DIR`:faiss的安装地址
* `FAISS_WITH_MKL`是指在编译faiss的过程中,是否使用了mkldnn,本文档中编译faiss,没有使用,而使用了openblas,故设置为`OFF`,若使用了mkldnn,则为`ON`. * `FAISS_WITH_MKL`:指在编译faiss的过程中是否使用mkldnn,本文档中编译faiss没有使用,而使用了openblas,故设置为`OFF`,若使用了mkldnn则为`ON`.
在执行上述命令,编译完成之后,会在当前路径下生成`build`文件夹,其中生成一个名为`pp_shitu`的可执行文件。 在执行上述命令,编译完成之后,会在当前路径下生成`build`文件夹,其中生成一个名为`pp_shitu`的可执行文件。
...@@ -276,60 +294,68 @@ cd .. ...@@ -276,60 +294,68 @@ cd ..
## 3. 运行demo ## 3. 运行demo
- 请参考[识别快速开始文档](../../docs/zh_CN/quick_start/quick_start_recognition.md),下载好相应的 轻量级通用主体检测模型、轻量级通用识别模型及瓶装饮料测试数据并解压。 - 按照如下命令下载好相应的轻量级通用主体检测模型、轻量级通用识别模型及瓶装饮料测试数据并解压。
```shell ```shell
# 进入deploy目录
cd deploy/
mkdir models mkdir models
cd models cd models
# 下载并解压主体检测模型
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar
tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar # 下载并解压特征提取模型
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/PP-ShiTuV2/general_PPLCNetV2_base_pretrained_v1.0_infer.tar
tar -xf general_PPLCNetV2_base_pretrained_v1.0_infer.tar
cd .. cd ..
mkdir data mkdir data
cd data cd data
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v2.0.tar
tar -xf drink_dataset_v1.0.tar tar -xf drink_dataset_v2.0.tar
cd .. cd ..
``` ```
- 将相应的yaml文件拷到当前文件夹下 - 将相应的yaml文件拷到当前文件夹下
```shell ```shell
cp ../configs/inference_drink.yaml . cp ../configs/inference_drink.yaml ./
``` ```
-`inference_drink.yaml`中的相对路径,改成基于本目录的路径或者绝对路径。涉及到的参数有 -`inference_drink.yaml`中的相对路径,改成基于 `deploy/cpp_shitu` 目录的相对路径或者绝对路径。涉及到的参数有
- Global.infer_imgs :此参数可以是具体的图像地址,也可以是图像集所在的目录 - `Global.infer_imgs` :此参数可以是具体的图像地址,也可以是图像集所在的目录
- Global.det_inference_model_dir : 检测模型存储目录 - `Global.det_inference_model_dir` : 检测模型存储目录
- Global.rec_inference_model_dir : 识别模型存储目录 - `Global.rec_inference_model_dir` : 识别模型存储目录
- IndexProcess.index_dir : 检索库的存储目录,在示例中,检索库在下载的demo数据中。 - `IndexProcess.index_dir` : 检索库的存储目录,在示例中,检索库在下载的demo数据中。
- 字典转换 - 标签文件转换
由于python的检索库的字典,使用`pickle`进行的序列化存储,导致C++不方便读取,因此进行转换 由于python的检索库的字典是使用`pickle`转换得到的序列化存储结果,导致C++不方便读取,因此需要先转换成普通的文本文件。
```shell ```shell
python tools/transform_id_map.py -c inference_drink.yaml python3.7 tools/transform_id_map.py -c inference_drink.yaml
``` ```
转换成功后,在`IndexProcess.index_dir`目录下生成`id_map.txt`方便c++ 读取。 转换成功后,在`IndexProcess.index_dir`目录下生成`id_map.txt`以便在C++推理时读取。
- 执行程序 - 执行程序
```shell ```shell
./build/pp_shitu -c inference_drink.yaml ./build/pp_shitu -c inference_drink.yaml
# or
./build/pp_shitu -config inference_drink.yaml
``` ```
若对图像集进行检索,则可能得到,如下结果。注意,此结果只做展示,具体以实际运行结果为准。 `drink_dataset_v2.0/test_images/nongfu_spring.jpeg` 作为输入图像,则执行上述推理命令可以得到如下结果
同时,需注意的是,由于opencv 版本问题,会导致图像在预处理的过程中,resize产生细微差别,导致python 和c++结果,轻微不同,如bbox相差几个像素,检索结果小数点后3位diff等。但不会改变最终检索label。 ```log
../../deploy/drink_dataset_v2.0/test_images/nongfu_spring.jpeg:
result0: bbox[0, 0, 729, 1094], score: 0.688691, label: 农夫山泉-饮用天然水
```
![](../../docs/images/quick_start/shitu_c++_result.png) 由于python和C++的opencv实现存在部分不同,可能导致python推理和C++推理结果有微小差异。但基本不影响最终的检索结果。
<a name="4"></a> <a name="4"></a>
......
# PP-ShiTu在Paddle-Lite端侧部署
本教程将介绍基于[Paddle Lite](https://github.com/PaddlePaddle/Paddle-Lite) 在移动端部署PaddleClas PP-ShiTu模型的详细步骤。
Paddle Lite是飞桨轻量化推理引擎,为手机、IoT端提供高效推理能力,并广泛整合跨平台硬件,为端侧部署及应用落地问题提供轻量化的部署方案。
## 1. 准备环境
### 运行准备
- 电脑(编译Paddle Lite)
- 安卓手机(armv7或armv8)
### 1.1 准备交叉编译环境
交叉编译环境用于编译 Paddle Lite 和 PaddleClas 的PP-ShiTu Lite demo。
支持多种开发环境,不同开发环境的编译流程请参考对应文档,请确保安装完成Java jdk、Android NDK(R17以上)。
1. [Docker](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#docker)
2. [Linux](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#linux)
3. [MAC OS](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#mac-os)
```shell
# 配置完成交叉编译环境后,更新环境变量
# for docker、Linux
source ~/.bashrc
# for Mac OS
source ~/.bash_profile
```
### 1.2 准备预测库
预测库有两种获取方式:
1. [**建议**]直接下载,预测库下载链接如下:
|平台| 架构 | 预测库下载链接|
|-|-|-|
|Android| arm7 | [inference_lite_lib](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.android.armv7.clang.c++_static.with_extra.with_cv.tar.gz) |
| Android | arm8 | [inference_lite_lib](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.android.armv8.clang.c++_static.with_extra.with_cv.tar.gz) |
| Android | arm8(FP16) | [inference_lite_lib](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.android.armv8_clang_c++_static_with_extra_with_cv_with_fp16.tiny_publish_427e46.zip) |
**注意**:1. 如果是从 Paddle-Lite [官方文档](https://paddle-lite.readthedocs.io/zh/latest/quick_start/release_lib.html#android-toolchain-gcc)下载的预测库,注意选择`with_extra=ON,with_cv=ON`的下载链接。2. 目前只提供Android端demo,IOS端demo可以参考[Paddle-Lite IOS demo](https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-ios-demo)
2. 编译Paddle-Lite得到预测库,Paddle-Lite的编译方式如下:
```shell
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
cd Paddle-Lite
# 如果使用编译方式,建议使用develop分支编译预测库
git checkout develop
# FP32
./lite/tools/build_android.sh --arch=armv8 --toolchain=clang --with_cv=ON --with_extra=ON
# FP16
./lite/tools/build_android.sh --arch=armv8 --toolchain=clang --with_cv=ON --with_extra=ON --with_arm82_fp16=ON
```
**注意**:编译Paddle-Lite获得预测库时,需要打开`--with_cv=ON --with_extra=ON`两个选项,`--arch`表示`arm`版本,这里指定为armv8,更多编译命令介绍请参考[链接](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_andriod.html#id2)
直接下载预测库并解压后,可以得到`inference_lite_lib.android.armv8.clang.c++_static.with_extra.with_cv/`文件夹,通过编译Paddle-Lite得到的预测库位于`Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/`文件夹下。
预测库的文件目录如下:
```
inference_lite_lib.android.armv8/
|-- cxx C++ 预测库和头文件
| |-- include C++ 头文件
| | |-- paddle_api.h
| | |-- paddle_image_preprocess.h
| | |-- paddle_lite_factory_helper.h
| | |-- paddle_place.h
| | |-- paddle_use_kernels.h
| | |-- paddle_use_ops.h
| | `-- paddle_use_passes.h
| `-- lib C++预测库
| |-- libpaddle_api_light_bundled.a C++静态库
| `-- libpaddle_light_api_shared.so C++动态库
|-- java Java预测库
| |-- jar
| | `-- PaddlePredictor.jar
| |-- so
| | `-- libpaddle_lite_jni.so
| `-- src
|-- demo C++和Java示例代码
| |-- cxx C++ 预测库demo
| `-- java Java 预测库demo
```
## 2 模型准备
### 2.1 模型准备
PaddleClas 提供了转换并优化后的推理模型,可以直接参考下方 2.1.1 小节进行下载。如果需要使用其他模型,请参考后续 2.1.2 小节自行转换并优化模型。
#### 2.1.1 使用PaddleClas提供的推理模型
```shell
# 进入lite_ppshitu目录
cd $PaddleClas/deploy/lite_shitu
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/lite/ppshitu_lite_models_v1.2.tar
tar -xf ppshitu_lite_models_v1.2.tar
rm -f ppshitu_lite_models_v1.2.tar
```
#### 2.1.2 使用其他模型
Paddle-Lite 提供了多种策略来自动优化原始的模型,其中包括量化、子图融合、混合调度、Kernel优选等方法,使用Paddle-Lite的`opt`工具可以自动对inference模型进行优化,目前支持两种优化方式,优化后的模型更轻量,模型运行速度更快。
**注意**:如果已经准备好了 `.nb` 结尾的模型文件,可以跳过此步骤。
##### 2.1.2.1 安装paddle_lite_opt工具
安装`paddle_lite_opt`工具有如下两种方法:
1. [**建议**]pip安装paddlelite并进行转换
```shell
pip install paddlelite==2.10rc
```
2. 源码编译Paddle-Lite生成`paddle_lite_opt`工具
模型优化需要Paddle-Lite的`opt`可执行文件,可以通过编译Paddle-Lite源码获得,编译步骤如下:
```shell
# 如果准备环境时已经clone了Paddle-Lite,则不用重新clone Paddle-Lite
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
cd Paddle-Lite
git checkout develop
# 启动编译
./lite/tools/build.sh build_optimize_tool
```
编译完成后,`opt`文件位于`build.opt/lite/api/`下,可通过如下方式查看`opt`的运行选项和使用方式;
```shell
cd build.opt/lite/api/
./opt
```
`opt`的使用方式与参数与上面的`paddle_lite_opt`完全一致。
之后使用`paddle_lite_opt`工具可以进行inference模型的转换。`paddle_lite_opt`的部分参数如下:
|选项|说明|
|-|-|
|--model_file|待优化的PaddlePaddle模型(combined形式)的网络结构文件路径|
|--param_file|待优化的PaddlePaddle模型(combined形式)的权重文件路径|
|--optimize_out_type|输出模型类型,目前支持两种类型:protobuf和naive_buffer,其中naive_buffer是一种更轻量级的序列化/反序列化实现,默认为naive_buffer|
|--optimize_out|优化模型的输出路径|
|--valid_targets|指定模型可执行的backend,默认为arm。目前可支持x86、arm、opencl、npu、xpu,可以同时指定多个backend(以空格分隔),Model Optimize Tool将会自动选择最佳方式。如果需要支持华为NPU(Kirin 810/990 Soc搭载的达芬奇架构NPU),应当设置为npu, arm|
更详细的`paddle_lite_opt`工具使用说明请参考[使用opt转化模型文档](https://paddle-lite.readthedocs.io/zh/latest/user_guides/opt/opt_bin.html)
`--model_file`表示inference模型的model文件地址,`--param_file`表示inference模型的param文件地址;`optimize_out`用于指定输出文件的名称(不需要添加`.nb`的后缀)。直接在命令行中运行`paddle_lite_opt`,也可以查看所有参数及其说明。
##### 2.1.2.2 转换示例
下面介绍使用`paddle_lite_opt`完成主体检测模型和识别模型的预训练模型,转成inference模型,最终转换成Paddle-Lite的优化模型的过程。
1. 转换主体检测模型
```shell
# 当前目录为 $PaddleClas/deploy/lite_shitu
# $code_path需替换成相应的运行目录,可以根据需要,将$code_path设置成需要的目录
export code_path=~
cd $code_path
git clone https://github.com/PaddlePaddle/PaddleDetection.git
# 进入PaddleDetection根目录
cd PaddleDetection
# 将预训练模型导出为inference模型
python tools/export_model.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml -o weights=https://paddledet.bj.bcebos.com/models/picodet_lcnet_x2_5_640_mainbody.pdparams export_post_process=False --output_dir=inference
# 将inference模型转化为Paddle-Lite优化模型
paddle_lite_opt --model_file=inference/picodet_lcnet_x2_5_640_mainbody/model.pdmodel --param_file=inference/picodet_lcnet_x2_5_640_mainbody/model.pdiparams --optimize_out=inference/picodet_lcnet_x2_5_640_mainbody/mainbody_det
# 将转好的模型复制到lite_shitu目录下
cd $PaddleClas/deploy/lite_shitu
mkdir models
cp $code_path/PaddleDetection/inference/picodet_lcnet_x2_5_640_mainbody/mainbody_det.nb $PaddleClas/deploy/lite_shitu/models
```
2. 转换识别模型
```shell
# 识别模型下载
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar
# 解压模型
tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar
# 转换为Paddle-Lite模型
paddle_lite_opt --model_file=general_PPLCNet_x2_5_lite_v1.0_infer/inference.pdmodel --param_file=general_PPLCNet_x2_5_lite_v1.0_infer/inference.pdiparams --optimize_out=general_PPLCNet_x2_5_lite_v1.0_infer/rec
# 将模型文件拷贝到lite_shitu下
cp general_PPLCNet_x2_5_lite_v1.0_infer/rec.nb deploy/lite_shitu/models/
```
**注意**`--optimize_out` 参数为优化后模型的保存路径,无需加后缀`.nb``--model_file` 参数为模型结构信息文件的路径,`--param_file` 参数为模型权重信息文件的路径,请注意文件名。
### 2.2 生成新的检索库
由于lite 版本的检索库用的是`faiss1.5.3`版本,与新版本不兼容,因此需要重新生成index库
#### 2.2.1 数据及环境配置
```shell
# 进入PaddleClas根目录
cd $PaddleClas
# 安装PaddleClas
python setup.py install
cd deploy
# 下载瓶装饮料数据集
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar && tar -xf drink_dataset_v1.0.tar
rm -rf drink_dataset_v1.0.tar
rm -rf drink_dataset_v1.0/index
# 安装1.5.3版本的faiss
pip install faiss-cpu==1.5.3
# 下载通用识别模型,可替换成自己的inference model
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar
tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar
rm -rf general_PPLCNet_x2_5_lite_v1.0_infer.tar
```
#### 2.2.2 生成新的index文件
```shell
# 生成新的index库,注意指定好识别模型的路径,同时将index_mothod修改成Flat,HNSW32和IVF在此版本中可能存在bug,请慎重使用。
# 如果使用自己的识别模型,对应的修改inference model的目录
python python/build_gallery.py -c configs/inference_drink.yaml -o Global.rec_inference_model_dir=general_PPLCNet_x2_5_lite_v1.0_infer -o IndexProcess.index_method=Flat
# 进入到lite_shitu目录
cd lite_shitu
mv ../drink_dataset_v1.0 .
```
### 2.3 将yaml文件转换成json文件
```shell
# 如果测试单张图像
python generate_json_config.py --det_model_path ppshitu_lite_models_v1.2/mainbody_PPLCNet_x2_5_640_v1.2_lite.nb --rec_model_path ppshitu_lite_models_v1.2/general_PPLCNet_x2_5_lite_v1.2_infer.nb --img_path images/demo.jpeg
# or
# 如果测试多张图像
python generate_json_config.py --det_model_path ppshitu_lite_models_v1.2/mainbody_PPLCNet_x2_5_640_v1.2_lite.nb --rec_model_path ppshitu_lite_models_v1.2/general_PPLCNet_x2_5_lite_v1.2_infer.nb --img_dir images
# 执行完成后,会在lit_shitu下生成shitu_config.json配置文件
```
### 2.4 index字典转换
由于python的检索库字典,使用`pickle`进行的序列化存储,导致C++不方便读取,因此需要进行转换
```shell
# 转化id_map.pkl为id_map.txt
python transform_id_map.py -c ../configs/inference_drink.yaml
```
转换成功后,会在`IndexProcess.index_dir`目录下生成`id_map.txt`
### 2.5 与手机联调
首先需要进行一些准备工作。
1. 准备一台arm8的安卓手机,如果编译的预测库是armv7,则需要arm7的手机,并修改Makefile中`ARM_ABI=arm7`
2. 电脑上安装ADB工具,用于调试。 ADB安装方式如下:
2.1. MAC电脑安装ADB:
```shell
brew cask install android-platform-tools
```
2.2. Linux安装ADB
```shell
sudo apt update
sudo apt install -y wget adb
```
2.3. Window安装ADB
win上安装需要去谷歌的安卓平台下载ADB软件包进行安装:[链接](https://developer.android.com/studio)
3. 手机连接电脑后,开启手机`USB调试`选项,选择`文件传输`模式,在电脑终端中输入:
```shell
adb devices
```
如果有device输出,则表示安装成功,如下所示:
```
List of devices attached
744be294 device
```
4. 编译lite部署代码生成移动端可执行文件
```shell
cd $PaddleClas/deploy/lite_shitu
# ${lite prediction library path}下载的Paddle-Lite库路径
inference_lite_path=${lite prediction library path}/inference_lite_lib.android.armv8.gcc.c++_static.with_extra.with_cv/
mkdir $inference_lite_path/demo/cxx/ppshitu_lite
cp -r * $inference_lite_path/demo/cxx/ppshitu_lite
cd $inference_lite_path/demo/cxx/ppshitu_lite
# 执行编译,等待完成后得到可执行文件main
make ARM_ABI=arm8
#如果是arm7,则执行 make ARM_ABI = arm7 (或者在Makefile中修改该项)
```
5. 准备优化后的模型、预测库文件、测试图像。
```shell
mkdir deploy
mv ppshitu_lite_models_v1.1 deploy/
mv drink_dataset_v1.0 deploy/
mv images deploy/
mv shitu_config.json deploy/
cp pp_shitu deploy/
# 将C++预测动态库so文件复制到deploy文件夹中
cp ../../../cxx/lib/libpaddle_light_api_shared.so deploy/
```
执行完成后,deploy文件夹下将有如下文件格式:
```shell
deploy/
|-- ppshitu_lite_models_v1.1/
| |--mainbody_PPLCNet_x2_5_640_quant_v1.1_lite.nb 优化后的主体检测模型文件
| |--general_PPLCNet_x2_5_lite_v1.1_infer.nb 优化后的识别模型文件
|-- images/
| |--demo.jpg 图片文件
|-- drink_dataset_v1.0/ 瓶装饮料demo数据
| |--index 检索index目录
|-- pp_shitu 生成的移动端执行文件
|-- shitu_config.json 执行时参数配置文件
|-- libpaddle_light_api_shared.so Paddle-Lite库文件
```
**注意:**
* `shitu_config.json` 包含了目标检测的超参数,请按需进行修改
6. 启动调试,上述步骤完成后就可以使用ADB将文件夹 `deploy/` push到手机上运行,步骤如下:
```shell
# 将上述deploy文件夹push到手机上
adb push deploy /data/local/tmp/
adb shell
cd /data/local/tmp/deploy
export LD_LIBRARY_PATH=/data/local/tmp/deploy:$LD_LIBRARY_PATH
# 修改权限为可执行
chmod 777 pp_shitu
# 执行程序
./pp_shitu shitu_config.json
```
如果对代码做了修改,则需要重新编译并push到手机上。
运行效果如下:
```
images/demo.jpeg:
result0: bbox[344, 98, 527, 593], score: 0.811656, label: 红牛-强化型
result1: bbox[0, 0, 600, 600], score: 0.729664, label: 红牛-强化型
```
## FAQ
Q1:如果想更换模型怎么办,需要重新按照流程走一遍吗?
A1:如果已经走通了上述步骤,更换模型只需要替换 `.nb` 模型文件即可,同时要注意修改下配置文件中的 `.nb` 文件路径以及类别映射文件(如有必要)。
Q2:换一个图测试怎么做?
A2:替换 deploy 下的测试图像为你想要测试的图像,并重新生成json配置文件(或者直接修改图像路径),使用 ADB 再次 push 到手机上即可。
../../docs/zh_CN/inference_deployment/lite_shitu.md
\ No newline at end of file
...@@ -9,15 +9,15 @@ ...@@ -9,15 +9,15 @@
# 默认编译时的${PWD}=PaddleClas/deploy/paddleserving/ # 默认编译时的${PWD}=PaddleClas/deploy/paddleserving/
python_name=${1:-'python'} export python_name=${1:-'python'}
apt-get update apt-get update
apt install -y libcurl4-openssl-dev libbz2-dev apt install -y libcurl4-openssl-dev libbz2-dev
wget -nc https://paddle-serving.bj.bcebos.com/others/centos_ssl.tar wget -nc https://paddle-serving.bj.bcebos.com/others/centos_ssl.tar
tar xf centos_ssl.tar tar xf centos_ssl.tar
rm -rf centos_ssl.tar rm -rf centos_ssl.tar
mv libcrypto.so.1.0.2k /usr/lib/libcrypto.so.1.0.2k \mv libcrypto.so.1.0.2k /usr/lib/libcrypto.so.1.0.2k
mv libssl.so.1.0.2k /usr/lib/libssl.so.1.0.2k \mv libssl.so.1.0.2k /usr/lib/libssl.so.1.0.2k
ln -sf /usr/lib/libcrypto.so.1.0.2k /usr/lib/libcrypto.so.10 ln -sf /usr/lib/libcrypto.so.1.0.2k /usr/lib/libcrypto.so.10
ln -sf /usr/lib/libssl.so.1.0.2k /usr/lib/libssl.so.10 ln -sf /usr/lib/libssl.so.1.0.2k /usr/lib/libssl.so.10
ln -sf /usr/lib/libcrypto.so.10 /usr/lib/libcrypto.so ln -sf /usr/lib/libcrypto.so.10 /usr/lib/libcrypto.so
......
...@@ -16,9 +16,8 @@ op: ...@@ -16,9 +16,8 @@ op:
#当op配置没有server_endpoints时,从local_service_conf读取本地服务配置 #当op配置没有server_endpoints时,从local_service_conf读取本地服务配置
local_service_conf: local_service_conf:
#uci模型路径 #uci模型路径
model_config: ../../models/general_PPLCNet_x2_5_lite_v1.0_serving model_config: ../../models/general_PPLCNetV2_base_pretrained_v1.0_serving
#计算硬件类型: 空缺时由devices决定(CPU/GPU),0=cpu, 1=gpu, 2=tensorRT, 3=arm cpu, 4=kunlun xpu #计算硬件类型: 空缺时由devices决定(CPU/GPU),0=cpu, 1=gpu, 2=tensorRT, 3=arm cpu, 4=kunlun xpu
device_type: 1 device_type: 1
...@@ -37,7 +36,7 @@ op: ...@@ -37,7 +36,7 @@ op:
local_service_conf: local_service_conf:
client_type: local_predictor client_type: local_predictor
device_type: 1 device_type: 1
devices: '0' devices: "0"
fetch_list: fetch_list:
- save_infer_model/scale_0.tmp_1 - save_infer_model/scale_0.tmp_1
model_config: ../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/ model_config: ../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/
import requests # Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
import json #
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import base64 import base64
import json
import os import os
imgpath = "../../drink_dataset_v1.0/test_images/001.jpeg" import requests
image_path = "../../drink_dataset_v2.0/test_images/100.jpeg"
def bytes_to_base64(image_bytes: bytes) -> bytes:
"""encode bytes using base64 algorithm
Args:
image_bytes (bytes): bytes object to be encoded
Returns:
bytes: base64 bytes
"""
return base64.b64encode(image_bytes).decode('utf8')
def cv2_to_base64(image):
return base64.b64encode(image).decode('utf8')
if __name__ == "__main__": if __name__ == "__main__":
url = "http://127.0.0.1:18081/recognition/prediction" url = "http://127.0.0.1:18081/recognition/prediction"
with open(os.path.join(".", imgpath), 'rb') as file: with open(os.path.join(".", image_path), 'rb') as file:
image_data1 = file.read() image_bytes = file.read()
image = cv2_to_base64(image_data1)
data = {"key": ["image"], "value": [image]} image_base64 = bytes_to_base64(image_bytes)
data = {"key": ["image"], "value": [image_base64]}
for i in range(1): for i in range(1):
r = requests.post(url=url, data=json.dumps(data)) r = requests.post(url=url, data=json.dumps(data))
......
...@@ -15,20 +15,33 @@ try: ...@@ -15,20 +15,33 @@ try:
from paddle_serving_server_gpu.pipeline import PipelineClient from paddle_serving_server_gpu.pipeline import PipelineClient
except ImportError: except ImportError:
from paddle_serving_server.pipeline import PipelineClient from paddle_serving_server.pipeline import PipelineClient
import base64 import base64
import os
client = PipelineClient() client = PipelineClient()
client.connect(['127.0.0.1:9994']) client.connect(['127.0.0.1:9994'])
imgpath = "../../drink_dataset_v1.0/test_images/001.jpeg" image_path = "../../drink_dataset_v2.0/test_images/100.jpeg"
def bytes_to_base64(image_bytes: bytes) -> bytes:
"""encode bytes using base64 algorithm
Args:
image_bytes (bytes): bytes to be encoded
Returns:
bytes: base64 bytes
"""
return base64.b64encode(image_bytes).decode('utf8')
def cv2_to_base64(image):
return base64.b64encode(image).decode('utf8')
if __name__ == "__main__": if __name__ == "__main__":
with open(imgpath, 'rb') as file: with open(os.path.join(".", image_path), 'rb') as file:
image_data = file.read() image_bytes = file.read()
image = cv2_to_base64(image_data) image_base64 = bytes_to_base64(image_bytes)
for i in range(1): for i in range(1):
ret = client.predict(feed_dict={"image": image}, fetch=["result"]) ret = client.predict(
feed_dict={"image": image_base64}, fetch=["result"])
print(ret) print(ret)
...@@ -15,7 +15,7 @@ feed_var { ...@@ -15,7 +15,7 @@ feed_var {
shape: 6 shape: 6
} }
fetch_var { fetch_var {
name: "save_infer_model/scale_0.tmp_1" name: "batch_norm_25.tmp_2"
alias_name: "features" alias_name: "features"
is_lod_tensor: false is_lod_tensor: false
fetch_type: 1 fetch_type: 1
......
...@@ -15,7 +15,7 @@ feed_var { ...@@ -15,7 +15,7 @@ feed_var {
shape: 6 shape: 6
} }
fetch_var { fetch_var {
name: "save_infer_model/scale_0.tmp_1" name: "batch_norm_25.tmp_2"
alias_name: "features" alias_name: "features"
is_lod_tensor: false is_lod_tensor: false
fetch_type: 1 fetch_type: 1
......
...@@ -11,17 +11,24 @@ ...@@ -11,17 +11,24 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from paddle_serving_server.web_service import WebService, Op import base64
import json
import logging import logging
import numpy as np import os
import pickle
import sys import sys
import cv2 import cv2
from paddle_serving_app.reader import *
import base64
import os
import faiss import faiss
import pickle import numpy as np
import json from paddle_serving_app.reader import BGR2RGB
from paddle_serving_app.reader import Div
from paddle_serving_app.reader import Normalize
from paddle_serving_app.reader import RCNNPostprocess
from paddle_serving_app.reader import Resize
from paddle_serving_app.reader import Sequential
from paddle_serving_app.reader import Transpose
from paddle_serving_server.web_service import Op, WebService
class DetOp(Op): class DetOp(Op):
...@@ -101,11 +108,11 @@ class RecOp(Op): ...@@ -101,11 +108,11 @@ class RecOp(Op):
def init_op(self): def init_op(self):
self.seq = Sequential([ self.seq = Sequential([
BGR2RGB(), Resize((224, 224)), Div(255), BGR2RGB(), Resize((224, 224)), Div(255),
Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], False),
False), Transpose((2, 0, 1)) Transpose((2, 0, 1))
]) ])
index_dir = "../../drink_dataset_v1.0/index" index_dir = "../../drink_dataset_v2.0/index"
assert os.path.exists(os.path.join( assert os.path.exists(os.path.join(
index_dir, "vector.index")), "vector.index not found ..." index_dir, "vector.index")), "vector.index not found ..."
assert os.path.exists(os.path.join( assert os.path.exists(os.path.join(
...@@ -136,7 +143,7 @@ class RecOp(Op): ...@@ -136,7 +143,7 @@ class RecOp(Op):
}) })
self.det_boxes = boxes self.det_boxes = boxes
#construct batch images for rec # construct batch images for rec
imgs = [] imgs = []
for box in boxes: for box in boxes:
box = [int(x) for x in box["bbox"]] box = [int(x) for x in box["bbox"]]
...@@ -192,7 +199,7 @@ class RecOp(Op): ...@@ -192,7 +199,7 @@ class RecOp(Op):
pred["rec_scores"] = scores[i][0] pred["rec_scores"] = scores[i][0]
results.append(pred) results.append(pred)
#do nms # do NMS
results = self.nms_to_rec_results(results, self.rec_nms_thresold) results = self.nms_to_rec_results(results, self.rec_nms_thresold)
return {"result": str(results)}, None, "" return {"result": str(results)}, None, ""
......
...@@ -3,12 +3,12 @@ gpu_id=$1 ...@@ -3,12 +3,12 @@ gpu_id=$1
# PP-ShiTu CPP serving script # PP-ShiTu CPP serving script
if [[ -n "${gpu_id}" ]]; then if [[ -n "${gpu_id}" ]]; then
nohup python3.7 -m paddle_serving_server.serve \ nohup python3.7 -m paddle_serving_server.serve \
--model ../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving ../../models/general_PPLCNet_x2_5_lite_v1.0_serving \ --model ../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving ../../models/general_PPLCNetV2_base_pretrained_v1.0_serving \
--op GeneralPicodetOp GeneralFeatureExtractOp \ --op GeneralPicodetOp GeneralFeatureExtractOp \
--port 9400 --gpu_id="${gpu_id}" > log_PPShiTu.txt 2>&1 & --port 9400 --gpu_id="${gpu_id}" > log_PPShiTu.txt 2>&1 &
else else
nohup python3.7 -m paddle_serving_server.serve \ nohup python3.7 -m paddle_serving_server.serve \
--model ../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving ../../models/general_PPLCNet_x2_5_lite_v1.0_serving \ --model ../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving ../../models/general_PPLCNetV2_base_pretrained_v1.0_serving \
--op GeneralPicodetOp GeneralFeatureExtractOp \ --op GeneralPicodetOp GeneralFeatureExtractOp \
--port 9400 > log_PPShiTu.txt 2>&1 & --port 9400 > log_PPShiTu.txt 2>&1 &
fi fi
...@@ -12,20 +12,19 @@ ...@@ -12,20 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import numpy as np import os
import pickle
from paddle_serving_client import Client
from paddle_serving_app.reader import *
import cv2 import cv2
import faiss import faiss
import os import numpy as np
import pickle from paddle_serving_client import Client
rec_nms_thresold = 0.05 rec_nms_thresold = 0.05
rec_score_thres = 0.5 rec_score_thres = 0.5
feature_normalize = True feature_normalize = True
return_k = 1 return_k = 1
index_dir = "../../drink_dataset_v1.0/index" index_dir = "../../drink_dataset_v2.0/index"
def init_index(index_dir): def init_index(index_dir):
...@@ -41,7 +40,7 @@ def init_index(index_dir): ...@@ -41,7 +40,7 @@ def init_index(index_dir):
return searcher, id_map return searcher, id_map
#get box # get box
def nms_to_rec_results(results, thresh=0.1): def nms_to_rec_results(results, thresh=0.1):
filtered_results = [] filtered_results = []
...@@ -91,21 +90,21 @@ def postprocess(fetch_dict, feature_normalize, det_boxes, searcher, id_map, ...@@ -91,21 +90,21 @@ def postprocess(fetch_dict, feature_normalize, det_boxes, searcher, id_map,
pred["rec_scores"] = scores[i][0] pred["rec_scores"] = scores[i][0]
results.append(pred) results.append(pred)
#do nms # do NMS
results = nms_to_rec_results(results, rec_nms_thresold) results = nms_to_rec_results(results, rec_nms_thresold)
return results return results
#do client # do client
if __name__ == "__main__": if __name__ == "__main__":
client = Client() client = Client()
client.load_client_config([ client.load_client_config([
"../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client", "../../models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client",
"../../models/general_PPLCNet_x2_5_lite_v1.0_client" "../../models/general_PPLCNetV2_base_pretrained_v1.0_client"
]) ])
client.connect(['127.0.0.1:9400']) client.connect(['127.0.0.1:9400'])
im = cv2.imread("../../drink_dataset_v1.0/test_images/001.jpeg") im = cv2.imread("../../drink_dataset_v2.0/test_images/100.jpeg")
im_shape = np.array(im.shape[:2]).reshape(-1) im_shape = np.array(im.shape[:2]).reshape(-1)
fetch_map = client.predict( fetch_map = client.predict(
feed={"image": im, feed={"image": im,
...@@ -113,7 +112,8 @@ if __name__ == "__main__": ...@@ -113,7 +112,8 @@ if __name__ == "__main__":
fetch=["features", "boxes"], fetch=["features", "boxes"],
batch=False) batch=False)
#add retrieval procedure # add retrieval procedure
print(fetch_map.keys())
det_boxes = fetch_map["boxes"] det_boxes = fetch_map["boxes"]
searcher, id_map = init_index(index_dir) searcher, id_map = init_index(index_dir)
results = postprocess(fetch_map, feature_normalize, det_boxes, searcher, results = postprocess(fetch_map, feature_normalize, det_boxes, searcher,
......
../../docs/zh_CN/inference_deployment/shitu_gallery_manager.md
\ No newline at end of file
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import mod.mainwindow
from paddleclas.deploy.utils import config, logger
from paddleclas.deploy.python.predict_rec import RecPredictor
from fastapi import FastAPI
import uvicorn
import numpy as np
import faiss
from typing import List
import pickle
import cv2
import socket
import json
import operator
from multiprocessing import Process
"""
完整的index库如下:
root_path/ # 库存储目录
|-- image_list.txt # 图像列表,每行:image_path label。由前端生成及修改。后端只读
|-- features.pkl # 建库之后,保存的embedding向量,后端生成,前端无需操作
|-- images # 图像存储目录,由前端生成及增删查等操作。后端只读
| |-- md5.jpg
| |-- md5.jpg
| |-- ……
|-- index # 真正的生成的index库存储目录,后端生成及操作,前端无需操作。
| |-- vector.index # faiss生成的索引库
| |-- id_map.pkl # 索引文件
"""
class ShiTuIndexManager(object):
def __init__(self, config):
self.root_path = None
self.image_list_path = "image_list.txt"
self.image_dir = "images"
self.index_path = "index/vector.index"
self.id_map_path = "index/id_map.pkl"
self.features_path = "features.pkl"
self.index = None
self.id_map = None
self.features = None
self.config = config
self.predictor = RecPredictor(config)
def _load_pickle(self, path):
if os.path.exists(path):
return pickle.load(open(path, 'rb'))
else:
return None
def _save_pickle(self, path, data):
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, 'wb') as fd:
pickle.dump(data, fd)
def _load_index(self):
self.index = faiss.read_index(
os.path.join(self.root_path, self.index_path))
self.id_map = self._load_pickle(
os.path.join(self.root_path, self.id_map_path))
self.features = self._load_pickle(
os.path.join(self.root_path, self.features_path))
def _save_index(self, index, id_map, features):
faiss.write_index(index, os.path.join(self.root_path, self.index_path))
self._save_pickle(os.path.join(self.root_path, self.id_map_path),
id_map)
self._save_pickle(os.path.join(self.root_path, self.features_path),
features)
def _update_path(self, root_path, image_list_path=None):
if root_path == self.root_path:
pass
else:
self.root_path = root_path
if not os.path.exists(os.path.join(root_path, "index")):
os.mkdir(os.path.join(root_path, "index"))
if image_list_path is not None:
self.image_list_path = image_list_path
def _cal_featrue(self, image_list):
batch_images = []
featrures = None
cnt = 0
for idx, image_path in enumerate(image_list):
image = cv2.imread(image_path)
if image is None:
return "{} is broken or not exist. Stop"
else:
image = image[:, :, ::-1]
batch_images.append(image)
cnt += 1
if cnt % self.config["Global"]["batch_size"] == 0 or (
idx + 1) == len(image_list):
if len(batch_images) == 0:
continue
batch_results = self.predictor.predict(batch_images)
featrures = batch_results if featrures is None else np.concatenate(
(featrures, batch_results), axis=0)
batch_images = []
return featrures
def _split_datafile(self, data_file, image_root):
'''
data_file: image path and info, which can be splitted by spacer
image_root: image path root
delimiter: delimiter
'''
gallery_images = []
gallery_docs = []
gallery_ids = []
with open(data_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
for _, ori_line in enumerate(lines):
line = ori_line.strip().split()
text_num = len(line)
assert text_num >= 2, f"line({ori_line}) must be splitted into at least 2 parts, but got {text_num}"
image_file = os.path.join(image_root, line[0])
gallery_images.append(image_file)
gallery_docs.append(ori_line.strip())
gallery_ids.append(os.path.basename(line[0]).split(".")[0])
return gallery_images, gallery_docs, gallery_ids
def create_index(self,
image_list: str,
index_method: str = "HNSW32",
image_root: str = None):
if not os.path.exists(image_list):
return "{} is not exist".format(image_list)
if index_method.lower() not in ['hnsw32', 'ivf', 'flat']:
return "The index method Only support: HNSW32, IVF, Flat"
self._update_path(os.path.dirname(image_list), image_list)
# get image_paths
image_root = image_root if image_root is not None else self.root_path
gallery_images, gallery_docs, image_ids = self._split_datafile(
image_list, image_root)
# gernerate index
if index_method == "IVF":
index_method = index_method + str(
min(max(int(len(gallery_images) // 32), 2), 65536)) + ",Flat"
index = faiss.index_factory(
self.config["IndexProcess"]["embedding_size"], index_method,
faiss.METRIC_INNER_PRODUCT)
self.index = faiss.IndexIDMap2(index)
features = self._cal_featrue(gallery_images)
self.index.train(features)
index_ids = np.arange(0, len(gallery_images)).astype(np.int64)
self.index.add_with_ids(features, index_ids)
self.id_map = dict()
for i, d in zip(list(index_ids), gallery_docs):
self.id_map[i] = d
self.features = {
"features": features,
"index_method": index_method,
"image_ids": image_ids,
"index_ids": index_ids.tolist()
}
self._save_index(self.index, self.id_map, self.features)
def open_index(self, root_path: str, image_list_path: str) -> str:
self._update_path(root_path)
_, _, image_ids = self._split_datafile(image_list_path, root_path)
if os.path.exists(os.path.join(self.root_path, self.index_path)) and \
os.path.exists(os.path.join(self.root_path, self.id_map_path)) and \
os.path.exists(os.path.join(self.root_path, self.features_path)):
self._update_path(root_path)
self._load_index()
if operator.eq(set(image_ids), set(self.features['image_ids'])):
return ""
else:
return "The image list is different from index, Please update index"
else:
return "File not exist: features.pkl, vector.index, id_map.pkl"
def update_index(self, image_list: str, image_root: str = None) -> str:
if self.index and self.id_map and self.features:
image_paths, image_docs, image_ids = self._split_datafile(
image_list,
image_root if image_root is not None else self.root_path)
# for add image
add_ids = list(
set(image_ids).difference(set(self.features["image_ids"])))
add_indexes = [i for i, x in enumerate(image_ids) if x in add_ids]
add_image_paths = [image_paths[i] for i in add_indexes]
add_image_docs = [image_docs[i] for i in add_indexes]
add_image_ids = [image_ids[i] for i in add_indexes]
self._add_index(add_image_paths, add_image_docs, add_image_ids)
# delete images
delete_ids = list(
set(self.features["image_ids"]).difference(set(image_ids)))
self._delete_index(delete_ids)
self._save_index(self.index, self.id_map, self.features)
return ""
else:
return "Failed. Please create or open index first"
def _add_index(self, image_list: List, image_docs: List, image_ids: List):
if len(image_ids) == 0:
return
featrures = self._cal_featrue(image_list)
index_ids = (np.arange(0, len(image_list)) + max(self.id_map.keys()) +
1).astype(np.int64)
self.index.add_with_ids(featrures, index_ids)
for i, d in zip(index_ids, image_docs):
self.id_map[i] = d
self.features['features'] = np.concatenate(
[self.features['features'], featrures], axis=0)
self.features['image_ids'].extend(image_ids)
self.features['index_ids'].extend(index_ids.tolist())
def _delete_index(self, image_ids: List):
if len(image_ids) == 0:
return
indexes = [
i for i, x in enumerate(self.features['image_ids'])
if x in image_ids
]
self.features["features"] = np.delete(self.features["features"],
indexes,
axis=0)
self.features["image_ids"] = np.delete(np.asarray(
self.features["image_ids"]),
indexes,
axis=0).tolist()
index_ids = np.delete(np.asarray(self.features["index_ids"]),
indexes,
axis=0).tolist()
id_map_values = [self.id_map[i] for i in index_ids]
self.index.reset()
ids = np.arange(0, len(id_map_values)).astype(np.int64)
self.index.add_with_ids(self.features['features'], ids)
self.id_map.clear()
for i, d in zip(ids, id_map_values):
self.id_map[i] = d
self.features["index_ids"] = ids
app = FastAPI()
@app.get("/new_index")
def new_index(image_list_path: str,
index_method: str = "HNSW32",
index_root_path: str = None,
force: bool = False):
result = ""
try:
if index_root_path is not None:
image_list_path = os.path.join(index_root_path, image_list_path)
index_path = os.path.join(index_root_path, "index", "vector.index")
id_map_path = os.path.join(index_root_path, "index", "id_map.pkl")
if not (os.path.exists(index_path)
and os.path.exists(id_map_path)) or force:
manager.create_index(image_list_path, index_method, index_root_path)
else:
result = "There alrealy has index in {}".format(index_root_path)
except Exception as e:
result = e.__str__()
data = {"error_message": result}
return json.dumps(data).encode()
@app.get("/open_index")
def open_index(index_root_path: str, image_list_path: str):
result = ""
try:
image_list_path = os.path.join(index_root_path, image_list_path)
result = manager.open_index(index_root_path, image_list_path)
except Exception as e:
result = e.__str__()
data = {"error_message": result}
return json.dumps(data).encode()
@app.get("/update_index")
def update_index(image_list_path: str, index_root_path: str = None):
result = ""
try:
if index_root_path is not None:
image_list_path = os.path.join(index_root_path, image_list_path)
result = manager.update_index(image_list=image_list_path,
image_root=index_root_path)
except Exception as e:
result = e.__str__()
data = {"error_message": result}
return json.dumps(data).encode()
def FrontInterface(server_process=None):
front = QtWidgets.QApplication([])
main_window = mod.mainwindow.MainWindow(process=server_process)
main_window.showMaximized()
sys.exit(front.exec_())
def Server(args):
[app, host, port] = args
uvicorn.run(app, host=host, port=port)
if __name__ == '__main__':
args = config.parse_args()
model_config = config.get_config(args.config,
overrides=args.override,
show=True)
manager = ShiTuIndexManager(model_config)
try:
ip = socket.gethostbyname(socket.gethostname())
except:
ip = '127.0.0.1'
port = 8000
p_server = Process(target=Server, args=([app, ip, port],))
p_server.start()
# p_client = Process(target=FrontInterface, args=())
# p_client.start()
# p_client.join()
FrontInterface(p_server)
p_server.terminate()
sys.exit(0)
import os
from PyQt5 import QtCore, QtWidgets
from mod import image_list_manager as imglistmgr
from mod import utils
from mod import ui_addclassifydialog
from mod import ui_renameclassifydialog
class ClassifyUiContext(QtCore.QObject):
# 分类界面相关业务
selected = QtCore.pyqtSignal(str) # 选择分类信号
def __init__(self, ui: QtWidgets.QListView, parent: QtWidgets.QMainWindow,
image_list_mgr: imglistmgr.ImageListManager):
super(ClassifyUiContext, self).__init__()
self.__ui = ui
self.__parent = parent
self.__imageListMgr = image_list_mgr
self.__menu = QtWidgets.QMenu()
self.__initMenu()
self.__initUi()
self.__connectSignal()
@property
def ui(self):
return self.__ui
@property
def parent(self):
return self.__parent
@property
def imageListManager(self):
return self.__imageListMgr
@property
def menu(self):
return self.__menu
def __initUi(self):
"""初始化分类界面"""
self.__ui.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
def __connectSignal(self):
"""连接信号"""
self.__ui.clicked.connect(self.uiClicked)
self.__ui.doubleClicked.connect(self.uiDoubleClicked)
def __initMenu(self):
"""初始化分类界面菜单"""
utils.setMenu(self.__menu, "添加分类", self.addClassify)
utils.setMenu(self.__menu, "移除分类", self.removeClassify)
utils.setMenu(self.__menu, "重命名分类", self.renemeClassify)
self.__ui.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.__ui.customContextMenuRequested.connect(self.__showMenu)
def __showMenu(self, pos):
"""显示分类界面菜单"""
if len(self.__imageListMgr.filePath) > 0:
self.__menu.exec_(self.__ui.mapToGlobal(pos))
def setClassifyList(self, classify_list):
"""设置分类列表"""
list_model = QtCore.QStringListModel(classify_list)
self.__ui.setModel(list_model)
def uiClicked(self, index):
"""分类列表点击"""
if not self.__ui.currentIndex().isValid():
return
txt = index.data()
self.selected.emit(txt)
def uiDoubleClicked(self, index):
"""分类列表双击"""
if not self.__ui.currentIndex().isValid():
return
ole_name = index.data()
dlg = QtWidgets.QDialog(parent=self.parent)
ui = ui_renameclassifydialog.Ui_RenameClassifyDialog()
ui.setupUi(dlg)
ui.oldNameLineEdit.setText(ole_name)
result = dlg.exec_()
new_name = ui.newNameLineEdit.text()
if result == QtWidgets.QDialog.Accepted:
mgr_result = self.__imageListMgr.renameClassify(ole_name, new_name)
if not mgr_result:
QtWidgets.QMessageBox.warning(self.parent, "重命名分类", "重命名分类错误")
else:
self.setClassifyList(self.__imageListMgr.classifyList)
self.__imageListMgr.writeFile()
def addClassify(self):
"""添加分类"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
dlg = QtWidgets.QDialog(parent=self.parent)
ui = ui_addclassifydialog.Ui_AddClassifyDialog()
ui.setupUi(dlg)
result = dlg.exec_()
txt = ui.lineEdit.text()
if result == QtWidgets.QDialog.Accepted:
mgr_result = self.__imageListMgr.addClassify(txt)
if not mgr_result:
QtWidgets.QMessageBox.warning(self.parent, "添加分类", "添加分类错误")
else:
self.setClassifyList(self.__imageListMgr.classifyList)
def removeClassify(self):
"""移除分类"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
if not self.__ui.currentIndex().isValid():
return
classify = self.__ui.currentIndex().data()
result = QtWidgets.QMessageBox.information(
self.parent,
"移除分类",
"确定移除分类: {}".format(classify),
buttons=QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
defaultButton=QtWidgets.QMessageBox.Cancel)
if result == QtWidgets.QMessageBox.Ok:
if len(self.__imageListMgr.imageList(classify)) > 0:
QtWidgets.QMessageBox.warning(self.parent, "移除分类",
"分类下存在图片,请先移除图片")
else:
self.__imageListMgr.removeClassify(classify)
self.setClassifyList(self.__imageListMgr.classifyList())
def renemeClassify(self):
"""重命名分类"""
idx = self.__ui.currentIndex()
if idx.isValid():
self.uiDoubleClicked(idx)
def searchClassify(self, classify):
"""查找分类"""
self.setClassifyList(self.__imageListMgr.findLikeClassify(classify))
import os
class ImageListManager:
"""
图像列表文件管理器
"""
def __init__(self, file_path="", encoding="utf-8"):
self.__filePath = ""
self.__dirName = ""
self.__dataList = {}
self.__findLikeClassifyResult = []
if file_path != "":
self.readFile(file_path, encoding)
@property
def filePath(self):
return self.__filePath
@property
def dirName(self):
return self.__dirName
@dirName.setter
def dirName(self, value):
self.__dirName = value
@property
def dataList(self):
return self.__dataList
@property
def classifyList(self):
return self.__dataList.keys()
@property
def findLikeClassifyResult(self):
return self.__findLikeClassifyResult
def imageList(self, classify: str):
"""
获取分类下的图片列表
Args:
classify (str): 分类名称
Returns:
list: 图片列表
"""
return self.__dataList[classify]
def readFile(self, file_path: str, encoding="utf-8"):
"""
读取文件内容
Args:
file_path (str): 文件路径
encoding (str, optional): 文件编码. 默认 "utf-8".
Raises:
Exception: 文件不存在
"""
if not os.path.exists(file_path):
raise Exception("文件不存在:{}".format(file_path))
self.__filePath = file_path
self.__dirName = os.path.dirname(self.__filePath)
self.__readData(file_path, encoding)
def __readData(self, file_path: str, encoding="utf-8"):
"""
读取文件内容
Args:
file_path (str): 文件路径
encoding (str, optional): 文件编码. 默认 "utf-8".
"""
with open(file_path, "r", encoding=encoding) as f:
self.__dataList.clear()
for line in f:
line = line.rstrip("\n")
data = line.split("\t")
self.__appendData(data)
def __appendData(self, data: list):
"""
添加数据
Args:
data (list): 数据
"""
if data[1] not in self.__dataList:
self.__dataList[data[1]] = []
self.__dataList[data[1]].append(data[0])
def writeFile(self, file_path="", encoding="utf-8"):
"""
写入文件
Args:
file_path (str, optional): 文件路径. 默认 "".
encoding (str, optional): 文件编码. 默认 "utf-8".
"""
if file_path == "":
file_path = self.__filePath
if not os.path.exists(file_path):
return False
self.__dirName = os.path.dirname(self.__filePath)
lines = []
for classify in self.__dataList.keys():
for path in self.__dataList[classify]:
lines.append("{}\t{}\n".format(path, classify))
with open(file_path, "w", encoding=encoding) as f:
f.writelines(lines)
return True
def realPath(self, image_path: str):
"""
获取真实路径
Args:
image_path (str): 图片路径
"""
return os.path.join(self.__dirName, image_path)
def realPathList(self, classify: str):
"""
获取分类下的真实路径列表
Args:
classify (str): 分类名称
Returns:
list: 真实路径列表
"""
if classify not in self.classifyList:
return []
paths = self.__dataList[classify]
if len(paths) == 0:
return []
for i in range(len(paths)):
paths[i] = os.path.join(self.__dirName, paths[i])
return paths
def findLikeClassify(self, name: str):
"""
查找类似的分类名称
Args:
name (str): 分类名称
Returns:
list: 类似的分类名称列表
"""
self.__findLikeClassifyResult.clear()
for classify in self.__dataList.keys():
word = str(name)
if (word in classify):
self.__findLikeClassifyResult.append(classify)
return self.__findLikeClassifyResult
def addClassify(self, classify: str):
"""
添加分类
Args:
classify (str): 分类名称
Returns:
bool: 如果分类名称已经存在,返回False,否则添加分类并返回True
"""
if classify in self.__dataList:
return False
self.__dataList[classify] = []
return True
def removeClassify(self, classify: str):
"""
移除分类
Args:
classify (str): 分类名称
Returns:
bool: 如果分类名称不存在,返回False,否则移除分类并返回True
"""
if classify not in self.__dataList:
return False
self.__dataList.pop(classify)
return True
def renameClassify(self, old_classify: str, new_classify: str):
"""
重命名分类名称
Args:
old_classify (str): 原分类名称
new_classify (str): 新分类名称
Returns:
bool: 如果原分类名称不存在,或者新分类名称已经存在,返回False,否则重命名分类名称并返回True
"""
if old_classify not in self.__dataList:
return False
if new_classify in self.__dataList:
return False
self.__dataList[new_classify] = self.__dataList[old_classify]
self.__dataList.pop(old_classify)
return True
def allClassfiyNotEmpty(self):
"""
检查所有分类是否都有图片
Returns:
bool: 如果有一个分类没有图片,返回False,否则返回True
"""
for classify in self.__dataList.keys():
if len(self.__dataList[classify]) == 0:
return False
return True
def resetImageList(self, classify: str, image_list: list):
"""
重置图片列表
Args:
classify (str): 分类名称
image_list (list): 图片相对路径列表
Returns:
bool: 如果分类名称不存在,返回False,否则重置图片列表并返回True
"""
if classify not in self.__dataList:
return False
self.__dataList[classify] = image_list
return True
import os
from stat import filemode
from PyQt5 import QtCore, QtGui, QtWidgets
from mod import image_list_manager as imglistmgr
from mod import utils
from mod import ui_renameclassifydialog
from mod import imageeditclassifydialog
# 图像缩放基数
BASE_IMAGE_SIZE = 64
class ImageListUiContext(QtCore.QObject):
# 图片列表界面相关业务,style sheet 在 MainWindow.ui 相应的 ImageListWidget 中设置
listCount = QtCore.pyqtSignal(int) # 图像列表图像的数量
selectedCount = QtCore.pyqtSignal(int) # 图像列表选择图像的数量
def __init__(self, ui: QtWidgets.QListWidget,
parent: QtWidgets.QMainWindow,
image_list_mgr: imglistmgr.ImageListManager):
super(ImageListUiContext, self).__init__()
self.__ui = ui
self.__parent = parent
self.__imageListMgr = image_list_mgr
self.__initUi()
self.__menu = QtWidgets.QMenu()
self.__initMenu()
self.__connectSignal()
self.__selectedClassify = ""
self.__imageScale = 1
@property
def ui(self):
return self.__ui
@property
def parent(self):
return self.__parent
@property
def imageListManager(self):
return self.__imageListMgr
@property
def menu(self):
return self.__menu
def __initUi(self):
"""初始化图片列表样式"""
self.__ui.setViewMode(QtWidgets.QListView.IconMode)
self.__ui.setSpacing(15)
self.__ui.setMovement(QtWidgets.QListView.Static)
self.__ui.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection)
def __initMenu(self):
"""初始化图片列表界面菜单"""
utils.setMenu(self.__menu, "添加图片", self.addImage)
utils.setMenu(self.__menu, "移除图片", self.removeImage)
utils.setMenu(self.__menu, "编辑图片分类", self.editImageClassify)
self.__menu.addSeparator()
utils.setMenu(self.__menu, "选择全部图片", self.selectAllImage)
utils.setMenu(self.__menu, "反向选择图片", self.reverseSelectImage)
utils.setMenu(self.__menu, "取消选择图片", self.cancelSelectImage)
self.__ui.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.__ui.customContextMenuRequested.connect(self.__showMenu)
def __showMenu(self, pos):
"""显示图片列表界面菜单"""
if len(self.__imageListMgr.filePath) > 0:
self.__menu.exec_(self.__ui.mapToGlobal(pos))
def __connectSignal(self):
"""连接信号与槽"""
self.__ui.itemSelectionChanged.connect(self.onSelectionChanged)
def setImageScale(self, scale: int):
"""设置图片大小"""
self.__imageScale = scale
size = QtCore.QSize(scale * BASE_IMAGE_SIZE, scale * BASE_IMAGE_SIZE)
self.__ui.setIconSize(size)
for i in range(self.__ui.count()):
item = self.__ui.item(i)
item.setSizeHint(size)
def setImageList(self, classify: str):
"""设置图片列表"""
size = QtCore.QSize(self.__imageScale * BASE_IMAGE_SIZE,
self.__imageScale * BASE_IMAGE_SIZE)
self.__selectedClassify = classify
image_list = self.__imageListMgr.imageList(classify)
self.__ui.clear()
count = 0
for i in image_list:
item = QtWidgets.QListWidgetItem(self.__ui)
item.setIcon(QtGui.QIcon(self.__imageListMgr.realPath(i)))
item.setData(QtCore.Qt.UserRole, i)
item.setSizeHint(size)
self.__ui.addItem(item)
count += 1
self.listCount.emit(count)
def clear(self):
"""清除图片列表"""
self.__ui.clear()
def addImage(self):
"""添加图片"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
filter = "图片 (*.png *.jpg *.jpeg *.PNG *.JPG *.JPEG);;所有文件(*.*)"
dlg = QtWidgets.QFileDialog(self.__parent)
dlg.setFileMode(QtWidgets.QFileDialog.ExistingFiles) # 多选文件
dlg.setViewMode(QtWidgets.QFileDialog.Detail) # 详细模式
file_paths = dlg.getOpenFileNames(filter=filter)[0]
if len(file_paths) == 0:
return
image_list_dir = self.__imageListMgr.dirName
file_list = []
for path in file_paths:
if not os.path.exists(path):
continue
new_file = self.__copyToImagesDir(path)
if new_file != "" and image_list_dir in new_file:
# 去掉 image_list_dir 的路径和斜杠
begin = len(image_list_dir) + 1
file_list.append(new_file[begin:])
if len(file_list) > 0:
if self.__selectedClassify == "":
QtWidgets.QMessageBox.warning(self.__parent, "提示", "请先选择分类")
return
new_list = self.__imageListMgr.imageList(
self.__selectedClassify) + file_list
self.__imageListMgr.resetImageList(self.__selectedClassify,
new_list)
self.setImageList(self.__selectedClassify)
self.__imageListMgr.writeFile()
def __copyToImagesDir(self, image_path: str):
md5 = utils.fileMD5(image_path)
file_ext = utils.fileExtension(image_path)
to_dir = os.path.join(self.__imageListMgr.dirName, "images")
new_path = os.path.join(to_dir, md5 + file_ext)
if os.path.exists(to_dir):
utils.copyFile(image_path, new_path)
return new_path
else:
return ""
def removeImage(self):
"""移除图片"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self.__parent, "提示",
"请先打开正确的图像库")
return
path_list = []
image_list = self.__ui.selectedItems()
if len(image_list) == 0:
return
question = QtWidgets.QMessageBox.question(self.__parent, "移除图片",
"确定移除所选图片吗?")
if question == QtWidgets.QMessageBox.No:
return
for i in range(self.__ui.count()):
item = self.__ui.item(i)
img_path = item.data(QtCore.Qt.UserRole)
if not item.isSelected():
path_list.append(img_path)
else:
# 从磁盘上删除图片
utils.removeFile(
os.path.join(self.__imageListMgr.dirName, img_path))
self.__imageListMgr.resetImageList(self.__selectedClassify, path_list)
self.setImageList(self.__selectedClassify)
self.__imageListMgr.writeFile()
def editImageClassify(self):
"""编辑图片分类"""
old_classify = self.__selectedClassify
dlg = imageeditclassifydialog.ImageEditClassifyDialog(
parent=self.__parent,
old_classify=old_classify,
classify_list=self.__imageListMgr.classifyList)
result = dlg.exec_()
new_classify = dlg.newClassify
if result == QtWidgets.QDialog.Accepted \
and new_classify != old_classify \
and new_classify != "":
self.__moveImage(old_classify, new_classify)
self.__imageListMgr.writeFile()
def __moveImage(self, old_classify, new_classify):
"""移动图片"""
keep_list = []
is_selected = False
move_list = self.__imageListMgr.imageList(new_classify)
for i in range(self.__ui.count()):
item = self.__ui.item(i)
txt = item.data(QtCore.Qt.UserRole)
if item.isSelected():
move_list.append(txt)
is_selected = True
else:
keep_list.append(txt)
if is_selected:
self.__imageListMgr.resetImageList(new_classify, move_list)
self.__imageListMgr.resetImageList(old_classify, keep_list)
self.setImageList(old_classify)
def selectAllImage(self):
"""选择所有图片"""
self.__ui.selectAll()
def reverseSelectImage(self):
"""反向选择图片"""
for i in range(self.__ui.count()):
item = self.__ui.item(i)
item.setSelected(not item.isSelected())
def cancelSelectImage(self):
"""取消选择图片"""
self.__ui.clearSelection()
def onSelectionChanged(self):
"""选择图像该变,发送选择的数量信号"""
count = len(self.__ui.selectedItems())
self.selectedCount.emit(count)
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from mod import image_list_manager
from mod import ui_imageeditclassifydialog
from mod import utils
class ImageEditClassifyDialog(QtWidgets.QDialog):
"""图像编辑分类对话框"""
def __init__(self, parent, old_classify, classify_list):
super(ImageEditClassifyDialog, self).__init__(parent)
self.ui = mod.ui_imageeditclassifydialog.Ui_Dialog()
self.ui.setupUi(self) # 初始化主窗口界面
self.__oldClassify = old_classify
self.__classifyList = classify_list
self.__newClassify = ""
self.__searchResult = []
self.__initUi()
self.__connectSignal()
@property
def newClassify(self):
return self.__newClassify
def __initUi(self):
self.ui.oldLineEdit.setText(self.__oldClassify)
self.__setClassifyList(self.__classifyList)
self.ui.classifyListView.setEditTriggers(
QtWidgets.QAbstractItemView.NoEditTriggers)
def __connectSignal(self):
self.ui.classifyListView.clicked.connect(self.selectedListView)
self.ui.searchButton.clicked.connect(self.searchClassify)
def __setClassifyList(self, classify_list):
list_model = QtCore.QStringListModel(classify_list)
self.ui.classifyListView.setModel(list_model)
def selectedListView(self, index):
if not self.ui.classifyListView.currentIndex().isValid():
return
txt = index.data()
self.ui.newLineEdit.setText(txt)
self.__newClassify = txt
def searchClassify(self):
txt = self.ui.searchWordLineEdit.text()
self.__searchResult.clear()
for classify in self.__classifyList:
if txt in classify:
self.__searchResult.append(classify)
self.__setClassifyList(self.__searchResult)
import json
import os
import urllib3
import urllib.parse
class IndexHttpClient():
"""索引库客户端,使用 urllib3 连接,使用 urllib.parse 进行 url 编码"""
def __init__(self, host: str, port: int):
self.__host = host
self.__port = port
self.__http = urllib3.PoolManager()
self.__headers = {"Content-type": "application/json"}
def url(self):
return "http://{}:{}".format(self.__host, self.__port)
def new_index(self,
image_list_path: str,
index_root_path: str,
index_method="HNSW32",
force=False):
"""新建 重建 库"""
if index_method not in ["HNSW32", "FLAT", "IVF"]:
raise Exception(
"index_method 必须是 HNSW32, FLAT, IVF,实际值为:{}".format(
index_method))
params = {"image_list_path":image_list_path, \
"index_root_path":index_root_path, \
"index_method":index_method, \
"force":force}
return self.__post(self.url() + "/new_index?", params)
def open_index(self, index_root_path: str, image_list_path: str):
"""打开库"""
params = {
"index_root_path": index_root_path,
"image_list_path": image_list_path
}
return self.__post(self.url() + "/open_index?", params)
def update_index(self, image_list_path: str, index_root_path: str):
"""更新索引库"""
params = {"image_list_path":image_list_path, \
"index_root_path":index_root_path}
return self.__post(self.url() + "/update_index?", params)
def __post(self, url: str, params: dict):
"""发送 url 并接收数据"""
http = self.__http
encode_params = urllib.parse.urlencode(params)
get_url = url + encode_params
req = http.request("GET", get_url, headers=self.__headers)
result = json.loads(req.data)
if isinstance(result, str):
result = eval(result)
msg = result["error_message"]
if msg != None and len(msg) == 0:
msg = None
return msg
from multiprocessing.dummy import active_children
from multiprocessing import Process
import os
import sys
import socket
from PyQt5 import QtCore, QtGui, QtWidgets
from mod import ui_mainwindow
from mod import image_list_manager
from mod import classify_ui_context
from mod import image_list_ui_context
from mod import ui_newlibrarydialog
from mod import index_http_client
from mod import utils
from mod import ui_waitdialog
import threading
TOOL_BTN_ICON_SIZE = 64
TOOL_BTN_ICON_SMALL = 48
try:
DEFAULT_HOST = socket.gethostbyname(socket.gethostname())
except:
DEFAULT_HOST = '127.0.0.1'
# DEFAULT_HOST = "localhost"
DEFAULT_PORT = 8000
PADDLECLAS_DOC_URL = "https://gitee.com/paddlepaddle/PaddleClas/docs/zh_CN/inference_deployment/shitu_gallery_manager.md"
class MainWindow(QtWidgets.QMainWindow):
"""主窗口"""
newIndexMsg = QtCore.pyqtSignal(str) # 新建索引库线程信号
openIndexMsg = QtCore.pyqtSignal(str) # 打开索引库线程信号
updateIndexMsg = QtCore.pyqtSignal(str) # 更新索引库线程信号
importImageCount = QtCore.pyqtSignal(int) # 导入图像数量信号
def __init__(self, process=None):
super(MainWindow, self).__init__()
self.server_process = process
self.ui = ui_mainwindow.Ui_MainWindow()
self.ui.setupUi(self) # 初始化主窗口界面
self.__imageListMgr = image_list_manager.ImageListManager()
self.__appMenu = QtWidgets.QMenu() # 应用菜单
self.__libraryAppendMenu = QtWidgets.QMenu() # 图像库附加功能菜单
self.__initAppMenu() # 初始化应用菜单
self.__pathBar = QtWidgets.QLabel(self) # 路径
self.__classifyCountBar = QtWidgets.QLabel(self) # 分类数量
self.__imageCountBar = QtWidgets.QLabel(self) # 图像列表数量
self.__imageSelectedBar = QtWidgets.QLabel(self) # 图像列表选择数量
self.__spaceBar1 = QtWidgets.QLabel(self) # 空格间隔栏
self.__spaceBar2 = QtWidgets.QLabel(self) # 空格间隔栏
self.__spaceBar3 = QtWidgets.QLabel(self) # 空格间隔栏
# 分类界面相关业务
self.__classifyUiContext = classify_ui_context.ClassifyUiContext(
ui=self.ui.classifyListView,
parent=self,
image_list_mgr=self.__imageListMgr)
# 图片列表界面相关业务
self.__imageListUiContext = image_list_ui_context.ImageListUiContext(
ui=self.ui.imageListWidget,
parent=self,
image_list_mgr=self.__imageListMgr)
# 搜索的历史记录回车快捷键
self.__historyCmbShortcut = QtWidgets.QShortcut(
QtGui.QKeySequence(QtCore.Qt.Key_Return),
self.ui.searchClassifyHistoryCmb)
self.__waitDialog = QtWidgets.QDialog() # 等待对话框
self.__waitDialogUi = ui_waitdialog.Ui_WaitDialog() # 等待对话框界面
self.__initToolBtn()
self.__connectSignal()
self.__initUI()
self.__initWaitDialog()
def __initUI(self):
"""初始化界面"""
# 窗口图标
self.setWindowIcon(QtGui.QIcon("./resource/app_icon.png"))
# 初始化分割窗口
self.ui.splitter.setStretchFactor(0, 20)
self.ui.splitter.setStretchFactor(1, 80)
# 初始化图像缩放
self.ui.imageScaleSlider.setValue(4)
# 状态栏界面设置
space_bar = " " # 间隔16空格
self.__spaceBar1.setText(space_bar)
self.__spaceBar2.setText(space_bar)
self.__spaceBar3.setText(space_bar)
self.ui.statusbar.addWidget(self.__pathBar)
self.ui.statusbar.addWidget(self.__spaceBar1)
self.ui.statusbar.addWidget(self.__classifyCountBar)
self.ui.statusbar.addWidget(self.__spaceBar2)
self.ui.statusbar.addWidget(self.__imageCountBar)
self.ui.statusbar.addWidget(self.__spaceBar3)
self.ui.statusbar.addWidget(self.__imageSelectedBar)
def __initToolBtn(self):
"""初始化工具按钮"""
self.__setToolButton(self.ui.appMenuBtn, "应用菜单",
"./resource/app_menu.png", TOOL_BTN_ICON_SIZE)
self.__setToolButton(self.ui.saveImageLibraryBtn, "保存图像库",
"./resource/save_image_Library.png",
TOOL_BTN_ICON_SIZE)
self.ui.saveImageLibraryBtn.clicked.connect(self.saveImageLibrary)
self.__setToolButton(self.ui.addClassifyBtn, "添加分类",
"./resource/add_classify.png",
TOOL_BTN_ICON_SIZE)
self.ui.addClassifyBtn.clicked.connect(
self.__classifyUiContext.addClassify)
self.__setToolButton(self.ui.removeClassifyBtn, "移除分类",
"./resource/remove_classify.png",
TOOL_BTN_ICON_SIZE)
self.ui.removeClassifyBtn.clicked.connect(
self.__classifyUiContext.removeClassify)
self.__setToolButton(self.ui.searchClassifyBtn, "查找分类",
"./resource/search_classify.png",
TOOL_BTN_ICON_SMALL)
self.ui.searchClassifyBtn.clicked.connect(
self.__classifyUiContext.searchClassify)
self.__setToolButton(self.ui.addImageBtn, "添加图片",
"./resource/add_image.png", TOOL_BTN_ICON_SMALL)
self.ui.addImageBtn.clicked.connect(self.__imageListUiContext.addImage)
self.__setToolButton(self.ui.removeImageBtn, "移除图片",
"./resource/remove_image.png",
TOOL_BTN_ICON_SMALL)
self.ui.removeImageBtn.clicked.connect(
self.__imageListUiContext.removeImage)
self.ui.searchClassifyHistoryCmb.setToolTip("查找分类历史")
self.ui.imageScaleSlider.setToolTip("图片缩放")
def __setToolButton(self, button, tool_tip: str, icon_path: str,
icon_size: int):
"""设置工具按钮"""
button.setToolTip(tool_tip)
button.setIcon(QtGui.QIcon(icon_path))
button.setIconSize(QtCore.QSize(icon_size, icon_size))
def __initAppMenu(self):
"""初始化应用菜单"""
utils.setMenu(self.__appMenu, "新建图像库", self.newImageLibrary)
utils.setMenu(self.__appMenu, "打开图像库", self.openImageLibrary)
utils.setMenu(self.__appMenu, "保存图像库", self.saveImageLibrary)
self.__libraryAppendMenu.setTitle("导入图像")
utils.setMenu(self.__libraryAppendMenu, "导入 image_list 图像",
self.importImageListImage)
utils.setMenu(self.__libraryAppendMenu, "导入多文件夹图像",
self.importDirsImage)
self.__appMenu.addMenu(self.__libraryAppendMenu)
self.__appMenu.addSeparator()
utils.setMenu(self.__appMenu, "新建/重建 索引库", self.newIndexLibrary)
utils.setMenu(self.__appMenu, "更新索引库", self.updateIndexLibrary)
self.__appMenu.addSeparator()
utils.setMenu(self.__appMenu, "帮助", self.showHelp)
utils.setMenu(self.__appMenu, "关于", self.showAbout)
utils.setMenu(self.__appMenu, "退出", self.exitApp)
self.ui.appMenuBtn.setMenu(self.__appMenu)
self.ui.appMenuBtn.setPopupMode(QtWidgets.QToolButton.InstantPopup)
def __initWaitDialog(self):
"""初始化等待对话框"""
self.__waitDialogUi.setupUi(self.__waitDialog)
self.__waitDialog.setWindowFlags(QtCore.Qt.Dialog
| QtCore.Qt.FramelessWindowHint)
def __startWait(self, msg: str):
"""开始显示等待对话框"""
self.setEnabled(False)
self.__waitDialogUi.msgLabel.setText(msg)
self.__waitDialog.setWindowFlags(QtCore.Qt.Dialog
| QtCore.Qt.FramelessWindowHint
| QtCore.Qt.WindowStaysOnTopHint)
self.__waitDialog.show()
self.__waitDialog.repaint()
def __stopWait(self):
"""停止显示等待对话框"""
self.setEnabled(True)
self.__waitDialogUi.msgLabel.setText("执行完毕!")
self.__waitDialog.setWindowFlags(QtCore.Qt.Dialog
| QtCore.Qt.FramelessWindowHint
| QtCore.Qt.CustomizeWindowHint)
self.__waitDialog.close()
def __connectSignal(self):
"""连接信号与槽"""
self.__classifyUiContext.selected.connect(
self.__imageListUiContext.setImageList)
self.ui.searchClassifyBtn.clicked.connect(self.searchClassify)
self.ui.imageScaleSlider.valueChanged.connect(
self.__imageListUiContext.setImageScale)
self.__imageListUiContext.listCount.connect(self.__setImageCountBar)
self.__imageListUiContext.selectedCount.connect(
self.__setImageSelectedCountBar)
self.__historyCmbShortcut.activated.connect(self.searchClassify)
self.newIndexMsg.connect(self.__onNewIndexMsg)
self.openIndexMsg.connect(self.__onOpenIndexMsg)
self.updateIndexMsg.connect(self.__onUpdateIndexMsg)
self.importImageCount.connect(self.__onImportImageCount)
def newImageLibrary(self):
"""新建图像库"""
dir_path = self.__openDirDialog("新建图像库")
if dir_path == None:
return
if not utils.isEmptyDir(dir_path):
QtWidgets.QMessageBox.warning(self, "错误", "该目录不为空,请选择空目录")
return
if not utils.initLibrary(dir_path):
QtWidgets.QMessageBox.warning(self, "错误", "新建图像库失败")
return
QtWidgets.QMessageBox.information(self, "提示", "新建图像库成功")
self.__reload(os.path.join(dir_path, "image_list.txt"), dir_path)
def __openDirDialog(self, title: str):
"""打开目录对话框"""
dlg = QtWidgets.QFileDialog(self)
dlg.setWindowTitle(title)
dlg.setOption(QtWidgets.QFileDialog.ShowDirsOnly, True)
dlg.setFileMode(QtWidgets.QFileDialog.Directory)
dlg.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
if dlg.exec_() == QtWidgets.QDialog.Accepted:
dir_path = dlg.selectedFiles()[0]
return dir_path
return None
def openImageLibrary(self):
"""打开图像库"""
dir_path = self.__openDirDialog("打开图像库")
if dir_path != None:
image_list_path = os.path.join(dir_path, "image_list.txt")
if os.path.exists(image_list_path) \
and os.path.exists(os.path.join(dir_path, "images")):
self.__reload(image_list_path, dir_path)
self.openIndexLibrary()
def __reload(self, image_list_path: str, msg: str):
"""重新加载图像库"""
self.__imageListMgr.readFile(image_list_path)
self.__imageListUiContext.clear()
self.__classifyUiContext.setClassifyList(
self.__imageListMgr.classifyList)
self.__setPathBar(msg)
self.__setClassifyCountBar(len(self.__imageListMgr.classifyList))
self.__setImageCountBar(0)
self.__setImageSelectedCountBar(0)
def saveImageLibrary(self):
"""保存图像库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.warning(self, "错误", "请先打开正确的图像库")
return
self.__imageListMgr.writeFile()
self.__reload(self.__imageListMgr.filePath,
self.__imageListMgr.dirName)
hint_str = "为保证图片准确识别,请在修改图片库后更新索引库。\n\
如果是新建图像库或者没有索引库,请新建索引库。"
QtWidgets.QMessageBox.information(self, "提示", hint_str)
def __onImportImageCount(self, count: int):
"""导入图像槽"""
self.__stopWait()
if count == -1:
QtWidgets.QMessageBox.warning(self, "错误", "导入到当前图像库错误")
return
QtWidgets.QMessageBox.information(self, "提示",
"导入图像库成功,导入图像:{}".format(count))
self.__reload(self.__imageListMgr.filePath,
self.__imageListMgr.dirName)
def __importImageListImageThread(self, from_path: str, to_path: str):
"""导入 image_list 图像 线程"""
count = utils.oneKeyImportFromFile(from_path=from_path,
to_path=to_path)
if count == None:
count = -1
self.importImageCount.emit(count)
def importImageListImage(self):
"""导入 image_list 图像 到当前图像库,建议当前库是新建的空库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
from_path = QtWidgets.QFileDialog.getOpenFileName(
caption="导入 image_list 图像", filter="txt (*.txt)")[0]
if not os.path.exists(from_path):
QtWidgets.QMessageBox.information(self, "提示", "打开的文件不存在")
return
from_mgr = image_list_manager.ImageListManager(from_path)
self.__startWait("正在导入图像,请等待。。。")
thread = threading.Thread(target=self.__importImageListImageThread,
args=(from_mgr.filePath,
self.__imageListMgr.filePath))
thread.start()
def __importDirsImageThread(self, from_dir: str, to_image_list_path: str):
"""导入多文件夹图像 线程"""
count = utils.oneKeyImportFromDirs(
from_dir=from_dir, to_image_list_path=to_image_list_path)
if count == None:
count = -1
self.importImageCount.emit(count)
def importDirsImage(self):
"""导入 多文件夹图像 到当前图像库,建议当前库是新建的空库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
dir_path = self.__openDirDialog("导入多文件夹图像")
if dir_path == None:
return
if not os.path.exists(dir_path):
QtWidgets.QMessageBox.information(self, "提示", "打开的目录不存在")
return
self.__startWait("正在导入图像,请等待。。。")
thread = threading.Thread(target=self.__importDirsImageThread,
args=(dir_path,
self.__imageListMgr.filePath))
thread.start()
def __newIndexThread(self, index_root_path: str, image_list_path: str,
index_method: str, force: bool):
"""新建重建索引库线程"""
try:
client = index_http_client.IndexHttpClient(
DEFAULT_HOST, DEFAULT_PORT)
err_msg = client.new_index(image_list_path=image_list_path,
index_root_path=index_root_path,
index_method=index_method,
force=force)
if err_msg == None:
err_msg = ""
self.newIndexMsg.emit(err_msg)
except Exception as e:
self.newIndexMsg.emit(str(e))
def __onNewIndexMsg(self, err_msg):
"""新建重建索引库槽"""
self.__stopWait()
if err_msg == "":
QtWidgets.QMessageBox.information(self, "提示", "新建/重建 索引库成功")
else:
QtWidgets.QMessageBox.warning(self, "错误", err_msg)
def newIndexLibrary(self):
"""新建重建索引库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
dlg = QtWidgets.QDialog(self)
ui = ui_newlibrarydialog.Ui_NewlibraryDialog()
ui.setupUi(dlg)
result = dlg.exec_()
index_method = ui.indexMethodCmb.currentText()
force = ui.resetCheckBox.isChecked()
if result == QtWidgets.QDialog.Accepted:
self.__startWait("正在 新建/重建 索引库,请等待。。。")
thread = threading.Thread(target=self.__newIndexThread,
args=(self.__imageListMgr.dirName,
"image_list.txt", index_method,
force))
thread.start()
def __openIndexThread(self, index_root_path: str, image_list_path: str):
"""打开索引库线程"""
try:
client = index_http_client.IndexHttpClient(
DEFAULT_HOST, DEFAULT_PORT)
err_msg = client.open_index(index_root_path=index_root_path,
image_list_path=image_list_path)
if err_msg == None:
err_msg = ""
self.openIndexMsg.emit(err_msg)
except Exception as e:
self.openIndexMsg.emit(str(e))
def __onOpenIndexMsg(self, err_msg):
"""打开索引库槽"""
self.__stopWait()
if err_msg == "":
QtWidgets.QMessageBox.information(self, "提示", "打开索引库成功")
else:
QtWidgets.QMessageBox.warning(self, "错误", err_msg)
def openIndexLibrary(self):
"""打开索引库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
self.__startWait("正在打开索引库,请等待。。。")
thread = threading.Thread(target=self.__openIndexThread,
args=(self.__imageListMgr.dirName,
"image_list.txt"))
thread.start()
def __updateIndexThread(self, index_root_path: str, image_list_path: str):
"""更新索引库线程"""
try:
client = index_http_client.IndexHttpClient(
DEFAULT_HOST, DEFAULT_PORT)
err_msg = client.update_index(image_list_path=image_list_path,
index_root_path=index_root_path)
if err_msg == None:
err_msg = ""
self.updateIndexMsg.emit(err_msg)
except Exception as e:
self.updateIndexMsg.emit(str(e))
def __onUpdateIndexMsg(self, err_msg):
"""更新索引库槽"""
self.__stopWait()
if err_msg == "":
QtWidgets.QMessageBox.information(self, "提示", "更新索引库成功")
else:
QtWidgets.QMessageBox.warning(self, "错误", err_msg)
def updateIndexLibrary(self):
"""更新索引库"""
if not os.path.exists(self.__imageListMgr.filePath):
QtWidgets.QMessageBox.information(self, "提示", "请先打开正确的图像库")
return
self.__startWait("正在更新索引库,请等待。。。")
thread = threading.Thread(target=self.__updateIndexThread,
args=(self.__imageListMgr.dirName,
"image_list.txt"))
thread.start()
def searchClassify(self):
"""查找分类"""
if len(self.__imageListMgr.classifyList) == 0:
return
cmb = self.ui.searchClassifyHistoryCmb
txt = cmb.currentText()
is_has = False
if txt != "":
for i in range(cmb.count()):
if cmb.itemText(i) == txt:
is_has = True
break
if not is_has:
cmb.addItem(txt)
self.__classifyUiContext.searchClassify(txt)
def showHelp(self):
"""显示帮助"""
QtGui.QDesktopServices.openUrl(QtCore.QUrl(PADDLECLAS_DOC_URL))
def showAbout(self):
"""显示关于对话框"""
QtWidgets.QMessageBox.information(self, "关于", "识图图像库管理 V1.0.0")
def exitApp(self):
"""退出应用"""
if isinstance(self.server_process, Process):
self.server_process.terminate()
# os.kill(self.server_pid)
sys.exit(0)
def __setPathBar(self, msg: str):
"""设置路径状态栏信息"""
self.__pathBar.setText("图像库路径:{}".format(msg))
def __setClassifyCountBar(self, msg: str):
self.__classifyCountBar.setText("分类总数量:{}".format(msg))
def __setImageCountBar(self, count: int):
"""设置图像数量状态栏信息"""
self.__imageCountBar.setText("当前图像数量:{}".format(count))
def __setImageSelectedCountBar(self, count: int):
"""设置选择图像数量状态栏信息"""
self.__imageSelectedBar.setText("选择图像数量:{}".format(count))
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/AddClassifyDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_AddClassifyDialog(object):
def setupUi(self, AddClassifyDialog):
AddClassifyDialog.setObjectName("AddClassifyDialog")
AddClassifyDialog.resize(286, 127)
AddClassifyDialog.setModal(True)
self.verticalLayout = QtWidgets.QVBoxLayout(AddClassifyDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(AddClassifyDialog)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.lineEdit = QtWidgets.QLineEdit(AddClassifyDialog)
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout.addWidget(self.lineEdit)
spacerItem = QtWidgets.QSpacerItem(20, 11,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(AddClassifyDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(AddClassifyDialog)
self.buttonBox.accepted.connect(AddClassifyDialog.accept)
self.buttonBox.rejected.connect(AddClassifyDialog.reject)
QtCore.QMetaObject.connectSlotsByName(AddClassifyDialog)
def retranslateUi(self, AddClassifyDialog):
_translate = QtCore.QCoreApplication.translate
AddClassifyDialog.setWindowTitle(
_translate("AddClassifyDialog", "添加分类"))
self.label.setText(_translate("AddClassifyDialog", "分类名称"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
AddClassifyDialog = QtWidgets.QDialog()
ui = Ui_AddClassifyDialog()
ui.setupUi(AddClassifyDialog)
AddClassifyDialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/ImageEditClassifyDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(414, 415)
Dialog.setMinimumSize(QtCore.QSize(0, 0))
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(Dialog)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.oldLineEdit = QtWidgets.QLineEdit(Dialog)
self.oldLineEdit.setEnabled(False)
self.oldLineEdit.setObjectName("oldLineEdit")
self.verticalLayout.addWidget(self.oldLineEdit)
self.label_2 = QtWidgets.QLabel(Dialog)
self.label_2.setObjectName("label_2")
self.verticalLayout.addWidget(self.label_2)
self.newLineEdit = QtWidgets.QLineEdit(Dialog)
self.newLineEdit.setEnabled(False)
self.newLineEdit.setObjectName("newLineEdit")
self.verticalLayout.addWidget(self.newLineEdit)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.searchWordLineEdit = QtWidgets.QLineEdit(Dialog)
self.searchWordLineEdit.setObjectName("searchWordLineEdit")
self.horizontalLayout.addWidget(self.searchWordLineEdit)
self.searchButton = QtWidgets.QPushButton(Dialog)
self.searchButton.setObjectName("searchButton")
self.horizontalLayout.addWidget(self.searchButton)
self.verticalLayout.addLayout(self.horizontalLayout)
self.classifyListView = QtWidgets.QListView(Dialog)
self.classifyListView.setEnabled(True)
self.classifyListView.setMinimumSize(QtCore.QSize(400, 200))
self.classifyListView.setObjectName("classifyListView")
self.verticalLayout.addWidget(self.classifyListView)
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "编辑图像分类"))
self.label.setText(_translate("Dialog", "原分类"))
self.label_2.setText(_translate("Dialog", "新分类"))
self.searchButton.setText(_translate("Dialog", "查找"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/MainWindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(833, 538)
MainWindow.setMinimumSize(QtCore.QSize(0, 0))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.appMenuBtn = QtWidgets.QToolButton(self.centralwidget)
self.appMenuBtn.setObjectName("appMenuBtn")
self.horizontalLayout_3.addWidget(self.appMenuBtn)
self.saveImageLibraryBtn = QtWidgets.QToolButton(self.centralwidget)
self.saveImageLibraryBtn.setObjectName("saveImageLibraryBtn")
self.horizontalLayout_3.addWidget(self.saveImageLibraryBtn)
self.addClassifyBtn = QtWidgets.QToolButton(self.centralwidget)
self.addClassifyBtn.setObjectName("addClassifyBtn")
self.horizontalLayout_3.addWidget(self.addClassifyBtn)
self.removeClassifyBtn = QtWidgets.QToolButton(self.centralwidget)
self.removeClassifyBtn.setObjectName("removeClassifyBtn")
self.horizontalLayout_3.addWidget(self.removeClassifyBtn)
spacerItem = QtWidgets.QSpacerItem(40, 20,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem)
self.imageScaleSlider = QtWidgets.QSlider(self.centralwidget)
self.imageScaleSlider.setMaximumSize(QtCore.QSize(400, 16777215))
self.imageScaleSlider.setMinimum(1)
self.imageScaleSlider.setMaximum(8)
self.imageScaleSlider.setPageStep(2)
self.imageScaleSlider.setOrientation(QtCore.Qt.Horizontal)
self.imageScaleSlider.setObjectName("imageScaleSlider")
self.horizontalLayout_3.addWidget(self.imageScaleSlider)
self.verticalLayout_3.addLayout(self.horizontalLayout_3)
self.splitter = QtWidgets.QSplitter(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.splitter.sizePolicy().hasHeightForWidth())
self.splitter.setSizePolicy(sizePolicy)
self.splitter.setOrientation(QtCore.Qt.Horizontal)
self.splitter.setObjectName("splitter")
self.widget = QtWidgets.QWidget(self.splitter)
self.widget.setObjectName("widget")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.widget)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.searchClassifyHistoryCmb = QtWidgets.QComboBox(self.widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.searchClassifyHistoryCmb.sizePolicy().hasHeightForWidth())
self.searchClassifyHistoryCmb.setSizePolicy(sizePolicy)
self.searchClassifyHistoryCmb.setEditable(True)
self.searchClassifyHistoryCmb.setObjectName("searchClassifyHistoryCmb")
self.horizontalLayout.addWidget(self.searchClassifyHistoryCmb)
self.searchClassifyBtn = QtWidgets.QToolButton(self.widget)
self.searchClassifyBtn.setObjectName("searchClassifyBtn")
self.horizontalLayout.addWidget(self.searchClassifyBtn)
self.verticalLayout_2.addLayout(self.horizontalLayout)
self.classifyListView = QtWidgets.QListView(self.widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.classifyListView.sizePolicy().hasHeightForWidth())
self.classifyListView.setSizePolicy(sizePolicy)
self.classifyListView.setMinimumSize(QtCore.QSize(200, 0))
self.classifyListView.setEditTriggers(
QtWidgets.QAbstractItemView.NoEditTriggers)
self.classifyListView.setObjectName("classifyListView")
self.verticalLayout_2.addWidget(self.classifyListView)
self.widget1 = QtWidgets.QWidget(self.splitter)
self.widget1.setObjectName("widget1")
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.addImageBtn = QtWidgets.QToolButton(self.widget1)
self.addImageBtn.setObjectName("addImageBtn")
self.horizontalLayout_2.addWidget(self.addImageBtn)
self.removeImageBtn = QtWidgets.QToolButton(self.widget1)
self.removeImageBtn.setObjectName("removeImageBtn")
self.horizontalLayout_2.addWidget(self.removeImageBtn)
spacerItem1 = QtWidgets.QSpacerItem(40, 20,
QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.imageListWidget = QtWidgets.QListWidget(self.widget1)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.imageListWidget.sizePolicy().hasHeightForWidth())
self.imageListWidget.setSizePolicy(sizePolicy)
self.imageListWidget.setMinimumSize(QtCore.QSize(200, 0))
self.imageListWidget.setStyleSheet(
"QListWidget::Item:hover{background:skyblue;padding-top:0px; padding-bottom:0px;}\n"
"QListWidget::item:selected{background:rgb(245, 121, 0); color:red;}"
)
self.imageListWidget.setObjectName("imageListWidget")
self.verticalLayout.addWidget(self.imageListWidget)
self.verticalLayout_3.addWidget(self.splitter)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "识图图像库管理"))
self.appMenuBtn.setText(_translate("MainWindow", "..."))
self.saveImageLibraryBtn.setText(_translate("MainWindow", "..."))
self.addClassifyBtn.setText(_translate("MainWindow", "..."))
self.removeClassifyBtn.setText(_translate("MainWindow", "..."))
self.searchClassifyBtn.setText(_translate("MainWindow", "..."))
self.addImageBtn.setText(_translate("MainWindow", "..."))
self.removeImageBtn.setText(_translate("MainWindow", "..."))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/NewlibraryDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_NewlibraryDialog(object):
def setupUi(self, NewlibraryDialog):
NewlibraryDialog.setObjectName("NewlibraryDialog")
NewlibraryDialog.resize(414, 230)
self.verticalLayout = QtWidgets.QVBoxLayout(NewlibraryDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(NewlibraryDialog)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.indexMethodCmb = QtWidgets.QComboBox(NewlibraryDialog)
self.indexMethodCmb.setEnabled(True)
self.indexMethodCmb.setObjectName("indexMethodCmb")
self.indexMethodCmb.addItem("")
self.indexMethodCmb.addItem("")
self.indexMethodCmb.addItem("")
self.verticalLayout.addWidget(self.indexMethodCmb)
self.resetCheckBox = QtWidgets.QCheckBox(NewlibraryDialog)
self.resetCheckBox.setObjectName("resetCheckBox")
self.verticalLayout.addWidget(self.resetCheckBox)
spacerItem = QtWidgets.QSpacerItem(20, 80,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(NewlibraryDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(NewlibraryDialog)
self.indexMethodCmb.setCurrentIndex(0)
self.buttonBox.accepted.connect(NewlibraryDialog.accept)
self.buttonBox.rejected.connect(NewlibraryDialog.reject)
QtCore.QMetaObject.connectSlotsByName(NewlibraryDialog)
def retranslateUi(self, NewlibraryDialog):
_translate = QtCore.QCoreApplication.translate
NewlibraryDialog.setWindowTitle(
_translate("NewlibraryDialog", "新建/重建 索引"))
self.label.setText(_translate("NewlibraryDialog", "索引方式"))
self.indexMethodCmb.setItemText(
0, _translate("NewlibraryDialog", "HNSW32"))
self.indexMethodCmb.setItemText(1,
_translate("NewlibraryDialog", "FLAT"))
self.indexMethodCmb.setItemText(2, _translate("NewlibraryDialog",
"IVF"))
self.resetCheckBox.setText(
_translate("NewlibraryDialog", "重建索引,警告:会覆盖原索引"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
NewlibraryDialog = QtWidgets.QDialog()
ui = Ui_NewlibraryDialog()
ui.setupUi(NewlibraryDialog)
NewlibraryDialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/RenameClassifyDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_RenameClassifyDialog(object):
def setupUi(self, RenameClassifyDialog):
RenameClassifyDialog.setObjectName("RenameClassifyDialog")
RenameClassifyDialog.resize(342, 194)
self.verticalLayout = QtWidgets.QVBoxLayout(RenameClassifyDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.oldlabel = QtWidgets.QLabel(RenameClassifyDialog)
self.oldlabel.setObjectName("oldlabel")
self.verticalLayout.addWidget(self.oldlabel)
self.oldNameLineEdit = QtWidgets.QLineEdit(RenameClassifyDialog)
self.oldNameLineEdit.setEnabled(False)
self.oldNameLineEdit.setObjectName("oldNameLineEdit")
self.verticalLayout.addWidget(self.oldNameLineEdit)
self.newlabel = QtWidgets.QLabel(RenameClassifyDialog)
self.newlabel.setObjectName("newlabel")
self.verticalLayout.addWidget(self.newlabel)
self.newNameLineEdit = QtWidgets.QLineEdit(RenameClassifyDialog)
self.newNameLineEdit.setObjectName("newNameLineEdit")
self.verticalLayout.addWidget(self.newNameLineEdit)
spacerItem = QtWidgets.QSpacerItem(20, 14,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.buttonBox = QtWidgets.QDialogButtonBox(RenameClassifyDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel
| QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(RenameClassifyDialog)
self.buttonBox.accepted.connect(RenameClassifyDialog.accept)
self.buttonBox.rejected.connect(RenameClassifyDialog.reject)
QtCore.QMetaObject.connectSlotsByName(RenameClassifyDialog)
def retranslateUi(self, RenameClassifyDialog):
_translate = QtCore.QCoreApplication.translate
RenameClassifyDialog.setWindowTitle(
_translate("RenameClassifyDialog", "重命名分类"))
self.oldlabel.setText(_translate("RenameClassifyDialog", "原名称"))
self.newlabel.setText(_translate("RenameClassifyDialog", "新名称"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
RenameClassifyDialog = QtWidgets.QDialog()
ui = Ui_RenameClassifyDialog()
ui.setupUi(RenameClassifyDialog)
RenameClassifyDialog.show()
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui/WaitDialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.5
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_WaitDialog(object):
def setupUi(self, WaitDialog):
WaitDialog.setObjectName("WaitDialog")
WaitDialog.setWindowModality(QtCore.Qt.NonModal)
WaitDialog.resize(324, 78)
self.verticalLayout = QtWidgets.QVBoxLayout(WaitDialog)
self.verticalLayout.setObjectName("verticalLayout")
self.msgLabel = QtWidgets.QLabel(WaitDialog)
self.msgLabel.setObjectName("msgLabel")
self.verticalLayout.addWidget(self.msgLabel)
self.progressBar = QtWidgets.QProgressBar(WaitDialog)
self.progressBar.setMaximum(0)
self.progressBar.setProperty("value", -1)
self.progressBar.setObjectName("progressBar")
self.verticalLayout.addWidget(self.progressBar)
spacerItem = QtWidgets.QSpacerItem(20, 1,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.retranslateUi(WaitDialog)
QtCore.QMetaObject.connectSlotsByName(WaitDialog)
def retranslateUi(self, WaitDialog):
_translate = QtCore.QCoreApplication.translate
WaitDialog.setWindowTitle(_translate("WaitDialog", "请等待"))
self.msgLabel.setText(_translate("WaitDialog", "正在更新索引库,请等待。。。"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WaitDialog = QtWidgets.QDialog()
ui = Ui_WaitDialog()
ui.setupUi(WaitDialog)
WaitDialog.show()
sys.exit(app.exec_())
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import hashlib
import shutil
from mod import image_list_manager
def setMenu(menu: QtWidgets.QMenu, text: str, triggered):
"""设置菜单"""
action = menu.addAction(text)
action.triggered.connect(triggered)
def fileMD5(file_path: str):
"""计算文件的MD5值"""
md5 = hashlib.md5()
with open(file_path, 'rb') as f:
md5.update(f.read())
return md5.hexdigest().lower()
def copyFile(from_path: str, to_path: str):
"""复制文件"""
shutil.copyfile(from_path, to_path)
return os.path.exists(to_path)
def removeFile(file_path: str):
"""删除文件"""
if os.path.exists(file_path):
os.remove(file_path)
return not os.path.exists(file_path)
def fileExtension(file_path: str):
"""获取文件的扩展名"""
return os.path.splitext(file_path)[1]
def copyImageToDir(self, from_image_path: str, to_dir_path: str):
"""复制图像文件到目标目录"""
if not os.path.exists(from_image_path) and not os.path.exists(to_dir_path):
return None
md5 = fileMD5(from_image_path)
file_ext = fileExtension(from_image_path)
new_path = os.path.join(to_dir_path, md5 + file_ext)
copyFile(from_image_path, new_path)
return new_path
def oneKeyImportFromFile(from_path: str, to_path: str):
"""从其它图像库 from_path {image_list.txt} 导入到图像库 to_path {image_list.txt}"""
if not os.path.exists(from_path) or not os.path.exists(to_path):
return None
if from_path == to_path:
return None
from_mgr = image_list_manager.ImageListManager(file_path=from_path)
to_mgr = image_list_manager.ImageListManager(file_path=to_path)
return oneKeyImport(from_mgr=from_mgr, to_mgr=to_mgr)
def oneKeyImportFromDirs(from_dir: str, to_image_list_path: str):
"""从其它图像库 from_dir 搜索子目录 导入到图像库 to_image_list_path"""
if not os.path.exists(from_dir) or not os.path.exists(to_image_list_path):
return None
if from_dir == os.path.dirname(to_image_list_path):
return None
from_mgr = image_list_manager.ImageListManager()
to_mgr = image_list_manager.ImageListManager(
file_path=to_image_list_path)
from_mgr.dirName = from_dir
sub_dir_list = os.listdir(from_dir)
for sub_dir in sub_dir_list:
real_sub_dir = os.path.join(from_dir, sub_dir)
if not os.path.isdir(real_sub_dir):
continue
img_list = os.listdir(real_sub_dir)
img_path = []
for img in img_list:
real_img = os.path.join(real_sub_dir, img)
if not os.path.isfile(real_img):
continue
img_path.append("{}/{}".format(sub_dir, img))
if len(img_path) == 0:
continue
from_mgr.addClassify(sub_dir)
from_mgr.resetImageList(sub_dir, img_path)
return oneKeyImport(from_mgr=from_mgr, to_mgr=to_mgr)
def oneKeyImport(from_mgr: image_list_manager.ImageListManager,
to_mgr: image_list_manager.ImageListManager):
"""一键导入"""
count = 0
for classify in from_mgr.classifyList:
img_list = from_mgr.realPathList(classify)
to_mgr.addClassify(classify)
to_img_list = to_mgr.imageList(classify)
new_img_list = []
for img in img_list:
from_image_path = img
to_dir_path = os.path.join(to_mgr.dirName, "images")
md5 = fileMD5(from_image_path)
file_ext = fileExtension(from_image_path)
new_path = os.path.join(to_dir_path, md5 + file_ext)
if os.path.exists(new_path):
# 如果新文件 MD5 重复跳过后面的复制文件操作
continue
copyFile(from_image_path, new_path)
new_img_list.append("images/" + md5 + file_ext)
count += 1
to_img_list += new_img_list
to_mgr.resetImageList(classify, to_img_list)
to_mgr.writeFile()
return count
def newFile(file_path: str):
"""创建文件"""
if os.path.exists(file_path):
return False
else:
with open(file_path, 'w') as f:
pass
return True
def isEmptyDir(dir_path: str):
"""判断目录是否为空"""
return not os.listdir(dir_path)
def initLibrary(dir_path: str):
"""初始化库"""
images_dir = os.path.join(dir_path, "images")
if not os.path.exists(images_dir):
os.makedirs(images_dir)
image_list_path = os.path.join(dir_path, "image_list.txt")
newFile(image_list_path)
return os.path.exists(dir_path)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddClassifyDialog</class>
<widget class="QDialog" name="AddClassifyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>286</width>
<height>127</height>
</rect>
</property>
<property name="windowTitle">
<string>添加分类</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>分类名称</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>11</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AddClassifyDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AddClassifyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>414</width>
<height>415</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>编辑图像分类</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>原分类</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="oldLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>新分类</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="newLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="searchWordLineEdit"/>
</item>
<item>
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>查找</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="classifyListView">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>400</width>
<height>200</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>833</width>
<height>538</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>识图图像库管理</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QToolButton" name="appMenuBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="saveImageLibraryBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addClassifyBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="removeClassifyBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSlider" name="imageScaleSlider">
<property name="maximumSize">
<size>
<width>400</width>
<height>16777215</height>
</size>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>8</number>
</property>
<property name="pageStep">
<number>2</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="searchClassifyHistoryCmb">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="searchClassifyBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="classifyListView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QToolButton" name="addImageBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="removeImageBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="imageListWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">QListWidget::Item:hover{background:skyblue;padding-top:0px; padding-bottom:0px;}
QListWidget::item:selected{background:rgb(245, 121, 0); color:red;}</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewlibraryDialog</class>
<widget class="QDialog" name="NewlibraryDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>414</width>
<height>230</height>
</rect>
</property>
<property name="windowTitle">
<string>新建/重建 索引</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>索引方式</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="indexMethodCmb">
<property name="enabled">
<bool>true</bool>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>HNSW32</string>
</property>
</item>
<item>
<property name="text">
<string>FLAT</string>
</property>
</item>
<item>
<property name="text">
<string>IVF</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QCheckBox" name="resetCheckBox">
<property name="text">
<string>重建索引,警告:会覆盖原索引</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>80</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewlibraryDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewlibraryDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RenameClassifyDialog</class>
<widget class="QDialog" name="RenameClassifyDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>342</width>
<height>194</height>
</rect>
</property>
<property name="windowTitle">
<string>重命名分类</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="oldlabel">
<property name="text">
<string>原名称</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="oldNameLineEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="newlabel">
<property name="text">
<string>新名称</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="newNameLineEdit"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>14</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RenameClassifyDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RenameClassifyDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WaitDialog</class>
<widget class="QDialog" name="WaitDialog">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>324</width>
<height>78</height>
</rect>
</property>
<property name="windowTitle">
<string>请等待</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="msgLabel">
<property name="text">
<string>正在更新索引库,请等待。。。</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>-1</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
docs/images/structure.jpg

98.7 KB | W: | H:

docs/images/structure.jpg

1.8 MB | W: | H:

docs/images/structure.jpg
docs/images/structure.jpg
docs/images/structure.jpg
docs/images/structure.jpg
  • 2-up
  • Swipe
  • Onion skin
## PP-ShiTuV2图像识别系统
## 目录
- [PP-ShiTuV2简介](#pp-shituv2简介)
- [数据集介绍](#数据集介绍)
- [模型训练](#模型训练)
- [模型评估](#模型评估)
- [模型推理](#模型推理)
- [模型部署](#模型部署)
- [模块介绍](#模块介绍)
- [主体检测模型](#主体检测模型)
- [特征提取模型](#特征提取模型)
- [训练数据集优化与扩充](#训练数据集优化与扩充)
- [骨干网络优化](#骨干网络优化)
- [网络结构优化](#网络结构优化)
- [数据增强优化](#数据增强优化)
- [参考文献](#参考文献)
## PP-ShiTuV2简介
PP-ShiTuV2 是基于 PP-ShiTuV1 改进的一个实用轻量级通用图像识别系统,相比 PP-ShiTuV1 具有更高的识别精度、更强的泛化能力以及相近的推理速度<sup>*</sup>。该系统主要针对**训练数据集**、特征提取两个部分进行优化,使用了更优的骨干网络、损失函数与训练策略。使得 PP-ShiTuV2 在多个实际应用场景上的检索性能有显著提升。
<div align="center">
<img src="../../images/structure.jpg" />
</div>
### 数据集介绍
我们将训练数据进行了合理扩充与优化,更多细节请参考 [PP-ShiTuV2 数据集](../image_recognition_pipeline/feature_extraction.md#4-实验部分)
下面以 [PP-ShiTuV2](../image_recognition_pipeline/feature_extraction.md#4-实验部分) 的数据集为例,介绍 PP-ShiTuV2 模型的训练、评估、推理流程。
### 模型训练
首先下载好 [PP-ShiTuV2 数据集](../image_recognition_pipeline/feature_extraction.md#4-实验部分) 中的16个数据集并手动进行合并、生成标注文本文件 `train_reg_all_data_v2.txt`,最后放置到 `dataset` 目录下。
合并后的文件夹结构如下所示:
```python
dataset/
├── Aliproduct/ # Aliproduct数据集文件夹
├── SOP/ # SOPt数据集文件夹
├── ...
├── Products-10k/ # Products-10k数据集文件夹
├── ...
└── train_reg_all_data_v2.txt # 标注文本文件
```
生成的 `train_reg_all_data_v2.txt` 内容如下所示:
```log
...
Aliproduct/train/50029/1766228.jpg 50029
Aliproduct/train/50029/1764348.jpg 50029
...
Products-10k/train/88823.jpg 186440
Products-10k/train/88824.jpg 186440
...
```
然后在终端运行以下命令进行训练:
```shell
# 使用0号GPU进行单卡训练
export CUDA_VISIBLE_DEVICES=0
python3.7 tools/train.py \
-c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml
# 使用0,1,2,3,4,5,6,7号GPU进行8卡分布式训练
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
python3.7 -m paddle.distributed.launch tools/train.py \
-c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml
```
**注:** 在训练时默认会开启`eval_during_train`,每训练完 `eval_interval` 个epoch就会在配置文件中 `Eval` 指定的数据集上(默认为 Aliproduct )进行模型评估并计算得到参考指标。
### 模型评估
参考 [模型评估](../image_recognition_pipeline/feature_extraction.md#53-模型评估)
### 模型推理
参考 [Python模型推理](../quick_start/quick_start_recognition.md#22-图像识别体验)[C++ 模型推理](../../../deploy/cpp_shitu/readme.md)
### 模型部署
参考 [模型部署](../inference_deployment/recognition_serving_deploy.md#3-图像识别服务部署)
## 模块介绍
### 主体检测模型
主体检测模型使用 `PicoDet-LCNet_x2_5`,详细信息参考:[picodet_lcnet_x2_5_640_mainbody](../image_recognition_pipeline/mainbody_detection.md)
### 特征提取模型
#### 训练数据集优化与扩充
在 PP-ShiTuV1 所用训练数据集的基础上,我们去掉了使用范围较小的 iCartoonFace 数据集,同时加入了更多常见、使用范围更广的数据集,如 bird400、Cars、Products-10k、fruits-262。
#### 骨干网络优化
我们将骨干网络从 `PPLCNet_x2_5` 替换成了 [`PPLCNetV2_base`](../models/PP-LCNetV2.md),相比 `PPLCNet_x2_5``PPLCNetV2_base` 基本保持了较高的分类精度,并减少了40%的推理时间<sup>*</sup>
**注:** <sup>*</sup>推理环境基于 Intel(R) Xeon(R) Gold 6271C CPU @ 2.60GHz 硬件平台,OpenVINO 推理平台。
#### 网络结构优化
我们对 `PPLCNetV2_base` 结构做了微调,并加入了在行人重检测、地标检索、人脸识别等任务上较为通用有效的优化调整。主要包括以下几点:
1. `PPLCNetV2_base` 结构微调:实验发现网络末尾的 [`ReLU`](../../../ppcls/arch/backbone/legendary_models/pp_lcnet_v2.py#L322) 对检索性能有较大影响, [`FC`](../../../ppcls/arch/backbone/legendary_models/pp_lcnet_v2.py#L325) 也会导致检索性能轻微掉点,因此我们去掉了 BackBone 末尾的 `ReLU``FC`
2. `last stride=1`:只将最后一个 stage 的 stride 改为1,即不进行下采样,以此增加最后输出的特征图的语义信息,同时不对推理速度产生太大影响。
3. `BN Neck`:在全局池化层后加入一个 `BatchNorm1D` 结构,对特征向量的每个维度进行标准化,使得模型更快地收敛。
| 模型 | training data | recall@1%(mAP%) |
| :----------------------------------------------------------------- | :---------------- | :-------------- |
| PP-ShiTuV1 | PP-ShiTuV1 数据集 | 63.0(51.5) |
| PP-ShiTuV1+`PPLCNetV2_base`+`last_stride=1`+`BNNeck`+`TripletLoss` | PP-ShiTuV1 数据集 | 72.3(60.5) |
4. `TripletAngularMarginLoss`:我们基于原始的 `TripletLoss` (困难三元组损失)进行了改进,将优化目标从 L2 欧几里得空间更换成余弦空间,并额外加入了 anchor 与 positive/negtive 之间的硬性距离约束,让训练与测试的目标更加接近,提升模型的泛化能力。
| 模型 | training data | recall@1%(mAP%) |
| :------------------------------------------------------------------------------ | :---------------- | :-------------- |
| PP-ShiTuV1+`PPLCNetV2_base`+`last_stride=1`+`BNNeck`+`TripletLoss` | PP-ShiTuV2 数据集 | 71.9(60.2) |
| PP-ShiTuV1+`PPLCNetV2_base`+`last_stride=1`+`BNNeck`+`TripletAngularMarginLoss` | PP-ShiTuV2 数据集 | 73.7(61.0) |
#### 数据增强优化
我们考虑到实际相机拍摄时目标主体可能出现一定的旋转而不一定能保持正立状态,因此我们在数据增强中加入了适当的 [随机旋转增强](../../../ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml#L117),以提升模型在真实场景中的检索能力。
结合以上3个优化点,最终在多个数据集的实验结果如下:
| 模型 | product<sup>*</sup> |
| :--------- | :------------------ |
| - | recall@1%(mAP%) |
| PP-ShiTuV1 | 63.0(51.5) |
| PP-ShiTuV2 | 73.7(61.0) |
| 模型 | Aliproduct | VeRI-Wild | LogoDet-3k | iCartoonFace | SOP | Inshop |
| :--------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- |
| - | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) |
| PP-ShiTuV1 | 83.9(83.2) | 88.7(60.1) | 86.1(73.6) | 84.1(72.3) | 79.7(58.6) | 89.1(69.4) |
| PP-ShiTuV2 | 84.2(83.3) | 87.8(68.8) | 88.0(63.2) | 53.6(27.5) | 77.6(55.3) | 90.8(74.3) |
| 模型 | gldv2 | imdb_face | iNat | instre | sketch | sop<sup>*</sup> |
| :--------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- |
| - | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) |
| PP-ShiTuV1 | 98.2(91.6) | 28.8(8.42) | 12.6(6.1) | 72.0(50.4) | 27.9(9.5) | 97.6(90.3) |
| PP-ShiTuV2 | 98.1(90.5) | 35.9(11.2) | 38.6(23.9) | 87.7(71.4) | 39.3(15.6) | 98.3(90.9) |
**注:** product数据集是为了验证PP-ShiTu的泛化性能而制作的数据集,所有的数据都没有在训练和测试集中出现。该数据包含8个大类(人脸、化妆品、地标、红酒、手表、车、运动鞋、饮料),299个小类。测试时,使用299个小类的标签进行测试;sop数据集来自[GPR1200: A Benchmark for General-Purpose Content-Based Image Retrieval](https://arxiv.org/abs/2111.13122),可视为“SOP”数据集的子集。
## 参考文献
1. Schall, Konstantin, et al. "GPR1200: A Benchmark for General-Purpose Content-Based Image Retrieval." International Conference on Multimedia Modeling. Springer, Cham, 2022.
2. Luo, Hao, et al. "A strong baseline and batch normalization neck for deep person re-identification." IEEE Transactions on Multimedia 22.10 (2019): 2597-2609.
...@@ -354,24 +354,24 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模 ...@@ -354,24 +354,24 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模
| 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 | | 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 |
|------------------------|-----------|-----------|------------------|------------------|----------|------------------------|------------------------|------------------------|------------------------| |------------------------|-----------|-----------|------------------|------------------|----------|------------------------|------------------------|------------------------|------------------------|
| ViT_small_<br/>patch16_224 | 0.7769 | 0.9342 | 3.71 | 9.05 | 16.72 | 9.41 | 48.60 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_small_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_small_patch16_224_infer.tar) | | ViT_small_<br/>patch16_224 | 0.7553 | 0.9211 | 3.71 | 9.05 | 16.72 | 9.41 | 48.60 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_small_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_small_patch16_224_infer.tar) |
| ViT_base_<br/>patch16_224 | 0.8195 | 0.9617 | 6.12 | 14.84 | 28.51 | 16.85 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_base_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_base_patch16_224_infer.tar) | | ViT_base_<br/>patch16_224 | 0.8187 | 0.9618 | 6.12 | 14.84 | 28.51 | 16.85 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_base_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_base_patch16_224_infer.tar) |
| ViT_base_<br/>patch16_384 | 0.8414 | 0.9717 | 14.15 | 48.38 | 95.06 | 49.35 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_base_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_base_patch16_384_infer.tar) | | ViT_base_<br/>patch16_384 | 0.8414 | 0.9717 | 14.15 | 48.38 | 95.06 | 49.35 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_base_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_base_patch16_384_infer.tar) |
| ViT_base_<br/>patch32_384 | 0.8176 | 0.9613 | 4.94 | 13.43 | 24.08 | 12.66 | 88.19 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_base_patch32_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_base_patch32_384_infer.tar) | | ViT_base_<br/>patch32_384 | 0.8176 | 0.9613 | 4.94 | 13.43 | 24.08 | 12.66 | 88.19 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_base_patch32_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_base_patch32_384_infer.tar) |
| ViT_large_<br/>patch16_224 | 0.8323 | 0.9650 | 15.53 | 49.50 | 94.09 | 59.65 | 304.12 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_large_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_large_patch16_224_infer.tar) | | ViT_large_<br/>patch16_224 | 0.8303 | 0.9655 | 15.53 | 49.50 | 94.09 | 59.65 | 304.12 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_large_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_large_patch16_224_infer.tar) |
|ViT_large_<br/>patch16_384| 0.8513 | 0.9736 | 39.51 | 152.46 | 304.06 | 174.70 | 304.12 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_large_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_large_patch16_384_infer.tar) | |ViT_large_<br/>patch16_384| 0.8513 | 0.9736 | 39.51 | 152.46 | 304.06 | 174.70 | 304.12 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_large_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_large_patch16_384_infer.tar) |
|ViT_large_<br/>patch32_384| 0.8153 | 0.9608 | 11.44 | 36.09 | 70.63 | 44.24 | 306.48 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_large_patch32_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_large_patch32_384_infer.tar) | |ViT_large_<br/>patch32_384| 0.8153 | 0.9608 | 11.44 | 36.09 | 70.63 | 44.24 | 306.48 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ViT_large_patch32_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ViT_large_patch32_384_infer.tar) |
| 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 | | 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 |
|------------------------|-----------|-----------|------------------|------------------|----------|------------------------|------------------------|------------------------|------------------------| |------------------------|-----------|-----------|------------------|------------------|----------|------------------------|------------------------|------------------------|------------------------|
| DeiT_tiny_<br>patch16_224 | 0.718 | 0.910 | 3.61 | 3.94 | 6.10 | 1.07 | 5.68 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_tiny_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_tiny_patch16_224_infer.tar) | | DeiT_tiny_<br>patch16_224 | 0.7208 | 0.9112 | 3.61 | 3.94 | 6.10 | 1.07 | 5.68 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_tiny_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_tiny_patch16_224_infer.tar) |
| DeiT_small_<br>patch16_224 | 0.796 | 0.949 | 3.61 | 6.24 | 10.49 | 4.24 | 21.97 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_small_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_small_patch16_224_infer.tar) | | DeiT_small_<br>patch16_224 | 0.7982 | 0.9495 | 3.61 | 6.24 | 10.49 | 4.24 | 21.97 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_small_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_small_patch16_224_infer.tar) |
| DeiT_base_<br>patch16_224 | 0.817 | 0.957 | 6.13 | 14.87 | 28.50 | 16.85 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_patch16_224_infer.tar) | | DeiT_base_<br>patch16_224 | 0.8180 | 0.9558 | 6.13 | 14.87 | 28.50 | 16.85 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_patch16_224_infer.tar) |
| DeiT_base_<br>patch16_384 | 0.830 | 0.962 | 14.12 | 48.80 | 97.60 | 49.35 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_patch16_384_infer.tar) | | DeiT_base_<br>patch16_384 | 0.8289 | 0.9624 | 14.12 | 48.80 | 97.60 | 49.35 | 86.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_patch16_384_infer.tar) |
| DeiT_tiny_<br>distilled_patch16_224 | 0.741 | 0.918 | 3.51 | 4.05 | 6.03 | 1.08 | 5.87 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_tiny_distilled_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_tiny_distilled_patch16_224_infer.tar) | | DeiT_tiny_<br>distilled_patch16_224 | 0.7449 | 0.9192 | 3.51 | 4.05 | 6.03 | 1.08 | 5.87 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_tiny_distilled_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_tiny_distilled_patch16_224_infer.tar) |
| DeiT_small_<br>distilled_patch16_224 | 0.809 | 0.953 | 3.70 | 6.20 | 10.53 | 4.26 | 22.36 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_small_distilled_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_small_distilled_patch16_224_infer.tar) | | DeiT_small_<br>distilled_patch16_224 | 0.8117 | 0.9538 | 3.70 | 6.20 | 10.53 | 4.26 | 22.36 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_small_distilled_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_small_distilled_patch16_224_infer.tar) |
| DeiT_base_<br>distilled_patch16_224 | 0.831 | 0.964 | 6.17 | 14.94 | 28.58 | 16.93 | 87.18 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_distilled_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_distilled_patch16_224_infer.tar) | | DeiT_base_<br>distilled_patch16_224 | 0.8330 | 0.9647 | 6.17 | 14.94 | 28.58 | 16.93 | 87.18 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_distilled_patch16_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_distilled_patch16_224_infer.tar) |
| DeiT_base_<br>distilled_patch16_384 | 0.851 | 0.973 | 14.12 | 48.76 | 97.09 | 49.43 | 87.18 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_distilled_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_distilled_patch16_384_infer.tar) | | DeiT_base_<br>distilled_patch16_384 | 0.8520 | 0.9720 | 14.12 | 48.76 | 97.09 | 49.43 | 87.18 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/DeiT_base_distilled_patch16_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/DeiT_base_distilled_patch16_384_infer.tar) |
<a name="RepVGG"></a> <a name="RepVGG"></a>
...@@ -426,14 +426,14 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模 ...@@ -426,14 +426,14 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模
| 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 | | 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 |
| ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| SwinTransformer_tiny_patch4_window7_224 | 0.8069 | 0.9534 | 6.59 | 9.68 | 16.32 | 4.35 | 28.26 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_tiny_patch4_window7_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_tiny_patch4_window7_224_infer.tar) | | SwinTransformer_tiny_patch4_window7_224 | 0.8110 | 0.9549 | 6.59 | 9.68 | 16.32 | 4.35 | 28.26 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_tiny_patch4_window7_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_tiny_patch4_window7_224_infer.tar) |
| SwinTransformer_small_patch4_window7_224 | 0.8275 | 0.9613 | 12.54 | 17.07 | 28.08 | 8.51 | 49.56 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_small_patch4_window7_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_small_patch4_window7_224_infer.tar) | | SwinTransformer_small_patch4_window7_224 | 0.8321 | 0.9622 | 12.54 | 17.07 | 28.08 | 8.51 | 49.56 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_small_patch4_window7_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_small_patch4_window7_224_infer.tar) |
| SwinTransformer_base_patch4_window7_224 | 0.8300 | 0.9626 | 13.37 | 23.53 | 39.11 | 15.13 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window7_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window7_224_infer.tar) | | SwinTransformer_base_patch4_window7_224 | 0.8337 | 0.9643 | 13.37 | 23.53 | 39.11 | 15.13 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window7_224_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window7_224_infer.tar) |
| SwinTransformer_base_patch4_window12_384 | 0.8439 | 0.9693 | 19.52 | 64.56 | 123.30 | 44.45 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window12_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window12_384_infer.tar) | | SwinTransformer_base_patch4_window12_384 | 0.8417 | 0.9674 | 19.52 | 64.56 | 123.30 | 44.45 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window12_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window12_384_infer.tar) |
| SwinTransformer_base_patch4_window7_224<sup>[1]</sup> | 0.8487 | 0.9746 | 13.53 | 23.46 | 39.13 | 15.13 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window7_224_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window7_224_infer.tar) | | SwinTransformer_base_patch4_window7_224<sup>[1]</sup> | 0.8516 | 0.9748 | 13.53 | 23.46 | 39.13 | 15.13 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window7_224_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window7_224_infer.tar) |
| SwinTransformer_base_patch4_window12_384<sup>[1]</sup> | 0.8642 | 0.9807 | 19.65 | 64.72 | 123.42 | 44.45 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window12_384_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window12_384_infer.tar) | | SwinTransformer_base_patch4_window12_384<sup>[1]</sup> | 0.8634 | 0.9798 | 19.65 | 64.72 | 123.42 | 44.45 | 87.70 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_base_patch4_window12_384_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_base_patch4_window12_384_infer.tar) |
| SwinTransformer_large_patch4_window7_224<sup>[1]</sup> | 0.8596 | 0.9783 | 15.74 | 38.57 | 71.49 | 34.02 | 196.43 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_large_patch4_window7_224_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_large_patch4_window7_224_22kto1k_infer.tar) | | SwinTransformer_large_patch4_window7_224<sup>[1]</sup> | 0.8619 | 0.9788 | 15.74 | 38.57 | 71.49 | 34.02 | 196.43 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_large_patch4_window7_224_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_large_patch4_window7_224_22kto1k_infer.tar) |
| SwinTransformer_large_patch4_window12_384<sup>[1]</sup> | 0.8719 | 0.9823 | 32.61 | 116.59 | 223.23 | 99.97 | 196.43 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_large_patch4_window12_384_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_large_patch4_window12_384_22kto1k_infer.tar) | | SwinTransformer_large_patch4_window12_384<sup>[1]</sup> | 0.8706 | 0.9814 | 32.61 | 116.59 | 223.23 | 99.97 | 196.43 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/SwinTransformer_large_patch4_window12_384_22kto1k_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SwinTransformer_large_patch4_window12_384_22kto1k_infer.tar) |
[1]:基于 ImageNet22k 数据集预训练,然后在 ImageNet1k 数据集迁移学习得到。 [1]:基于 ImageNet22k 数据集预训练,然后在 ImageNet1k 数据集迁移学习得到。
...@@ -446,7 +446,7 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模 ...@@ -446,7 +446,7 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模
| 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(M) | Params(M) | 预训练模型下载地址 | inference模型下载地址 | | 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(M) | Params(M) | 预训练模型下载地址 | inference模型下载地址 |
| ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| LeViT_128S | 0.7598 | 0.9269 | | | | 281 | 7.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_128S_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_128S_infer.tar) | | LeViT_128S | 0.7598 | 0.9269 | | | | 281 | 7.42 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_128S_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_128S_infer.tar) |
| LeViT_128 | 0.7810 | 0.9371 | | | | 365 | 8.87 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_128_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_128_infer.tar) | | LeViT_128 | 0.7810 | 0.9372 | | | | 365 | 8.87 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_128_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_128_infer.tar) |
| LeViT_192 | 0.7934 | 0.9446 | | | | 597 | 10.61 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_192_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_192_infer.tar) | | LeViT_192 | 0.7934 | 0.9446 | | | | 597 | 10.61 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_192_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_192_infer.tar) |
| LeViT_256 | 0.8085 | 0.9497 | | | | 1049 | 18.45 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_256_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_256_infer.tar) | | LeViT_256 | 0.8085 | 0.9497 | | | | 1049 | 18.45 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_256_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_256_infer.tar) |
| LeViT_384 | 0.8191 | 0.9551 | | | | 2234 | 38.45 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_384_infer.tar) | | LeViT_384 | 0.8191 | 0.9551 | | | | 2234 | 38.45 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/LeViT_384_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/LeViT_384_infer.tar) |
...@@ -461,12 +461,12 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模 ...@@ -461,12 +461,12 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模
| 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 | | 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 |
| ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| pcpvt_small | 0.8082 | 0.9552 | 7.32 | 10.51 | 15.27 |3.67 | 24.06 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/pcpvt_small_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/pcpvt_small_infer.tar) | | pcpvt_small | 0.8115 | 0.9567 | 7.32 | 10.51 | 15.27 |3.67 | 24.06 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/pcpvt_small_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/pcpvt_small_infer.tar) |
| pcpvt_base | 0.8242 | 0.9619 | 12.20 | 16.22 | 23.16 | 6.44 | 43.83 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/pcpvt_base_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/pcpvt_base_infer.tar) | | pcpvt_base | 0.8268 | 0.9627 | 12.20 | 16.22 | 23.16 | 6.44 | 43.83 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/pcpvt_base_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/pcpvt_base_infer.tar) |
| pcpvt_large | 0.8273 | 0.9650 | 16.47 | 22.90 | 32.73 | 9.50 | 60.99 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/pcpvt_large_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/pcpvt_large_infer.tar) | | pcpvt_large | 0.8306 | 0.9659 | 16.47 | 22.90 | 32.73 | 9.50 | 60.99 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/pcpvt_large_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/pcpvt_large_infer.tar) |
| alt_gvt_small | 0.8140 | 0.9546 | 6.94 | 9.01 | 12.27 |2.81 | 24.06 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/alt_gvt_small_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/alt_gvt_small_infer.tar) | | alt_gvt_small | 0.8177 | 0.9557 | 6.94 | 9.01 | 12.27 |2.81 | 24.06 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/alt_gvt_small_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/alt_gvt_small_infer.tar) |
| alt_gvt_base | 0.8294 | 0.9621 | 9.37 | 15.02 | 24.54 | 8.34 | 56.07 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/alt_gvt_base_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/alt_gvt_base_infer.tar) | | alt_gvt_base | 0.8315 | 0.9629 | 9.37 | 15.02 | 24.54 | 8.34 | 56.07 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/alt_gvt_base_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/alt_gvt_base_infer.tar) |
| alt_gvt_large | 0.8331 | 0.9642 | 11.76 | 22.08 | 35.12 | 14.81 | 99.27 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/alt_gvt_large_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/alt_gvt_large_infer.tar) | | alt_gvt_large | 0.8364 | 0.9651 | 11.76 | 22.08 | 35.12 | 14.81 | 99.27 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/alt_gvt_large_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/alt_gvt_large_infer.tar) |
**注**:与 Reference 的精度差异源于数据预处理不同。 **注**:与 Reference 的精度差异源于数据预处理不同。
...@@ -551,13 +551,13 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模 ...@@ -551,13 +551,13 @@ ViT(Vision Transformer) 与 DeiT(Data-efficient Image Transformers)系列模
| 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 | | 模型 | Top-1 Acc | Top-5 Acc | time(ms)<br>bs=1 | time(ms)<br>bs=4 | time(ms)<br/>bs=8 | FLOPs(G) | Params(M) | 预训练模型下载地址 | inference模型下载地址 |
| ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | ---------- | --------- | --------- | ---------------- | ---------------- | -------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| PVT_V2_B0 | 0.705 | 0.902 | - | - | - | 0.53 | 3.7 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B0_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B0_infer.tar) | | PVT_V2_B0 | 0.7052 | 0.9016 | - | - | - | 0.53 | 3.7 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B0_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B0_infer.tar) |
| PVT_V2_B1 | 0.787 | 0.945 | - | - | - | 2.0 | 14.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B1_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B1_infer.tar) | | PVT_V2_B1 | 0.7869 | 0.9450 | - | - | - | 2.0 | 14.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B1_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B1_infer.tar) |
| PVT_V2_B2 | 0.821 | 0.960 | - | - | - | 3.9 | 25.4 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B2_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B2_infer.tar) | | PVT_V2_B2 | 0.8206 | 0.9599 | - | - | - | 3.9 | 25.4 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B2_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B2_infer.tar) |
| PVT_V2_B2_Linear | 0.821 | 0.961 | - | - | - | 3.8 | 22.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B2_Linear_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B2_Linear_infer.tar) | | PVT_V2_B2_Linear | 0.8205 | 0.9605 | - | - | - | 3.8 | 22.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B2_Linear_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B2_Linear_infer.tar) |
| PVT_V2_B3 | 0.831 | 0.965 | - | - |- | 6.7 | 45.2 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B3_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B3_infer.tar) | | PVT_V2_B3 | 0.8310 | 0.9648 | - | - |- | 6.7 | 45.2 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B3_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B3_infer.tar) |
| PVT_V2_B4 | 0.836 | 0.967 | - | - | - | 9.8 | 62.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B4_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B4_infer.tar) | | PVT_V2_B4 | 0.8361 | 0.9666 | - | - | - | 9.8 | 62.6 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B4_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B4_infer.tar) |
| PVT_V2_B5 | 0.837 | 0.966 | - | - | - | 11.4 | 82.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B5_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B5_infer.tar) | | PVT_V2_B5 | 0.8374 | 0.9662 | - | - | - | 11.4 | 82.0 | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/PVT_V2_B5_pretrained.pdparams) | [下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PVT_V2_B5_infer.tar) |
<a name="MobileViT"></a> <a name="MobileViT"></a>
......
...@@ -344,7 +344,7 @@ PaddleClas 提供了基于 Paddle2ONNX 来完成 inference 模型转换 ONNX 模 ...@@ -344,7 +344,7 @@ PaddleClas 提供了基于 Paddle2ONNX 来完成 inference 模型转换 ONNX 模
#### 5.1 方法总结与对比 #### 5.1 方法总结与对比
上述算法能快速地迁移至多数的ReID模型中,能进一步提升ReID模型的性能。 上述算法能快速地迁移至多数的ReID模型中(参考 [PP-ShiTuV2](../PPShiTu/PPShiTuV2_introduction.md) ),能进一步提升ReID模型的性能,
#### 5.2 使用建议/FAQ #### 5.2 使用建议/FAQ
......
简体中文|[English](../../en/image_recognition_pipeline/feature_extraction_en.md) 简体中文 | [English](../../en/image_recognition_pipeline/feature_extraction_en.md)
# 特征提取 # 特征提取
## 目录 ## 目录
...@@ -6,10 +6,10 @@ ...@@ -6,10 +6,10 @@
- [1. 摘要](#1-摘要) - [1. 摘要](#1-摘要)
- [2. 介绍](#2-介绍) - [2. 介绍](#2-介绍)
- [3. 方法](#3-方法) - [3. 方法](#3-方法)
- [3.1 Backbone](#31-backbone) - [3.1 Backbone](#31-backbone)
- [3.2 Neck](#32-neck) - [3.2 Neck](#32-neck)
- [3.3 Head](#33-head) - [3.3 Head](#33-head)
- [3.4 Loss](#34-loss) - [3.4 Loss](#34-loss)
- [4. 实验部分](#4-实验部分) - [4. 实验部分](#4-实验部分)
- [5. 自定义特征提取](#5-自定义特征提取) - [5. 自定义特征提取](#5-自定义特征提取)
- [5.1 数据准备](#51-数据准备) - [5.1 数据准备](#51-数据准备)
...@@ -35,56 +35,72 @@ ...@@ -35,56 +35,72 @@
![](../../images/feature_extraction_framework.png) ![](../../images/feature_extraction_framework.png)
图中各个模块的功能为: 图中各个模块的功能为:
- **Backbone**: 用于提取输入图像初步特征的骨干网络,一般由配置文件中的 [`Backbone`](../../../ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml#L26-L29) 以及 [`BackboneStopLayer`](../../../ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml#L30-L31) 字段共同指定。 - **Backbone**: 用于提取输入图像初步特征的骨干网络,一般由配置文件中的 [Backbone](../../../ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml#L33-L37) 以及 [BackboneStopLayer](../../../ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml#L38-L39) 字段共同指定。
- **Neck**: 用以特征增强及特征维度变换。可以是一个简单的 FC Layer,用来做特征维度变换;也可以是较复杂的 FPN 结构,用以做特征增强,一般由配置文件中的 [`Neck`](../../../ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml#L32-L35)字段指定。 - **Neck**: 用以特征增强及特征维度变换。可以是一个简单的 FC Layer,用来做特征维度变换;也可以是较复杂的 FPN 结构,用以做特征增强,一般由配置文件中的 [Neck](../../../ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml#L40-L51) 字段指定。
- **Head**: 用来将 feature 转化为 logits,让模型在训练阶段能以分类任务的形式进行训练。除了常用的 FC Layer 外,还可以替换为 cosmargin, arcmargin, circlemargin 等模块,一般由配置文件中的 [`Head`](../../../ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml#L36-L41)字段指定。 - **Head**: 用来将 `Neck` 的输出 feature 转化为 logits,让模型在训练阶段能以分类任务的形式进行训练。除了常用的 FC Layer 外,还可以替换为 [CosMargin](../../../ppcls/arch/gears/cosmargin.py), [ArcMargin](../../../ppcls/arch/gears/arcmargin.py), [CircleMargin](../../../ppcls/arch/gears/circlemargin.py) 等模块,一般由配置文件中的 [Head](`../../../ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml#L52-L60) 字段指定。
- **Loss**: 指定所使用的 Loss 函数。我们将 Loss 设计为组合 loss 的形式,可以方便地将 Classification Loss 和 Metric learning Loss 组合在一起,一般由配置文件中的 [`Loss`](../../../ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml#L44-L50)字段指定。 - **Loss**: 指定所使用的 Loss 函数。我们将 Loss 设计为组合 loss 的形式,可以方便地将 Classification Loss 和 Metric learning Loss 组合在一起,一般由配置文件中的 [Loss](../../../ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml#L63-L77) 字段指定。
<a name="3"></a> <a name="3"></a>
## 3. 方法 ## 3. 方法
### 3.1 Backbone #### 3.1 Backbone
Backbone 部分采用了 [PP_LCNet_x2_5](../models/PP-LCNet.md),其针对Intel CPU端的性能优化探索了多个有效的结构设计方案,最终实现了在不增加推理时间的情况下,进一步提升模型的性能,最终大幅度超越现有的 SOTA 模型。 Backbone 部分采用了 [PP-LCNetV2_base](../models/PP-LCNetV2.md),其针对Intel CPU端的性能优化探索了多个有效的结构设计方案,最终实现了在不增加推理时间的情况下,进一步提升模型的性能,最终大幅度超越现有的 SOTA 模型。
### 3.2 Neck #### 3.2 Neck
Neck 部分采用了 [FC Layer](../../../ppcls/arch/gears/fc.py),对 Backbone 抽取得到的特征进行降维,减少了特征存储的成本与计算量 Neck 部分采用了 [BN Neck](../../../ppcls/arch/gears/bnneck.py),对 Backbone 抽取得到的特征的每个维度进行标准化操作,减少了同时优化度量学习损失和分类损失的难度
### 3.3 Head #### 3.3 Head
Head 部分选用 [ArcMargin](../../../ppcls/arch/gears/arcmargin.py),在训练时通过指定margin,增大同类特征之间的角度差异再进行分类,进一步提升抽取特征的表征能力 Head 部分选用 [FC Layer](../../../ppcls/arch/gears/fc.py),使用分类头将 feature 转换成 logits 供后续计算分类损失
### 3.4 Loss #### 3.4 Loss
Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训练时以分类任务的损失函数来指导网络进行优化。详细的配置文件见[通用识别配置文件](../../../ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml) Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py)[TripletAngularMarginLoss](../../../ppcls/loss/tripletangularmarginloss.py),在训练时以分类损失和基于角度的三元组损失来指导网络进行优化。详细的配置文件见 [GeneralRecognitionV2_PPLCNetV2_base.yaml](../../../ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml#L63-77)
<a name="4"></a> <a name="4"></a>
## 4. 实验部分 ## 4. 实验部分
训练数据为如下 7 个公开数据集的汇总: 我们对原有的训练数据进行了合理扩充与优化,最终使用如下 16 个公开数据集的汇总:
| 数据集 | 数据量 | 类别数 | 场景 | 数据集地址 | | 数据集 | 数据量 | 类别数 | 场景 | 数据集地址 |
| :----------: | :-----: | :------: | :------: | :--------------------------------------------------------------------------: | | :--------------------- | :-----: | :------: | :---: | :----------------------------------------------------------------------------------: |
| Aliproduct | 2498771 | 50030 | 商品 | [地址](https://retailvisionworkshop.github.io/recognition_challenge_2020/) | | Aliproduct | 2498771 | 50030 | 商品 | [地址](https://retailvisionworkshop.github.io/recognition_challenge_2020/) |
| GLDv2 | 1580470 | 81313 | 地标 | [地址](https://github.com/cvdfoundation/google-landmark) | | GLDv2 | 1580470 | 81313 | 地标 | [地址](https://github.com/cvdfoundation/google-landmark) |
| VeRI-Wild | 277797 | 30671 | 车辆 | [地址](https://github.com/PKU-IMRE/VERI-Wild) | | VeRI-Wild | 277797 | 30671 | 车辆 | [地址](https://github.com/PKU-IMRE/VERI-Wild) |
| LogoDet-3K | 155427 | 3000 | Logo | [地址](https://github.com/Wangjing1551/LogoDet-3K-Dataset) | | LogoDet-3K | 155427 | 3000 | Logo | [地址](https://github.com/Wangjing1551/LogoDet-3K-Dataset) |
| iCartoonFace | 389678 | 5013 | 动漫人物 | [地址](http://challenge.ai.iqiyi.com/detail?raceId=5def69ace9fcf68aef76a75d) | | SOP | 59551 | 11318 | 商品 | [地址](https://cvgl.stanford.edu/projects/lifted_struct/) |
| SOP | 59551 | 11318 | 商品 | [地址](https://cvgl.stanford.edu/projects/lifted_struct/) | | Inshop | 25882 | 3997 | 商品 | [地址](http://mmlab.ie.cuhk.edu.hk/projects/DeepFashion.html) |
| Inshop | 25882 | 3997 | 商品 | [地址](http://mmlab.ie.cuhk.edu.hk/projects/DeepFashion.html) | | bird400 | 58388 | 400 | 鸟类 | [地址](https://www.kaggle.com/datasets/gpiosenka/100-bird-species) |
| **Total** | **5M** | **185K** | ---- | ---- | | 104flows | 12753 | 104 | 花类 | [地址](https://www.robots.ox.ac.uk/~vgg/data/flowers/102/) |
| Cars | 58315 | 112 | 车辆 | [地址](https://ai.stanford.edu/~jkrause/cars/car_dataset.html) |
最终的模型效果如下表所示: | Fashion Product Images | 44441 | 47 | 商品 | [地址](https://www.kaggle.com/datasets/paramaggarwal/fashion-product-images-dataset) |
| flowerrecognition | 24123 | 59 | 花类 | [地址](https://www.kaggle.com/datasets/aymenktari/flowerrecognition) |
| 模型 | Aliproduct | VeRI-Wild | LogoDet-3K | iCartoonFace | SOP | Inshop | Latency(ms) | | food-101 | 101000 | 101 | 食物 | [地址](https://data.vision.ee.ethz.ch/cvl/datasets_extra/food-101/) |
| :-----------------------------: | :--------: | :-------: | :--------: | :----------: | :---: | :----: | :---------: | | fruits-262 | 225639 | 262 | 水果 | [地址](https://www.kaggle.com/datasets/aelchimminut/fruits262) |
| GeneralRecognition_PPLCNet_x2_5 | 0.839 | 0.888 | 0.861 | 0.841 | 0.793 | 0.892 | 5.0 | | inaturalist | 265213 | 1010 | 自然 | [地址](https://github.com/visipedia/inat_comp/tree/master/2017) |
| indoor-scenes | 15588 | 67 | 室内 | [地址](https://www.kaggle.com/datasets/itsahmad/indoor-scenes-cvpr-2019) |
* 预训练模型地址:[通用识别预训练模型](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/general_PPLCNet_x2_5_pretrained_v1.0.pdparams) | Products-10k | 141931 | 9691 | 商品 | [地址](https://products-10k.github.io/) |
* 采用的评测指标为:`Recall@1` | CompCars | 16016 | 431 | 车辆 | [地址](http://​​​​​​http://ai.stanford.edu/~jkrause/cars/car_dataset.html​) |
| **Total** | **6M** | **192K** | - | - |
最终的模型精度指标如下表所示:
| 模型 | Aliproduct | VeRI-Wild | LogoDet-3k | iCartoonFace | SOP | Inshop |
| :--------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- |
| - | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) |
| PP-ShiTuV2 | 84.2(83.3) | 87.8(68.8) | 88.0(63.2) | 53.6(27.5) | 77.6(55.3) | 90.8(74.3) |
| 模型 | gldv2 | imdb_face | iNat | instre | sketch | sop<sup>*</sup> |
| :--------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- | :-------------- |
| - | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) | recall@1%(mAP%) |
| PP-ShiTuV2 | 98.1(90.5) | 35.9(11.2) | 38.6(23.9) | 87.7(71.4) | 39.3(15.6) | 98.3(90.9) |
* 预训练模型地址:[general_PPLCNetV2_base_pretrained_v1.0.pdparams](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/PPShiTuV2/general_PPLCNetV2_base_pretrained_v1.0.pdparams)
* 采用的评测指标为:`Recall@1``mAP`
* 速度评测机器的 CPU 具体信息为:`Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz` * 速度评测机器的 CPU 具体信息为:`Intel(R) Xeon(R) Gold 6148 CPU @ 2.40GHz`
* 速度指标的评测条件为: 开启 MKLDNN, 线程数设置为 10 * 速度指标的评测条件为: 开启 MKLDNN, 线程数设置为 10
...@@ -94,47 +110,52 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训 ...@@ -94,47 +110,52 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训
自定义特征提取,是指依据自己的任务,重新训练特征提取模型。 自定义特征提取,是指依据自己的任务,重新训练特征提取模型。
下面基于`GeneralRecognition_PPLCNet_x2_5.yaml`配置文件,介绍主要的四个步骤:1)数据准备;2)模型训练;3)模型评估;4)模型推理 下面基于 `GeneralRecognitionV2_PPLCNetV2_base.yaml` 配置文件,介绍主要的四个步骤:1)数据准备;2)模型训练;3)模型评估;4)模型推理
<a name="5.1"></a> <a name="5.1"></a>
### 5.1 数据准备 ### 5.1 数据准备
首先需要基于任务定制自己的数据集。数据集格式与文件结构详见[数据集格式说明](../data_preparation/recognition_dataset.md) 首先需要基于任务定制自己的数据集。数据集格式与文件结构详见 [数据集格式说明](../data_preparation/recognition_dataset.md)
准备完毕之后还需要在配置文件中修改数据配置相关的内容, 主要包括数据集的地址以及类别数量。对应到配置文件中的位置如下所示: 准备完毕之后还需要在配置文件中修改数据配置相关的内容, 主要包括数据集的地址以及类别数量。对应到配置文件中的位置如下所示:
- 修改类别数: - 修改类别数:
```yaml ```yaml
Head: Head:
name: ArcMargin name: FC
embedding_size: 512 embedding_size: *feat_dim
class_num: 185341 # 此处表示类别数 class_num: 192612 # 此处表示类别数
weight_attr:
initializer:
name: Normal
std: 0.001
bias_attr: False
``` ```
- 修改训练数据集配置: - 修改训练数据集配置:
```yaml ```yaml
Train: Train:
dataset: dataset:
name: ImageNetDataset name: ImageNetDataset
image_root: ./dataset/ # 此处表示train数据所在的目录 image_root: ./dataset/ # 此处表示train数据集所在的目录
cls_label_path: ./dataset/train_reg_all_data.txt # 此处表示train数据集label文件的地址 cls_label_path: ./dataset/train_reg_all_data_v2.txt # 此处表示train数据集对应标注文件的地址
relabel: True
``` ```
- 修改评估数据集中query数据配置: - 修改评估数据集中query数据配置:
```yaml ```yaml
Query: Query:
dataset: dataset:
name: VeriWild name: VeriWild
image_root: ./dataset/Aliproduct/ # 此处表示query数据集所在的目录 image_root: ./dataset/Aliproduct/ # 此处表示query数据集所在的目录
cls_label_path: ./dataset/Aliproduct/val_list.txt # 此处表示query数据集label文件的地址 cls_label_path: ./dataset/Aliproduct/val_list.txt # 此处表示query数据集对应标注文件的地址
``` ```
- 修改评估数据集中gallery数据配置: - 修改评估数据集中gallery数据配置:
```yaml ```yaml
Gallery: Gallery:
dataset: dataset:
name: VeriWild name: VeriWild
image_root: ./dataset/Aliproduct/ # 此处表示gallery数据集所在的目录 image_root: ./dataset/Aliproduct/ # 此处表示gallery数据集所在的目录
cls_label_path: ./dataset/Aliproduct/val_list.txt # 此处表示gallery数据集label文件的地址 cls_label_path: ./dataset/Aliproduct/val_list.txt # 此处表示gallery数据集对应标注文件的地址
``` ```
<a name="5.2"></a> <a name="5.2"></a>
...@@ -147,14 +168,14 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训 ...@@ -147,14 +168,14 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训
```shell ```shell
export CUDA_VISIBLE_DEVICES=0 export CUDA_VISIBLE_DEVICES=0
python3.7 tools/train.py \ python3.7 tools/train.py \
-c ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml -c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml
``` ```
- 单机多卡训练 - 单机多卡训练
```shell ```shell
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python3.7 -m paddle.distributed.launch \ python3.7 -m paddle.distributed.launch --gpus="0,1,2,3" \
--gpus="0,1,2,3" tools/train.py \ tools/train.py \
-c ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml -c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml
``` ```
**注意:** **注意:**
配置文件中默认采用`在线评估`的方式,如果你想加快训练速度,可以关闭`在线评估`功能,只需要在上述命令的后面,增加 `-o Global.eval_during_train=False` 配置文件中默认采用`在线评估`的方式,如果你想加快训练速度,可以关闭`在线评估`功能,只需要在上述命令的后面,增加 `-o Global.eval_during_train=False`
...@@ -165,15 +186,15 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训 ...@@ -165,15 +186,15 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训
```shell ```shell
export CUDA_VISIBLE_DEVICES=0 export CUDA_VISIBLE_DEVICES=0
python3.7 tools/train.py \ python3.7 tools/train.py \
-c ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \ -c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml \
-o Global.checkpoint="output/RecModel/latest" -o Global.checkpoint="output/RecModel/latest"
``` ```
- 单机多卡断点恢复训练 - 单机多卡断点恢复训练
```shell ```shell
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python3.7 -m paddle.distributed.launch \ python3.7 -m paddle.distributed.launch --gpus="0,1,2,3" \
--gpus="0,1,2,3" tools/train.py \ tools/train.py \
-c ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \ -c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml \
-o Global.checkpoint="output/RecModel/latest" -o Global.checkpoint="output/RecModel/latest"
``` ```
...@@ -187,16 +208,16 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训 ...@@ -187,16 +208,16 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训
```shell ```shell
export CUDA_VISIBLE_DEVICES=0 export CUDA_VISIBLE_DEVICES=0
python3.7 tools/eval.py \ python3.7 tools/eval.py \
-c ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \ -c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml \
-o Global.pretrained_model="output/RecModel/best_model" -o Global.pretrained_model="output/RecModel/best_model"
``` ```
- 多卡评估 - 多卡评估
```shell ```shell
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python3.7 -m paddle.distributed.launch \ python3.7 -m paddle.distributed.launch --gpus="0,1,2,3" \
--gpus="0,1,2,3" tools/eval.py \ tools/eval.py \
-c ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \ -c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml \
-o Global.pretrained_model="output/RecModel/best_model" -o Global.pretrained_model="output/RecModel/best_model"
``` ```
**注:** 建议使用多卡评估。该方式可以利用多卡并行计算快速得到全部数据的特征,能够加速评估的过程。 **注:** 建议使用多卡评估。该方式可以利用多卡并行计算快速得到全部数据的特征,能够加速评估的过程。
...@@ -212,7 +233,7 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训 ...@@ -212,7 +233,7 @@ Loss 部分选用 [Cross entropy loss](../../../ppcls/loss/celoss.py),在训
首先需要将 `*.pdparams` 模型文件转换成 inference 格式,转换命令如下。 首先需要将 `*.pdparams` 模型文件转换成 inference 格式,转换命令如下。
```shell ```shell
python3.7 tools/export_model.py \ python3.7 tools/export_model.py \
-c ppcls/configs/GeneralRecognition/GeneralRecognition_PPLCNet_x2_5.yaml \ -c ./ppcls/configs/GeneralRecognitionV2/GeneralRecognitionV2_PPLCNetV2_base.yaml \
-o Global.pretrained_model="output/RecModel/best_model" -o Global.pretrained_model="output/RecModel/best_model"
``` ```
生成的推理模型默认位于 `PaddleClas/inference` 目录,里面包含三个文件,分别为 `inference.pdmodel``inference.pdiparams``inference.pdiparams.info` 生成的推理模型默认位于 `PaddleClas/inference` 目录,里面包含三个文件,分别为 `inference.pdmodel``inference.pdiparams``inference.pdiparams.info`
...@@ -228,10 +249,18 @@ python3.7 python/predict_rec.py \ ...@@ -228,10 +249,18 @@ python3.7 python/predict_rec.py \
-c configs/inference_rec.yaml \ -c configs/inference_rec.yaml \
-o Global.rec_inference_model_dir="../inference" -o Global.rec_inference_model_dir="../inference"
``` ```
得到的特征输出格式如下图所示: 得到的特征输出格式如下所示:
![](../../images/feature_extraction_output.png)
```log
wangzai.jpg: [-4.72979844e-02 3.40038240e-02 -4.06982675e-02 2.46225717e-03
9.67078656e-03 2.70162839e-02 -8.85589980e-03 -4.71983254e-02
6.18615076e-02 1.38509814e-02 -1.11342799e-02 6.73768669e-02
-1.03065455e-02 -4.88462113e-02 8.46387446e-03 1.57074817e-02
...
-3.14170569e-02 7.35917836e-02 -3.09373233e-02 -2.31755469e-02]
```
在实际使用过程中,仅仅得到特征可能并不能满足业务需求。如果想进一步通过特征检索来进行图像识别,可以参照文档[向量检索](./vector_search.md) 在实际使用过程中,仅仅得到特征可能并不能满足业务需求。如果想进一步通过特征检索来进行图像识别,可以参照文档 [向量检索](./vector_search.md)
<a name="6"></a> <a name="6"></a>
...@@ -244,4 +273,4 @@ python3.7 python/predict_rec.py \ ...@@ -244,4 +273,4 @@ python3.7 python/predict_rec.py \
## 7. 参考文献 ## 7. 参考文献
1. [PP-LCNet: A Lightweight CPU Convolutional Neural Network](https://arxiv.org/pdf/2109.15099.pdf) 1. [PP-LCNet: A Lightweight CPU Convolutional Neural Network](https://arxiv.org/pdf/2109.15099.pdf)
2. [ArcFace: Additive Angular Margin Loss for Deep Face Recognition](https://arxiv.org/abs/1801.07698) 2. [Bag of Tricks and A Strong Baseline for Deep Person Re-identification](https://openaccess.thecvf.com/content_CVPRW_2019/papers/TRMTMCT/Luo_Bag_of_Tricks_and_a_Strong_Baseline_for_Deep_Person_CVPRW_2019_paper.pdf)
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
- [1. 数据集](#1) - [1. 数据集](#1)
- [2. 模型选择](#2) - [2. 模型选择](#2)
- [2.1 轻量级主体检测模型](#2.1) - [2.1 轻量级主体检测模型](#2.1)
- [2.2 服务端主体检测模型](#2.2)
- [3. 模型训练](#3) - [3. 模型训练](#3)
- [3.1 环境准备](#3.1) - [3.1 环境准备](#3.1)
- [3.2 数据准备](#3.2) - [3.2 数据准备](#3.2)
...@@ -45,14 +44,13 @@ ...@@ -45,14 +44,13 @@
## 2. 模型选择 ## 2. 模型选择
目标检测方法种类繁多,比较常用的有两阶段检测器(如 FasterRCNN 系列等);单阶段检测器(如 YOLO、SSD 等);anchor-free 检测器(如 PicoDet、FCOS 等)。PaddleDetection 中针对服务端使用场景,自研了 PP-YOLO 系列模型;针对端侧(CPU 和移动端等)使用场景,自研了 PicoDet 系列模型,在服务端和端侧均处于业界较为领先的水平。 目标检测方法种类繁多,比较常用的有两阶段检测器(如 FasterRCNN 系列等);单阶段检测器(如 YOLO、SSD 等);anchor-free 检测器(如 PicoDet、FCOS 等)。在主体检测中,我们使用[PicoDet](https://github.com/PaddlePaddle/PaddleDetection/tree/release/2.5/configs/picodet)系列模型,其在CPU端与移动端,速度较快、精度较好,处于较为领先的业界水平。
基于上述研究,PaddleClas 中提供了 2 个通用主体检测模型,为轻量级与服务端主体检测模型,分别适用于端侧场景以及服务端场景。下面的表格中给出了在上述 5 个数据集上的平均 mAP 以及它们的模型大小、预测速度对比信息。 基于上述研究,PaddleClas 中提供了 1 个通用主体检测模型,既轻量级主体检测模型,分别适用于端侧场景以及服务端场景。下面的表格中给出了在上述 5 个数据集上的平均 mAP 以及它们的模型大小、预测速度对比信息。
| 模型 | 模型结构 | 预训练模型下载地址 | inference 模型下载地址 | mAP | inference 模型大小(MB) | 单张图片预测耗时(不包含预处理)(ms) | | 模型 | 模型结构 | 预训练模型下载地址 | inference 模型下载地址 | mAP | inference 模型大小(MB) |
| ------------------ | --------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----- | ---------------------- | ---------------------------------- | | ------------------ | -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----- | ---------------------- |
| 轻量级主体检测模型 | PicoDet | [地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_pretrained.pdparams) | [tar 格式文件地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar) [zip 格式文件地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.zip) | 40.1% | 30.1 | 29.8 | | 轻量级主体检测模型 | PicoDet | [地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_pretrained.pdparams) | [tar 格式文件地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar) [zip 格式文件地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.zip) | 41.5% | 30.1 |
| 服务端主体检测模型 | PP-YOLOv2 | [地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/ppyolov2_r50vd_dcn_mainbody_v1.0_pretrained.pdparams) | [tar 格式文件地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar) [zip 格式文件地址](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.zip) | 42.5% | 210.5 | 466.6 |
* 注意 * 注意
* 由于部分解压缩软件在解压上述 `tar` 格式文件时存在问题,建议非命令行用户下载 `zip` 格式文件并解压。`tar` 格式文件建议使用命令 `tar xf xxx.tar` 解压。 * 由于部分解压缩软件在解压上述 `tar` 格式文件时存在问题,建议非命令行用户下载 `zip` 格式文件并解压。`tar` 格式文件建议使用命令 `tar xf xxx.tar` 解压。
...@@ -65,37 +63,16 @@ ...@@ -65,37 +63,16 @@
PicoDet 由 [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) 提出,是一个适用于 CPU 或者移动端场景的目标检测算法。具体地,它融合了下面一系列优化算法。 PicoDet 由 [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) 提出,是一个适用于 CPU 或者移动端场景的目标检测算法。具体地,它融合了下面一系列优化算法。
- [ATSS](https://arxiv.org/abs/1912.02424) - [VFL](https://arxiv.org/abs/2008.13367) + [GFL](https://arxiv.org/abs/2006.04388)
- [Generalized Focal Loss](https://arxiv.org/abs/2006.04388) - 新的PAN Neck结构
- 余弦学习率策略 - 余弦学习率策略
- Cycle-EMA - Cycle-EMA
- 轻量级检测 head - [ATSS](https://arxiv.org/abs/1912.02424)[SimOTA](https://arxiv.org/abs/2107.08430) 标签分配策略
更多关于 PicoDet 的优化细节与 benchmark 可以参考 [PicoDet 系列模型介绍](https://github.com/PaddlePaddle/PaddleDetection/blob/develop/configs/picodet/README.md) 更多关于 PicoDet 的优化细节与 benchmark 可以参考 [PicoDet 系列模型介绍](https://github.com/PaddlePaddle/PaddleDetection/tree/release/2.5/configs/picodet)
在轻量级主体检测任务中,为了更好地兼顾检测速度与效果,我们使用 PPLCNet_x2_5 作为主体检测模型的骨干网络,同时将训练与预测的图像尺度修改为了 640x640,其余配置与 [picodet_lcnet_1_5x_416_coco.yml](https://github.com/PaddlePaddle/PaddleDetection/blob/develop/configs/picodet/more_config/picodet_lcnet_1_5x_416_coco.yml) 完全一致。将数据集更换为自定义的主体检测数据集,进行训练,最终得到检测模型。 在轻量级主体检测任务中,为了更好地兼顾检测速度与效果,我们使用 PPLCNet_x2_5 作为主体检测模型的骨干网络,同时将训练与预测的图像尺度修改为了 640x640,其余配置与 [picodet_lcnet_1_5x_416_coco.yml](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.5/configs/picodet/picodet_l_416_coco_lcnet.yml) 完全一致。将数据集更换为自定义的主体检测数据集,进行训练,最终得到检测模型。
<a name="2.2"></a>
### 2.2 服务端主体检测模型
PP-YOLO 由 [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) 提出,从骨干网络、数据增广、正则化策略、损失函数、后处理等多个角度对 yolov3 模型进行深度优化,最终在“速度-精度”方面达到了业界领先的水平。具体地,优化的策略如下。
- 更优的骨干网络: ResNet50vd-DCN
- 更大的训练 batch size: 8 GPUs,每 GPU batch_size=24,对应调整学习率和迭代轮数
- [Drop Block](https://arxiv.org/abs/1810.12890)
- [Exponential Moving Average](https://www.investopedia.com/terms/e/ema.asp)
- [IoU Loss](https://arxiv.org/pdf/1902.09630.pdf)
- [Grid Sensitive](https://arxiv.org/abs/2004.10934)
- [Matrix NMS](https://arxiv.org/pdf/2003.10152.pdf)
- [CoordConv](https://arxiv.org/abs/1807.03247)
- [Spatial Pyramid Pooling](https://arxiv.org/abs/1406.4729)
- 更优的预训练模型
更多关于 PP-YOLO 的详细介绍可以参考:[PP-YOLO 模型](https://github.com/PaddlePaddle/PaddleDetection/blob/release%2F2.1/configs/ppyolo/README_cn.md)
在服务端主体检测任务中,为了保证检测效果,我们使用 ResNet50vd-DCN 作为检测模型的骨干网络,使用配置文件 [ppyolov2_r50vd_dcn_365e_coco.yml](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml),更换为自定义的主体检测数据集,进行训练,最终得到检测模型。
<a name="3"></a> <a name="3"></a>
...@@ -118,13 +95,13 @@ cd PaddleDetection ...@@ -118,13 +95,13 @@ cd PaddleDetection
pip install -r requirements.txt pip install -r requirements.txt
``` ```
更多安装教程,请参考: [安装文档](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/docs/tutorials/INSTALL_cn.md) 更多安装教程,请参考: [安装文档](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.5/docs/tutorials/INSTALL_cn.md)
<a name="3.2"></a> <a name="3.2"></a>
### 3.2 数据准备 ### 3.2 数据准备
对于自定义数据集,首先需要将自己的数据集修改为 COCO 格式,可以参考[自定义检测数据集教程](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.1/static/docs/tutorials/Custom_DataSet.md)制作 COCO 格式的数据集。 对于自定义数据集,首先需要将自己的数据集修改为 COCO 格式,可以参考[自定义检测数据集教程](https://github.com/PaddlePaddle/PaddleDetection/tree/release/2.5/docs/tutorials/data)制作 COCO 格式的数据集。
主体检测任务中,所有的检测框均属于前景,在这里需要将标注文件中,检测框的 `category_id` 修改为 1,同时将整个标注文件中的 `categories` 映射表修改为下面的格式,即整个类别映射表中只包含`前景`类别。 主体检测任务中,所有的检测框均属于前景,在这里需要将标注文件中,检测框的 `category_id` 修改为 1,同时将整个标注文件中的 `categories` 映射表修改为下面的格式,即整个类别映射表中只包含`前景`类别。
...@@ -136,22 +113,20 @@ pip install -r requirements.txt ...@@ -136,22 +113,20 @@ pip install -r requirements.txt
### 3.3 配置文件改动和说明 ### 3.3 配置文件改动和说明
我们使用 `configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml` 配置进行训练,配置文件摘要如下: 我们使用 [mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.5/configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml) 配置进行训练,配置文件摘要如下:
![](../../images/det/PaddleDetection_config.png) ![](../../images/det/PaddleDetection_config.png)
从上图看到 `ppyolov2_r50vd_dcn_365e_coco.yml` 配置需要依赖其他的配置文件,这些配置文件的含义如下: 从上图看到 `mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml` 配置需要依赖其他的配置文件,这些配置文件的含义如下:
``` ```
coco_detection.yml:主要说明了训练数据和验证数据的路径
runtime.yml:主要说明了公共的运行参数,比如是否使用 GPU、每多少个 epoch 存储 checkpoint 等 runtime.yml:主要说明了公共的运行参数,比如是否使用 GPU、每多少个 epoch 存储 checkpoint 等
optimizer_365e.yml:主要说明了学习率和优化器的配置 optimizer_100e.yml:主要说明了学习率和优化器的配置
ppyolov2_r50vd_dcn.yml:主要说明模型和主干网络的情况 picodet_esnet.yml:主要说明模型和主干网络的情况
ppyolov2_reader.yml:主要说明数据读取器配置,如 batch size,并发加载子进程数等,同时包含读取后预处理操作,如 resize、数据增强等等 picodet_640_reader.yml:主要说明数据读取器配置,如 batch size,并发加载子进程数等,同时包含读取后预处理操作,如 resize、数据增强等等
``` ```
在主体检测任务中,需要将 `datasets/coco_detection.yml` 中的 `num_classes` 参数修改为 1(只有 1 个前景类别),同时将训练集和测试集的路径修改为自定义数据集的路径。 在主体检测任务中,需要将 `datasets/coco_detection.yml` 中的 `num_classes` 参数修改为 1(只有 1 个前景类别),同时将训练集和测试集的路径修改为自定义数据集的路径。
...@@ -169,14 +144,14 @@ PaddleDetection 提供了单卡/多卡训练模式,满足用户多种训练需 ...@@ -169,14 +144,14 @@ PaddleDetection 提供了单卡/多卡训练模式,满足用户多种训练需
```bash ```bash
# windows 和 Mac 下不需要执行该命令 # windows 和 Mac 下不需要执行该命令
export CUDA_VISIBLE_DEVICES=0 export CUDA_VISIBLE_DEVICES=0
python tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml python tools/train.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml
``` ```
* GPU 多卡训练 * GPU 多卡训练
```bash ```bash
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --eval python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml --eval
``` ```
--eval:表示边训练边验证。 --eval:表示边训练边验证。
...@@ -188,7 +163,7 @@ python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppy ...@@ -188,7 +163,7 @@ python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppy
```bash ```bash
export CUDA_VISIBLE_DEVICES=0 export CUDA_VISIBLE_DEVICES=0
# 指定 pretrain_weights 参数,加载通用的主体检测预训练模型 # 指定 pretrain_weights 参数,加载通用的主体检测预训练模型
python tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml -o pretrain_weights=https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/pretrain/ppyolov2_r50vd_dcn_mainbody_v1.0_pretrained.pdparams python tools/train.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml -o pretrain_weights=https://paddledet.bj.bcebos.com/models/picodet_lcnet_x2_5_640_mainbody.pdparams
``` ```
* 模型恢复训练 * 模型恢复训练
...@@ -197,10 +172,14 @@ python tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml -o pret ...@@ -197,10 +172,14 @@ python tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml -o pret
```bash ```bash
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --eval -r output/ppyolov2_r50vd_dcn_365e_coco/10000 python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml --eval -r output/picodet_lcnet_x2_5_640_mainbody/20
``` ```
注意:如果遇到 "`Out of memory error`" 问题, 尝试在 `ppyolov2_reader.yml` 文件中调小 `batch_size`,同时等比例调小学习率。 注意:
- `-r`命令中最后`20`表示从第20个epoch保存的权重开始训练,使用时确保`20.pdparams 20.pdopt`文件存在。请根据实际自行修改
- 如果遇到 "`Out of memory error`" 问题, 尝试在 `picodet_640_reader.yml` 文件中调小 `batch_size`,同时等比例调小学习率。
<a name="3.5"></a> <a name="3.5"></a>
...@@ -210,12 +189,13 @@ python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppy ...@@ -210,12 +189,13 @@ python -m paddle.distributed.launch --gpus 0,1,2,3 tools/train.py -c configs/ppy
```bash ```bash
export CUDA_VISIBLE_DEVICES=0 export CUDA_VISIBLE_DEVICES=0
python tools/infer.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --infer_img=your_image_path.jpg --output_dir=infer_output/ --draw_threshold=0.5 -o weights=output/ppyolov2_r50vd_dcn_365e_coco/model_final python tools/infer.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml --infer_img=your_image_path.jpg --output_dir=infer_output/ --draw_threshold=0.5 -o weights=output/picodet_lcnet_x2_5_640_mainbody/model_final
``` ```
`--draw_threshold` 是个可选参数. 根据 [NMS](https://ieeexplore.ieee.org/document/1699659) 的计算,不同阈值会产生不同的结果 `keep_top_k` 表示设置输出目标的最大数量,默认值为 100,用户可以根据自己的实际情况进行设定。 `--draw_threshold` 是个可选参数. 根据 [NMS](https://ieeexplore.ieee.org/document/1699659) 的计算,不同阈值会产生不同的结果 `keep_top_k` 表示设置输出目标的最大数量,默认值为 100,用户可以根据自己的实际情况进行设定。
<a name="4"></a> <a name="4"></a>
## 4. 模型推理部署 ## 4. 模型推理部署
<a name="4.1"></a> <a name="4.1"></a>
...@@ -224,16 +204,16 @@ python tools/infer.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --infer ...@@ -224,16 +204,16 @@ python tools/infer.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --infer
执行导出模型脚本: 执行导出模型脚本:
```bash ```bash
python tools/export_model.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml --output_dir=./inference -o weights=output/ppyolov2_r50vd_dcn_365e_coco/model_final.pdparams python tools/export_model.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml --output_dir=./inference -o weights=output/picodet_lcnet_x2_5_640_mainbody/model_final.pdparams
``` ```
预测模型会导出到 `inference/ppyolov2_r50vd_dcn_365e_coco` 目录下,分别为 `infer_cfg.yml` (预测不需要), `model.pdiparams`, `model.pdiparams.info`, `model.pdmodel` 预测模型会导出到 `inference/picodet_lcnet_x2_5_640_mainbody` 目录下,分别为 `infer_cfg.yml` (预测不需要), `model.pdiparams`, `model.pdiparams.info`, `model.pdmodel`
注意: `PaddleDetection` 导出的 inference 模型的文件格式为 `model.xxx`,这里如果希望与 PaddleClas 的 inference 模型文件格式保持一致,需要将其 `model.xxx` 文件修改为 `inference.xxx` 文件,用于后续主体检测的预测部署。 注意: `PaddleDetection` 导出的 inference 模型的文件格式为 `model.xxx`,这里如果希望与 PaddleClas 的 inference 模型文件格式保持一致,需要将其 `model.xxx` 文件修改为 `inference.xxx` 文件,用于后续主体检测的预测部署。
更多模型导出教程,请参考: [EXPORT_MODEL](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.4/deploy/EXPORT_MODEL.md) 更多模型导出教程,请参考: [EXPORT_MODEL](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.5/deploy/EXPORT_MODEL.md)
最终,目录 `inference/ppyolov2_r50vd_dcn_365e_coco` 中包含 `inference.pdiparams`, `inference.pdiparams.info` 以及 `inference.pdmodel` 文件,其中 `inference.pdiparams` 为保存的 inference 模型权重文件,`inference.pdmodel` 为保存的 inference 模型结构文件。 最终,目录 `inference/picodet_lcnet_x2_5_640_mainbody` 中包含 `inference.pdiparams`, `inference.pdiparams.info` 以及 `inference.pdmodel` 文件,其中 `inference.pdiparams` 为保存的 inference 模型权重文件,`inference.pdmodel` 为保存的 inference 模型结构文件。
<a name="4.2"></a> <a name="4.2"></a>
### 4.2 基于python预测引擎推理 ### 4.2 基于python预测引擎推理
...@@ -244,7 +224,7 @@ python tools/export_model.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml ...@@ -244,7 +224,7 @@ python tools/export_model.py -c configs/ppyolo/ppyolov2_r50vd_dcn_365e_coco.yml
<a name="4.3"></a> <a name="4.3"></a>
### 4.3 其他推理方式 ### 4.3 其他推理方式
其他推理方法,如C++推理部署、PaddleServing部署等请参考[检测模型推理部署](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.4/deploy/README.md) 其他推理方法,如C++推理部署、PaddleServing部署等请参考[检测模型推理部署](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.5/deploy/README.md)
### FAQ ### FAQ
......
# PP-ShiTu在Paddle-Lite端侧部署
本教程将介绍基于[Paddle Lite](https://github.com/PaddlePaddle/Paddle-Lite) 在移动端部署PaddleClas PP-ShiTu模型的详细步骤。
Paddle Lite是飞桨轻量化推理引擎,为手机、IoT端提供高效推理能力,并广泛整合跨平台硬件,为端侧部署及应用落地问题提供轻量化的部署方案。
## 目录
- [1. 环境准备](#1)
- [1.1 准备交叉编译环境](#1.1)
- [1.2 准备预测库](#1.2)
- [2. 编译流程](#2)
- [2.1 模型准备](#2.1)
- [2.1.1 使用PaddleClase提供的推理模型](#2.1.1)
- [2.1.2 使用其他模型](#2.1.2)
- [2.1.2.1 安装paddle_lite_opt工具](#2.1.2.1)
- [2.1.2.2 转换示例](#2.1.2.2)
- [2.2 生成新的索引库](#2.2)
- [2.2.1 数据集环境配置](#2.2.1)
- [2.2.2 生成新的index文件](#2.2.2)
- [2.3 将yaml文件转换成json文件](#2.3)
- [2.4 index字典转换](#2.4)
- [2.5 与手机联调](#2.5)
- [FAQ](#FAQ)
<a name="1"></a>
## 1. 环境准备
### 运行准备
- 电脑(编译Paddle Lite)
- 安卓手机(armv7或armv8)
<a name="1.1"></a>
### 1.1 准备交叉编译环境
交叉编译环境用于编译 Paddle Lite 和 PaddleClas 的PP-ShiTu Lite demo。
支持多种开发环境,不同开发环境的编译流程请参考对应文档,请确保安装完成Java jdk、Android NDK(R17以上)。
1. [Docker](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#docker)
2. [Linux](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#linux)
3. [MAC OS](https://paddle-lite.readthedocs.io/zh/latest/source_compile/compile_env.html#mac-os)
```shell
# 配置完成交叉编译环境后,更新环境变量
# for docker、Linux
source ~/.bashrc
# for Mac OS
source ~/.bash_profile
```
<a name="1.2"></a>
### 1.2 准备预测库
预测库有两种获取方式:
1. [**建议**]直接下载,预测库下载链接如下:
|平台| 架构 | 预测库下载链接|
|-|-|-|
|Android| arm7 | [inference_lite_lib](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.android.armv7.clang.c++_static.with_extra.with_cv.tar.gz) |
| Android | arm8 | [inference_lite_lib](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.android.armv8.clang.c++_static.with_extra.with_cv.tar.gz) |
| Android | arm8(FP16) | [inference_lite_lib](https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.android.armv8_clang_c++_static_with_extra_with_cv_with_fp16.tiny_publish_427e46.zip) |
**注意**:1. 如果是从 Paddle-Lite [官方文档](https://paddle-lite.readthedocs.io/zh/latest/quick_start/release_lib.html#android-toolchain-gcc)下载的预测库,注意选择`with_extra=ON,with_cv=ON`的下载链接。2. 目前只提供Android端demo,IOS端demo可以参考[Paddle-Lite IOS demo](https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/master/PaddleLite-ios-demo)
2. 编译Paddle-Lite得到预测库,Paddle-Lite的编译方式如下:
```shell
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
cd Paddle-Lite
# 如果使用编译方式,建议使用develop分支编译预测库
git checkout develop
# FP32
./lite/tools/build_android.sh --arch=armv8 --toolchain=clang --with_cv=ON --with_extra=ON
# FP16
./lite/tools/build_android.sh --arch=armv8 --toolchain=clang --with_cv=ON --with_extra=ON --with_arm82_fp16=ON
```
**注意**:编译Paddle-Lite获得预测库时,需要打开`--with_cv=ON --with_extra=ON`两个选项,`--arch`表示`arm`版本,这里指定为armv8,更多编译命令介绍请参考[链接](https://paddle-lite.readthedocs.io/zh/latest/demo_guides/arm_cpu.html)
直接下载预测库并解压后,可以得到`inference_lite_lib.android.armv8.clang.c++_static.with_extra.with_cv/`文件夹,通过编译Paddle-Lite得到的预测库位于`Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8/`文件夹下。
预测库的文件目录如下:
```
inference_lite_lib.android.armv8/
|-- cxx C++ 预测库和头文件
| |-- include C++ 头文件
| | |-- paddle_api.h
| | |-- paddle_image_preprocess.h
| | |-- paddle_lite_factory_helper.h
| | |-- paddle_place.h
| | |-- paddle_use_kernels.h
| | |-- paddle_use_ops.h
| | `-- paddle_use_passes.h
| `-- lib C++预测库
| |-- libpaddle_api_light_bundled.a C++静态库
| `-- libpaddle_light_api_shared.so C++动态库
|-- java Java预测库
| |-- jar
| | `-- PaddlePredictor.jar
| |-- so
| | `-- libpaddle_lite_jni.so
| `-- src
|-- demo C++和Java示例代码
| |-- cxx C++ 预测库demo
| `-- java Java 预测库demo
```
<a name="2"></a>
## 2 编译流程
<a name="2.1"></a>
### 2.1 模型准备
PaddleClas 提供了转换并优化后的推理模型,可以直接参考下方 2.1.1 小节进行下载。如果需要使用其他模型,请参考后续 2.1.2 小节自行转换并优化模型。
<a name="2.1.1"></a>
#### 2.1.1 使用PaddleClas提供的推理模型
```shell
# 进入lite_ppshitu目录
cd $PaddleClas/deploy/lite_shitu
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/lite/ppshitu_lite_models_v1.2.tar
tar -xf ppshitu_lite_models_v1.2.tar
rm -f ppshitu_lite_models_v1.2.tar
```
<a name="2.1.2"></a>
#### 2.1.2 使用其他模型
Paddle-Lite 提供了多种策略来自动优化原始的模型,其中包括量化、子图融合、混合调度、Kernel优选等方法,使用Paddle-Lite的`opt`工具可以自动对inference模型进行优化,目前支持两种优化方式,优化后的模型更轻量,模型运行速度更快。
**注意**:如果已经准备好了 `.nb` 结尾的模型文件,可以跳过此步骤。
<a name="2.1.2.1"></a>
##### 2.1.2.1 安装paddle_lite_opt工具
安装`paddle_lite_opt`工具有如下两种方法:
1. [**建议**]pip安装paddlelite并进行转换
```shell
pip install paddlelite==2.10rc
```
2. 源码编译Paddle-Lite生成`paddle_lite_opt`工具
模型优化需要Paddle-Lite的`opt`可执行文件,可以通过编译Paddle-Lite源码获得,编译步骤如下:
```shell
# 如果准备环境时已经clone了Paddle-Lite,则不用重新clone Paddle-Lite
git clone https://github.com/PaddlePaddle/Paddle-Lite.git
cd Paddle-Lite
git checkout develop
# 启动编译
./lite/tools/build.sh build_optimize_tool
```
编译完成后,`opt`文件位于`build.opt/lite/api/`下,可通过如下方式查看`opt`的运行选项和使用方式;
```shell
cd build.opt/lite/api/
./opt
```
`opt`的使用方式与参数与上面的`paddle_lite_opt`完全一致。
之后使用`paddle_lite_opt`工具可以进行inference模型的转换。`paddle_lite_opt`的部分参数如下:
|选项|说明|
|-|-|
|--model_file|待优化的PaddlePaddle模型(combined形式)的网络结构文件路径|
|--param_file|待优化的PaddlePaddle模型(combined形式)的权重文件路径|
|--optimize_out_type|输出模型类型,目前支持两种类型:protobuf和naive_buffer,其中naive_buffer是一种更轻量级的序列化/反序列化实现,默认为naive_buffer|
|--optimize_out|优化模型的输出路径|
|--valid_targets|指定模型可执行的backend,默认为arm。目前可支持x86、arm、opencl、npu、xpu,可以同时指定多个backend(以空格分隔),Model Optimize Tool将会自动选择最佳方式。如果需要支持华为NPU(Kirin 810/990 Soc搭载的达芬奇架构NPU),应当设置为npu, arm|
更详细的`paddle_lite_opt`工具使用说明请参考[使用opt转化模型文档](https://paddle-lite.readthedocs.io/zh/latest/user_guides/opt/opt_bin.html)
`--model_file`表示inference模型的model文件地址,`--param_file`表示inference模型的param文件地址;`optimize_out`用于指定输出文件的名称(不需要添加`.nb`的后缀)。直接在命令行中运行`paddle_lite_opt`,也可以查看所有参数及其说明。
<a name="2.1.2.2"></a>
##### 2.1.2.2 转换示例
下面介绍使用`paddle_lite_opt`完成主体检测模型和识别模型的预训练模型,转成inference模型,最终转换成Paddle-Lite的优化模型的过程。
1. 转换主体检测模型
```shell
# 当前目录为 $PaddleClas/deploy/lite_shitu
# $code_path需替换成相应的运行目录,可以根据需要,将$code_path设置成需要的目录
export code_path=~
cd $code_path
git clone https://github.com/PaddlePaddle/PaddleDetection.git
# 进入PaddleDetection根目录
cd PaddleDetection
# 将预训练模型导出为inference模型
python tools/export_model.py -c configs/picodet/legacy_model/application/mainbody_detection/picodet_lcnet_x2_5_640_mainbody.yml -o weights=https://paddledet.bj.bcebos.com/models/picodet_lcnet_x2_5_640_mainbody.pdparams export_post_process=False --output_dir=inference
# 将inference模型转化为Paddle-Lite优化模型
paddle_lite_opt --model_file=inference/picodet_lcnet_x2_5_640_mainbody/model.pdmodel --param_file=inference/picodet_lcnet_x2_5_640_mainbody/model.pdiparams --optimize_out=inference/picodet_lcnet_x2_5_640_mainbody/mainbody_det
# 将转好的模型复制到lite_shitu目录下
cd $PaddleClas/deploy/lite_shitu
mkdir models
cp $code_path/PaddleDetection/inference/picodet_lcnet_x2_5_640_mainbody/mainbody_det.nb $PaddleClas/deploy/lite_shitu/models
```
2. 转换识别模型
```shell
# 识别模型下载
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar
# 解压模型
tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar
# 转换为Paddle-Lite模型
paddle_lite_opt --model_file=general_PPLCNet_x2_5_lite_v1.0_infer/inference.pdmodel --param_file=general_PPLCNet_x2_5_lite_v1.0_infer/inference.pdiparams --optimize_out=general_PPLCNet_x2_5_lite_v1.0_infer/rec
# 将模型文件拷贝到lite_shitu下
cp general_PPLCNet_x2_5_lite_v1.0_infer/rec.nb deploy/lite_shitu/models/
```
**注意**`--optimize_out` 参数为优化后模型的保存路径,无需加后缀`.nb``--model_file` 参数为模型结构信息文件的路径,`--param_file` 参数为模型权重信息文件的路径,请注意文件名。
<a name="2.2"></a>
### 2.2 生成新的检索库
由于lite 版本的检索库用的是`faiss1.5.3`版本,与新版本不兼容,因此需要重新生成index库
<a name="2.2.1"></a>
#### 2.2.1 数据及环境配置
```shell
# 进入PaddleClas根目录
cd $PaddleClas
# 安装PaddleClas
python setup.py install
cd deploy
# 下载瓶装饮料数据集
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar && tar -xf drink_dataset_v1.0.tar
rm -rf drink_dataset_v1.0.tar
rm -rf drink_dataset_v1.0/index
# 安装1.5.3版本的faiss
pip install faiss-cpu==1.5.3
# 下载通用识别模型,可替换成自己的inference model
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar
tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar
rm -rf general_PPLCNet_x2_5_lite_v1.0_infer.tar
```
<a name="2.2.2"></a>
#### 2.2.2 生成新的index文件
```shell
# 生成新的index库,注意指定好识别模型的路径,同时将index_mothod修改成Flat,HNSW32和IVF在此版本中可能存在bug,请慎重使用。
# 如果使用自己的识别模型,对应的修改inference model的目录
python python/build_gallery.py -c configs/inference_drink.yaml -o Global.rec_inference_model_dir=general_PPLCNet_x2_5_lite_v1.0_infer -o IndexProcess.index_method=Flat
# 进入到lite_shitu目录
cd lite_shitu
mv ../drink_dataset_v1.0 .
```
<a name="2.3"></a>
### 2.3 将yaml文件转换成json文件
```shell
# 如果测试单张图像,路径使用相对路径
python generate_json_config.py --det_model_path ppshitu_lite_models_v1.2/mainbody_PPLCNet_x2_5_640_v1.2_lite.nb --rec_model_path ppshitu_lite_models_v1.2/general_PPLCNet_x2_5_lite_v1.2_infer.nb --img_path images/demo.jpeg
# or
# 如果测试多张图像
python generate_json_config.py --det_model_path ppshitu_lite_models_v1.2/mainbody_PPLCNet_x2_5_640_v1.2_lite.nb --rec_model_path ppshitu_lite_models_v1.2/general_PPLCNet_x2_5_lite_v1.2_infer.nb --img_dir images
# 执行完成后,会在lit_shitu下生成shitu_config.json配置文件
```
<a name="2.4"></a>
### 2.4 index字典转换
由于python的检索库字典,使用`pickle`进行的序列化存储,导致C++不方便读取,因此需要进行转换
```shell
# 转化id_map.pkl为id_map.txt
python transform_id_map.py -c ../configs/inference_drink.yaml
```
转换成功后,会在`IndexProcess.index_dir`目录下生成`id_map.txt`
<a name="2.5"></a>
### 2.5 与手机联调
首先需要进行一些准备工作。
1. 准备一台arm8的安卓手机,如果编译的预测库是armv7,则需要arm7的手机,并修改Makefile中`ARM_ABI=arm7`
2. 电脑上安装ADB工具,用于调试。 ADB安装方式如下:
2.1. MAC电脑安装ADB:
```shell
brew cask install android-platform-tools
```
2.2. Linux安装ADB
```shell
sudo apt update
sudo apt install -y wget adb
```
2.3. Window安装ADB
win上安装需要去谷歌的安卓平台下载ADB软件包进行安装:[链接](https://developer.android.com/studio)
3. 手机连接电脑后,开启手机`USB调试`选项,选择`文件传输`模式,在电脑终端中输入:
```shell
adb devices
```
如果有device输出,则表示安装成功,如下所示:
```
List of devices attached
744be294 device
```
4. 编译lite部署代码生成移动端可执行文件
```shell
cd $PaddleClas/deploy/lite_shitu
# ${lite prediction library path}下载的Paddle-Lite库路径
inference_lite_path=${lite prediction library path}/inference_lite_lib.android.armv8.gcc.c++_static.with_extra.with_cv/
mkdir $inference_lite_path/demo/cxx/ppshitu_lite
cp -r * $inference_lite_path/demo/cxx/ppshitu_lite
cd $inference_lite_path/demo/cxx/ppshitu_lite
# 执行编译,等待完成后得到可执行文件main
make ARM_ABI=arm8
#如果是arm7,则执行 make ARM_ABI = arm7 (或者在Makefile中修改该项)
```
5. 准备优化后的模型、预测库文件、测试图像。
```shell
mkdir deploy
# 移动的模型路径要和之前生成的json文件中模型路径一致
mv ppshitu_lite_models_v1.2 deploy/
mv drink_dataset_v1.0 deploy/
mv images deploy/
mv shitu_config.json deploy/
cp pp_shitu deploy/
# 将C++预测动态库so文件复制到deploy文件夹中
cp ../../../cxx/lib/libpaddle_light_api_shared.so deploy/
```
执行完成后,deploy文件夹下将有如下文件格式:
```shell
deploy/
|-- ppshitu_lite_models_v1.1/
| |--mainbody_PPLCNet_x2_5_640_quant_v1.1_lite.nb 优化后的主体检测模型文件
| |--general_PPLCNet_x2_5_lite_v1.1_infer.nb 优化后的识别模型文件
|-- images/
| |--demo.jpg 图片文件
|-- drink_dataset_v1.0/ 瓶装饮料demo数据
| |--index 检索index目录
|-- pp_shitu 生成的移动端执行文件
|-- shitu_config.json 执行时参数配置文件
|-- libpaddle_light_api_shared.so Paddle-Lite库文件
```
**注意:**
* `shitu_config.json` 包含了目标检测的超参数,请按需进行修改
6. 启动调试,上述步骤完成后就可以使用ADB将文件夹 `deploy/` push到手机上运行,步骤如下:
```shell
# 将上述deploy文件夹push到手机上
adb push deploy /data/local/tmp/
adb shell
cd /data/local/tmp/deploy
export LD_LIBRARY_PATH=/data/local/tmp/deploy:$LD_LIBRARY_PATH
# 修改权限为可执行
chmod 777 pp_shitu
# 执行程序
./pp_shitu shitu_config.json
```
如果对代码做了修改,则需要重新编译并push到手机上。
运行效果如下:
```
images/demo.jpeg:
result0: bbox[344, 98, 527, 593], score: 0.811656, label: 红牛-强化型
result1: bbox[0, 0, 600, 600], score: 0.729664, label: 红牛-强化型
```
<a name="FAQ"></a>
## FAQ
Q1:如果想更换模型怎么办,需要重新按照流程走一遍吗?
A1:如果已经走通了上述步骤,更换模型只需要替换 `.nb` 模型文件即可,同时要注意修改下配置文件中的 `.nb` 文件路径以及类别映射文件(如有必要)。
Q2:换一个图测试怎么做?
A2:替换 deploy 下的测试图像为你想要测试的图像,并重新生成json配置文件(或者直接修改图像路径),使用 ADB 再次 push 到手机上即可。
# Python 预测推理 # Python 预测推理
---
首先请参考文档[环境准备](../installation/install_paddleclas.md)配置运行环境。 首先请参考文档[环境准备](../installation/install_paddleclas.md)配置运行环境。
## 目录 ## 目录
...@@ -13,47 +11,50 @@ ...@@ -13,47 +11,50 @@
- [2.3 PP-ShiTu PipeLine推理](#2.3) - [2.3 PP-ShiTu PipeLine推理](#2.3)
<a name="1"></a> <a name="1"></a>
## 1. 图像分类推理 ## 1. 图像分类推理
首先请参考文档[模型导出](./export_model.md)准备 inference 模型,然后进入 PaddleClas 的 `deploy` 目录下: 首先请参考文档[模型导出](./export_model.md)准备 inference 模型,然后进入 PaddleClas 的 `deploy` 目录下:
```shell ```shell
cd /path/to/PaddleClas/deploy cd PaddleClas/deploy
``` ```
使用以下命令进行预测: 使用以下命令进行预测:
```shell ```shell
python python/predict_cls.py -c configs/inference_cls.yaml python3.7 python/predict_cls.py -c configs/inference_cls.yaml
``` ```
在配置文件 `configs/inference_cls.yaml` 中有以下字段用于配置预测参数: 在配置文件 `configs/inference_cls.yaml` 中有以下字段用于配置预测参数:
* `Global.infer_imgs`:待预测的图片文件路径; * `Global.infer_imgs`:待预测的图片文件(夹)路径;
* `Global.inference_model_dir`:inference 模型文件所在目录,该目录下需要有文件 `inference.pdmodel``inference.pdiparams` 两个文件; * `Global.inference_model_dir`:inference 模型文件所在文件夹的路径,该文件夹下需要有文件 `inference.pdmodel``inference.pdiparams` 两个文件;
* `Global.use_tensorrt`:是否使用 TesorRT 预测引擎,默认为 `False`
* `Global.use_gpu`:是否使用 GPU 预测,默认为 `True` * `Global.use_gpu`:是否使用 GPU 预测,默认为 `True`
* `Global.enable_mkldnn`:是否启用 `MKL-DNN` 加速库,默认为 `False`。注意 `enable_mkldnn``use_gpu` 同时为 `True` 时,将忽略 `enable_mkldnn`,而使用 GPU 预测; * `Global.enable_mkldnn`:是否启用 `MKL-DNN` 加速库,默认为 `False`。注意 `enable_mkldnn``use_gpu` 同时为 `True` 时,将忽略 `enable_mkldnn`,而使用 GPU 预测;
* `Global.use_fp16`:是否启用 `FP16`,默认为 `False` * `Global.use_fp16`:是否启用 `FP16`,默认为 `False`
* `Global.use_tensorrt`:是否使用 TesorRT 预测引擎,默认为 `False`
* `PreProcess`:用于数据预处理配置; * `PreProcess`:用于数据预处理配置;
* `PostProcess`:由于后处理配置; * `PostProcess`:由于后处理配置;
* `PostProcess.Topk.class_id_map_file`:数据集 label 的映射文件,默认为 `./utils/imagenet1k_label_list.txt`,该文件为 PaddleClas 所使用的 ImageNet 数据集 label 映射文件。 * `PostProcess.Topk.class_id_map_file`:数据集 label 的映射文件,默认为 `../ppcls/utils/imagenet1k_label_list.txt`,该文件为 PaddleClas 所使用的 ImageNet 数据集 label 映射文件。
**注意**: **注意**:
* 如果使用 VisionTransformer 系列模型,如 `DeiT_***_384`, `ViT_***_384` 等,请注意模型的输入数据尺寸,部分模型需要修改参数: `PreProcess.resize_short=384`, `PreProcess.resize=384` * 如果使用 VisionTransformer 系列模型,如 `DeiT_***_384`, `ViT_***_384` 等,请注意模型的输入数据尺寸,该类模型需要修改参数: `PreProcess.resize_short=384`, `PreProcess.resize=384`
* 如果你希望提升评测模型速度,使用 GPU 评测时,建议开启 TensorRT 加速预测,使用 CPU 评测时,建议开启 MKL-DNN 加速预测。 * 如果你希望提升评测模型速度,使用 GPU 评测时,建议开启 TensorRT 加速预测,使用 CPU 评测时,建议开启 MKL-DNN 加速预测。
<a name="2"></a> <a name="2"></a>
## 2. PP-ShiTu模型推理 ## 2. PP-ShiTu模型推理
PP-ShiTu整个Pipeline包含三部分:主体检测、特提取模型、特征检索。其中主体检测、特征模型可以单独推理使用。单独主体检测详见[2.1](#2.1),特征提取模型单独推理详见[2.2](#2.2), PP-ShiTu整体推理详见[2.3](#2.3) PP-ShiTu整个Pipeline包含三部分:主体检测、特征提取模型、特征检索。其中主体检测模型、特征提取模型可以单独推理使用。单独使用主体检测详见[主体检测模型推理](#2.1),特征提取模型单独推理详见[特征提取模型推理](#2.2), PP-ShiTu整体推理详见[PP-ShiTu PipeLine推理](#2.3)
<a name="2.1"></a> <a name="2.1"></a>
### 2.1 主体检测模型推理 ### 2.1 主体检测模型推理
进入 PaddleClas 的 `deploy` 目录下: 进入 PaddleClas 的 `deploy` 目录下:
```shell ```shell
cd /path/to/PaddleClas/deploy cd PaddleClas/deploy
``` ```
准备 PaddleClas 提供的主体检测 inference 模型: 准备 PaddleClas 提供的主体检测 inference 模型:
...@@ -61,28 +62,28 @@ cd /path/to/PaddleClas/deploy ...@@ -61,28 +62,28 @@ cd /path/to/PaddleClas/deploy
```shell ```shell
mkdir -p models mkdir -p models
# 下载通用检测 inference 模型并解压 # 下载通用检测 inference 模型并解压
wget -P ./models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar wget -nc -P ./models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
tar -xf ./models/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar -C ./models/ tar -xf ./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar -C ./models/
``` ```
使用以下命令进行预测: 使用以下命令进行预测:
```shell ```shell
python python/predict_det.py -c configs/inference_det.yaml python3.7 python/predict_det.py -c configs/inference_det.yaml
``` ```
在配置文件 `configs/inference_det.yaml` 中有以下字段用于配置预测参数: 在配置文件 `configs/inference_det.yaml` 中有以下字段用于配置预测参数:
* `Global.infer_imgs`:待预测的图片文件路径; * `Global.infer_imgs`:待预测的图片文件路径;
* `Global.use_gpu`: 是否使用 GPU 预测,默认为 `True` * `Global.use_gpu`: 是否使用 GPU 预测,默认为 `True`
<a name="2.2"></a> <a name="2.2"></a>
### 2.2 特征提取模型推理 ### 2.2 特征提取模型推理
下面以商品特征提取为例,介绍特征提取模型推理。首先进入 PaddleClas 的 `deploy` 目录下: 下面以商品图片的特征提取为例,介绍特征提取模型推理。首先进入 PaddleClas 的 `deploy` 目录下:
```shell ```shell
cd /path/to/PaddleClas/deploy cd PaddleClas/deploy
``` ```
准备 PaddleClas 提供的商品特征提取 inference 模型: 准备 PaddleClas 提供的商品特征提取 inference 模型:
...@@ -90,13 +91,24 @@ cd /path/to/PaddleClas/deploy ...@@ -90,13 +91,24 @@ cd /path/to/PaddleClas/deploy
```shell ```shell
mkdir -p models mkdir -p models
# 下载商品特征提取 inference 模型并解压 # 下载商品特征提取 inference 模型并解压
wget -P ./models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/product_ResNet50_vd_aliproduct_v1.0_infer.tar wget -nc -P ./models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/PP-ShiTuV2/general_PPLCNetV2_base_pretrained_v1.0_infer.tar
tar -xf ./models/product_ResNet50_vd_aliproduct_v1.0_infer.tar -C ./models/ tar -xf ./models/general_PPLCNetV2_base_pretrained_v1.0_infer.tar -C ./models/
```
使用以下命令进行预测:
```shell
python3.7 python/predict_rec.py -c configs/inference_rec.yaml
``` ```
上述预测命令可以得到一个 512 维的特征向量,直接输出在在命令行中。 上述预测命令可以得到一个 512 维的特征向量,直接输出在在命令行中。
在配置文件 `configs/inference_det.yaml` 中有以下字段用于配置预测参数:
* `Global.infer_imgs`:待预测的图片文件路径;
* `Global.use_gpu`: 是否使用 GPU 预测,默认为 `True`
<a name="2.3"></a> <a name="2.3"></a>
### 2.3. PP-ShiTu PipeLine推理 ### 2.3. PP-ShiTu PipeLine推理
主体检测、特征提取和向量检索的串联预测,可以参考图像识别[快速体验](../quick_start/quick_start_recognition.md) 主体检测、特征提取和向量检索的串联预测,可以参考[图像识别快速开始](../quick_start/quick_start_recognition.md)
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
- [4. FAQ](#4-faq) - [4. FAQ](#4-faq)
<a name="1"></a> <a name="1"></a>
## 1. 简介 ## 1. 简介
[Paddle Serving](https://github.com/PaddlePaddle/Serving) 旨在帮助深度学习开发者轻松部署在线预测服务,支持一键部署工业级的服务能力、客户端和服务端之间高并发和高效通信、并支持多种编程语言开发客户端。 [Paddle Serving](https://github.com/PaddlePaddle/Serving) 旨在帮助深度学习开发者轻松部署在线预测服务,支持一键部署工业级的服务能力、客户端和服务端之间高并发和高效通信、并支持多种编程语言开发客户端。
...@@ -21,6 +22,7 @@ ...@@ -21,6 +22,7 @@
该部分以 HTTP 预测服务部署为例,介绍怎样在 PaddleClas 中使用 PaddleServing 部署模型服务。目前只支持 Linux 平台部署,暂不支持 Windows 平台。 该部分以 HTTP 预测服务部署为例,介绍怎样在 PaddleClas 中使用 PaddleServing 部署模型服务。目前只支持 Linux 平台部署,暂不支持 Windows 平台。
<a name="2"></a> <a name="2"></a>
## 2. Serving 安装 ## 2. Serving 安装
Serving 官网推荐使用 docker 安装并部署 Serving 环境。首先需要拉取 docker 环境并创建基于 Serving 的 docker。 Serving 官网推荐使用 docker 安装并部署 Serving 环境。首先需要拉取 docker 环境并创建基于 Serving 的 docker。
...@@ -59,12 +61,12 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -59,12 +61,12 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
* 如果安装速度太慢,可以通过 `-i https://pypi.tuna.tsinghua.edu.cn/simple` 更换源,加速安装过程。 * 如果安装速度太慢,可以通过 `-i https://pypi.tuna.tsinghua.edu.cn/simple` 更换源,加速安装过程。
* 其他环境配置安装请参考:[使用Docker安装Paddle Serving](https://github.com/PaddlePaddle/Serving/blob/v0.7.0/doc/Install_CN.md) * 其他环境配置安装请参考:[使用Docker安装Paddle Serving](https://github.com/PaddlePaddle/Serving/blob/v0.7.0/doc/Install_CN.md)
<a name="3"></a> <a name="3"></a>
## 3. 图像识别服务部署 ## 3. 图像识别服务部署
使用 PaddleServing 做图像识别服务化部署时,**需要将保存的多个 inference 模型都转换为 Serving 模型**。 下面以 PP-ShiTu 中的超轻量图像识别模型为例,介绍图像识别服务的部署。 使用 PaddleServing 做图像识别服务化部署时,**需要将保存的多个 inference 模型都转换为 Serving 模型**。 下面以 PP-ShiTu 中的超轻量图像识别模型为例,介绍图像识别服务的部署。
<a name="3.1"></a> <a name="3.1"></a>
### 3.1 模型转换 ### 3.1 模型转换
...@@ -79,8 +81,8 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -79,8 +81,8 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
mkdir models mkdir models
cd models cd models
# 下载并解压通用识别模型 # 下载并解压通用识别模型
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/PP-ShiTuV2/general_PPLCNetV2_base_pretrained_v1.0_infer.tar
tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar tar -xf general_PPLCNetV2_base_pretrained_v1.0_infer.tar
# 下载并解压通用检测模型 # 下载并解压通用检测模型
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
...@@ -89,37 +91,26 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -89,37 +91,26 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
```shell ```shell
# 转换通用识别模型 # 转换通用识别模型
python3.7 -m paddle_serving_client.convert \ python3.7 -m paddle_serving_client.convert \
--dirname ./general_PPLCNet_x2_5_lite_v1.0_infer/ \ --dirname ./general_PPLCNetV2_base_pretrained_v1.0_infer/ \
--model_filename inference.pdmodel \ --model_filename inference.pdmodel \
--params_filename inference.pdiparams \ --params_filename inference.pdiparams \
--serving_server ./general_PPLCNet_x2_5_lite_v1.0_serving/ \ --serving_server ./general_PPLCNetV2_base_pretrained_v1.0_serving/ \
--serving_client ./general_PPLCNet_x2_5_lite_v1.0_client/ --serving_client ./general_PPLCNetV2_base_pretrained_v1.0_client/
``` ```
上述命令的参数含义与[#3.1 模型转换](#3.1)相同 上述命令的参数含义与[#3.1 模型转换](#3.1)相同
通用识别 inference 模型转换完成后,会在当前文件夹多出 `general_PPLCNet_x2_5_lite_v1.0_serving/``general_PPLCNet_x2_5_lite_v1.0_client/` 的文件夹,具备如下结构: 通用识别 inference 模型转换完成后,会在当前文件夹多出 `general_PPLCNetV2_base_pretrained_v1.0_serving/``general_PPLCNetV2_base_pretrained_v1.0_client/` 的文件夹,具备如下结构:
```shell ```shell
├── general_PPLCNet_x2_5_lite_v1.0_serving/ ├── general_PPLCNetV2_base_pretrained_v1.0_serving/
│ ├── inference.pdiparams │ ├── inference.pdiparams
│ ├── inference.pdmodel │ ├── inference.pdmodel
│ ├── serving_server_conf.prototxt │ ├── serving_server_conf.prototxt
│ └── serving_server_conf.stream.prototxt │ └── serving_server_conf.stream.prototxt
└── general_PPLCNet_x2_5_lite_v1.0_client/ └── general_PPLCNetV2_base_pretrained_v1.0_client/
├── serving_client_conf.prototxt ├── serving_client_conf.prototxt
└── serving_client_conf.stream.prototxt └── serving_client_conf.stream.prototxt
``` ```
- 转换通用检测 inference 模型为 Serving 模型: 接下来分别修改 `general_PPLCNetV2_base_pretrained_v1.0_serving/``general_PPLCNetV2_base_pretrained_v1.0_client/` 目录下的 `serving_server_conf.prototxt` 中的 `alias` 名字: 将 `fetch_var` 中的 `alias_name` 改为 `features`。修改后的 `serving_server_conf.prototxt` 内容如下
```shell
# 转换通用检测模型
python3.7 -m paddle_serving_client.convert --dirname ./picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer/ \
--model_filename inference.pdmodel \
--params_filename inference.pdiparams \
--serving_server ./picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/ \
--serving_client ./picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/
```
上述命令的参数含义与[#3.1 模型转换](#3.1)相同
识别推理模型转换完成后,会在当前文件夹多出 `general_PPLCNet_x2_5_lite_v1.0_serving/``general_PPLCNet_x2_5_lite_v1.0_client/` 的文件夹。分别修改 `general_PPLCNet_x2_5_lite_v1.0_serving/``general_PPLCNet_x2_5_lite_v1.0_client/` 目录下的 `serving_server_conf.prototxt` 中的 `alias` 名字: 将 `fetch_var` 中的 `alias_name` 改为 `features`。 修改后的 `serving_server_conf.prototxt` 内容如下
```log ```log
feed_var { feed_var {
...@@ -132,13 +123,24 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -132,13 +123,24 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
shape: 224 shape: 224
} }
fetch_var { fetch_var {
name: "save_infer_model/scale_0.tmp_1" name: "batch_norm_25.tmp_2"
alias_name: "features" alias_name: "features"
is_lod_tensor: false is_lod_tensor: false
fetch_type: 1 fetch_type: 1
shape: 512 shape: 512
} }
``` ```
- 转换通用检测 inference 模型为 Serving 模型:
```shell
# 转换通用检测模型
python3.7 -m paddle_serving_client.convert --dirname ./picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer/ \
--model_filename inference.pdmodel \
--params_filename inference.pdiparams \
--serving_server ./picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/ \
--serving_client ./picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/
```
上述命令的参数含义与[#3.1 模型转换](#3.1)相同
通用检测 inference 模型转换完成后,会在当前文件夹多出 `picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/``picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/` 的文件夹,具备如下结构: 通用检测 inference 模型转换完成后,会在当前文件夹多出 `picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/``picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/` 的文件夹,具备如下结构:
```shell ```shell
├── picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/ ├── picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/
...@@ -151,25 +153,27 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -151,25 +153,27 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
├── serving_client_conf.prototxt ├── serving_client_conf.prototxt
└── serving_client_conf.stream.prototxt └── serving_client_conf.stream.prototxt
``` ```
上述命令中参数具体含义如下表所示 上述转换命令的参数具体含义如下表所示
| 参数 | 类型 | 默认值 | 描述 | | 参数 | 类型 | 默认值 | 描述 |
| ----------------- | ---- | ------------------ | ------------------------------------------------------------ | | ----------------- | ---- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dirname` | str | - | 需要转换的模型文件存储路径,Program结构文件和参数文件均保存在此目录。 | | `dirname` | str | - | 需要转换的模型文件存储路径,Program结构文件和参数文件均保存在此目录。 |
| `model_filename` | str | None | 存储需要转换的模型Inference Program结构的文件名称。如果设置为None,则使用 `__model__` 作为默认的文件名 | | `model_filename` | str | None | 存储需要转换的模型Inference Program结构的文件名称。如果设置为None,则使用 `__model__` 作为默认的文件名 |
| `params_filename` | str | None | 存储需要转换的模型所有参数的文件名称。当且仅当所有模型参数被保>存在一个单独的二进制文件中,它才需要被指定。如果模型参数是存储在各自分离的文件中,设置它的值为None | | `params_filename` | str | None | 存储需要转换的模型所有参数的文件名称。当且仅当所有模型参数被保>存在一个单独的二进制文件中,它才需要被指定。如果模型参数是存储在各自分离的文件中,设置它的值为None |
| `serving_server` | str | `"serving_server"` | 转换后的模型文件和配置文件的存储路径。默认值为serving_server | | `serving_server` | str | `"serving_server"` | 转换后的模型文件和配置文件的存储路径。默认值为serving_server |
| `serving_client` | str | `"serving_client"` | 转换后的客户端配置文件存储路径。默认值为serving_client | | `serving_client` | str | `"serving_client"` | 转换后的客户端配置文件存储路径。默认值为serving_client |
- 下载并解压已经构建后完成的检索库 index - 下载并解压已经构建后完成的检索库 index
```shell ```shell
# 回到deploy目录 # 回到deploy目录
cd ../ cd ../
# 下载构建完成的检索库 index # 下载构建完成的检索库 index
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v2.0.tar
# 解压构建完成的检索库 index # 解压构建完成的检索库 index
tar -xf drink_dataset_v1.0.tar tar -xf drink_dataset_v2.0.tar
``` ```
<a name="3.2"></a> <a name="3.2"></a>
### 3.2 服务部署和请求 ### 3.2 服务部署和请求
**注意:** 识别服务涉及到多个模型,出于性能考虑采用 PipeLine 部署方式。Pipeline 部署方式当前不支持 windows 平台。 **注意:** 识别服务涉及到多个模型,出于性能考虑采用 PipeLine 部署方式。Pipeline 部署方式当前不支持 windows 平台。
...@@ -190,6 +194,7 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -190,6 +194,7 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
``` ```
<a name="3.2.1"></a> <a name="3.2.1"></a>
#### 3.2.1 Python Serving #### 3.2.1 Python Serving
- 启动服务: - 启动服务:
...@@ -204,30 +209,32 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -204,30 +209,32 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
``` ```
成功运行后,模型预测的结果会打印在客户端中,如下所示: 成功运行后,模型预测的结果会打印在客户端中,如下所示:
```log ```log
{'err_no': 0, 'err_msg': '', 'key': ['result'], 'value': ["[{'bbox': [345, 95, 524, 576], 'rec_docs': '红牛-强化型', 'rec_scores': 0.79903316}]"], 'tensors': []} {'err_no': 0, 'err_msg': '', 'key': ['result'], 'value': ["[{'bbox': [438, 71, 660, 712], 'rec_docs': '元气森林', 'rec_scores': 0.7581642}, {'bbox': [220, 72, 449, 689], 'rec_docs': '元气森林', 'rec_scores': 0.68961805}, {'bbox': [794, 104, 978, 652], 'rec_docs': '元气森林', 'rec_scores': 0.63075215}]"], 'tensors': []}
``` ```
<a name="3.2.2"></a> <a name="3.2.2"></a>
#### 3.2.2 C++ Serving #### 3.2.2 C++ Serving
与Python Serving不同,C++ Serving客户端调用 C++ OP来预测,因此在启动服务之前,需要编译并安装 serving server包,并设置 `SERVING_BIN` 与Python Serving不同,C++ Serving客户端调用 C++ OP来预测,因此在启动服务之前,需要编译并安装 serving server包,并设置 `SERVING_BIN`
- 编译并安装Serving server包 - 编译并安装Serving server包
```shell ```shell
# 进入工作目录 # 进入工作目录
cd PaddleClas/deploy/paddleserving cd ./deploy/paddleserving
# 一键编译安装Serving server、设置 SERVING_BIN # 一键编译安装Serving server、设置 SERVING_BIN
source ./build_server.sh python3.7 source ./build_server.sh python3.7
``` ```
**注:**[build_server.sh](../build_server.sh#L55-L62)所设定的路径可能需要根据实际机器上的环境如CUDA、python版本等作一定修改,然后再编译;如果执行`build_server.sh`过程中遇到非网络原因的报错,则可以手动将脚本中的命令逐条复制到终端执行。 **注:** [build_server.sh](../build_server.sh#L55-L62) 所设定的路径可能需要根据实际机器上的环境如CUDA、python版本等作一定修改,然后再编译;如果执行 `build_server.sh` 过程中遇到非网络原因的报错,则可以手动将脚本中的命令逐条复制到终端执行。
- C++ Serving使用的输入输出格式与Python不同,因此需要执行以下命令,将4个文件复制到下的文件覆盖掉[3.1](#31-模型转换)得到文件夹中的对应4个prototxt文件。 - C++ Serving使用的输入输出格式与Python不同,因此需要执行以下命令,将4个文件复制到下的文件覆盖掉[3.1](#31-模型转换)得到文件夹中的对应4个prototxt文件。
```shell ```shell
# 进入PaddleClas/deploy目录 # 回到deploy目录
cd PaddleClas/deploy/ cd ../
# 覆盖prototxt文件 # 覆盖prototxt文件
\cp ./paddleserving/recognition/preprocess/general_PPLCNet_x2_5_lite_v1.0_serving/*.prototxt ./models/general_PPLCNet_x2_5_lite_v1.0_serving/ \cp ./paddleserving/recognition/preprocess/general_PPLCNetV2_base_pretrained_v1.0_serving/*.prototxt ./models/general_PPLCNetV2_base_pretrained_v1.0_serving/
\cp ./paddleserving/recognition/preprocess/general_PPLCNet_x2_5_lite_v1.0_client/*.prototxt ./models/general_PPLCNet_x2_5_lite_v1.0_client/ \cp ./paddleserving/recognition/preprocess/general_PPLCNetV2_base_pretrained_v1.0_client/*.prototxt ./models/general_PPLCNetV2_base_pretrained_v1.0_client/
\cp ./paddleserving/recognition/preprocess/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/*.prototxt ./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/ \cp ./paddleserving/recognition/preprocess/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/*.prototxt ./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_client/
\cp ./paddleserving/recognition/preprocess/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/*.prototxt ./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/ \cp ./paddleserving/recognition/preprocess/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/*.prototxt ./models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_serving/
``` ```
...@@ -235,7 +242,7 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -235,7 +242,7 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
- 启动服务: - 启动服务:
```shell ```shell
# 进入工作目录 # 进入工作目录
cd PaddleClas/deploy/paddleserving/recognition cd ./paddleserving/recognition
# 端口号默认为9400;运行日志默认保存在 log_PPShiTu.txt 中 # 端口号默认为9400;运行日志默认保存在 log_PPShiTu.txt 中
# CPU部署 # CPU部署
...@@ -252,9 +259,9 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -252,9 +259,9 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
成功运行后,模型预测的结果会打印在客户端中,如下所示: 成功运行后,模型预测的结果会打印在客户端中,如下所示:
```log ```log
WARNING: Logging before InitGoogleLogging() is written to STDERR WARNING: Logging before InitGoogleLogging() is written to STDERR
I0614 03:01:36.273097 6084 naming_service_thread.cpp:202] brpc::policy::ListNamingService("127.0.0.1:9400"): added 1 I0903 16:03:20.020586 35600 naming_service_thread.cpp:202] brpc::policy::ListNamingService("127.0.0.1:9400"): added 1
I0614 03:01:37.393564 6084 general_model.cpp:490] [client]logid=0,client_cost=1107.82ms,server_cost=1101.75ms. I0903 16:03:21.346057 35600 general_model.cpp:490] [client]logid=0,client_cost=1306.26ms,server_cost=1293.65ms.
[{'bbox': [345, 95, 524, 585], 'rec_docs': '红牛-强化型', 'rec_scores': 0.8073724}] [{'bbox': [437, 71, 660, 727], 'rec_docs': '元气森林', 'rec_scores': 0.76902336}, {'bbox': [222, 72, 449, 700], 'rec_docs': '元气森林', 'rec_scores': 0.69347066}, {'bbox': [794, 104, 979, 652], 'rec_docs': '元气森林', 'rec_scores': 0.6305151}]
``` ```
- 关闭服务 - 关闭服务
...@@ -265,6 +272,7 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD ...@@ -265,6 +272,7 @@ python3.7 -m pip install paddle-serving-server-gpu==0.7.0.post112 # GPU with CUD
执行完毕后出现`Process stopped`信息表示成功关闭服务。 执行完毕后出现`Process stopped`信息表示成功关闭服务。
<a name="4"></a> <a name="4"></a>
## 4. FAQ ## 4. FAQ
**Q1**: 发送请求后没有结果返回或者提示输出解码报错 **Q1**: 发送请求后没有结果返回或者提示输出解码报错
...@@ -276,6 +284,6 @@ unset http_proxy ...@@ -276,6 +284,6 @@ unset http_proxy
``` ```
**Q2**: 启动服务后没有任何反应 **Q2**: 启动服务后没有任何反应
**A2**: 可以检查`config.yml``model_config`对应的路径是否存在,文件夹命名是否正确 **A2**: 可以检查 `config.yml``model_config` 对应的路径是否存在,文件夹命名是否正确
更多的服务部署类型,如 `RPC 预测服务` 等,可以参考 Serving 的[github 官网](https://github.com/PaddlePaddle/Serving/tree/v0.9.0/examples) 更多的服务部署类型,如 `RPC 预测服务` 等,可以参考 Serving 的[github 官网](https://github.com/PaddlePaddle/Serving/tree/v0.9.0/examples)
# PP-ShiTu 库管理工具
本工具是PP-ShiTu的离线库管理工具,主要功能包括:新建图像库、更改图像库、建立索引库、更新索引库等功能。此工具是为了用户能够可视化的管理图像及对应的index库,用户可根据实际情况,灵活的增删改查相应的gallery图像库及索引文件,在提升用户体验的同时,辅助PP-ShiTu在实际应用的过程中达到更好的效果。
## 目录
- [1. 功能介绍](#1)
- [1.1 新建图像库](#1.1)
- [1.2 打开图像库](#1.2)
- [1.3 导入图像](#1.3)
- [1.4 图像操作](#1.3)
- [1.5 其他功能](#1.5)
- [2. 使用说明](#2)
- [2.1 环境安装](#2.1)
- [2.2 模型准备](#2.2)
- [2.3运行使用](#2.3)
- [3.生成文件介绍](#3)
- [致谢](#4)
- [FAQ](#FAQ)
<a name="1"></a>
## 1. 功能介绍
此工具主要功能包括:
- 构建`PP-ShiTu`中索引库对应的`gallery`图像库
- 根据构建的`gallery`图像库,生成索引库
-`gallery`图像库进行操作,如增删改查等操作,并更新对应的索引库
其中主界面的按钮如下图所示
<div align="center">
<img src="https://user-images.githubusercontent.com/11568925/188273082-b1ada7ed-e56e-4b6a-9e79-2dda01a3db69.png" width = "600" />
<p>界面按钮展示</p>
</div>
上图中第一行包括:`主要功能按钮``保存按钮``新增类别按钮``删减类别按钮`
第二行包括:`搜索框``搜索确定键``新加图像按钮``删除图像按钮`
下面将进行具体功能介绍,其操作入口,可以点击`主要功能按钮`下拉菜单查看,如下图所示:
<div align="center">
<img src="https://user-images.githubusercontent.com/11568925/188273056-04b376f5-7275-47ac-898b-474a667bc6a7.png" width = "600" />
<p>主要功能展示</p>
</div>
<a name="1.1"></a>
### 1.1 新建图像库
点击新建库功能后,会选择一个**空的存储目录**或者**新建目录**,此时所有的图片及对应的索引库都会存放在此目录下。完成操作后,如下图所示
<div align="center">
<img src="https://user-images.githubusercontent.com/11568925/188273108-8789b8cf-d2ab-49d5-bc82-f0bf7b41c686.png" width = "600" />
<p>新建库</p>
</div>
此时,用户可以新建类别具体可以点击`新增类别按钮``删减类别按钮`。选中类别后,可以进行添加图像及相关操作,具体可以点击及`新加图像按钮``删除图像按钮`。完成操作后,**注意保存**
<a name="1.2"></a>
### 1.2 打开图像库
此功能是,用此工具存储好的库,进行打开编辑。注意,**打开库时,请选择打开的是新建库时文件夹路径**。打开库后,示例如下
<div align="center">
<img src="https://user-images.githubusercontent.com/11568925/188273143-00ff0558-ccc9-4b8d-9364-43eef5dce334.png" width = "600" />
<p>打开库</p>
</div>
<a name="1.3"></a>
### 1.3 导入图像
在打开图像库或者新建图像库完成后,可以使用导入图像功能,即导入用户自己生成好的图像库。具体有支持两种导入格式
- image_list格式:打开具体的`.txt`文件。`.txt`文件中每一行格式: `image_path label`。跟据文件路径及label导入
- 多文件夹格式:打开`具体文件夹`,此文件夹下存储多个子文件夹,每个子文件夹名字为`label_name`,每个子文件夹中存储对应的图像数据。
<a name="1.4"></a>
### 1.4 图像操作
选择图像后,鼠标右击可以进行如下操作,可以根据需求,选择具体的操作,**注意修改完成图像后,请点击保存按钮,进行保存**
<div align="center">
<img src="https://user-images.githubusercontent.com/11568925/188273178-5eff2f2e-7a8b-4a2b-809e-78f99479162d.png" width = "600" />
<p>图像操作</p>
</div>
<a name="1.5"></a>
### 1.5 生成、更新index库
在用户完成图像库的新建、打开或者修改,并完成保存操作后。可以点击`主要功能按钮``新建/重建索引库``更新索引库`等功能,进行索引库的新建或者更新,生成`PP-ShiTu`使用的Index库
<a name="2"></a>
## 2. 使用说明
<a name="2.1"></a>
### 2.1 环境安装
安装好`PaddleClas`
```shell
pip install fastapi
pip install uvicorn
pip install pyqt5
```
<a name="2.2"></a>
### 2.2 模型准备
请按照[PP-ShiTu快速体验](../quick_start/quick_start_recognition.md#2.2.1)中下载及准备inference model,并修改好`${PaddleClas}/deploy/configs/inference_drink.yaml`的相关参数。
<a name="2.3"></a>
### 2.3 运行使用
运行方式如下
```shell
cd ${PaddleClas}/deploy/shitu_index_manager
python index_manager.py -c ../configs/inference_drink.yaml
```
<a name="3"></a>
## 3. 生成文件介绍
使用此工具后,会生成如下格式的文件
```shell
index_root/ # 库存储目录
|-- image_list.txt # 图像列表,每行:image_path label。由前端生成及修改,后端只读
|-- images # 图像存储目录,由前端生成及增删查等操作。后端只读
| |-- md5.jpg
| |-- md5.jpg
| |-- ……
|-- features.pkl # 建库之后,保存的embedding向量,后端生成,前端无需操作
|-- index # 真正的生成的index库存储目录,后端生成及操作,前端无需操作。
| |-- vector.index # faiss生成的索引库
| |-- id_map.pkl # 索引文件
```
其中`index_root`是使用此工具时,用户选择的存储目录,库的索引文件存储在`index`文件夹中。
使用`PP-ShiTu`时,索引文件目录需换成`index`文件夹的地址。
<a name="4"></a>
## 致谢
此工具的前端主要由[国内qt论坛](http://www.qtcn.org/)总版主[小熊宝宝](https://github.com/cnhemiya)完成,感谢**小熊宝宝**的大力支持~~
此工具前端原项目地址:https://github.com/cnhemiya/shitu-manager
<a name="FAQ"></a>
## FAQ
- 问题1: 点击新建索引库后,程序假死
答:生成索引库比较耗时,耐心等待一段时间就好
- 问题2: 导入图像是什么格式?
答: 目前支持两种格式 1)image_list 格式,list中每行格式:path label。2)文件夹格式:类似`ImageNet`存储方式
- 问题3: 生成 index库报错
答:在修改图像后,必须点击保存按钮,保存完成后,再继续生成index库。
- 问题4: 报错 图像与index库不一致
答:可能用户自己修改了image_list.txt,修改完成后,请及时更新index库,保证其一致。
# PP-ShiTu应用场景介绍
该文档介绍了PP-ShiTu提供的各种应用场景库简介、下载链接以及使用简介。
------
## 目录
- [1. 应用场景介绍](#1-应用场景介绍)
- [2. 使用说明](#2-使用说明)
- [2.1 环境配置](#21-环境配置)
- [2.2 下载、解压场景库数据](#22-下载解压场景库数据)
- [2.3 准备模型](#23-准备模型)
- [2.4 场景库识别与检索](#24-场景库识别与检索)
- [2.4.1 识别单张图像](#241-识别单张图像)
- [2.4.2 基于文件夹的批量识别](#242-基于文件夹的批量识别)
<a name="1. 应用场景介绍"></a>
## 1. 应用场景介绍
PP-ShiTu对原数据集进行了`Gallery`库和`Query`库划分,并生成了对应的`Index`索引库,具体应用场景介绍和下载地址如下表所示。
| 场景 |示例图|场景简介|Recall@1|场景库下载地址|原数据集下载地址|
|:---:|:---:|:---:|:---:|:---:|:---:|
| 球类 | <img src="../../images/ppshitu_application_scenarios/Ball.jpg" height = "100"> |各种球类识别 | 0.9769 | [Balls](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Balls.tar) | [原数据下载地址](https://www.kaggle.com/datasets/gpiosenka/balls-image-classification) |
| 狗识别 | <img src="../../images/ppshitu_application_scenarios/DogBreeds.jpg" height = "100"> | 狗细分类识别,包括69种狗的图像 | 0.9606 | [DogBreeds](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/DogBreeds.tar) | [原数据下载地址](https://www.kaggle.com/datasets/gpiosenka/70-dog-breedsimage-data-set) |
| 宝石 | <img src="../../images/ppshitu_application_scenarios/Gemstones.jpg" height = "100"> | 宝石种类识别 | 0.9653 | [Gemstones](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Gemstones.tar) | [原数据下载地址](https://www.kaggle.com/datasets/lsind18/gemstones-images) |
| 动物 | <img src="../../images/ppshitu_application_scenarios/AnimalImageDataset.jpg" height = "100"> |各种动物识别 | 0.9078 | [AnimalImageDataset](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/AnimalImageDataset.tar) | [原数据下载地址](https://www.kaggle.com/datasets/iamsouravbanerjee/animal-image-dataset-90-different-animals) |
| 鸟类 | <img src="../../images/ppshitu_application_scenarios/Bird400.jpg" height = "100"> |鸟细分类识别,包括400种各种姿态的鸟类图像 | 0.9673 | [Bird400](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Bird400.tar) | [原数据下载地址](https://www.kaggle.com/datasets/gpiosenka/100-bird-species) |
| 交通工具 | <img src="../../images/ppshitu_application_scenarios/Vechicles.jpg" height = "100"> |车、船等交通工具粗分类识别 | 0.9307 | [Vechicles](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Vechicles.tar) | [原数据下载地址](https://www.kaggle.com/datasets/rishabkoul1/vechicle-dataset) |
| 花 | <img src="../../images/ppshitu_application_scenarios/104flowers.jpeg" height = "100"> |104种花细分类识别 | 0.9788 | [104flowers](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/104flowrs.tar) | [原数据下载地址](https://www.kaggle.com/datasets/msheriey/104-flowers-garden-of-eden) |
| 运动种类 | <img src="../../images/ppshitu_application_scenarios/100sports.jpg" height = "100"> |100种运动图像识别 | 0.9413 | [100sports](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/100sports.tar) | [原数据下载地址](https://www.kaggle.com/datasets/gpiosenka/sports-classification) |
| 乐器 | <img src="../../images/ppshitu_application_scenarios/MusicInstruments.jpg" height = "100"> |30种不同乐器种类识别 | 0.9467 | [MusicInstruments](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/MusicInstruments.tar) | [原数据下载地址](https://www.kaggle.com/datasets/gpiosenka/musical-instruments-image-classification) |
| 宝可梦 | <img src="../../images/ppshitu_application_scenarios/Pokemon.png" height = "100"> |宝可梦神奇宝贝识别 | 0.9236 | [Pokemon](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Pokemon.tar) | [原数据下载地址](https://www.kaggle.com/datasets/lantian773030/pokemonclassification) |
| 船 | <img src="../../images/ppshitu_application_scenarios/Boat.jpg" height = "100"> |船种类识别 |0.9242 | [Boat](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Boat.tar) | [原数据下载地址](https://www.kaggle.com/datasets/imsparsh/dockship-boat-type-classification) |
| 鞋子 | <img src="../../images/ppshitu_application_scenarios/Shoes.jpeg" height = "100"> |鞋子种类识别,包括靴子、拖鞋等 | 0.9000 | [Shoes](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Shoes.tar) | [原数据下载地址](https://www.kaggle.com/datasets/noobyogi0100/shoe-dataset) |
| 巴黎建筑 | <img src="../../images/ppshitu_application_scenarios/Paris.jpg" height = "100"> |巴黎著名建筑景点识别,如:巴黎铁塔、圣母院等 | 1.000 | [Paris](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Paris.tar) | [原数据下载地址](https://www.kaggle.com/datasets/skylord/oxbuildings) |
| 蝴蝶 | <img src="../../images/ppshitu_application_scenarios/Butterfly.jpg" height = "100"> |75种蝴蝶细分类识别 | 0.9360 | [Butterfly](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Butterfly.tar) | [原数据下载地址](https://www.kaggle.com/datasets/gpiosenka/butterfly-images40-species) |
| 野外植物 | <img src="../../images/ppshitu_application_scenarios/WildEdiblePlants.jpg" height = "100"> |野外植物识别 | 0.9758 | [WildEdiblePlants](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/WildEdiblePlants.tar) | [原数据下载地址](https://www.kaggle.com/datasets/ryanpartridge01/wild-edible-plants) |
| 天气 | <img src="../../images/ppshitu_application_scenarios/WeatherImageRecognition.jpg" height = "100"> |各种天气场景识别,如:雨天、打雷、下雪等 | 0.9924 | [WeatherImageRecognition](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/WeatherImageRecognition.tar) | [原数据下载地址](https://www.kaggle.com/datasets/jehanbhathena/weather-dataset) |
| 坚果 | <img src="../../images/ppshitu_application_scenarios/TreeNuts.jpg" height = "100"> |各种坚果种类识别 | 0.9412 | [TreeNuts](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/TreeNuts.tar) | [原数据下载地址](https://www.kaggle.com/datasets/gpiosenka/tree-nuts-image-classification) |
| 时装 | <img src="../../images/ppshitu_application_scenarios/FashionProductsImage.jpg" height = "100"> |首饰、挎包、化妆品等时尚商品识别 | 0.9555 | [FashionProductImageSmall](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/FashionProductImageSmall.tar) | [原数据下载地址](https://www.kaggle.com/datasets/paramaggarwal/fashion-product-images-small) |
| 垃圾 | <img src="../../images/ppshitu_application_scenarios/Garbage12.jpg" height = "100"> |12种垃圾分类识别 | 0.9845 | [Garbage12](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Garbage12.tar) | [原数据下载地址](https://www.kaggle.com/datasets/mostafaabla/garbage-classification) |
| 航拍场景 | <img src="../../images/ppshitu_application_scenarios/AID.jpg" height = "100"> |各种航拍场景识别,如机场、火车站等 | 0.9797 | [AID](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/AID.tar) | [原数据下载地址](https://www.kaggle.com/datasets/jiayuanchengala/aid-scene-classification-datasets) |
| 蔬菜 | <img src="../../images/ppshitu_application_scenarios/Veg200.jpg" height = "100"> |各种蔬菜识别 | 0.8929 | [Veg200](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Veg200.tar) | [原数据下载地址](https://www.kaggle.com/datasets/zhaoyj688/vegfru) |
| 商标 | <img src="../../images/ppshitu_application_scenarios/Logo3K.jpg" height = "100"> |两千多种logo识别 | 0.9313 | [Logo3k](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/PP-ShiTuV2_application_dataset/Logo3k.tar) | [原数据下载地址](https://github.com/Wangjing1551/LogoDet-3K-Dataset) |
<a name="2. 使用说明"></a>
## 2. 使用说明
<a name="2.1 环境配置"></a>
### 2.1 环境配置
- 安装:请先参考文档[环境准备](../installation/install_paddleclas.md)配置PaddleClas运行环境
- 进入`deploy`运行目录,本部分所有内容与命令均需要在`deploy`目录下运行,可以通过下面命令进入`deploy`目录。
```shell
cd deploy
```
<a name="2.2 下载、解压场景库数据"></a>
### 2.2 下载、解压场景库数据
首先创建存放场景库的地址`deploy/datasets`:
```shell
mkdir datasets
```
下载并解压对应场景库到`deploy/datasets`中。
```shell
cd datasets
# 下载并解压场景库数据
wget {场景库下载链接} && tar -xf {压缩包的名称}
```
`dataset_name`为例,解压完毕后,`datasets/dataset_name`文件夹下应有如下文件结构:
```shel
├── dataset_name/
│ ├── Gallery/
│ ├── Index/
│ ├── Query/
│ ├── gallery_list.txt/
│ ├── query_list.txt/
│ ├── label_list.txt/
├── ...
```
其中,`Gallery`文件夹中存放的是用于构建索引库的原始图像,`Index`表示基于原始图像构建得到的索引库信息,`Query`文件夹存放的是用于检索的图像列表,`gallery_list.txt``query_list.txt`分别为索引库和检索图像的标签文件,`label_list.txt`是标签的中英文对照文件(注意:商标场景库文件不包含中英文对照文件)。
<a name="2.3 准备识别模型"></a>
### 2.3 准备模型
创建存放模型的文件夹`deploy/models`,并下载轻量级主体检测、识别模型,命令如下:
```shell
cd ..
mkdir models
cd models
# 下载检测模型并解压
# wget {检测模型下载链接} && tar -xf {检测模型压缩包名称}
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/PP-ShiTuV2/general_PPLCNetV2_base_pretrained_v1.0_infer.tar && tar -xf general_PPLCNetV2_base_pretrained_v1.0_infer.tar
# 下载识别 inference 模型并解压
#wget {识别模型下载链接} && tar -xf {识别模型压缩包名称}
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar && tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
```
解压完成后,`models`文件夹下有如下文件结构:
```
├── inference_model_name
│ ├── inference.pdiparams
│ ├── inference.pdiparams.info
│ └── inference.pdmodel
└── det_model_name
├── inference.pdiparams
├── inference.pdiparams.info
└── inference.pdmodel
```
<a name="2.4 场景库识别与检索"></a>
### 2.4 场景库识别与检索
`动物识别`场景为例,展示识别和检索过程(如果希望尝试其他场景库的识别与检索效果,在下载解压好对应的场景库数据和模型后,替换对应的配置文件即可完成预测)。
注意,此部分使用了`faiss`作为检索库,安装方法如下:
```shell
pip install faiss-cpu==1.7.1post2
```
若使用时,不能正常引用,则`uninstall`之后,重新`install`,尤其是在windows下。
<a name="2.4.1 识别单张图像"></a>
#### 2.4.1 识别单张图像
假设需要测试`./datasets/AnimalImageDataset/Query/羚羊/0a37838e99.jpg`这张图像识别和检索效果。
首先分别修改配置文件`./configs/inference_general.yaml`中的`Global.det_inference_model_dir``Global.rec_inference_model_dir`字段为对应的检测和识别模型文件夹,以及修改测试图像地址字段`Global.infer_imgs`示例如下:
```shell
Global:
infer_imgs: './datasets/AnimalImageDataset/Query/羚羊/0a37838e99.jpg'
det_inference_model_dir: './models/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar'
rec_inference_model_dir: './models/general_PPLCNetV2_base_pretrained_v1.0_infer.tar'
```
并修改配置文件`./configs/inference_general.yaml`中的`IndexProcess.index_dir`字段为对应场景index库地址:
```shell
IndexProcess:
index_dir:'./datasets/AnimalImageDataset/Index/'
```
运行下面的命令,对图像`./datasets/AnimalImageDataset/Query/羚羊/0a37838e99.jpg`进行识别与检索
```shell
# 使用下面的命令使用 GPU 进行预测
python3.7 python/predict_system.py -c configs/inference_general.yaml
# 使用下面的命令使用 CPU 进行预测
python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.use_gpu=False
```
最终输出结果如下:
```
[{'bbox': [609, 70, 1079, 629], 'rec_docs': '羚羊', 'rec_scores': 0.6571544}]
```
其中`bbox`表示检测出的主体所在位置,`rec_docs`表示索引库中与检测框最为相似的类别,`rec_scores`表示对应的置信度。
检测的可视化结果也保存在`output`文件夹下,对于本张图像,识别结果可视化如下所示。
![](../../images/ppshitu_application_scenarios/systerm_result.jpg)
<a name="2.4.2 基于文件夹的批量识别"></a>
#### 2.4.2 基于文件夹的批量识别
如果希望预测文件夹内的图像,可以直接修改配置文件中`Global.infer_imgs`字段,也可以通过下面的`-o`参数修改对应的配置。
```shell
# 使用下面的命令使用 GPU 进行预测,如果希望使用 CPU 预测,可以在命令后面添加 -o Global.use_gpu=False
python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.infer_imgs="./datasets/AnimalImageDataset/Query/羚羊"
```
终端中会输出该文件夹内所有图像的识别结果,如下所示。
```
...
[{'bbox': [0, 0, 1200, 675], 'rec_docs': '羚羊', 'rec_scores': 0.6153812}]
[{'bbox': [0, 0, 275, 183], 'rec_docs': '羚羊', 'rec_scores': 0.77218026}]
[{'bbox': [264, 79, 1088, 850], 'rec_docs': '羚羊', 'rec_scores': 0.81452656}]
[{'bbox': [0, 0, 188, 268], 'rec_docs': '羚羊', 'rec_scores': 0.637074}]
[{'bbox': [118, 41, 235, 161], 'rec_docs': '羚羊', 'rec_scores': 0.67315465}]
[{'bbox': [0, 0, 175, 287], 'rec_docs': '羚羊', 'rec_scores': 0.68271667}]
[{'bbox': [0, 0, 310, 163], 'rec_docs': '羚羊', 'rec_scores': 0.6706451}]
...
```
所有图像的识别结果可视化图像也保存在`output`文件夹内。
...@@ -18,7 +18,7 @@ LeViT 是一种快速推理的、用于图像分类任务的混合神经网络 ...@@ -18,7 +18,7 @@ LeViT 是一种快速推理的、用于图像分类任务的混合神经网络
| Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(M) | Params<br>(M) | | Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(M) | Params<br>(M) |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:| |:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| LeViT-128S | 0.7598 | 0.9269 | 0.766 | 0.929 | 305 | 7.8 | | LeViT-128S | 0.7598 | 0.9269 | 0.766 | 0.929 | 305 | 7.8 |
| LeViT-128 | 0.7810 | 0.9371 | 0.786 | 0.940 | 406 | 9.2 | | LeViT-128 | 0.7810 | 0.9372 | 0.786 | 0.940 | 406 | 9.2 |
| LeViT-192 | 0.7934 | 0.9446 | 0.800 | 0.947 | 658 | 11 | | LeViT-192 | 0.7934 | 0.9446 | 0.800 | 0.947 | 658 | 11 |
| LeViT-256 | 0.8085 | 0.9497 | 0.816 | 0.954 | 1120 | 19 | | LeViT-256 | 0.8085 | 0.9497 | 0.816 | 0.954 | 1120 | 19 |
| LeViT-384 | 0.8191 | 0.9551 | 0.826 | 0.960 | 2353 | 39 | | LeViT-384 | 0.8191 | 0.9551 | 0.826 | 0.960 | 2353 | 39 |
......
...@@ -57,7 +57,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在 ...@@ -57,7 +57,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在
### 1.2.1 Rep 策略 ### 1.2.1 Rep 策略
卷积核的大小决定了卷积层感受野的大小,通过组合使用不同大小的卷积核,能够获取不同尺度的特征,因此 PPLCNetV2 在 Stage3、Stage4 中,在同一层组合使用 kernel size 分别为 5、3、1 的 DW 卷积,同时为了避免对模型效率的影响,使用重参数化(Re parameterization,Rep)策略对同层的 DW 卷积进行融合,如下图所示。 卷积核的大小决定了卷积层感受野的大小,通过组合使用不同大小的卷积核,能够获取不同尺度的特征,因此 PPLCNetV2 在 Stage4、Stage5 中,在同一层组合使用 kernel size 分别为 5、3、1 的 DW 卷积,同时为了避免对模型效率的影响,使用重参数化(Re parameterization,Rep)策略对同层的 DW 卷积进行融合,如下图所示。
![](../../images/PP-LCNetV2/rep.png) ![](../../images/PP-LCNetV2/rep.png)
...@@ -65,7 +65,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在 ...@@ -65,7 +65,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在
### 1.2.2 PW 卷积 ### 1.2.2 PW 卷积
深度可分离卷积通常由一层 DW 卷积和一层 PW 卷积组成,用以替换标准卷积,为了使深度可分离卷积具有更强的拟合能力,我们尝试使用两层 PW 卷积,同时为了控制模型效率不受影响,两层 PW 卷积设置为:第一个在通道维度对特征图压缩,第二个再通过放大还原特征图通道,如下图所示。通过实验发现,该策略能够显著提高模型性能,同时为了平衡对模型效率带来的影响,PPLCNetV2 仅在 Stage4、Stage5 中使用了该策略。 深度可分离卷积通常由一层 DW 卷积和一层 PW 卷积组成,用以替换标准卷积,为了使深度可分离卷积具有更强的拟合能力,我们尝试使用两层 PW 卷积,同时为了控制模型效率不受影响,两层 PW 卷积设置为:第一个在通道维度对特征图压缩,第二个再通过放大还原特征图通道,如下图所示。通过实验发现,该策略能够显著提高模型性能,同时为了平衡对模型效率带来的影响,PPLCNetV2 仅在 Stage4 中使用了该策略。
![](../../images/PP-LCNetV2/split_pw.png) ![](../../images/PP-LCNetV2/split_pw.png)
...@@ -73,7 +73,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在 ...@@ -73,7 +73,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在
### 1.2.3 Shortcut ### 1.2.3 Shortcut
残差结构(residual)自提出以来,被诸多模型广泛使用,但在轻量级卷积神经网络中,由于残差结构所带来的元素级(element-wise)加法操作,会对模型的速度造成影响,我们在 PP-LCNetV2 中,以 Stage 为单位实验了 残差结构对模型的影响,发现残差结构的使用并非一定会带来性能的提高,因此 PPLCNetV2 仅在最后一个 Stage 中的使用了残差结构:在 Block 中增加 Shortcut,如下图所示。 残差结构(residual)自提出以来,被诸多模型广泛使用,但在轻量级卷积神经网络中,由于残差结构所带来的元素级(element-wise)加法操作,会对模型的速度造成影响,我们在 PP-LCNetV2 中,以 Stage 为单位实验了残差结构对模型的影响,发现残差结构的使用并非一定会带来性能的提高,因此 PPLCNetV2 仅在最后一个 Stage 中的使用了残差结构:在 Block 中增加 Shortcut,如下图所示。
![](../../images/PP-LCNetV2/shortcut.png) ![](../../images/PP-LCNetV2/shortcut.png)
...@@ -87,7 +87,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在 ...@@ -87,7 +87,7 @@ PP-LCNetV2 模型的网络整体结构如上图所示。PP-LCNetV2 模型是在
### 1.2.5 SE 模块 ### 1.2.5 SE 模块
虽然 SE 模块能够显著提高模型性能,但其对模型速度的影响同样不可忽视,在 PP-LCNetV1 中,我们发现在模型中后部使用 SE 模块能够获得最大化的收益。在 PP-LCNetV2 的优化过程中,我们以 Stage 为单位对 SE 模块的位置做了进一步实验,并发现在 Stage3 中使用能够取得更好的平衡。 虽然 SE 模块能够显著提高模型性能,但其对模型速度的影响同样不可忽视,在 PP-LCNetV1 中,我们发现在模型中后部使用 SE 模块能够获得最大化的收益。在 PP-LCNetV2 的优化过程中,我们以 Stage 为单位对 SE 模块的位置做了进一步实验,并发现在 Stage4 中使用能够取得更好的平衡。
<a name="1.3"></a> <a name="1.3"></a>
......
...@@ -18,10 +18,10 @@ PVTV2 是 VisionTransformer 系列模型,该模型基于 PVT(Pyramid Vision ...@@ -18,10 +18,10 @@ PVTV2 是 VisionTransformer 系列模型,该模型基于 PVT(Pyramid Vision
| Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(G) | Params<br>(M) | | Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(G) | Params<br>(M) |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:| |:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| PVT_V2_B0 | 0.705 | 0.902 | 0.705 | - | 0.53 | 3.7 | | PVT_V2_B0 | 0.7052 | 0.9016 | 0.705 | - | 0.53 | 3.7 |
| PVT_V2_B1 | 0.787 | 0.945 | 0.787 | - | 2.0 | 14.0 | | PVT_V2_B1 | 0.7869 | 0.9450 | 0.787 | - | 2.0 | 14.0 |
| PVT_V2_B2 | 0.821 | 0.960 | 0.820 | - | 3.9 | 25.4 | | PVT_V2_B2 | 0.8206 | 0.9599 | 0.820 | - | 3.9 | 25.4 |
| PVT_V2_B3 | 0.831 | 0.965 | 0.831 | - | 6.7 | 45.2 | | PVT_V2_B3 | 0.8310 | 0.9648 | 0.831 | - | 6.7 | 45.2 |
| PVT_V2_B4 | 0.836 | 0.967 | 0.836 | - | 9.8 | 62.6 | | PVT_V2_B4 | 0.8361 | 0.9666 | 0.836 | - | 9.8 | 62.6 |
| PVT_V2_B5 | 0.837 | 0.966 | 0.838 | - | 11.4 | 82.0 | | PVT_V2_B5 | 0.8374 | 0.9662 | 0.838 | - | 11.4 | 82.0 |
| PVT_V2_B2_Linear | 0.821 | 0.961 | 0.821 | - | 3.8 | 22.6 | | PVT_V2_B2_Linear | 0.8205 | 0.9605 | 0.820 | - | 3.8 | 22.6 |
...@@ -33,19 +33,17 @@ Swin Transformer 是一种新的视觉 Transformer 网络,可以用作计算 ...@@ -33,19 +33,17 @@ Swin Transformer 是一种新的视觉 Transformer 网络,可以用作计算
| Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPs<br>(G) | Params<br>(M) | | Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPs<br>(G) | Params<br>(M) |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:| |:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| SwinTransformer_tiny_patch4_window7_224 | 0.8069 | 0.9534 | 0.812 | 0.955 | 4.5 | 28 | | SwinTransformer_tiny_patch4_window7_224 | 0.8110 | 0.9549 | 0.812 | 0.955 | 4.5 | 28 |
| SwinTransformer_small_patch4_window7_224 | 0.8275 | 0.9613 | 0.832 | 0.962 | 8.7 | 50 | | SwinTransformer_small_patch4_window7_224 | 0.8321 | 0.9622 | 0.832 | 0.962 | 8.7 | 50 |
| SwinTransformer_base_patch4_window7_224 | 0.8300 | 0.9626 | 0.835 | 0.965 | 15.4 | 88 | | SwinTransformer_base_patch4_window7_224 | 0.8337 | 0.9643 | 0.835 | 0.965 | 15.4 | 88 |
| SwinTransformer_base_patch4_window12_384 | 0.8439 | 0.9693 | 0.845 | 0.970 | 47.1 | 88 | | SwinTransformer_base_patch4_window12_384 | 0.8417 | 0.9674 | 0.845 | 0.970 | 47.1 | 88 |
| SwinTransformer_base_patch4_window7_224<sup>[1]</sup> | 0.8487 | 0.9746 | 0.852 | 0.975 | 15.4 | 88 | | SwinTransformer_base_patch4_window7_224<sup>[1]</sup> | 0.8516 | 0.9748 | 0.852 | 0.975 | 15.4 | 88 |
| SwinTransformer_base_patch4_window12_384<sup>[1]</sup> | 0.8642 | 0.9807 | 0.864 | 0.980 | 47.1 | 88 | | SwinTransformer_base_patch4_window12_384<sup>[1]</sup> | 0.8634 | 0.9798 | 0.864 | 0.980 | 47.1 | 88 |
| SwinTransformer_large_patch4_window7_224<sup>[1]</sup> | 0.8596 | 0.9783 | 0.863 | 0.979 | 34.5 | 197 | | SwinTransformer_large_patch4_window7_224<sup>[1]</sup> | 0.8619 | 0.9788 | 0.863 | 0.979 | 34.5 | 197 |
| SwinTransformer_large_patch4_window12_384<sup>[1]</sup> | 0.8719 | 0.9823 | 0.873 | 0.982 | 103.9 | 197 | | SwinTransformer_large_patch4_window12_384<sup>[1]</sup> | 0.8706 | 0.9814 | 0.873 | 0.982 | 103.9 | 197 |
[1]:基于 ImageNet22k 数据集预训练,然后在 ImageNet1k 数据集迁移学习得到。 [1]:基于 ImageNet22k 数据集预训练,然后在 ImageNet1k 数据集迁移学习得到。
**注**:与 Reference 的精度差异源于数据预处理不同。
<a name='3'></a> <a name='3'></a>
### 1.3 Benchmark ### 1.3 Benchmark
...@@ -68,14 +66,14 @@ Swin Transformer 是一种新的视觉 Transformer 网络,可以用作计算 ...@@ -68,14 +66,14 @@ Swin Transformer 是一种新的视觉 Transformer 网络,可以用作计算
**备注:** 精度类型为 FP32,推理过程使用 TensorRT。 **备注:** 精度类型为 FP32,推理过程使用 TensorRT。
<a name="2"></a> <a name="2"></a>
## 2. 模型快速体验 ## 2. 模型快速体验
安装 paddlepaddle 和 paddleclas 即可快速对图片进行预测,体验方法可以参考[ResNet50 模型快速体验](./ResNet.md#2-模型快速体验) 安装 paddlepaddle 和 paddleclas 即可快速对图片进行预测,体验方法可以参考[ResNet50 模型快速体验](./ResNet.md#2-模型快速体验)
<a name="3"></a> <a name="3"></a>
## 3. 模型训练、评估和预测 ## 3. 模型训练、评估和预测
...@@ -83,52 +81,51 @@ Swin Transformer 是一种新的视觉 Transformer 网络,可以用作计算 ...@@ -83,52 +81,51 @@ Swin Transformer 是一种新的视觉 Transformer 网络,可以用作计算
**备注:** 由于 SwinTransformer 系列模型默认使用的 GPU 数量为 8 个,所以在训练时,需要指定8个GPU,如`python3 -m paddle.distributed.launch --gpus="0,1,2,3,4,5,6,7" tools/train.py -c xxx.yaml`, 如果使用 4 个 GPU 训练,默认学习率需要减小一半,精度可能有损。 **备注:** 由于 SwinTransformer 系列模型默认使用的 GPU 数量为 8 个,所以在训练时,需要指定8个GPU,如`python3 -m paddle.distributed.launch --gpus="0,1,2,3,4,5,6,7" tools/train.py -c xxx.yaml`, 如果使用 4 个 GPU 训练,默认学习率需要减小一半,精度可能有损。
<a name="4"></a> <a name="4"></a>
## 4. 模型推理部署 ## 4. 模型推理部署
<a name="4.1"></a> <a name="4.1"></a>
### 4.1 推理模型准备 ### 4.1 推理模型准备
Paddle Inference 是飞桨的原生推理库, 作用于服务器端和云端,提供高性能的推理能力。相比于直接基于预训练模型进行预测,Paddle Inference可使用 MKLDNN、CUDNN、TensorRT 进行预测加速,从而实现更优的推理性能。更多关于Paddle Inference推理引擎的介绍,可以参考[Paddle Inference官网教程](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/infer/inference/inference_cn.html) Paddle Inference 是飞桨的原生推理库, 作用于服务器端和云端,提供高性能的推理能力。相比于直接基于预训练模型进行预测,Paddle Inference可使用 MKLDNN、CUDNN、TensorRT 进行预测加速,从而实现更优的推理性能。更多关于Paddle Inference推理引擎的介绍,可以参考[Paddle Inference官网教程](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/infer/inference/inference_cn.html)
Inference 的获取可以参考 [ResNet50 推理模型准备](./ResNet.md#41-推理模型准备) Inference 的获取可以参考 [ResNet50 推理模型准备](./ResNet.md#41-推理模型准备)
<a name="4.2"></a> <a name="4.2"></a>
### 4.2 基于 Python 预测引擎推理 ### 4.2 基于 Python 预测引擎推理
PaddleClas 提供了基于 python 预测引擎推理的示例。您可以参考[ResNet50 基于 Python 预测引擎推理](./ResNet.md#42-基于-python-预测引擎推理) 对 SwinTransformer 完成推理预测。 PaddleClas 提供了基于 python 预测引擎推理的示例。您可以参考[ResNet50 基于 Python 预测引擎推理](./ResNet.md#42-基于-python-预测引擎推理) 对 SwinTransformer 完成推理预测。
<a name="4.3"></a> <a name="4.3"></a>
### 4.3 基于 C++ 预测引擎推理 ### 4.3 基于 C++ 预测引擎推理
PaddleClas 提供了基于 C++ 预测引擎推理的示例,您可以参考[服务器端 C++ 预测](../inference_deployment/cpp_deploy.md)来完成相应的推理部署。如果您使用的是 Windows 平台,可以参考[基于 Visual Studio 2019 Community CMake 编译指南](../inference_deployment/cpp_deploy_on_windows.md)完成相应的预测库编译和模型预测工作。 PaddleClas 提供了基于 C++ 预测引擎推理的示例,您可以参考[服务器端 C++ 预测](../inference_deployment/cpp_deploy.md)来完成相应的推理部署。如果您使用的是 Windows 平台,可以参考[基于 Visual Studio 2019 Community CMake 编译指南](../inference_deployment/cpp_deploy_on_windows.md)完成相应的预测库编译和模型预测工作。
<a name="4.4"></a> <a name="4.4"></a>
### 4.4 服务化部署 ### 4.4 服务化部署
Paddle Serving 提供高性能、灵活易用的工业级在线推理服务。Paddle Serving 支持 RESTful、gRPC、bRPC 等多种协议,提供多种异构硬件和多种操作系统环境下推理解决方案。更多关于Paddle Serving 的介绍,可以参考[Paddle Serving 代码仓库](https://github.com/PaddlePaddle/Serving) Paddle Serving 提供高性能、灵活易用的工业级在线推理服务。Paddle Serving 支持 RESTful、gRPC、bRPC 等多种协议,提供多种异构硬件和多种操作系统环境下推理解决方案。更多关于Paddle Serving 的介绍,可以参考[Paddle Serving 代码仓库](https://github.com/PaddlePaddle/Serving)
PaddleClas 提供了基于 Paddle Serving 来完成模型服务化部署的示例,您可以参考[模型服务化部署](../inference_deployment/paddle_serving_deploy.md)来完成相应的部署工作。 PaddleClas 提供了基于 Paddle Serving 来完成模型服务化部署的示例,您可以参考[模型服务化部署](../inference_deployment/paddle_serving_deploy.md)来完成相应的部署工作。
<a name="4.5"></a> <a name="4.5"></a>
### 4.5 端侧部署 ### 4.5 端侧部署
Paddle Lite 是一个高性能、轻量级、灵活性强且易于扩展的深度学习推理框架,定位于支持包括移动端、嵌入式以及服务器端在内的多硬件平台。更多关于 Paddle Lite 的介绍,可以参考[Paddle Lite 代码仓库](https://github.com/PaddlePaddle/Paddle-Lite) Paddle Lite 是一个高性能、轻量级、灵活性强且易于扩展的深度学习推理框架,定位于支持包括移动端、嵌入式以及服务器端在内的多硬件平台。更多关于 Paddle Lite 的介绍,可以参考[Paddle Lite 代码仓库](https://github.com/PaddlePaddle/Paddle-Lite)
PaddleClas 提供了基于 Paddle Lite 来完成模型端侧部署的示例,您可以参考[端侧部署](../inference_deployment/paddle_lite_deploy.md)来完成相应的部署工作。 PaddleClas 提供了基于 Paddle Lite 来完成模型端侧部署的示例,您可以参考[端侧部署](../inference_deployment/paddle_lite_deploy.md)来完成相应的部署工作。
<a name="4.6"></a> <a name="4.6"></a>
### 4.6 Paddle2ONNX 模型转换与预测 ### 4.6 Paddle2ONNX 模型转换与预测
Paddle2ONNX 支持将 PaddlePaddle 模型格式转化到 ONNX 模型格式。通过 ONNX 可以完成将 Paddle 模型到多种推理引擎的部署,包括TensorRT/OpenVINO/MNN/TNN/NCNN,以及其它对 ONNX 开源格式进行支持的推理引擎或硬件。更多关于 Paddle2ONNX 的介绍,可以参考[Paddle2ONNX 代码仓库](https://github.com/PaddlePaddle/Paddle2ONNX) Paddle2ONNX 支持将 PaddlePaddle 模型格式转化到 ONNX 模型格式。通过 ONNX 可以完成将 Paddle 模型到多种推理引擎的部署,包括TensorRT/OpenVINO/MNN/TNN/NCNN,以及其它对 ONNX 开源格式进行支持的推理引擎或硬件。更多关于 Paddle2ONNX 的介绍,可以参考[Paddle2ONNX 代码仓库](https://github.com/PaddlePaddle/Paddle2ONNX)
PaddleClas 提供了基于 Paddle2ONNX 来完成 inference 模型转换 ONNX 模型并作推理预测的示例,您可以参考[Paddle2ONNX 模型转换与预测](@shuilong)来完成相应的部署工作。 PaddleClas 提供了基于 Paddle2ONNX 来完成 inference 模型转换 ONNX 模型并作推理预测的示例,您可以参考[Paddle2ONNX 模型转换与预测](@shuilong)来完成相应的部署工作。
...@@ -17,14 +17,12 @@ Twins 网络包括 Twins-PCPVT 和 Twins-SVT,其重点对空间注意力机制 ...@@ -17,14 +17,12 @@ Twins 网络包括 Twins-PCPVT 和 Twins-SVT,其重点对空间注意力机制
| Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPs<br>(G) | Params<br>(M) | | Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPs<br>(G) | Params<br>(M) |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:| |:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| pcpvt_small | 0.8082 | 0.9552 | 0.812 | - | 3.7 | 24.1 | | pcpvt_small | 0.8115 | 0.9567 | 0.812 | - | 3.7 | 24.1 |
| pcpvt_base | 0.8242 | 0.9619 | 0.827 | - | 6.4 | 43.8 | | pcpvt_base | 0.8268 | 0.9627 | 0.827 | - | 6.4 | 43.8 |
| pcpvt_large | 0.8273 | 0.9650 | 0.831 | - | 9.5 | 60.9 | | pcpvt_large | 0.8306 | 0.9659 | 0.831 | - | 9.5 | 60.9 |
| alt_gvt_small | 0.8140 | 0.9546 | 0.817 | - | 2.8 | 24 | | alt_gvt_small | 0.8177 | 0.9557 | 0.817 | - | 2.8 | 24 |
| alt_gvt_base | 0.8294 | 0.9621 | 0.832 | - | 8.3 | 56 | | alt_gvt_base | 0.8315 | 0.9629 | 0.832 | - | 8.3 | 56 |
| alt_gvt_large | 0.8331 | 0.9642 | 0.837 | - | 14.8 | 99.2 | | alt_gvt_large | 0.8364 | 0.9651 | 0.837 | - | 14.8 | 99.2 |
**注**:与 Reference 的精度差异源于数据预处理不同。
<a name='3'></a> <a name='3'></a>
......
...@@ -21,27 +21,25 @@ DeiT(Data-efficient Image Transformers)系列模型是由 FaceBook 在 2020 ...@@ -21,27 +21,25 @@ DeiT(Data-efficient Image Transformers)系列模型是由 FaceBook 在 2020
| Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(G) | Params<br>(M) | | Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(G) | Params<br>(M) |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:| |:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| ViT_small_patch16_224 | 0.7769 | 0.9342 | 0.7785 | 0.9342 | 9.41 | 48.60 | | ViT_small_patch16_224 | 0.7553 | 0.9211 | 0.7785 | 0.9342 | 9.41 | 48.60 |
| ViT_base_patch16_224 | 0.8195 | 0.9617 | 0.8178 | 0.9613 | 16.85 | 86.42 | | ViT_base_patch16_224 | 0.8187 | 0.9618 | 0.8178 | 0.9613 | 16.85 | 86.42 |
| ViT_base_patch16_384 | 0.8414 | 0.9717 | 0.8420 | 0.9722 | 49.35 | 86.42 | | ViT_base_patch16_384 | 0.8414 | 0.9717 | 0.8420 | 0.9722 | 49.35 | 86.42 |
| ViT_base_patch32_384 | 0.8176 | 0.9613 | 0.8166 | 0.9613 | 12.66 | 88.19 | | ViT_base_patch32_384 | 0.8176 | 0.9613 | 0.8166 | 0.9613 | 12.66 | 88.19 |
| ViT_large_patch16_224 | 0.8323 | 0.9650 | 0.8306 | 0.9644 | 59.65 | 304.12 | | ViT_large_patch16_224 | 0.8303 | 0.9655 | 0.8306 | 0.9644 | 59.65 | 304.12 |
| ViT_large_patch16_384 | 0.8513 | 0.9736 | 0.8517 | 0.9736 | 174.70 | 304.12 | | ViT_large_patch16_384 | 0.8513 | 0.9736 | 0.8517 | 0.9736 | 174.70 | 304.12 |
| ViT_large_patch32_384 | 0.8153 | 0.9608 | 0.815 | - | 44.24 | 306.48 | | ViT_large_patch32_384 | 0.8153 | 0.9608 | 0.815 | - | 44.24 | 306.48 |
| Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(G) | Params<br>(M) | | Models | Top1 | Top5 | Reference<br>top1 | Reference<br>top5 | FLOPS<br>(G) | Params<br>(M) |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:| |:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| DeiT_tiny_patch16_224 | 0.718 | 0.910 | 0.722 | 0.911 | 1.07 | 5.68 | | DeiT_tiny_patch16_224 | 0.7208 | 0.9112 | 0.722 | 0.911 | 1.07 | 5.68 |
| DeiT_small_patch16_224 | 0.796 | 0.949 | 0.799 | 0.950 | 4.24 | 21.97 | | DeiT_small_patch16_224 | 0.7982 | 0.9495 | 0.799 | 0.950 | 4.24 | 21.97 |
| DeiT_base_patch16_224 | 0.817 | 0.957 | 0.818 | 0.956 | 16.85 | 86.42 | | DeiT_base_patch16_224 | 0.8180 | 0.9558 | 0.818 | 0.956 | 16.85 | 86.42 |
| DeiT_base_patch16_384 | 0.830 | 0.962 | 0.829 | 0.972 | 49.35 | 86.42 | | DeiT_base_patch16_384 | 0.8289 | 0.9624 | 0.829 | 0.972 | 49.35 | 86.42 |
| DeiT_tiny_distilled_patch16_224 | 0.741 | 0.918 | 0.745 | 0.919 | 1.08 | 5.87 | | DeiT_tiny_distilled_patch16_224 | 0.7449 | 0.9192 | 0.745 | 0.919 | 1.08 | 5.87 |
| DeiT_small_distilled_patch16_224 | 0.809 | 0.953 | 0.812 | 0.954 | 4.26 | 22.36 | | DeiT_small_distilled_patch16_224 | 0.8117 | 0.9538 | 0.812 | 0.954 | 4.26 | 22.36 |
| DeiT_base_distilled_patch16_224 | 0.831 | 0.964 | 0.834 | 0.965 | 16.93 | 87.18 | | DeiT_base_distilled_patch16_224 | 0.8330 | 0.9647 | 0.834 | 0.965 | 16.93 | 87.18 |
| DeiT_base_distilled_patch16_384 | 0.851 | 0.973 | 0.852 | 0.972 | 49.43 | 87.18 | | DeiT_base_distilled_patch16_384 | 0.8520 | 0.9720 | 0.852 | 0.972 | 49.43 | 87.18 |
关于 Params、FLOPs、Inference speed 等信息,敬请期待。
<a name='3'></a> <a name='3'></a>
...@@ -67,4 +65,3 @@ DeiT(Data-efficient Image Transformers)系列模型是由 FaceBook 在 2020 ...@@ -67,4 +65,3 @@ DeiT(Data-efficient Image Transformers)系列模型是由 FaceBook 在 2020
| DeiT_small_<br>distilled_patch16_224 | 256 | 224 | 3.70 | 6.20 | 10.53 | | DeiT_small_<br>distilled_patch16_224 | 256 | 224 | 3.70 | 6.20 | 10.53 |
| DeiT_base_<br>distilled_patch16_224 | 256 | 224 | 6.17 | 14.94 | 28.58 | | DeiT_base_<br>distilled_patch16_224 | 256 | 224 | 6.17 | 14.94 | 28.58 |
| DeiT_base_<br>distilled_patch16_384 | 384 | 384 | 14.12 | 48.76 | 97.09 | | DeiT_base_<br>distilled_patch16_384 | 384 | 384 | 14.12 | 48.76 | 97.09 |
# 图像识别 # 图像识别
---
在 PaddleClas 中,图像识别,是指给定一张查询图像,系统能够识别该查询图像类别。广义上,图像分类也是图像识别的一种。但是与普通图像识别不同的是,图像分类只能判别出模型已经学习的类别,如果需要添加新的类别,分类模型只能重新训练。PaddleClas 中的图像识别,**对于陌生类别,只需要更新相应的检索库**,就能够正确的识别出查询图像的类别,而无需重新训练模型,这大大增加了识别系统的可用性,同时降低了更新模型的需求,方便用户部署应用。 在 PaddleClas 中,**图像识别**是指给定一张查询图像,系统能够识别该查询图像类别。广义上,图像分类也是图像识别的一种。但图像分类只能判断模型学习过的类别,如果需要添加新的类别,分类模型只能重新训练,这显然会增加实际应用的成本,限制了应用场景。
因此 PaddleClas 通过主体检测+特征提取+特征检索的方式来实现图像识别,其好处是**对于陌生类别,只需要更新相应的检索库**,就能够正确的识别出查询图像的类别,而无需重新训练模型,这大大增加了识别系统的可用性,同时降低了更新模型的需求,方便用户部署应用。
对于一张待查询图片,PaddleClas 中的图像识别流程主要分为三部分: 对于一张待查询图片,PaddleClas 中的图像识别流程主要分为三部分:
1. 主体检测:对于给定一个查询图像,主体检测器首先检测出图像的物体,从而去掉无用背景信息,提高识别精度。 1. 主体检测:对于一张给定的查询图像,主体检测器检测出图像中的主体候选区域,过滤掉无用的背景信息,提高后续识别精度。
2. 特征提取:对主体检测的各个候选区域,通过特征模型,进行特征提取 2. 特征提取:将主体检测的各个候选区域裁剪出来,输入到通过特征提取模型中进行特征提取。
3. 特征检索:将提取的特征与特征库中的向量进行相似度比对,得到其标签信息 3. 特征检索:将提取的特征与特征库中的向量进行相似度比对,计算其相似度和标签信息。
完整的图像识别系统,如下图所示
其中特征库,需要利用已经标注好的图像数据集提前建立。完整的图像识别系统,如下图所示 <img src="../../images/structure.png"/>
![](../../images/structure.jpg) 在Android端或PC端体验整体图像识别系统,或查看特征库建立方法,可以参考 [图像识别快速开始文档](../quick_start/quick_start_recognition.md)
体验整体图像识别系统,或查看特征库建立方法,详见[图像识别快速开始文档](../quick_start/quick_start_recognition.md)。其中,图像识别快速开始文档主要讲解整体流程的使用过程。以下内容,主要对上述三个步骤的训练部分进行介绍。
首先,请参考[安装指南](../installation/install_paddleclas.md)配置运行环境 以下内容,主要对上述三个步骤的训练部分进行介绍
## 目录 在训练开始之前,请参考 [安装指南](../installation/install_paddleclas.md) 配置运行环境。
- [1. 主体检测](#1) ## 目录
- [2. 特征模型训练](#2)
- [2.1. 特征模型数据准备与处理](#2.1)
- [2. 2 特征模型基于单卡 GPU 上的训练与评估](#2.2)
- [2.2.1 特征模型训练](#2.2.2)
- [2.2.2 特征模型恢复训练](#2.2.2)
- [2.2.3 特征模型评估](#2.2.3)
- [2.3 特征模型导出 inference 模型](#2.3)
- [3. 特征检索](#3)
- [4. 基础知识](#4)
<a name="1"></a> - [1. 主体检测](#1-主体检测)
- [2. 特征提取模型训练](#2-特征提取模型训练)
- [2.1 特征提取模型数据的准备与处理](#21-特征提取模型数据的准备与处理)
- [2.2 特征提取模型在 GPU 上的训练与评估](#22-特征提取模型在-gpu-上的训练与评估)
- [2.2.1 特征提取模型训练](#221-特征提取模型训练)
- [2.2.2 特征提取模型恢复训练](#222-特征提取模型恢复训练)
- [2.2.3 特征提取模型评估](#223-特征提取模型评估)
- [2.3 特征提取模型导出 inference 模型](#23-特征提取模型导出-inference-模型)
- [3. 特征检索](#3-特征检索)
- [4. 基础知识](#4-基础知识)
<a name="1"></a>
## 1. 主体检测 ## 1. 主体检测
...@@ -38,142 +43,143 @@ ...@@ -38,142 +43,143 @@
[{u'id': 1, u'name': u'foreground', u'supercategory': u'foreground'}] [{u'id': 1, u'name': u'foreground', u'supercategory': u'foreground'}]
``` ```
关于主体检测训练方法可以参考: [PaddleDetection 训练教程](https://github.com/PaddlePaddle/PaddleDetection/blob/develop/docs/tutorials/GETTING_STARTED_cn.md#4-%E8%AE%AD%E7%BB%83) 关于主体检测数据集构造与模型训练方法可以参考: [30分钟快速上手PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection/blob/develop/docs/tutorials/GETTING_STARTED_cn.md#30%E5%88%86%E9%92%9F%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8Bpaddledetection)
更多关于 PaddleClas 中提供的主体检测的模型介绍与下载请参考:[主体检测教程](../image_recognition_pipeline/mainbody_detection.md) 更多关于 PaddleClas 中提供的主体检测的模型介绍与下载请参考:[主体检测教程](../image_recognition_pipeline/mainbody_detection.md)
<a name="2"></a> <a name="2"></a>
## 2. 特征模型训练 ## 2. 特征提取模型训练
<a name="2.1"></a> 为了快速体验 PaddleClas 图像检索模块,以下使用经典的200类鸟类细粒度分类数据集 [CUB_200_2011](http://vision.ucsd.edu/sites/default/files/WelinderEtal10_CUB-200.pdf) 为例,介绍特征提取模型训练过程。CUB_200_2011 下载方式请参考 [CUB_200_2011官网](https://www.vision.caltech.edu/datasets/cub_200_2011/)
### 2.1 特征模型数据的准备与处理 <a name="2.1"></a>
* 进入 `PaddleClas` 目录。 ### 2.1 特征提取模型数据的准备与处理
```bash * 进入 `PaddleClas` 目录
## linux or mac, $path_to_PaddleClas 表示 PaddleClas 的根目录,用户需要根据自己的真实目录修改
cd $path_to_PaddleClas
```
* 进入 `dataset` 目录,为了快速体验 PaddleClas 图像检索模块,此处使用的数据集为 [CUB_200_2011](http://vision.ucsd.edu/sites/default/files/WelinderEtal10_CUB-200.pdf),其是一个包含 200 类鸟的细粒度鸟类数据集。首先,下载 CUB_200_2011 数据集,下载方式请参考[官网](http://www.vision.caltech.edu/visipedia/CUB-200-2011.html) ```shell
cd PaddleClas
```
```shell * 进入 `dataset` 目录
# linux or mac
cd dataset
# 将下载后的数据拷贝到此目录 ```shell
cp {数据存放的路径}/CUB_200_2011.tgz . # 进入dataset目录
cd dataset
# 解压 # 将下载后的数据拷贝到dataset目录下
tar -xzvf CUB_200_2011.tgz cp {数据存放的路径}/CUB_200_2011.tgz ./
#进入 CUB_200_2011 目录 # 解压该数据集
cd CUB_200_2011 tar -xzvf CUB_200_2011.tgz
```
该数据集在用作图像检索任务时,通常将前 100 类当做训练集,后 100 类当做测试集,所以此处需要将下载的数据集做一些后处理,来更好的适应 PaddleClas 的图像检索训练。 #进入 CUB_200_2011 目录
cd CUB_200_2011
```
```shell * 该数据集在用作图像检索任务时,通常将前 100 类当做训练集,后 100 类当做测试集,所以此处需要将下载的数据集做一些后处理,来更好的适应 PaddleClas 的图像检索训练。
#新建 train 和 test 目录
mkdir train && mkdir test
#将数据分成训练集和测试集,前 100 类作为训练集,后 100 类作为测试集 ```shell
ls images | awk -F "." '{if(int($1)<101)print "mv images/"$0" train/"int($1)}' | sh #新建 train 和 test 目录
ls images | awk -F "." '{if(int($1)>100)print "mv images/"$0" test/"int($1)}' | sh mkdir train
mkdir test
#生成 train_list 和 test_list #将数据分成训练集和测试集,前 100 类作为训练集,后 100 类作为测试集
tree -r -i -f train | grep jpg | awk -F "/" '{print $0" "int($2) " "NR}' > train_list.txt ls images | awk -F "." '{if(int($1)<101)print "mv images/"$0" train/"int($1)}' | sh
tree -r -i -f test | grep jpg | awk -F "/" '{print $0" "int($2) " "NR}' > test_list.txt ls images | awk -F "." '{if(int($1)>100)print "mv images/"$0" test/"int($1)}' | sh
```
至此,现在已经得到 `CUB_200_2011` 的训练集(`train` 目录)、测试集(`test` 目录)、`train_list.txt``test_list.txt`
数据处理完毕后,`CUB_200_2011` 中的 `train` 目录下应有如下结构: #生成 train_list 和 test_list
tree -r -i -f train | grep jpg | awk -F "/" '{print $0" "int($2) " "NR}' > train_list.txt
tree -r -i -f test | grep jpg | awk -F "/" '{print $0" "int($2) " "NR}' > test_list.txt
```
``` 至此,现在已经得到 `CUB_200_2011` 的训练集(`train` 目录)、测试集(`test` 目录)、`train_list.txt``test_list.txt`
├── 1
│ ├── Black_Footed_Albatross_0001_796111.jpg
│ ├── Black_Footed_Albatross_0002_55.jpg
...
├── 10
│ ├── Red_Winged_Blackbird_0001_3695.jpg
│ ├── Red_Winged_Blackbird_0005_5636.jpg
...
```
`train_list.txt` 应为 数据处理完毕后,`CUB_200_2011` 中的 `train` 目录下应有如下结构
``` ```
train/99/Ovenbird_0137_92639.jpg 99 1 CUB_200_2011/train/
train/99/Ovenbird_0136_92859.jpg 99 2 ├── 1
train/99/Ovenbird_0135_93168.jpg 99 3 │ ├── Black_Footed_Albatross_0001_796111.jpg
train/99/Ovenbird_0131_92559.jpg 99 4 │ ├── Black_Footed_Albatross_0002_55.jpg
train/99/Ovenbird_0130_92452.jpg 99 5 ...
... ├── 10
``` │ ├── Red_Winged_Blackbird_0001_3695.jpg
其中,分隔符为空格" ", 三列数据的含义分别是训练数据的路径、训练数据的 label 信息、训练数据的 unique id。 │ ├── Red_Winged_Blackbird_0005_5636.jpg
...
```
测试集格式与训练集格式相同。 `train_list.txt` 应为:
**注意** ```
train/99/Ovenbird_0137_92639.jpg 99 1
train/99/Ovenbird_0136_92859.jpg 99 2
train/99/Ovenbird_0135_93168.jpg 99 3
train/99/Ovenbird_0131_92559.jpg 99 4
train/99/Ovenbird_0130_92452.jpg 99 5
...
```
其中,分隔符为空格`" "`, 三列数据的含义分别是`训练数据的相对路径``训练数据的 label 标签``训练数据的 unique id`。测试集格式与训练集格式相同。
* 当 gallery dataset 和 query dataset 相同时,为了去掉检索得到的第一个数据(检索图片本身无须评估),每个数据需要对应一个 unique id,用于后续评测 mAP、recall@1 等指标。关于 gallery dataset 与 query dataset 的解析请参考[图像检索数据集介绍](#图像检索数据集介绍), 关于 mAP、recall@1 等评测指标请参考[图像检索评价指标](#图像检索评价指标) * 构建完毕后返回 `PaddleClas` 根目录
返回 `PaddleClas` 根目录 ```shell
# linux or mac
cd ../../
```
```shell **注意**
# linux or mac
cd ../../
```
<a name="2.2"></a> * 当 gallery dataset 和 query dataset 相同时,为了去掉检索得到的第一个数据(检索图片本身不能出现在gallery中),每个数据需要对应一个 unique id(一般使用从1开始的自然数为unique id,如1,2,3,...),用于后续评测 `mAP``recall@1` 等指标。关于 gallery dataset 与 query dataset 的解析请参考[图像检索数据集介绍](#图像检索数据集介绍), 关于 `mAP``recall@1` 等评测指标请参考[图像检索评价指标](#图像检索评价指标)
### 2.2 特征模型 GPU 上的训练与评估 <a name="2.2"></a>
在基于单卡 GPU 上训练与评估,推荐使用 `tools/train.py``tools/eval.py` 脚本。 ### 2.2 特征提取模型在 GPU 上的训练与评估
PaddleClas 支持使用 VisualDL 可视化训练过程。VisualDL 是飞桨可视化分析工具,以丰富的图表呈现训练参数变化趋势、模型结构、数据样本、高维数据分布等。可帮助用户更清晰直观地理解深度学习模型训练过程及模型结构,进而实现高效的模型优化。更多细节请查看[VisualDL](../others/VisualDL.md) 下面以 MobileNetV1 模型为例,介绍特征提取模型在 GPU 上的训练与评估流程
<a name="2.2.1"></a> <a name="2.2.1"></a>
#### 2.2.1 特征模型训练 #### 2.2.1 特征提取模型训练
准备好配置文件之后,可以使用下面的方式启动图像检索任务的训练。PaddleClas 训练图像检索任务的方法是度量学习,关于度量学习的解析请参考[度量学习](#度量学习) 准备好配置文件之后,可以使用下面的方式启动图像检索任务的训练。PaddleClas 训练图像检索任务的方法是度量学习,关于度量学习的解析请参考[度量学习](#度量学习)
```shell ```shell
# 单卡 GPU # 单卡 GPU
python3 tools/train.py \ python3.7 tools/train.py \
-c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \ -c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \
-o Arch.Backbone.pretrained=True \ -o Arch.Backbone.pretrained=True \
-o Global.device=gpu -o Global.device=gpu
# 多卡 GPU # 多卡 GPU
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python3 -m paddle.distributed.launch tools/train.py \ python3.7 -m paddle.distributed.launch tools/train.py \
-c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \ -c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \
-o Arch.Backbone.pretrained=True \ -o Arch.Backbone.pretrained=True \
-o Global.device=gpu -o Global.device=gpu
``` ```
其中,`-c` 用于指定配置文件的路径,`-o` 用于指定需要修改或者添加的参数,其中 `-o Arch.Backbone.pretrained=True` 表示 Backbone 部分使用预训练模型,此外,`Arch.Backbone.pretrained` 也可以指定具体的模型权重文件的地址,使用时需要换成自己的预训练模型权重文件的路径。`-o Global.device=gpu` 表示使用 GPU 进行训练。如果希望使用 CPU 进行训练,则需要将 `Global.device` 设置为 `cpu` **注**:其中,`-c` 用于指定配置文件的路径,`-o` 用于指定需要修改或者添加的参数,其中 `-o Arch.Backbone.pretrained=True` 表示 Backbone 在训练开始前会加载预训练模型;`-o Arch.Backbone.pretrained` 也可以指定为模型权重文件的路径,使用时换成自己的预训练模型权重文件的路径即可;`-o Global.device=gpu` 表示使用 GPU 进行训练。如果希望使用 CPU 进行训练,则设置 `-o Global.device=cpu`即可
更详细的训练配置,也可以直接修改模型对应的配置文件。具体配置参数参考[配置文档](config_description.md) 更详细的训练配置,也可以直接修改模型对应的配置文件。具体配置参数参考[配置文档](config_description.md)
运行上述命令,可以看到输出日志,示例如下: 运行上述训练命令,可以看到输出日志,示例如下:
``` ```log
... ...
[Train][Epoch 1/50][Avg]CELoss: 6.59110, TripletLossV2: 0.54044, loss: 7.13154 [Train][Epoch 1/50][Avg]CELoss: 6.59110, TripletLossV2: 0.54044, loss: 7.13154
... ...
[Eval][Epoch 1][Avg]recall1: 0.46962, recall5: 0.75608, mAP: 0.21238 [Eval][Epoch 1][Avg]recall1: 0.46962, recall5: 0.75608, mAP: 0.21238
... ...
``` ```
此处配置文件的 Backbone 是 MobileNetV1,如果想使用其他 Backbone,可以重写参数 `Arch.Backbone.name`,比如命令中增加 `-o Arch.Backbone.name={其他 Backbone}`。此外,由于不同模型 `Neck` 部分的输入维度不同,更换 Backbone 后可能需要改写此处的输入大小,改写方式类似替换 Backbone 的名字。
此处配置文件的 Backbone 是 MobileNetV1,如果想使用其他 Backbone,可以重写参数 `Arch.Backbone.name`,比如命令中增加 `-o Arch.Backbone.name={其他 Backbone 的名字}`。此外,由于不同模型 `Neck` 部分的输入维度不同,更换 Backbone 后可能需要改写 `Neck` 的输入大小,改写方式类似替换 Backbone 的名字。
在训练 Loss 部分,此处使用了 [CELoss](../../../ppcls/loss/celoss.py)[TripletLossV2](../../../ppcls/loss/triplet.py),配置文件如下: 在训练 Loss 部分,此处使用了 [CELoss](../../../ppcls/loss/celoss.py)[TripletLossV2](../../../ppcls/loss/triplet.py),配置文件如下:
``` ```yaml
Loss: Loss:
Train: Train:
- CELoss: - CELoss:
...@@ -183,110 +189,113 @@ Loss: ...@@ -183,110 +189,113 @@ Loss:
margin: 0.5 margin: 0.5
``` ```
最终的总 Loss 是所有 Loss 的加权和,其中 weight 定义了特定 Loss 在最终总 Loss 的权重。如果想替换其他 Loss,也可以在配置文件中更改 Loss 字段,目前支持的 Loss 请参考 [Loss](../../../ppcls/loss) 最终的总 Loss 是所有 Loss 的加权和,其中 weight 定义了特定 Loss 在最终总 Loss 的权重。如果想替换其他 Loss,也可以在配置文件中更改 Loss 字段,目前支持的 Loss 请参考 [Loss](../../../ppcls/loss/__init__.py)
<a name="2.2.2"></a> <a name="2.2.2"></a>
#### 2.2.2 特征模型恢复训练 #### 2.2.2 特征提取模型恢复训练
如果训练任务因为其他原因被终止,可以加载断点权重文件,继续训练: 如果训练任务因为其他原因被终止,且训练过程中有保存权重文件,可以加载断点权重文件,继续训练:
```shell ```shell
# 单卡 # 单卡恢复训练
python3 tools/train.py \ python33.7 tools/train.py \
-c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \ -c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \
-o Global.checkpoints="./output/RecModel/epoch_5" \ -o Global.checkpoints="./output/RecModel/epoch_5" \
-o Global.device=gpu -o Global.device=gpu
# 多卡
# 多卡恢复训练
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python3 -m paddle.distributed.launch tools/train.py \ python3.7 -m paddle.distributed.launch tools/train.py \
-c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \ -c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \
-o Global.checkpoints="./output/RecModel/epoch_5" \ -o Global.checkpoints="./output/RecModel/epoch_5" \
-o Global.device=gpu -o Global.device=gpu
``` ```
其中配置文件不需要做任何修改,只需要在继续训练时设置 `Global.checkpoints` 参数即可,表示加载的断点权重文件路径,使用该参数会同时加载保存的断点权重和学习率、优化器等信息。 其中配置文件不需要做任何修改,只需要在继续训练时设置 `Global.checkpoints` 参数即可,表示加载的断点权重文件路径,使用该参数会同时加载保存的断点权重和学习率、优化器等信息。
**注意** **注意**
* `-o Global.checkpoints` 参数无需包含断点权重文件的后缀名,上述训练命令会在训练过程中生成如下所示的断点权重文件,若想从断点 `5` 继续训练,则 `Global.checkpoints` 参数只需设置为 `"./output/RecModel/epoch_5"`,PaddleClas 会自动补充后缀名。 * `-o Global.checkpoints` 后的参数无需包含断点权重文件的后缀名,上述训练命令会在训练过程中生成如下所示的断点权重文件,若想从断点 `epoch_5` 继续训练,则 `Global.checkpoints` 参数只需设置为 `"./output/RecModel/epoch_5"`,PaddleClas 会自动补充后缀名。
```shell `epoch_5.pdparams`所在目录如下所示:
output/
└── RecModel ```log
├── best_model.pdopt output/
├── best_model.pdparams └── RecModel
├── best_model.pdstates ├── best_model.pdopt
├── epoch_1.pdopt ├── best_model.pdparams
├── epoch_1.pdparams ├── best_model.pdstates
├── epoch_1.pdstates ├── epoch_5.pdopt
. ├── epoch_5.pdparams
. ├── epoch_5.pdstates
. .
``` .
.
```
<a name="2.2.3"></a> <a name="2.2.3"></a>
#### 2.2.3 特征模型评估 #### 2.2.3 特征提取模型评估
可以通过以下命令进行模型评估。 可以通过以下命令进行指定模型进行评估。
```bash ```bash
# 单卡 # 单卡评估
python3 tools/eval.py \ python3.7 tools/eval.py \
-c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \ -c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \
-o Global.pretrained_model=./output/RecModel/best_model -o Global.pretrained_model=./output/RecModel/best_model
# 多卡
# 多卡评估
export CUDA_VISIBLE_DEVICES=0,1,2,3 export CUDA_VISIBLE_DEVICES=0,1,2,3
python3 -m paddle.distributed.launch tools/eval.py \ python3.7 -m paddle.distributed.launch tools/eval.py \
-c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \ -c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \
-o Global.pretrained_model=./output/RecModel/best_model -o Global.pretrained_model=./output/RecModel/best_model
``` ```
上述命令将使用 `./configs/quick_start/MobileNetV1_retrieval.yaml` 作为配置文件,对上述训练得到的模型 `./output/RecModel/best_model` 进行评估。你也可以通过更改配置文件中的参数来设置评估,也可以通过 `-o` 参数更新配置,如上所示。 上述命令将使用 `./configs/quick_start/MobileNetV1_retrieval.yaml` 作为配置文件,对上述训练得到的模型 `./output/RecModel/best_model.pdparams` 进行评估。你也可以通过更改配置文件中的参数来设置评估,也可以通过 `-o` 参数更新配置,如上所示。
可配置的部分评估参数说明如下: 可配置的部分评估参数说明如下:
* `Arch.name`:模型名称
* `Global.pretrained_model`:待评估的模型的预训练模型文件路径,不同于 `Global.Backbone.pretrained`,此处的预训练模型是整个模型的权重,而 `Global.Backbone.pretrained` 只是 Backbone 部分的权重。当需要做模型评估时,需要加载整个模型的权重。 * `Global.pretrained_model`:待评估的模型的预训练模型文件路径,不同于 `Global.Backbone.pretrained`,此处的预训练模型是整个模型的权重,而 `Global.Backbone.pretrained` 只是 Backbone 部分的权重。当需要做模型评估时,需要加载整个模型的权重。
* `Metric.Eval`:待评估的指标,默认评估 recall@1、recall@5、mAP。当你不准备评测某一项指标时,可以将对应的试标从配置文件中删除;当你想增加某一项评测指标时,也可以参考 [Metric](../../../ppcls/metric/metrics.py) 部分在配置文件 `Metric.Eval` 中添加相关的指标。 * `Metric.Eval`:待评估的指标,默认评估 `recall@1``recall@5``mAP`。当你不准备评测某一项指标时,可以将对应的试标从配置文件中删除;当你想增加某一项评测指标时,也可以参考 [Metric](../../../ppcls/metric/metrics.py) 部分在配置文件 `Metric.Eval` 中添加相关的指标。
**注意:** **注意:**
* 在加载待评估模型时,需要指定模型文件的路径,但无需包含文件后缀名,PaddleClas 会自动补齐 `.pdparams` 的后缀,如 [2.2.2 特征模型恢复训练](#2.2.2) * 在加载待评估模型时,需要指定模型文件的路径,但无需包含文件后缀名,PaddleClas 会自动补齐 `.pdparams` 的后缀,如 [2.2.2 特征提取模型恢复训练](#2.2.2)
* Metric learning 任务一般不评测 TopkAcc * Metric learning 任务一般不评测 `TopkAcc` 指标
<a name="2.3"></a> <a name="2.3"></a>
### 2.3 特征模型导出 inference 模型 ### 2.3 特征提取模型导出 inference 模型
通过导出 inference 模型,PaddlePaddle 支持使用预测引擎进行预测推理。对训练好的模型进行转换: 通过导出 inference 模型,PaddlePaddle 支持使用预测引擎进行预测推理。对训练好的模型进行转换:
```bash ```bash
python3 tools/export_model.py \ python3.7 tools/export_model.py \
-c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \ -c ./ppcls/configs/quick_start/MobileNetV1_retrieval.yaml \
-o Global.pretrained_model=output/RecModel/best_model \ -o Global.pretrained_model=output/RecModel/best_model \
-o Global.save_inference_dir=./inference -o Global.save_inference_dir=./inference
``` ```
其中,`Global.pretrained_model` 用于指定模型文件路径,该路径仍无需包含模型文件后缀名(如[2.2.2 特征模型恢复训练](#2.2.2))。当执行后,会在当前目录下生成 `./inference` 目录,目录下包含 `inference.pdiparams``inference.pdiparams.info``inference.pdmodel` 文件。`Global.save_inference_dir` 可以指定导出 inference 模型的路径。此处保存的 inference 模型在 embedding 特征层做了截断,即模型最终的输出为 n 维 embedding 特征。 其中,`Global.pretrained_model` 用于指定模型文件路径,该路径仍无需包含模型文件后缀名(如[2.2.2 特征提取模型恢复训练](#2.2.2))。当执行后,会在当前目录下生成 `./inference` 目录,目录下包含 `inference.pdiparams``inference.pdiparams.info``inference.pdmodel` 文件。`Global.save_inference_dir` 可以指定导出 inference 模型文件夹的路径。此处保存的 inference 模型在 embedding 特征层做了截断,即模型的推理输出为 n 维特征。
上述命令将生成模型结构文件(`inference.pdmodel`)和模型权重文件(`inference.pdiparams`),然后可以使用预测引擎进行推理。使用 inference 模型推理的流程可以参考[基于 Python 预测引擎预测推理](../inference_deployment/python_deploy.md) 有了上述命令将生成的模型结构文件(`inference.pdmodel`)和模型权重文件(`inference.pdiparams`),接下来就可以使用预测引擎进行推理。使用 inference 模型推理的流程可以参考[基于 Python 预测引擎预测推理](../inference_deployment/python_deploy.md)
<a name="3"></a> <a name="3"></a>
## 3. 特征检索 ## 3. 特征检索
PaddleClas 图像检索部分目前支持的环境如下: PaddleClas 图像检索部分目前支持的环境如下:
```shell | 操作系统 | 推理硬件 |
└── CPU/单卡 GPU | :------- | :------- |
├── Linux | Linux | CPU/GPU |
├── MacOS | Windows | CPU/GPU |
└── Windows | MacOS | CPU/GPU |
```
此部分使用了 [Faiss](https://github.com/facebookresearch/faiss) 作为检索库,其是一个高效的特征检索及聚类的库。此库中集成了多种相似度检索算法,以满足不同的检索场景。在 PaddleClas 中,支持三种检索算法:
此部分使用了第三方开源库 [Faiss](https://github.com/facebookresearch/faiss) 作为检索工具,它是一个高效的特征检索与聚类的库,集成了多种相似度检索算法,以满足不同的检索场景。PaddleClas 目前支持三种检索算法:
- **HNSW32**: 一种图索引方法。检索精度较高,速度较快。但是特征库只支持添加图像功能,不支持删除图像特征功能。(默认方法) - **HNSW32**: 一种图索引方法。检索精度较高,速度较快。但是特征库只支持添加图像功能,不支持删除图像特征功能。(默认方法)
- **IVF**:倒排索引检索方法。速度较快,但是精度略低。特征库支持增加、删除图像特功能。 - **IVF**:倒排索引检索方法。速度较快,但是精度略低。特征库支持增加、删除图像特功能。
...@@ -296,22 +305,27 @@ PaddleClas 图像检索部分目前支持的环境如下: ...@@ -296,22 +305,27 @@ PaddleClas 图像检索部分目前支持的环境如下:
具体安装方法如下: 具体安装方法如下:
```python ```shell
pip install faiss-cpu==1.7.1post2 python3.7 -m pip install faiss-cpu==1.7.1post2
``` ```
若使用时,不能正常引用,则 `uninstall` 之后,重新 `install`,尤其是 `windows` 下。 若无法正常使用faiss,可以按以下命令先将其卸载,然后重新安装(Windows系统中该问题比较常见)。
```shell
python3.7 -m pip uninstall faiss-cpu
python3.7 -m pip install faiss-cpu==1.7.1post2
```
<a name="4"></a> <a name="4"></a>
## 4. 基础知识 ## 4. 基础知识
图像检索指的是给定一个包含特定实例(例如特定目标、场景、物品等)的查询图像,图像检索旨在从数据库图像中找到包含相同实例的图像。不同于图像分类,图像检索解决的是一个开集问题,训练集中可能不包含被识别的图像的类别。图像检索的整体流程为:首先将图像中表示为一个合适的特征向量,其次,对这些图像的特征向量用欧式距离或余弦距离进行最近邻搜索以找到底库中相似的图像,最后,可以使用一些后处理技术对检索结果进行微调,确定被识别图像的类别等信息。所以,决定一个图像检索算法性能的关键在于图像对应的特征向量的好坏 图像检索指的是给定一个包含特定实例(例如特定目标、场景、物品等)的查询图像,图像检索旨在从数据库图像中找到包含相同实例的图像。不同于图像分类,图像检索解决的是一个开集问题,训练集中可能不包含被识别的图像的类别。图像检索的整体流程为:首先将图像中表示为一个合适的特征向量,其次对这些图像的特征向量用合适的距离度量函数进行最近邻搜索以找到数据库图像中相似的图像,最后,可能会使用一些后处理对检索结果进行进一步优化,得到待识别图像的类别、相似度等信息。所以,图像检索算法性能的关键在于图像提取的特征向量的表示能力强弱
<a name="度量学习"></a> <a name="度量学习"></a>
- 度量学习(Metric Learning) - 度量学习(Metric Learning)
度量学习研究如何在一个特定的任务上学习一个距离函数,使得该距离函数能够帮助基于近邻的算法(kNN、k-means 等)取得较好的性能。深度度量学习(Deep Metric Learning)是度量学习的一种方法,它的目标是学习一个从原始特征到低维稠密的向量空间(嵌入空间,embedding space)的映射,使得同类对象在嵌入空间上使用常用的距离函数(欧氏距离、cosine 距离等)计算的距离比较近,而不同类的对象之间的距离则比较远。深度度量学习在计算机视觉领域取得了非常多的成功的应用,比如人脸识别、商品识别、图像检索、行人重识别等。更详细的介绍请参考[此文档](../algorithm_introduction/metric_learning.md) 度量学习研究如何在一个特定的任务上学习一个距离函数,使得该距离函数能够帮助基于近邻的算法(kNN、k-means 等)取得较好的性能。深度度量学习(Deep Metric Learning)是度量学习的一种方法,它的目标是学习一个从原始特征到低维稠密的向量空间(嵌入空间,embedding space)的映射,使得同类对象在嵌入空间上使用常用的距离函数(欧氏距离、cosine 距离等)计算的距离比较近,而不同类的对象之间的距离则比较远。深度度量学习在计算机视觉领域取得了非常多的成功的应用,比如人脸识别、商品识别、图像检索、行人重识别等。更详细的介绍请参考[此文档](../algorithm_introduction/metric_learning.md)
<a name="图像检索数据集介绍"></a> <a name="图像检索数据集介绍"></a>
...@@ -319,19 +333,17 @@ pip install faiss-cpu==1.7.1post2 ...@@ -319,19 +333,17 @@ pip install faiss-cpu==1.7.1post2
- 训练集合(train dataset):用来训练模型,使模型能够学习该集合的图像特征。 - 训练集合(train dataset):用来训练模型,使模型能够学习该集合的图像特征。
- 底库数据集合(gallery dataset):用来提供图像检索任务中的底库数据,该集合可与训练集或测试集相同,也可以不同,当与训练集相同时,测试集的类别体系应与训练集的类别体系相同。 - 底库数据集合(gallery dataset):用来提供图像检索任务中的底库数据,该集合可与训练集或测试集相同,也可以不同,当与训练集相同时,测试集的类别体系应与训练集的类别体系相同。
- 测试集合(query dataset):用来测试模型的好坏,通常要对测试集的每一张测试图片进行特征提取,之后和底库数据的特征进行距离匹配,得到识别结果,后根据识别结果计算整个测试集的指标。 - 测试集合(query dataset):用来测试模型的检索性能,通常要对测试集的每一张测试图片进行特征提取,之后和底库数据的特征进行距离匹配,得到检索结果,后根据检索结果计算模型在整个测试集上的性能指标。
<a name="图像检索评价指标"></a> <a name="图像检索评价指标"></a>
- 图像检索评价指标 - 图像检索评价指标
<a name="召回率"></a> <a name="召回率"></a>
- 召回率(recall):表示预测为正例且标签为正例的个数 / 标签为正例的个数 - 召回率(recall):表示预测为正例且标签为正例的个数 / 标签为正例的个数
- `recall@k`:检索的 top-k 结果中预测为正例且标签为正例的个数 / 标签为正例的个数
- recall@1:检索的 top-1 中预测正例且标签为正例的个数 / 标签为正例的个数
- recall@5:检索的 top-5 中所有预测正例且标签为正例的个数 / 标签为正例的个数
<a name="平均检索精度"></a> <a name="平均检索精度"></a>
- 平均检索精度(mAP) - 平均检索精度(mAP)
- AP: AP 指的是不同召回率上的正确率的平均值 - `AP`: AP 指的是不同召回率上的正确率的平均值
- mAP: 测试集中所有图片对应的 AP 的平均值 - `mAP`: 测试集中所有图片对应的 AP 的平均值
# 更新日志 # 更新日志
- 2022.4.21 新增 CVPR2022 oral论文 [MixFormer](https://arxiv.org/pdf/2204.02557.pdf) 相关[代码](https://github.com/PaddlePaddle/PaddleClas/pull/1820/files)
- 2021.11.1 发布[PP-ShiTu技术报告](https://arxiv.org/pdf/2111.00775.pdf),新增饮料识别demo。 - 2021.11.1 发布[PP-ShiTu技术报告](https://arxiv.org/pdf/2111.00775.pdf),新增饮料识别demo。
- 2021.10.23 发布轻量级图像识别系统PP-ShiTu,CPU上0.2s即可完成在10w+库的图像识别。[点击这里](../quick_start/quick_start_recognition.md)立即体验。 - 2021.10.23 发布轻量级图像识别系统PP-ShiTu,CPU上0.2s即可完成在10w+库的图像识别。[点击这里](../quick_start/quick_start_recognition.md)立即体验。
- 2021.09.17 发布PP-LCNet系列超轻量骨干网络模型, 在Intel CPU上,单张图像预测速度约5ms,ImageNet-1K数据集上Top1识别准确率达到80.82%,超越ResNet152的模型效果。PP-LCNet的介绍可以参考[论文](https://arxiv.org/pdf/2109.15099.pdf), 或者[PP-LCNet模型介绍](../models/PP-LCNet.md),相关指标和预训练权重可以从 [这里](../algorithm_introduction/ImageNet_models.md)下载。 - 2021.09.17 发布PP-LCNet系列超轻量骨干网络模型, 在Intel CPU上,单张图像预测速度约5ms,ImageNet-1K数据集上Top1识别准确率达到80.82%,超越ResNet152的模型效果。PP-LCNet的介绍可以参考[论文](https://arxiv.org/pdf/2109.15099.pdf), 或者[PP-LCNet模型介绍](../models/PP-LCNet.md),相关指标和预训练权重可以从 [这里](../algorithm_introduction/ImageNet_models.md)下载。
......
...@@ -11,15 +11,15 @@ ...@@ -11,15 +11,15 @@
- [1.2 功能体验](#12-功能体验) - [1.2 功能体验](#12-功能体验)
- [1.2.1 图像检索](#121-图像检索) - [1.2.1 图像检索](#121-图像检索)
- [1.2.2 图像加库](#122-图像加库) - [1.2.2 图像加库](#122-图像加库)
- [1.2.3 库保存](#123-库保存) - [1.2.3 保存检索库](#123-保存检索库)
- [1.2.4 更换检索库](#124-更换检索库) - [1.2.4 初始化检索库](#124-初始化检索库)
- [1.2.5 查看检索库标签](#125-查看检索库标签) - [1.2.5 查看检索库标签](#125-查看检索库标签)
- [1.3 功能详细介绍](#13-功能详细介绍) - [1.3 功能详细介绍](#13-功能详细介绍)
- [1.3.1 图像检索](#131-图像检索) - [1.3.1 图像检索](#131-图像检索)
- [1.3.2 图像加库](#132-图像加库) - [1.3.2 图像加库](#132-图像加库)
- [1.3.3 库保存](#133-库保存) - [1.3.3 保存检索库](#133-保存检索库)
- [1.3.4 更换检索库](#134-更换检索库) - [1.3.4 初始化检索库](#134-初始化检索库)
- [1.3.5 查看检索库的标签](#135-查看检索库的标签) - [1.3.5 查看检索库标签](#135-查看检索库标签)
- [2. PP-ShiTu PC端 demo 快速体验](#2-pp-shitu-pc端-demo-快速体验) - [2. PP-ShiTu PC端 demo 快速体验](#2-pp-shitu-pc端-demo-快速体验)
- [2.1 环境配置](#21-环境配置) - [2.1 环境配置](#21-环境配置)
- [2.2 图像识别体验](#22-图像识别体验) - [2.2 图像识别体验](#22-图像识别体验)
...@@ -43,14 +43,12 @@ ...@@ -43,14 +43,12 @@
可以通过扫描二维码或者[点击链接](https://paddle-imagenet-models-name.bj.bcebos.com/demos/PP-ShiTu.apk)下载并安装APP 可以通过扫描二维码或者[点击链接](https://paddle-imagenet-models-name.bj.bcebos.com/demos/PP-ShiTu.apk)下载并安装APP
**注:** 华为鸿蒙OS 3.0的系统可能会出现无法调用摄像头的情况,建议更换低版本系统或者使用其它安卓机型进行快速体验。 <div align=center><img src="../../images/quick_start/android_demo/PPShiTu_qrcode.png" height="400" width="400"/></div>
<div align=center><img src="../../images/quick_start/android_demo/PPShiTu_qcode.png" height="400" width="400"/></div>
<a name="功能体验"></a> <a name="功能体验"></a>
### 1.2 功能体验 ### 1.2 功能体验
目前 PP-ShiTu android demo 具有图像检索、图像加库、库保存、更换检索库、查看检索库内的标签等基本功能,接下来介绍如何体验这几个功能。 目前 PP-ShiTu android demo 具有图像检索、图像加库、保存检索库、初始化检索库、查看检索库标签等基本功能,接下来介绍如何体验这几个功能。
#### 1.2.1 图像检索 #### 1.2.1 图像检索
点击下方的“拍照识别”按钮<img src="../../images/quick_start/android_demo/paizhaoshibie_100.png" width="25" height="25"/>或者“本地识别”按钮<img src="../../images/quick_start/android_demo/bendishibie_100.png" width="25" height="25"/>,即可拍摄一张图像或者选中一张图像,然后等待几秒钟,APP便会将图像中的主体框标注出来并且在图像下方给出预测的类别以及预测时间等信息。 点击下方的“拍照识别”按钮<img src="../../images/quick_start/android_demo/paizhaoshibie_100.png" width="25" height="25"/>或者“本地识别”按钮<img src="../../images/quick_start/android_demo/bendishibie_100.png" width="25" height="25"/>,即可拍摄一张图像或者选中一张图像,然后等待几秒钟,APP便会将图像中的主体框标注出来并且在图像下方给出预测的类别以及预测时间等信息。
...@@ -66,11 +64,11 @@ ...@@ -66,11 +64,11 @@
#### 1.2.2 图像加库 #### 1.2.2 图像加库
点击上方的“拍照上传”按钮<img src="../../images/quick_start/android_demo/paizhaoshangchuan_100.png" width="25" height="25"/>或者“本地上传”按钮<img src="../../images/quick_start/android_demo/bendishangchuan_100.png" width="25" height="25"/>,即可拍摄一张图像或从图库中选择一张图像,然后再输入这张图像的类别名字(比如`keyboard`),点击“确定”按钮,即可将图片对应的特征向量与标签加入检索库。 点击上方的“拍照上传”按钮<img src="../../images/quick_start/android_demo/paizhaoshangchuan_100.png" width="25" height="25"/>或者“本地上传”按钮<img src="../../images/quick_start/android_demo/bendishangchuan_100.png" width="25" height="25"/>,即可拍摄一张图像或从图库中选择一张图像,然后再输入这张图像的类别名字(比如`keyboard`),点击“确定”按钮,即可将图片对应的特征向量与标签加入检索库。
#### 1.2.3 库保存 #### 1.2.3 保存检索库
点击上方的“保存修改”按钮<img src="../../images/quick_start/android_demo/baocunxiugai_100.png" width="25" height="25"/>再输入希望保存的检索库名字(如填入`database_1`。为了简化体验逻辑,检索向量库文件 `*.index` 与检索标签库文件 `*.txt` 使用相同的文件名),即可将当前库保存到手机内存中 点击上方的“保存修改”按钮<img src="../../images/quick_start/android_demo/baocunxiugai_100.png" width="25" height="25"/>即可将当前库以 `latest` 的库名保存下来
#### 1.2.4 更换检索库 #### 1.2.4 初始化检索库
点击上方的“齿轮”按钮<img src="../../images/quick_start/android_demo/shezhi.png" width="25" height="25"/>,点击“Label Path”一栏,在弹出的选项中选择需要更换的库,然后再点击“Index Path”,在弹出的选项中选择需要更换的类别标签文件(一般一个库对应一个类别标签文件,因此更换检索库意味着需要同时更换检索向量库文件和检索标签文件,否则可能会造成检索结果错误) 点击上方的“初始化 ”按钮<img src="../../images/quick_start/android_demo/reset_100.png" width="25" height="25"/>,即可将当前库初始化为 `original`
#### 1.2.5 查看检索库标签 #### 1.2.5 查看检索库标签
点击“类别查询”按钮<img src="../../images/quick_start/android_demo/leibiechaxun_100.png" width="25" height="25"/>,即可在弹窗中查看。 点击“类别查询”按钮<img src="../../images/quick_start/android_demo/leibiechaxun_100.png" width="25" height="25"/>,即可在弹窗中查看。
...@@ -85,14 +83,14 @@ ...@@ -85,14 +83,14 @@
#### 1.3.2 图像加库 #### 1.3.2 图像加库
在选择好要入库的图片之后,首先会通过检测模型进行主体检测,得到图像中的物体的区域,然后将这块区域裁剪出来输入到识别模型中,得到对应的特征向量,再与用户输入的图像标签一起加入到检索库中。 在选择好要入库的图片之后,首先会通过检测模型进行主体检测,得到图像中的物体的区域,然后将这块区域裁剪出来输入到识别模型中,得到对应的特征向量,再与用户输入的图像标签一起加入到检索库中。
#### 1.3.3 库保存 #### 1.3.3 保存检索库
此功能只需输入希望保存的文件名即可,如输入`database_1`,则会将检索向量库保存为`database_1.index`,检索标签库保存为`database_1.txt` 将当前程序中的库以 `latest` 的库名保存到手机中,并且自动切换到该库上。保存逻辑与一般软件的“另存为”类似。如果当前库已经是 `latest` 则会自动覆盖,如果是 `original` 则会切换到 `latest`
#### 1.3.4 更换检索库 #### 1.3.4 初始化检索库
切换好检索向量库后,需要同时切换与之匹配的检索标签库 初始化库时会自动将检索库和标签库切换成 `original.index``original.txt`,并自动删除手机中的 `latest.index``latest.txt`(如果存在的话)
#### 1.3.5 查看检索库标签 #### 1.3.5 查看检索库标签
可按照[功能体验-查看检索库的标签](#125-查看检索库标签)中说明进行查看,当检索标签库过多(如本demo自带的196类检索标签库)时,可在弹窗中滑动查看。 可按照 [功能体验-查看检索库标签](#125-查看检索库标签) 中说明进行查看,当检索标签库过多(如本demo自带的196类检索标签库)时,可在弹窗中滑动查看。
## 2. PP-ShiTu PC端 demo 快速体验 ## 2. PP-ShiTu PC端 demo 快速体验
...@@ -117,46 +115,28 @@ ...@@ -117,46 +115,28 @@
<a name="轻量级通用主体检测模型与轻量级通用识别模型"></a> <a name="轻量级通用主体检测模型与轻量级通用识别模型"></a>
| 模型简介 | 推荐场景 | inference 模型 | 预测配置文件 | | 模型简介 | 推荐场景 | inference 模型 | 预测配置文件 |
| ------------ | ------------- | -------- | ------- | | ---------------------- | -------- | ----------- | ------------ |
| 轻量级通用主体检测模型 | 通用场景 |[tar 格式下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar) [zip 格式文件下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.zip) | - | | 轻量级通用主体检测模型 | 通用场景 | [tar 格式下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar) \| [zip 格式下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.zip) | - |
| 轻量级通用识别模型 | 通用场景 | [tar 格式下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar) [zip 格式文件下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.zip) | [inference_general.yaml](../../../deploy/configs/inference_general.yaml) | | 轻量级通用识别模型 | 通用场景 | [tar 格式下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/PP-ShiTuV2/general_PPLCNetV2_base_pretrained_v1.0_infer.tar) \| [zip 格式下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/PP-ShiTuV2/general_PPLCNetV2_base_pretrained_v1.0_infer.zip) | [inference_general.yaml](../../../deploy/configs/inference_general.yaml) |
| 轻量级通用识别二值模型 | 检索库很大, 存储受限场景 | [tar 格式下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_binary_v1.0_infer.tar) [zip 格式文件下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_binary_v1.0_infer.zip)| [inference_general_binary.yaml](../../../deploy/configs/inference_general_binary.yaml) |
注意:由于部分解压缩软件在解压上述 `tar` 格式文件时存在问题,建议非命令行用户下载 `zip` 格式文件并解压。`tar` 格式文件建议使用命令 `tar xf xxx.tar` 解压。
本章节 demo 数据下载地址如下: [瓶装饮料数据下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar) 注意:由于部分解压缩软件在解压上述 `tar` 格式文件时存在问题,建议非命令行用户下载 `zip` 格式文件并解压。`tar` 格式文件建议使用命令 `tar -xf xxx.tar` 解压
本章节 demo 数据下载地址如下: [drink_dataset_v2.0.tar(瓶装饮料数据)](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v2.0.tar)
如果希望体验服务端主体检测和各垂类方向的识别模型,可以参考[2.4 服务端识别模型列表](#24-服务端识别模型列表) 如果希望体验服务端主体检测和各垂类方向的识别模型,可以参考 [2.4 服务端识别模型列表](#24-服务端识别模型列表)
**注意** **注意**
1. windows 环境下如果没有安装 wget, 可以按照下面的步骤安装 wget 与 tar 命令,也可以在下载模型时将链接复制到浏览器中下载,并解压放置在相应目录下; linux 或者 macOS 用户可以右键点击,然后复制下载链接,即可通过 `wget` 命令下载。 - windows 环境下如果没有安装 wget, 可以按照下面的步骤安装 wget 与 tar 命令,也可以在下载模型时将链接复制到浏览器中下载,并解压放置在相应目录下; linux 或者 macOS 用户可以右键点击,然后复制下载链接,即可通过 `wget` 命令下载。
2. 如果 macOS 环境下没有安装 `wget` 命令,可以运行下面的命令进行安装。 - 如果 macOS 环境下没有安装 `wget` 命令,可以运行下面的命令进行安装。
```shell
```shell # 安装 homebrew
# 安装 homebrew ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)";
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; # 安装 wget
# 安装 wget brew install wget
brew install wget ```
``` - 如果希望在 windows 环境下安装 wget,可以参考:[链接](https://www.cnblogs.com/jeshy/p/10518062.html);如果希望在 windows 环境中安装 tar 命令,可以参考:[链接](https://www.cnblogs.com/chooperman/p/14190107.html)
4. 如果希望在 windows 环境下安装 wget,可以参考:[链接](https://www.cnblogs.com/jeshy/p/10518062.html);如果希望在 windows 环境中安装 tar 命令,可以参考:[链接](https://www.cnblogs.com/chooperman/p/14190107.html)
* 可以按照下面的命令下载并解压数据与模型
```shell
mkdir models
cd models
# 下载识别 inference 模型并解压
wget {模型下载链接地址} && tar -xf {压缩包的名称}
cd ..
# 下载 demo 数据并解压
wget {数据下载链接地址} && tar -xf {压缩包的名称}
```
<a name="2.2.1"></a> <a name="2.2.1"></a>
...@@ -170,30 +150,30 @@ cd models ...@@ -170,30 +150,30 @@ cd models
# 下载通用检测 inference 模型并解压 # 下载通用检测 inference 模型并解压
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar && tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar && tar -xf picodet_PPLCNet_x2_5_mainbody_lite_v1.0_infer.tar
# 下载识别 inference 模型并解压 # 下载识别 inference 模型并解压
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/general_PPLCNet_x2_5_lite_v1.0_infer.tar && tar -xf general_PPLCNet_x2_5_lite_v1.0_infer.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/PP-ShiTuV2/general_PPLCNetV2_base_pretrained_v1.0_infer.tar && tar -xf general_PPLCNetV2_base_pretrained_v1.0_infer.tar
cd ../ cd ../
# 下载 demo 数据并解压 # 下载 demo 数据并解压
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v1.0.tar && tar -xf drink_dataset_v1.0.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_dataset_v2.0.tar && tar -xf drink_dataset_v2.0.tar
``` ```
解压完毕后,`drink_dataset_v1.0/` 文件夹下应有如下文件结构: 解压完毕后,`drink_dataset_v2.0/` 文件夹下应有如下文件结构:
```log ```log
├── drink_dataset_v1.0/ ├── drink_dataset_v2.0/
│ ├── gallery/ │ ├── gallery/
│ ├── index/ │ ├── index/
│ ├── test_images/ │ ├── index_all/
│ └── test_images/
├── ... ├── ...
``` ```
其中 `gallery` 文件夹中存放的是用于构建索引库的原始图像,`index` 表示基于原始图像构建得到的索引库信息,`test_images` 文件夹中存放的是用于测试识别效果的图像列表。 其中 `gallery` 文件夹中存放的是用于构建索引库的原始图像,`index` 表示基于原始图像构建得到的索引库信息,`test_images` 文件夹中存放的是用于测试识别效果的图像列表。
`models` 文件夹下应有如下文件结构: `models` 文件夹下应有如下文件结构:
```log ```log
├── general_PPLCNet_x2_5_lite_v1.0_infer ├── general_PPLCNetV2_base_pretrained_v1.0_infer
│ ├── inference.pdiparams │ ├── inference.pdiparams
│ ├── inference.pdiparams.info │ ├── inference.pdiparams.info
│ └── inference.pdmodel │ └── inference.pdmodel
...@@ -208,8 +188,9 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_da ...@@ -208,8 +188,9 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/drink_da
如果使用服务端通用识别模型,Demo 数据需要重新提取特征、够建索引,方式如下: 如果使用服务端通用识别模型,Demo 数据需要重新提取特征、够建索引,方式如下:
```shell ```shell
# 下面是使用下载的服务端商品识别模型进行索引库构建 python3.7 python/build_gallery.py \
python3.7 python/build_gallery.py -c configs/inference_general.yaml -o Global.rec_inference_model_dir=./models/general_PPLCNet_x2_5_lite_v1.0_infer -c configs/inference_general.yaml \
-o Global.rec_inference_model_dir=./models/general_PPLCNetV2_base_pretrained_v1.0_infer
``` ```
<a name="瓶装饮料识别与检索"></a> <a name="瓶装饮料识别与检索"></a>
...@@ -221,7 +202,7 @@ python3.7 python/build_gallery.py -c configs/inference_general.yaml -o Global.re ...@@ -221,7 +202,7 @@ python3.7 python/build_gallery.py -c configs/inference_general.yaml -o Global.re
注意,此部分使用了 `faiss` 作为检索库,安装方法如下: 注意,此部分使用了 `faiss` 作为检索库,安装方法如下:
```python ```python
pip install faiss-cpu==1.7.1post2 python3.7 -m pip install faiss-cpu==1.7.1post2
``` ```
若使用时,不能正常引用,则 `uninstall` 之后,重新 `install`,尤其是 windows 下。 若使用时,不能正常引用,则 `uninstall` 之后,重新 `install`,尤其是 windows 下。
...@@ -230,31 +211,32 @@ pip install faiss-cpu==1.7.1post2 ...@@ -230,31 +211,32 @@ pip install faiss-cpu==1.7.1post2
##### 2.2.2.1 识别单张图像 ##### 2.2.2.1 识别单张图像
运行下面的命令,对图像 `./drink_dataset_v1.0/test_images/nongfu_spring.jpeg` 进行识别与检索 运行下面的命令,对图像 `./drink_dataset_v2.0/test_images/100.jpeg` 进行识别与检索
待检索图像如下所示
![](../../images/recognition/drink_data_demo/test_images/100.jpeg)
```shell ```shell
# 使用下面的命令使用 GPU 进行预测 # 使用下面的命令使用 GPU 进行预测
python3.7 python/predict_system.py -c configs/inference_general.yaml python3.7 python/predict_system.py -c configs/inference_general.yaml
# 使用下面的命令使用 CPU 进行预测 # 使用下面的命令使用 CPU 进行预测
python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.use_gpu=False python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.use_gpu=False
``` ```
待检索图像如下所示。
![](../../images/recognition/drink_data_demo/test_images/nongfu_spring.jpeg)
最终输出结果如下。 最终输出结果如下。
```log ```log
[{'bbox': [244, 49, 509, 964], 'rec_docs': '农夫山泉-饮用天然水', 'rec_scores': 0.7585664}] [{'bbox': [437, 71, 660, 728], 'rec_docs': '元气森林', 'rec_scores': 0.7740249}, {'bbox': [221, 72, 449, 701], 'rec_docs': '元气森林', 'rec_scores': 0.6950992}, {'bbox': [794, 104, 979, 652], 'rec_docs': '元气森林', 'rec_scores': 0.6305153}]
``` ```
其中 `bbox` 表示检测出的主体所在位置,`rec_docs` 表示索引库中与检测框最为相似的类别,`rec_scores` 表示对应的置信度。 其中 `bbox` 表示检测出的主体所在位置,`rec_docs` 表示索引库中与检测框最为相似的类别,`rec_scores` 表示对应的置信度。
检测的可视化结果保存在 `output` 文件夹下,对于本张图像,识别结果可视化如下所示。 检测的可视化结果默认保存在 `output` 文件夹下,对于本张图像,识别结果可视化如下所示。
![](../../images/recognition/drink_data_demo/output/nongfu_spring.jpeg) ![](../../images/recognition/drink_data_demo/output/100.jpeg)
<a name="基于文件夹的批量识别"></a> <a name="基于文件夹的批量识别"></a>
...@@ -265,62 +247,61 @@ python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.u ...@@ -265,62 +247,61 @@ python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.u
```shell ```shell
# 使用下面的命令使用 GPU 进行预测,如果希望使用 CPU 预测,可以在命令后面添加 -o Global.use_gpu=False # 使用下面的命令使用 GPU 进行预测,如果希望使用 CPU 预测,可以在命令后面添加 -o Global.use_gpu=False
python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.infer_imgs="./drink_dataset_v1.0/test_images/" python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.infer_imgs="./drink_dataset_v2.0/test_images/"
``` ```
终端中会输出该文件夹内所有图像的识别结果,如下所示。 终端中会输出该文件夹内所有图像的识别结果,如下所示。
```log ```log
... ...
[{'bbox': [345, 95, 524, 586], 'rec_docs': '红牛-强化型', 'rec_scores': 0.80164653}] [{'bbox': [0, 0, 600, 600], 'rec_docs': '红牛-强化型', 'rec_scores': 0.74081033}]
Inference: 23.43583106994629 ms per batch image Inference: 120.39852142333984 ms per batch image
[{'bbox': [233, 0, 372, 436], 'rec_docs': '康师傅矿物质水', 'rec_scores': 0.72513914}] [{'bbox': [0, 0, 514, 436], 'rec_docs': '康师傅矿物质水', 'rec_scores': 0.6918598}]
Inference: 117.95639991760254 ms per batch image Inference: 32.045602798461914 ms per batch image
[{'bbox': [138, 40, 573, 1198], 'rec_docs': '乐虎功能饮料', 'rec_scores': 0.7855944}] [{'bbox': [138, 40, 573, 1198], 'rec_docs': '乐虎功能饮料', 'rec_scores': 0.68214047}]
Inference: 22.172927856445312 ms per batch image Inference: 113.41428756713867 ms per batch image
[{'bbox': [328, 7, 467, 272], 'rec_docs': '脉动', 'rec_scores': 0.5829516}] [{'bbox': [328, 7, 467, 272], 'rec_docs': '脉动', 'rec_scores': 0.60406065}]
Inference: 118.08514595031738 ms per batch image Inference: 122.04337120056152 ms per batch image
[{'bbox': [242, 82, 498, 726], 'rec_docs': '味全_每日C', 'rec_scores': 0.75581443}] [{'bbox': [242, 82, 498, 726], 'rec_docs': '味全_每日C', 'rec_scores': 0.5428652}]
Inference: 150.06470680236816 ms per batch image Inference: 37.95266151428223 ms per batch image
[{'bbox': [437, 71, 660, 728], 'rec_docs': '元气森林', 'rec_scores': 0.8478892}, {'bbox': [221, 72, 449, 701], 'rec_docs': '元气森林', 'rec_scores': 0.6790612}, {'bbox': [794, 104, 979, 652], 'rec_docs': '元气森林', 'rec_scores': 0.6292581}] [{'bbox': [437, 71, 660, 728], 'rec_docs': '元气森林', 'rec_scores': 0.7740249}, {'bbox': [221, 72, 449, 701], 'rec_docs': '元气森林', 'rec_scores': 0.6950992}, {'bbox': [794, 104, 979, 652], 'rec_docs': '元气森林', 'rec_scores': 0.6305153}]
... ...
``` ```
所有图像的识别结果可视化图像也保存在 `output` 文件夹内。 所有图像的识别结果可视化图像也保存在 `output` 文件夹内。
更多地,可以通过修改 `Global.rec_inference_model_dir` 字段来更改识别 inference 模型的路径,通过修改 `IndexProcess.index_dir` 字段来更改索引库索引的路径。 更多地,可以通过修改 `Global.rec_inference_model_dir` 字段来更改识别 inference 模型的路径,通过修改 `IndexProcess.index_dir` 字段来更改索引库索引的路径。
<a name="未知类别的图像识别体验"></a> <a name="未知类别的图像识别体验"></a>
### 2.3 未知类别的图像识别体验 ### 2.3 未知类别的图像识别体验
对图像 `./drink_dataset_v1.0/test_images/mosilian.jpeg` 进行识别,命令如下 对图像 `./drink_dataset_v2.0/test_images/mosilian.jpeg` 进行识别
```shell
# 使用下面的命令使用 GPU 进行预测,如果希望使用 CPU 预测,可以在命令后面添加 -o Global.use_gpu=False
python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.infer_imgs="./drink_dataset_v1.0/test_images/mosilian.jpeg"
```
待检索图像如下所示。 待检索图像如下
![](../../images/recognition/drink_data_demo/test_images/mosilian.jpeg) ![](../../images/recognition/drink_data_demo/test_images/mosilian.jpeg)
执行如下识别命令
```shell
# 使用下面的命令使用 GPU 进行预测,如果希望使用 CPU 预测,可以在命令后面添加 -o Global.use_gpu=False
python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.infer_imgs="./drink_dataset_v2.0/test_images/mosilian.jpeg"
```
输出结果为空。 可以发现输出结果为空
由于默认的索引库中不包含对应的索引信息,所以这里的识别结果有误,此时我们可以通过构建新的索引库的方式,完成未知类别的图像识别。 由于默认的索引库中不包含对应的索引信息,所以这里的识别结果有误,此时我们可以通过构建新的索引库的方式,完成未知类别的图像识别。
当索引库中的图像无法覆盖我们实际识别的场景时,即在预测未知类别的图像时,我们需要将对应类别的相似图像添加到索引库中,从而完成对未知类别的图像识别,这一过程是不需要重新训练的 当索引库中的图像无法覆盖我们实际识别的场景时,即识别未知类别的图像前,我们需要将该未知类别的相似图像(至少一张)添加到索引库中,从而完成对未知类别的图像识别。这一过程不需要重新训练模型,以识别 `mosilian.jpeg` 为例,只需按以下步骤重新构建新的索引库即可
<a name="准备新的数据与标签"></a> <a name="准备新的数据与标签"></a>
#### 2.3.1 准备新的数据与标签 #### 2.3.1 准备新的数据与标签
首先需要将与待检索图像相似的图像列表拷贝到索引库原始图像的文件夹。这里 PaddleClas 已经将所有的图像数据都放在文件夹 `drink_dataset_v1.0/gallery/` 中。 首先需要将与待检索图像相似的图像列表拷贝到索引库原始图像的文件夹中。这里 PaddleClas 已经将所有的图像数据都放在文件夹 `drink_dataset_v2.0/gallery/` 中。
然后需要编辑记录了图像路径和标签信息的文本文件,这里 PaddleClas 将更正后的标签信息文件放在了 `drink_dataset_v1.0/gallery/drink_label_all.txt` 文件中。可以与默认的 `drink_dataset_v1.0/gallery/drink_label.txt` 标签文件进行对比,添加了光明和三元系列牛奶的索引图像。
然后需要编辑记录了图像路径和标签信息的文本文件,这里 PaddleClas 将更新后的标签信息文件放在了 `drink_dataset_v2.0/gallery/drink_label_all.txt` 文件中。与原始的 `drink_dataset_v2.0/gallery/drink_label.txt` 标签文件进行对比,可以发现新增了光明和三元系列牛奶的索引图像。
每一行的文本中,第一个字段表示图像的相对路径,第二个字段表示图像对应的标签信息,中间用 `\t` 键分隔开(注意:有些编辑器会将 `tab` 自动转换为 `空格`,这种情况下会导致文件解析报错)。 每一行的文本中,第一个字段表示图像的相对路径,第二个字段表示图像对应的标签信息,中间用 `\t` 键分隔开(注意:有些编辑器会将 `tab` 自动转换为 `空格`,这种情况下会导致文件解析报错)。
...@@ -328,64 +309,64 @@ python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.i ...@@ -328,64 +309,64 @@ python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.i
#### 2.3.2 建立新的索引库 #### 2.3.2 建立新的索引库
使用下面的命令构建 `index` 索引,加速识别后的检索过程 使用下面的命令构建新的索引库 `index_all`
```shell ```shell
python3.7 python/build_gallery.py -c configs/inference_general.yaml -o IndexProcess.data_file="./drink_dataset_v1.0/gallery/drink_label_all.txt" -o IndexProcess.index_dir="./drink_dataset_v1.0/index_all" python3.7 python/build_gallery.py -c configs/inference_general.yaml -o IndexProcess.data_file="./drink_dataset_v2.0/gallery/drink_label_all.txt" -o IndexProcess.index_dir="./drink_dataset_v2.0/index_all"
``` ```
最终新的索引信息保存在文件夹 `./drink_dataset_v1.0/index_all`。具体 `yaml` 请参考[向量检索文档](../image_recognition_pipeline/vector_search.md) 最终构建完毕的新的索引库保存在文件夹 `./drink_dataset_v2.0/index_all`。具体 `yaml` 请参考[向量检索文档](../image_recognition_pipeline/vector_search.md)
<a name="基于新的索引库的图像识别"></a> <a name="基于新的索引库的图像识别"></a>
#### 2.3.3 基于新的索引库的图像识别 #### 2.3.3 基于新的索引库的图像识别
使用新的索引库,对上述图像进行识别,运行命令如下。 使用新的索引库,重新对 `mosilian.jpeg` 图像进行识别,运行命令如下。
```shell ```shell
# 使用下面的命令使用 GPU 进行预测,如果希望使用 CPU 预测,可以在命令后面添加 -o Global.use_gpu=False # 使用下面的命令使用 GPU 进行预测,如果希望使用 CPU 预测,可以在命令后面添加 -o Global.use_gpu=False
python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.infer_imgs="././drink_dataset_v1.0/test_images/mosilian.jpeg" -o IndexProcess.index_dir="./drink_dataset_v1.0/index_all" python3.7 python/predict_system.py -c configs/inference_general.yaml -o Global.infer_imgs="./drink_dataset_v2.0/test_images/mosilian.jpeg" -o IndexProcess.index_dir="./drink_dataset_v2.0/index_all"
``` ```
输出结果如下。 输出结果如下。
```log ```log
[{'bbox': [396, 553, 508, 621], 'rec_docs': '光明_莫斯利安', 'rec_scores': 0.5921005}] [{'bbox': [290, 297, 564, 919], 'rec_docs': '光明_莫斯利安', 'rec_scores': 0.59137374}]
``` ```
最终识别结果为`光明_莫斯利安`,识别正确,识别结果可视化如下所示。 最终识别结果为 `光明_莫斯利安` ,识别正确,识别结果可视化如下所示。
![](../../images/recognition/drink_data_demo/output/mosilian.jpeg) ![](../../images/recognition/drink_data_demo/output/mosilian.jpeg)
<a name="5"></a> <a name="5"></a>
### 2.4 服务端识别模型列表 ### 2.4 服务端识别模型列表
目前,我们更推荐您使用[轻量级通用主体检测模型与轻量级通用识别模型](#轻量级通用主体检测模型与轻量级通用识别模型),以获得更好的测试结果。但是如果您希望体验服务端识别模型,服务器端通用主体检测模型与各方向识别模型、测试数据下载地址以及对应的配置文件地址如下。 目前,我们更推荐您使用[轻量级通用主体检测模型与轻量级通用识别模型](#轻量级通用主体检测模型与轻量级通用识别模型),以获得更好的测试结果。但是如果您希望体验服务端识别模型,服务器端通用主体检测模型与各方向识别模型、测试数据下载地址以及对应的配置文件地址如下。
| 模型简介 | 推荐场景 | inference 模型 | 预测配置文件 | | 模型简介 | 推荐场景 | inference 模型 | 预测配置文件 |
| ------------ | ------------- | -------- | ------- | | ---------------- | -------------- | ------------ | ----------- |
| 通用主体检测模型 | 通用场景 |[模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar) | - | | 通用主体检测模型 | 通用场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar) | - |
| Logo 识别模型 | Logo 场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/logo_rec_ResNet50_Logo3K_v1.0_infer.tar) | [inference_logo.yaml](../../../deploy/configs/inference_logo.yaml) | | Logo 识别模型 | Logo 场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/logo_rec_ResNet50_Logo3K_v1.0_infer.tar) | [inference_logo.yaml](../../../deploy/configs/inference_logo.yaml) |
| 动漫人物识别模型 | 动漫人物场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/cartoon_rec_ResNet50_iCartoon_v1.0_infer.tar) | [inference_cartoon.yaml](../../../deploy/configs/inference_cartoon.yaml) | | 动漫人物识别模型 | 动漫人物场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/cartoon_rec_ResNet50_iCartoon_v1.0_infer.tar) | [inference_cartoon.yaml](../../../deploy/configs/inference_cartoon.yaml) |
| 车辆细分类模型 | 车辆场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/vehicle_cls_ResNet50_CompCars_v1.0_infer.tar) | [inference_vehicle.yaml](../../../deploy/configs/inference_vehicle.yaml) | | 车辆细分类模型 | 车辆场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/vehicle_cls_ResNet50_CompCars_v1.0_infer.tar) | [inference_vehicle.yaml](../../../deploy/configs/inference_vehicle.yaml) |
| 商品识别模型 | 商品场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/product_ResNet50_vd_aliproduct_v1.0_infer.tar) | [inference_product.yaml](../../../deploy/configs/inference_product.yaml) | | 商品识别模型 | 商品场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/product_ResNet50_vd_aliproduct_v1.0_infer.tar) | [inference_product.yaml](../../../deploy/configs/inference_product.yaml) |
| 车辆 ReID 模型 | 车辆 ReID 场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/vehicle_reid_ResNet50_VERIWild_v1.0_infer.tar) | [inference_vehicle.yaml](../../../deploy/configs/inference_vehicle.yaml) | | 车辆 ReID 模型 | 车辆 ReID 场景 | [模型下载链接](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/vehicle_reid_ResNet50_VERIWild_v1.0_infer.tar) | [inference_vehicle.yaml](../../../deploy/configs/inference_vehicle.yaml) |
可以按照如下命令下载上述模型到 `deploy/models` 文件夹中,以供识别任务使用
```shell ```shell
cd PaddleClas/deploy/ cd ./deploy
mkdir -p models mkdir -p models
```
```shell
cd ./models cd ./models
# 下载通用主体检测模型并解压 # 下载服务器端通用主体检测模型并解压
wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar && tar -xf ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/models/inference/ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar && tar -xf ppyolov2_r50vd_dcn_mainbody_v1.0_infer.tar
# 下载识别模型并解压 # 下载通用识别模型并解压
wget {识别模型下载链接地址} && tar -xf {压缩包的名称} wget {识别模型下载链接地址} && tar -xf {压缩包的名称}
``` ```
使用如下命令下载各方向识别模型的测试数据: 然后使用如下命令下载各个识别场景的测试数据:
```shell ```shell
# 回到 deploy 目录下 # 回到 deploy 目录下
...@@ -409,6 +390,6 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognit ...@@ -409,6 +390,6 @@ wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/rec/data/recognit
├── ... ├── ...
``` ```
按照上述步骤下载模型和测试数据后,您可以进行相关方向识别模型的测试。 按照上述步骤下载模型和测试数据后,您可以重新建立索引库,并进行相关方向识别模型的测试。
* 更多关于主体检测的介绍可以参考:[主体检测教程文档](../image_recognition_pipeline/mainbody_detection.md);关于特征提取的介绍可以参考:[特征提取教程文档](../image_recognition_pipeline/feature_extraction.md);关于向量检索的介绍可以参考:[向量检索教程文档](../image_recognition_pipeline/vector_search.md) * 更多关于主体检测的介绍可以参考:[主体检测教程文档](../image_recognition_pipeline/mainbody_detection.md);关于特征提取的介绍可以参考:[特征提取教程文档](../image_recognition_pipeline/feature_extraction.md);关于向量检索的介绍可以参考:[向量检索教程文档](../image_recognition_pipeline/vector_search.md)
...@@ -23,7 +23,7 @@ Arch: ...@@ -23,7 +23,7 @@ Arch:
infer_output_key: features infer_output_key: features
infer_add_softmax: False infer_add_softmax: False
Backbone: Backbone:
name: PPLCNet_x2_5 name: PPLCNet_x2_5
pretrained: True pretrained: True
use_ssld: True use_ssld: True
...@@ -34,7 +34,7 @@ Arch: ...@@ -34,7 +34,7 @@ Arch:
embedding_size: 1280 embedding_size: 1280
class_num: 512 class_num: 512
Head: Head:
name: ArcMargin name: ArcMargin
embedding_size: 512 embedding_size: 512
class_num: 185341 class_num: 185341
margin: 0.2 margin: 0.2
...@@ -57,10 +57,9 @@ Optimizer: ...@@ -57,10 +57,9 @@ Optimizer:
learning_rate: 0.04 learning_rate: 0.04
warmup_epoch: 5 warmup_epoch: 5
regularizer: regularizer:
name: 'L2' name: "L2"
coeff: 0.00001 coeff: 0.00001
# data loader for train and eval # data loader for train and eval
DataLoader: DataLoader:
Train: Train:
...@@ -80,7 +79,7 @@ DataLoader: ...@@ -80,7 +79,7 @@ DataLoader:
scale: 1.0/255.0 scale: 1.0/255.0
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
sampler: sampler:
name: DistributedBatchSampler name: DistributedBatchSampler
...@@ -93,7 +92,7 @@ DataLoader: ...@@ -93,7 +92,7 @@ DataLoader:
Eval: Eval:
Query: Query:
dataset: dataset:
name: VeriWild name: VeriWild
image_root: ./dataset/Aliproduct/ image_root: ./dataset/Aliproduct/
cls_label_path: ./dataset/Aliproduct/val_list.txt cls_label_path: ./dataset/Aliproduct/val_list.txt
...@@ -107,7 +106,7 @@ DataLoader: ...@@ -107,7 +106,7 @@ DataLoader:
scale: 0.00392157 scale: 0.00392157
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
sampler: sampler:
name: DistributedBatchSampler name: DistributedBatchSampler
batch_size: 64 batch_size: 64
...@@ -118,7 +117,7 @@ DataLoader: ...@@ -118,7 +117,7 @@ DataLoader:
use_shared_memory: True use_shared_memory: True
Gallery: Gallery:
dataset: dataset:
name: VeriWild name: VeriWild
image_root: ./dataset/Aliproduct/ image_root: ./dataset/Aliproduct/
cls_label_path: ./dataset/Aliproduct/val_list.txt cls_label_path: ./dataset/Aliproduct/val_list.txt
...@@ -132,7 +131,7 @@ DataLoader: ...@@ -132,7 +131,7 @@ DataLoader:
scale: 0.00392157 scale: 0.00392157
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
sampler: sampler:
name: DistributedBatchSampler name: DistributedBatchSampler
batch_size: 64 batch_size: 64
...@@ -146,3 +145,4 @@ Metric: ...@@ -146,3 +145,4 @@ Metric:
Eval: Eval:
- Recallk: - Recallk:
topk: [1, 5] topk: [1, 5]
- mAP: {}
...@@ -20,8 +20,8 @@ Arch: ...@@ -20,8 +20,8 @@ Arch:
name: RecModel name: RecModel
infer_output_key: features infer_output_key: features
infer_add_softmax: False infer_add_softmax: False
Backbone: Backbone:
name: MobileNetV1 name: MobileNetV1
pretrained: False pretrained: False
BackboneStopLayer: BackboneStopLayer:
...@@ -31,12 +31,12 @@ Arch: ...@@ -31,12 +31,12 @@ Arch:
embedding_size: 1024 embedding_size: 1024
class_num: 512 class_num: 512
Head: Head:
name: ArcMargin name: ArcMargin
embedding_size: 512 embedding_size: 512
class_num: 101 class_num: 101
margin: 0.15 margin: 0.15
scale: 30 scale: 30
# loss function config for traing/eval process # loss function config for traing/eval process
Loss: Loss:
Train: Train:
...@@ -60,7 +60,7 @@ Optimizer: ...@@ -60,7 +60,7 @@ Optimizer:
verbose: False verbose: False
last_epoch: -1 last_epoch: -1
regularizer: regularizer:
name: 'L2' name: "L2"
coeff: 0.0005 coeff: 0.0005
# data loader for train and eval # data loader for train and eval
...@@ -82,7 +82,7 @@ DataLoader: ...@@ -82,7 +82,7 @@ DataLoader:
scale: 0.00392157 scale: 0.00392157
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
- RandomErasing: - RandomErasing:
EPSILON: 0.5 EPSILON: 0.5
sl: 0.02 sl: 0.02
...@@ -98,10 +98,10 @@ DataLoader: ...@@ -98,10 +98,10 @@ DataLoader:
loader: loader:
num_workers: 4 num_workers: 4
use_shared_memory: True use_shared_memory: True
Eval: Eval:
Query: Query:
dataset: dataset:
name: VeriWild name: VeriWild
image_root: ./dataset/CUB_200_2011/ image_root: ./dataset/CUB_200_2011/
cls_label_path: ./dataset/CUB_200_2011/test_list.txt cls_label_path: ./dataset/CUB_200_2011/test_list.txt
...@@ -115,7 +115,7 @@ DataLoader: ...@@ -115,7 +115,7 @@ DataLoader:
scale: 0.00392157 scale: 0.00392157
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
sampler: sampler:
name: DistributedBatchSampler name: DistributedBatchSampler
batch_size: 64 batch_size: 64
...@@ -126,7 +126,7 @@ DataLoader: ...@@ -126,7 +126,7 @@ DataLoader:
use_shared_memory: True use_shared_memory: True
Gallery: Gallery:
dataset: dataset:
name: VeriWild name: VeriWild
image_root: ./dataset/CUB_200_2011/ image_root: ./dataset/CUB_200_2011/
cls_label_path: ./dataset/CUB_200_2011/test_list.txt cls_label_path: ./dataset/CUB_200_2011/test_list.txt
...@@ -140,7 +140,7 @@ DataLoader: ...@@ -140,7 +140,7 @@ DataLoader:
scale: 1.0/255.0 scale: 1.0/255.0
mean: [0.485, 0.456, 0.406] mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225] std: [0.229, 0.224, 0.225]
order: '' order: ""
sampler: sampler:
name: DistributedBatchSampler name: DistributedBatchSampler
batch_size: 64 batch_size: 64
...@@ -155,4 +155,3 @@ Metric: ...@@ -155,4 +155,3 @@ Metric:
- Recallk: - Recallk:
topk: [1, 5] topk: [1, 5]
- mAP: {} - mAP: {}
...@@ -72,7 +72,12 @@ def build_dataloader(config, mode, device, use_dali=False, seed=None): ...@@ -72,7 +72,12 @@ def build_dataloader(config, mode, device, use_dali=False, seed=None):
# build dataset # build dataset
if use_dali: if use_dali:
from ppcls.data.dataloader.dali import dali_dataloader from ppcls.data.dataloader.dali import dali_dataloader
return dali_dataloader(config, mode, paddle.device.get_device(), seed) return dali_dataloader(
config,
mode,
paddle.device.get_device(),
num_threads=config[mode]['loader']["num_workers"],
seed=seed)
class_num = config.get("class_num", None) class_num = config.get("class_num", None)
config_dataset = config[mode]['dataset'] config_dataset = config[mode]['dataset']
......
...@@ -143,7 +143,7 @@ class HybridValPipe(Pipeline): ...@@ -143,7 +143,7 @@ class HybridValPipe(Pipeline):
return self.epoch_size("Reader") return self.epoch_size("Reader")
def dali_dataloader(config, mode, device, seed=None): def dali_dataloader(config, mode, device, num_threads=4, seed=None):
assert "gpu" in device, "gpu training is required for DALI" assert "gpu" in device, "gpu training is required for DALI"
device_id = int(device.split(':')[1]) device_id = int(device.split(':')[1])
config_dataloader = config[mode] config_dataloader = config[mode]
...@@ -248,6 +248,7 @@ def dali_dataloader(config, mode, device, seed=None): ...@@ -248,6 +248,7 @@ def dali_dataloader(config, mode, device, seed=None):
device_id, device_id,
shard_id, shard_id,
num_shards, num_shards,
num_threads=num_threads,
seed=seed + shard_id, seed=seed + shard_id,
pad_output=pad_output, pad_output=pad_output,
output_dtype=output_dtype) output_dtype=output_dtype)
...@@ -270,6 +271,7 @@ def dali_dataloader(config, mode, device, seed=None): ...@@ -270,6 +271,7 @@ def dali_dataloader(config, mode, device, seed=None):
device_id=device_id, device_id=device_id,
shard_id=0, shard_id=0,
num_shards=1, num_shards=1,
num_threads=num_threads,
seed=seed, seed=seed,
pad_output=pad_output, pad_output=pad_output,
output_dtype=output_dtype) output_dtype=output_dtype)
...@@ -298,6 +300,7 @@ def dali_dataloader(config, mode, device, seed=None): ...@@ -298,6 +300,7 @@ def dali_dataloader(config, mode, device, seed=None):
device_id=device_id, device_id=device_id,
shard_id=shard_id, shard_id=shard_id,
num_shards=num_shards, num_shards=num_shards,
num_threads=num_threads,
pad_output=pad_output, pad_output=pad_output,
output_dtype=output_dtype) output_dtype=output_dtype)
else: else:
...@@ -311,6 +314,7 @@ def dali_dataloader(config, mode, device, seed=None): ...@@ -311,6 +314,7 @@ def dali_dataloader(config, mode, device, seed=None):
mean, mean,
std, std,
device_id=device_id, device_id=device_id,
num_threads=num_threads,
pad_output=pad_output, pad_output=pad_output,
output_dtype=output_dtype) output_dtype=output_dtype)
pipe.build() pipe.build()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册