Browse Source

merge develop

will-jl944 4 years ago
parent
commit
b3bd512d18
48 changed files with 446 additions and 1625 deletions
  1. 1 1
      PaddleDetection
  2. 2 1
      deploy/cpp/README.md
  3. 6 0
      deploy/cpp/docs/compile/openvino/README.md
  4. 112 0
      deploy/cpp/docs/compile/openvino/openvino_linux.md
  5. 144 0
      deploy/cpp/docs/compile/openvino/openvino_windows.md
  6. 13 12
      deploy/cpp/docs/demo/model_infer.md
  7. 6 5
      paddlex/cv/models/base.py
  8. 50 19
      paddlex/cv/models/detector.py
  9. 1 3
      paddlex/cv/models/load_model.py
  10. 4 0
      paddlex/cv/models/segmenter.py
  11. 6 6
      paddlex/cv/models/utils/infer_nets.py
  12. 12 2
      paddlex/cv/transforms/batch_operators.py
  13. 61 27
      paddlex/deploy.py
  14. 26 16
      paddlex/utils/utils.py
  15. 1 97
      static/docs/examples/industrial_quality_inspection/README.md
  16. 0 97
      static/docs/examples/industrial_quality_inspection/accuracy_improvement.md
  17. 0 14
      static/docs/examples/industrial_quality_inspection/dataset.md
  18. 0 116
      static/docs/examples/industrial_quality_inspection/gpu_solution.md
  19. 0 102
      static/docs/examples/industrial_quality_inspection/tp_fp_list.md
  20. 1 97
      static/examples/industrial_quality_inspection/README.md
  21. 0 93
      static/examples/industrial_quality_inspection/accuracy_improvement.md
  22. 0 214
      static/examples/industrial_quality_inspection/cal_tp_fp.py
  23. 0 160
      static/examples/industrial_quality_inspection/compare.py
  24. 0 14
      static/examples/industrial_quality_inspection/dataset.md
  25. 0 26
      static/examples/industrial_quality_inspection/error_analysis.py
  26. 0 114
      static/examples/industrial_quality_inspection/gpu_solution.md
  27. BIN
      static/examples/industrial_quality_inspection/image/after_clahe.png
  28. BIN
      static/examples/industrial_quality_inspection/image/allclasses_analysis_example.png
  29. BIN
      static/examples/industrial_quality_inspection/image/before_clahe.png
  30. BIN
      static/examples/industrial_quality_inspection/image/budaodian_analysis_example.png
  31. BIN
      static/examples/industrial_quality_inspection/image/cahua_analysis_example.png
  32. BIN
      static/examples/industrial_quality_inspection/image/compare_budaodian-116.jpg
  33. BIN
      static/examples/industrial_quality_inspection/image/image-level_tp_fp.png
  34. BIN
      static/examples/industrial_quality_inspection/image/jiaoweiloudi_analysis_example.png
  35. BIN
      static/examples/industrial_quality_inspection/image/jupi_analysis_example.png
  36. BIN
      static/examples/industrial_quality_inspection/image/loudi_analysis_example.png
  37. BIN
      static/examples/industrial_quality_inspection/image/penliu_analysis_example.png
  38. BIN
      static/examples/industrial_quality_inspection/image/qikeng_analysis_example.png
  39. BIN
      static/examples/industrial_quality_inspection/image/qipao_analysis_example.png
  40. BIN
      static/examples/industrial_quality_inspection/image/visualize_budaodian-116.jpg
  41. BIN
      static/examples/industrial_quality_inspection/image/zangdian_analysis_example.png
  42. BIN
      static/examples/industrial_quality_inspection/image/zase_analysis_example.png
  43. 0 61
      static/examples/industrial_quality_inspection/params_analysis.py
  44. 0 35
      static/examples/industrial_quality_inspection/predict.py
  45. 0 102
      static/examples/industrial_quality_inspection/tp_fp_list.md
  46. 0 58
      static/examples/industrial_quality_inspection/train_pruned_yolov3.py
  47. 0 74
      static/examples/industrial_quality_inspection/train_rcnn.py
  48. 0 59
      static/examples/industrial_quality_inspection/train_yolov3.py

+ 1 - 1
PaddleDetection

@@ -1 +1 @@
-Subproject commit 6ec36068ee450df33c0038fb0cdc575b21510acd
+Subproject commit 3bdf2671f3188de3c4158c9056a46e949cf02eb8

+ 2 - 1
deploy/cpp/README.md

@@ -38,6 +38,7 @@
 
 
 ## ONNX模型部署
-Paddle的模型除了直接通过PaddleInference部署外,还可以通过[Paddle2ONNX](https://github.com/PaddlePaddle/Paddle2ONNX.git)转为ONNX后使用第三方推理引擎进行部署,在本目录下,我们提供了基于Triton和TensorRT两个引擎的部署支持。
+Paddle的模型除了直接通过PaddleInference部署外,还可以通过[Paddle2ONNX](https://github.com/PaddlePaddle/Paddle2ONNX.git)转为ONNX后使用第三方推理引擎进行部署,在本目录下,我们提供了基于OpenVINO、Triton和TensorRT三个引擎的部署支持。
+- [OpenVINO部署](./docs/compile/openvino/README.md)
 - [Triton部署](./docs/compile/triton/docker.md)
 - [TensorRT部署](./docs/compile/tensorrt/trt.md)

+ 6 - 0
deploy/cpp/docs/compile/openvino/README.md

@@ -0,0 +1,6 @@
+## 基于OpenVINO的C++部署
+
+目前在Windows、Linux环境下,支持用OpenVINO进行加速部署。
+
+- [OpenVINO Linux部署](./openvino_linux.md)
+- [OpenVINO Windows部署](./openvino_windows.md)

+ 112 - 0
deploy/cpp/docs/compile/openvino/openvino_linux.md

@@ -0,0 +1,112 @@
+# 基于OpenVINO的推理-Linux环境编译
+
+本文档指引用户如何基于OpenVINO对飞桨模型进行推理,并编译执行。进行以下编译操作前请先安装好OpenVINO,OpenVINO安装请参考官网[OpenVINO-Linux](https://docs.openvinotoolkit.org/latest/_docs_install_guides_installing_openvino_linux.html)
+
+**注意:** 
+
+- 我们测试的openvino版本为2021.3,如果你使用其它版本遇到问题,可以尝试切换到该版本
+- 当前检测模型转换为openvino格式是有问题的,暂时只支持分割和分类模型
+
+## 1 准备模型
+
+以[ResNet50](https://bj.bcebos.com/paddlex/deploy2/models/resnet50_trt.tar.gz)为例:
+
+### 1.1 导出Paddle Inference模型
+
+通过[PaddleClas模型部署指南](../../models/paddleclas.md) 得到Paddle Inference类型的ResNet50模型,其他套件模型请参考:[PaddleDetection模型部署指南](../../models/paddledetection.md) 、[PaddleSeg模型部署指南](../../models/paddleseg.md)
+
+下载的ResNet50解压后的目录结构如下:
+
+```
+ResNet50
+  |-- model.pdiparams        # 静态图模型参数
+  |-- model.pdiparams.info   # 参数额外信息,一般无需关注
+  |-- model.pdmodel          # 静态图模型文件
+  |-- resnet50_imagenet.yml  # 配置文件
+```
+
+### 1.2 转换为ONNX模型
+
+将paddle inference模型转为onnx模型, 详细可参考[Paddle2ONNX](https://github.com/PaddlePaddle/Paddle2ONNX.git)文档
+
+ResNet50模型转换如下,转换后模型输出在 onnx_models/resnet50_onnx/model.onnx
+
+```
+# model_dir需要ResNet50解压后的路径
+paddle2onnx --model_dir ResNet50  --save_file onnx_models/resnet50_onnx/model.onnx  --opset_version 9 --enable_onnx_checker True --model_filename model.pdmodel --params_filename model.pdiparams
+```
+
+### 1.3 转换为openvino模型
+
+将onnx模型转为openvino模型, 详细可参考官网文档[转换onnx模型](https://docs.openvinotoolkit.org/latest/openvino_docs_MO_DG_prepare_model_convert_model_Convert_Model_From_ONNX.html)
+
+以上文的ResNet50模型为例,转换指令如下:
+
+```
+# 使用mo.py 也可以
+mo_onnx.py --input_model onnx_models/resnet50_onnx/model.onnx --output_dir openvino_model/resnet50 --input_shape \[1\,3\,224\,224\]
+```
+
+转换后的openvino_model/resnet50目录下会出现三个文件, 目录结构如下:
+
+```
+resnet50
+├── ResNet50.bin
+├── ResNet50.mapping
+└── ResNet50.xml
+```
+
+**注意:**
+
+- 留意模型转换的输出,比如转换onnx时根据提示调整opset_version的值
+- paddle inference模型中的配置文件(如 `resnet50_imagenet.yml`)包含了前后处理、标签等信息,对转换后的openvino模型进行推理时还会用到。
+
+
+## 编译步骤
+### Step 1. 获取部署代码
+```
+git clone https://github.com/PaddlePaddle/PaddleX.git
+cd PaddleX/deploy/cpp
+```
+**说明**:`C++`预测代码在`PaddleX/deploy/cpp` 目录,该目录不依赖任何`PaddleX`下其他目录。所有的推理实现代码在`model_deploy`目录下,所有示例代码都在`demo`目录下,script目录为编译脚本。
+
+### Step 2. 修改编译参数
+根据自己的系统环境,修改`PaddleX/deploy/cpp/script/openvino_build.sh`脚本中的参数,主要修改的参数为以下几个
+| 参数          | 说明                                                                                 |
+| :------------ | :----------------------------------------------------------------------------------- |
+| OPENVINO_DIR      | OpenVINO预编译库inference_engine的路径                             |
+| NGRAPH_LIB    | OpenVINO的ngraph lib的路径,编译openvino时通常会生成                   |
+| GFLAGS_DIR      | gflags所在的目录路径,如果采用自动下载不用改                                            |
+| OPENCV_DIR     | opencv所在的目录路径,如果采用自动下载不用改                                                       |
+
+### Step 4. 编译
+修改完openvino_build.sh后执行编译, **[注意]**: 以下命令在`PaddleX/deploy/cpp`目录下进行执行
+
+```
+sh scripts/onnx/openvino_build.sh
+```
+#### 编译环境无法联网导致编译失败?
+
+> 编译过程,会联网下载opencv、gflag,如无法联网,用户按照下操作手动下载
+>
+> 1. 根据系统版本,点击右侧链接下载不同版本的opencv依赖 [Ubuntu 16.04](https://bj.bcebos.com/paddleseg/deploy/opencv3.4.6gcc4.8ffmpeg.tar.gz2)/[Ubuntu 18.04](https://bj.bcebos.com/paddlex/deploy/opencv3.4.6gcc4.8ffmpeg_ubuntu_18.04.tar.gz2)
+> 2. 解压下载的opencv依赖(解压后目录名为opencv3.4.6gcc4.8ffmpeg),创建目录`PaddleX/deploy/cpp/deps`,将解压后的目录拷贝至该创建的目录下
+> 3. 点击[下载gflags依赖包](https://bj.bcebos.com/paddlex/deploy/gflags.tar.gz),解压至`deps`目录
+
+### Step 5. 编译结果
+
+编译后会在`PaddleX/deploy/cpp/build/demo`目录下生成`model_infer` 可执行二进制文件示例,用于加载模型进行预测。以上面转换的ResNet50模型为例,运行指令如下:
+
+```
+./build/demo/model_infer --xml_file openvino_model/resnet50/ResNet50_vd.xml --bin_file openvino_model/resnet50/ResNet50_vd.bin --cfg_file openvino_model/resnet50/resnet50_imagenet.yml --model_type clas --image test.jpeg
+```
+
+**参数说明**
+
+| 参数名称   | 含义                                                         |
+| ---------- | ------------------------------------------------------------ |
+| xml_file | openvino转换的xml模型文件                                                 |
+| bin_file | openvino转换的xml模型文件                                                 |
+| cfg_file   | Paddle套件导出的模型配置文件,如`resnet50/deploy.yml`    |
+| image      | 需要预测的单张图片的文件路径                                 |
+| model_type | 模型来源,det/seg/clas/paddlex,分别表示模型来源于PaddleDetection、PaddleSeg、PaddleClas和PaddleX |

+ 144 - 0
deploy/cpp/docs/compile/openvino/openvino_windows.md

@@ -0,0 +1,144 @@
+# 基于OpenVINO的推理-Windows环境编译
+
+本文档指引用户如何基于OpenVINO对飞桨模型进行推理,并编译执行。进行以下编译操作前请先安装好OpenVINO,OpenVINO安装请参考官网[OpenVINO-windows](https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_windows.html)
+
+**注意:** 
+
+- 我们测试的openvino版本为2021.3,如果你使用其它版本遇到问题,可以尝试切换到该版本
+- 当前检测模型转换为openvino格式是有问题的,暂时只支持分割和分类模型
+
+## 1 准备模型
+
+以[ResNet50](https://bj.bcebos.com/paddlex/deploy2/models/resnet50_trt.tar.gz)为例:
+
+### 1.1 导出Paddle Inference模型
+
+通过[PaddleClas模型部署指南](../../models/paddleclas.md) 得到Paddle Inference类型的ResNet50模型,其他套件模型请参考:[PaddleDetection模型部署指南](../../models/paddledetection.md) 、[PaddleSeg模型部署指南](../../models/paddleseg.md)
+
+下载的ResNet50解压后的目录结构如下:
+
+```
+ResNet50
+  |-- model.pdiparams        # 静态图模型参数
+  |-- model.pdiparams.info   # 参数额外信息,一般无需关注
+  |-- model.pdmodel          # 静态图模型文件
+  |-- resnet50_imagenet.yml  # 配置文件
+```
+
+### 1.2 转换为ONNX模型
+
+将paddle inference模型转为onnx模型, 详细可参考[Paddle2ONNX](https://github.com/PaddlePaddle/Paddle2ONNX.git)文档
+
+ResNet50模型转换如下,转换后模型输出在 onnx_models/resnet50_onnx/model.onnx
+
+```
+# model_dir需要ResNet50解压后的路径
+paddle2onnx --model_dir ResNet50  --save_file onnx_models/resnet50_onnx/model.onnx  --opset_version 9 --enable_onnx_checker True --model_filename model.pdmodel --params_filename model.pdiparams
+```
+
+### 1.3 转换为openvino模型
+
+将onnx模型转为openvino模型, 详细可参考官网文档[转换onnx模型](https://docs.openvinotoolkit.org/latest/openvino_docs_MO_DG_prepare_model_convert_model_Convert_Model_From_ONNX.html)
+
+以上文的ResNet50模型为例,转换指令如下:
+
+```
+# 使用mo.py 也可以
+mo_onnx.py --input_model onnx_models/resnet50_onnx/model.onnx --output_dir openvino_model/resnet50 --input_shape \[1\,3\,224\,224\]
+```
+
+转换后的openvino_model/resnet50目录下会出现三个文件, 目录结构如下:
+
+```
+resnet50
+├── ResNet50.bin
+├── ResNet50.mapping
+└── ResNet50.xml
+```
+
+**注意:**
+
+- 留意模型转换的输出,比如转换onnx时根据提示调整opset_version的值
+- paddle inference模型中的配置文件(如 `resnet50_imagenet.yml`)包含了前后处理、标签等信息,对转换后的openvino模型进行推理时还会用到。
+
+
+## 编译步骤
+### Step 1. 获取部署代码
+```
+git clone https://github.com/PaddlePaddle/PaddleX.git
+```
+**说明**:`C++`预测代码在`PaddleX/deploy/cpp` 目录,该目录不依赖任何`PaddleX`下其他目录。所有的推理实现代码在`model_deploy`目录下,所有示例代码都在`demo`目录下。
+
+### Step 2. 环境准备
+1. 在OpenCV官网下载适用于Windows平台的3.4.6版本  [下载地址](https://bj.bcebos.com/paddleseg/deploy/opencv-3.4.6-vc14_vc15.exe)  
+2. 运行下载的可执行文件,将OpenCV解压至指定目录,例如`D:\projects\opencv`
+3. 配置环境变量,如下流程所示  
+   - 我的电脑->属性->高级系统设置->环境变量
+   - 在系统变量中找到Path(如没有,自行创建),并双击编辑
+   - 新建,将opencv路径填入并保存,如`D:\projects\opencv\build\x64\vc15\bin`
+   - 在进行cmake构建时,会有相关提示,请注意vs2019的输出
+4. 点击[下载gflags依赖包](https://bj.bcebos.com/paddlex/deploy/gflags_windows.zip),解压至`deps`目录
+
+### Step 4. 编译
+1. 打开Visual Studio 2019 Community,点击`继续但无需代码`
+   
+   ![](../../images/vs2019_step1.png)
+
+2. 点击: `文件`->`打开`->`CMake`
+
+![](../../images/vs2019_step2.png)
+
+选择C++预测代码所在路径(例如`D:\projects\PaddleX\dygraph\deploy\cpp`),并打开`CMakeList.txt`:
+![](../../images/vs2019_step3.png)
+
+3. 打开项目时,可能会自动构建。由于没有进行下面的依赖路径设置会报错,这个报错可以先忽略。
+
+  点击:`项目`->`CMake设置`(也可能叫`PaddleDeploy`的CMake设置)
+  ![](../../images/vs2019_step4.png)
+
+4. 点击`浏览`,分别设置编译选项指定`gflag`、`OpenCV`、`OpenVINO`的路径(也可以点击右上角的“编辑 JSON”,直接修改json文件,然后保存点 项目->生成缓存)
+
+   ![](../../images/vs2019_step5.png)
+   
+  依赖库路径的含义说明如下,注意OpenVINO编译只需要勾选和填写以下参数即可:
+
+| 参数名     | 含义                                                                                                                                                |
+| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
+| WITH_OPENVINO  | 是否使用OpenVINO推理引擎,默认为False。 勾选或者json文件填写为True,表示编译OpenVINO推理引擎  |
+| OPENCV_DIR | OpenCV的安装路径,例如`D:\\projects\\opencv`   |
+| GFLAGS_DIR | gflag的路径,例如`D:\\projects\\PaddleX\\dygraph\\deploy\\cpp\\deps\\gflags` |
+| OPENVINO_DIR | OpenVINO的路径,例如`C:\\Program Files (x86)\\Intel\\openvino_2021\\inference_engine` |
+| NGRAPH_LIB | OpenVINO的ngraph路径,例如`C:\\Program Files (x86)\\Intel\\openvino_2021\\deployment_tools\\ngraph` |
+
+5. 保存并生成CMake缓存
+
+![](../../images/vs2019_step6.png)
+**设置完成后**, 点击上图中`保存并生成CMake缓存以加载变量`。然后我们可以看到vs的输出会打印CMake生成的过程,出现`CMake 生成完毕`且无报错代表生成完毕。
+
+6. 点击`生成`->`全部生成`,生成demo里的可执行文件。
+
+![step6](../../images/vs2019_step7.png)
+
+#### 编译环境无法联网导致编译失败?
+
+- 如果无法联网,请手动点击下载 [yaml-cpp.zip](https://bj.bcebos.com/paddlex/deploy/deps/yaml-cpp.zip),无需解压,并修改`PaddleX\dygraph\deploy\cpp\cmake\yaml.cmake`中将`URL https://bj.bcebos.com/paddlex/deploy/deps/yaml-cpp.zip` 中的网址替换为第3步中下载的路径,如改为`URL D:\projects\yaml-cpp.zip`。
+- 一定要勾选WITH_OPENVINO选项, WITH_GPU、WITH_TENSORRT选项可以取消掉
+- 不支持debug编译,注意切换成Release
+
+### Step5: 编译结果
+
+编译后会在`PaddleX/dygraph/deploy/cpp/build/demo`目录下生成`model_infer`可执行二进制文件示例,用于加载模型进行预测。以上面转换的ResNet50模型为例,运行指令如下:
+
+```
+./model_infer.exe --xml_file openvino_model/resnet50/ResNet50_vd.xml --bin_file openvino_model/resnet50/ResNet50_vd.bin --cfg_file openvino_model/resnet50/resnet50_imagenet.yml --model_type clas --image test.jpeg
+```
+
+**参数说明**
+
+| 参数名称   | 含义                                                         |
+| ---------- | ------------------------------------------------------------ |
+| xml_file | openvino转换的xml模型文件                                                 |
+| bin_file | openvino转换的xml模型文件                                                 |
+| cfg_file   | Paddle套件导出的模型配置文件,如`resnet50/deploy.yml`    |
+| image      | 需要预测的单张图片的文件路径                                 |
+| model_type | 模型来源,det/seg/clas/paddlex,分别表示模型来源于PaddleDetection、PaddleSeg、PaddleClas和PaddleX |

+ 13 - 12
deploy/cpp/docs/demo/model_infer.md

@@ -21,13 +21,25 @@
 以步骤二中下载的YOLOv3模型为例,执行如下命令即可进行模型加载和预测
 
 ```sh
-# 使用GPU 加参数 --use_gpu=1
 build/demo/model_infer --model_filename=yolov3_mbv1/model/model.pdmodel \
                        --params_filename=yolov3_mbv1/model/model.pdiparams \
                        --cfg_file=yolov3_mbv1/model/infer_cfg.yml \
                        --image=yolov3_mbv1/images/000000010583.jpg \
                        --model_type=det
 ```
+**注意:如果要使用GPU进行预测,请同时指定`--use_gpu=1`。根据模型来源的套件,设置正确的`model_type`。**
+
+| 参数            | 说明                                                                                                         |
+| --------------- | ------------------------------------------------------------------------------------------------------------ |
+| model_filename  | **[必填]** 模型结构文件路径,如`yolov3_darknet/model.pdmodel`                                                |
+| params_filename | **[必填]** 模型权重文件路径,如`yolov3_darknet/model.pdiparams`                                              |
+| cfg_file        | **[必填]** 模型配置文件路径,如`yolov3_darknet/infer_cfg.yml`                                                |
+| model_type      | **[必填]** 模型来源,**det/seg/clas/paddlex**,分别表示模型来源于PaddleDetection、PaddleSeg、PaddleClas和PaddleX |
+| image           | 待预测的图片文件路径                                                                                         |
+| use_gpu         | 是否使用GPU,1或者0。默认为0,不使用GPU                                                                         |
+| gpu_id          | 使用GPU预测时的GUI设备ID,默认为0                                                                            |
+
+
 输出结果如下(分别为类别id、标签、置信度、xmin、ymin、w, h)
 ```
 Box(0	person	0.0386442	2.11425	53.4415	36.2138	197.833)
@@ -39,17 +51,6 @@ Box(44	spoon	0.0234474	509.703	189.959	100.65	93.9368)
 Box(45	bowl	0.0461333	0	0	223.386	83.5562)
 Box(45	bowl	0.0191819	3.91156	1.276	225.888	214.273)
 ```
-### 参数说明
-
-| 参数            | 说明                                                                                                         |
-| --------------- | ------------------------------------------------------------------------------------------------------------ |
-| model_filename  | **[必填]** 模型结构文件路径,如`yolov3_darknet/model.pdmodel`                                                |
-| params_filename | **[必填]** 模型权重文件路径,如`yolov3_darknet/model.pdiparams`                                              |
-| cfg_file        | **[必填]** 模型配置文件路径,如`yolov3_darknet/infer_cfg.yml`                                                |
-| model_type      | **[必填]** 模型来源,det/seg/clas/paddlex,分别表示模型来源于PaddleDetection、PaddleSeg、PaddleClas和PaddleX |
-| image           | 待预测的图片文件路径                                                                                         |
-| use_gpu         | 是否使用GPU,0或者1,默认为0                                                                                 |
-| gpu_id          | 使用GPU预测时的GUI设备ID,默认为0                                                                            |
 
 
 

+ 6 - 5
paddlex/cv/models/base.py

@@ -437,13 +437,11 @@ class BaseModel:
                             criterion='l1_norm',
                             save_dir='output'):
         """
-
         Args:
             dataset(paddlex.dataset): Dataset used for evaluation during sensitivity analysis.
             batch_size(int, optional): Batch size used in evaluation. Defaults to 8.
             criterion({'l1_norm', 'fpgm'}, optional): Pruning criterion. Defaults to 'l1_norm'.
             save_dir(str, optional): The directory to save sensitivity file of the model. Defaults to 'output'.
-
         """
         if self.__class__.__name__ in ['FasterRCNN', 'MaskRCNN']:
             raise Exception("{} does not support pruning currently!".format(
@@ -479,12 +477,10 @@ class BaseModel:
 
     def prune(self, pruned_flops, save_dir=None):
         """
-
         Args:
             pruned_flops(float): Ratio of FLOPs to be pruned.
             save_dir(None or str, optional): If None, the pruned model will not be saved.
                 Otherwise, the pruned model will be saved at save_dir. Defaults to None.
-
         """
         if self.status == "Pruned":
             raise Exception(
@@ -505,6 +501,10 @@ class BaseModel:
             logging.info("Pruned model is saved at {}".format(save_dir))
 
     def _prepare_qat(self, quant_config):
+        if self.status == 'Infer':
+            logging.error(
+                "Exported inference model does not support quantization aware training.",
+                exit=True)
         if quant_config is None:
             # default quantization configuration
             quant_config = {
@@ -580,7 +580,8 @@ class BaseModel:
         return pipeline_info
 
     def _build_inference_net(self):
-        infer_net = InferNet(self.net, self.model_type)
+        infer_net = self.net if self.model_type == 'detector' else InferNet(
+            self.net, self.model_type)
         infer_net.eval()
         return infer_net
 

+ 50 - 19
paddlex/cv/models/detector.py

@@ -105,8 +105,6 @@ class BaseDetector(BaseModel):
         if mode in ['train', 'eval']:
             outputs = net_out
         else:
-            for key in ['im_shape', 'scale_factor']:
-                net_out[key] = inputs[key]
             outputs = dict()
             for key in net_out:
                 outputs[key] = net_out[key].numpy()
@@ -194,8 +192,11 @@ class BaseDetector(BaseModel):
             resume_checkpoint(str or None, optional): The path of the checkpoint to resume training from.
                 If None, no training checkpoint will be resumed. At most one of `resume_checkpoint` and
                 `pretrain_weights` can be set simultaneously. Defaults to None.
-
         """
+        if self.status == 'Infer':
+            logging.error(
+                "Exported inference model does not support training.",
+                exit=True)
         if pretrain_weights is not None and resume_checkpoint is not None:
             logging.error(
                 "pretrain_weights and resume_checkpoint cannot be set simultaneously.",
@@ -338,7 +339,6 @@ class BaseDetector(BaseModel):
                 configuration will be used. Defaults to None.
             resume_checkpoint(str or None, optional): The path of the checkpoint to resume quantization-aware training
                 from. If None, no training checkpoint will be resumed. Defaults to None.
-
         """
         self._prepare_qat(quant_config)
         self.train(
@@ -376,10 +376,8 @@ class BaseDetector(BaseModel):
             metric({'VOC', 'COCO', None}, optional):
                 Evaluation metric. If None, determine the metric according to the dataset format. Defaults to None.
             return_details(bool, optional): Whether to return evaluation details. Defaults to False.
-
         Returns:
             collections.OrderedDict with key-value pairs: {"mAP(0.50, 11point)":`mean average precision`}.
-
         """
 
         if metric is None:
@@ -475,7 +473,6 @@ class BaseDetector(BaseModel):
                 meaning all images to be predicted as a mini-batch.
             transforms(paddlex.transforms.Compose or None, optional):
                 Transforms for inputs. If None, the transforms for evaluation process will be used. Defaults to None.
-
         Returns:
             If img_file is a string or np.array, the result is a list of dict with key-value pairs:
             {"category_id": `category_id`, "category": `category`, "bbox": `[x, y, w, h]`, "score": `score`}.
@@ -485,7 +482,6 @@ class BaseDetector(BaseModel):
             bbox(list): bounding box in [x, y, w, h] format
             score(str): confidence
             mask(dict): Only for instance segmentation task. Mask of the object in RLE format
-
         """
         if transforms is None and not hasattr(self, 'test_transforms'):
             raise Exception("transforms need to be defined, now is None.")
@@ -515,8 +511,14 @@ class BaseDetector(BaseModel):
         batch_transforms = self._compose_batch_transform(transforms, 'test')
         batch_samples = batch_transforms(batch_samples)
         if to_tensor:
-            for k, v in batch_samples.items():
-                batch_samples[k] = paddle.to_tensor(v)
+            if isinstance(batch_samples, dict):
+                for k in batch_samples:
+                    batch_samples[k] = paddle.to_tensor(batch_samples[k])
+            else:
+                for sample in batch_samples:
+                    for k in sample:
+                        sample[k] = paddle.to_tensor(sample[k])
+
         return batch_samples
 
     def _postprocess(self, batch_pred):
@@ -985,6 +987,18 @@ class FasterRCNN(BaseDetector):
         super(FasterRCNN, self).__init__(
             model_name='FasterRCNN', num_classes=num_classes, **params)
 
+    def run(self, net, inputs, mode):
+        if mode in ['train', 'eval']:
+            outputs = net(inputs)
+        else:
+            outputs = []
+            for sample in inputs:
+                net_out = net(sample)
+                for key in net_out:
+                    net_out[key] = net_out[key].numpy()
+                outputs.append(net_out)
+        return outputs
+
     def _compose_batch_transform(self, transforms, mode='train'):
         if mode == 'train':
             default_batch_transforms = [
@@ -1008,7 +1022,8 @@ class FasterRCNN(BaseDetector):
 
         batch_transforms = BatchCompose(
             custom_batch_transforms + default_batch_transforms,
-            collate_batch=collate_batch)
+            collate_batch=collate_batch,
+            return_list=mode == 'test')
 
         return batch_transforms
 
@@ -1054,6 +1069,13 @@ class FasterRCNN(BaseDetector):
         self.fixed_input_shape = image_shape
         return self._define_input_spec(image_shape)
 
+    def _postprocess(self, batch_pred):
+        prediction = [
+            super(FasterRCNN, self)._postprocess(pred)[0]
+            for pred in batch_pred
+        ]
+        return prediction
+
 
 class PPYOLO(YOLOv3):
     def __init__(self,
@@ -1462,20 +1484,28 @@ class PPYOLOv2(YOLOv3):
             image_shape = self._check_image_shape(image_shape)
             self._fix_transforms_shape(image_shape[-2:])
         else:
-            image_shape = [None, 3, 608, 608]
+            image_shape = [None, 3, 640, 640]
+            if hasattr(self, 'test_transforms'):
+                if self.test_transforms is not None:
+                    for idx, op in enumerate(self.test_transforms.transforms):
+                        name = op.__class__.__name__
+                        if name == 'Resize':
+                            image_shape = [None, 3] + list(
+                                self.test_transforms.transforms[
+                                    idx].target_size)
             logging.warning(
                 '[Important!!!] When exporting inference model for {},'.format(
                     self.__class__.__name__) +
-                ' if fixed_input_shape is not set, it will be forcibly set to [None, 3, 608, 608]. '
-                +
-                'Please check image shape after transforms is [3, 608, 608], if not, fixed_input_shape '
-                + 'should be specified manually.')
+                ' if fixed_input_shape is not set, it will be forcibly set to {}. '.
+                format(image_shape) +
+                'Please check image shape after transforms is {}, if not, fixed_input_shape '.
+                format(image_shape[1:]) + 'should be specified manually.')
 
         self.fixed_input_shape = image_shape
         return self._define_input_spec(image_shape)
 
 
-class MaskRCNN(BaseDetector):
+class MaskRCNN(FasterRCNN):
     def __init__(self,
                  num_classes=80,
                  backbone='ResNet50_vd',
@@ -1710,7 +1740,7 @@ class MaskRCNN(BaseDetector):
                 'mask_post_process': mask_post_process
             })
         self.with_fpn = with_fpn
-        super(MaskRCNN, self).__init__(
+        super(FasterRCNN, self).__init__(
             model_name='MaskRCNN', num_classes=num_classes, **params)
 
     def _compose_batch_transform(self, transforms, mode='train'):
@@ -1736,7 +1766,8 @@ class MaskRCNN(BaseDetector):
 
         batch_transforms = BatchCompose(
             custom_batch_transforms + default_batch_transforms,
-            collate_batch=collate_batch)
+            collate_batch=collate_batch,
+            return_list=mode == 'test')
 
         return batch_transforms
 

+ 1 - 3
paddlex/cv/models/load_model.py

@@ -20,7 +20,6 @@ import paddleslim
 import paddlex
 import paddlex.utils.logging as logging
 from paddlex.cv.transforms import build_transforms
-from .utils.infer_nets import InferNet
 
 
 def load_rcnn_inference_model(model_dir):
@@ -117,8 +116,7 @@ def load_model(model_dir, **params):
                     logging.error(
                         "Exported quantized model can not be loaded, only deployment is supported.",
                         exit=True)
-                model.net = InferNet(
-                    net=model.net, model_type=model.model_type)
+                model.net = model._build_inference_net()
                 if model_info['Model'] in ['FasterRCNN', 'MaskRCNN']:
                     net_state_dict = load_rcnn_inference_model(model_dir)
                 else:

+ 4 - 0
paddlex/cv/models/segmenter.py

@@ -235,6 +235,10 @@ class BaseSegmenter(BaseModel):
                 `pretrain_weights` can be set simultaneously. Defaults to None.
 
         """
+        if self.status == 'Infer':
+            logging.error(
+                "Exported inference model does not support training.",
+                exit=True)
         if pretrain_weights is not None and resume_checkpoint is not None:
             logging.error(
                 "pretrain_weights and resume_checkpoint cannot be set simultaneously.",

+ 6 - 6
paddlex/cv/models/utils/infer_nets.py

@@ -23,13 +23,13 @@ class PostProcessor(paddle.nn.Layer):
     def forward(self, net_outputs):
         if self.model_type == 'classifier':
             outputs = paddle.nn.functional.softmax(net_outputs, axis=1)
-        elif self.model_type == 'segmenter':
+        else:
             # score_map, label_map
-            outputs = paddle.transpose(paddle.nn.functional.softmax(net_outputs, axis=1), perm=[0, 2, 3, 1]), \
-                      paddle.transpose(paddle.argmax(net_outputs, axis=1, keepdim=True, dtype='int32'),
+            logit = net_outputs[0]
+            outputs = paddle.transpose(paddle.nn.functional.softmax(logit, axis=1), perm=[0, 2, 3, 1]), \
+                      paddle.transpose(paddle.argmax(logit, axis=1, keepdim=True, dtype='int32'),
                                        perm=[0, 2, 3, 1])
-        else:
-            outputs = net_outputs
+
         return outputs
 
 
@@ -40,7 +40,7 @@ class InferNet(paddle.nn.Layer):
         self.postprocessor = PostProcessor(model_type)
 
     def forward(self, x):
-        net_outputs = self.net(x)[0]
+        net_outputs = self.net(x)
         outputs = self.postprocessor(net_outputs)
 
         return outputs

+ 12 - 2
paddlex/cv/transforms/batch_operators.py

@@ -26,10 +26,14 @@ from paddlex.utils import logging
 
 
 class BatchCompose(Transform):
-    def __init__(self, batch_transforms=None, collate_batch=True):
+    def __init__(self,
+                 batch_transforms=None,
+                 collate_batch=True,
+                 return_list=False):
         super(BatchCompose, self).__init__()
         self.batch_transforms = batch_transforms
         self.collate_batch = collate_batch
+        self.return_list = return_list
 
     def __call__(self, samples):
         if self.batch_transforms is not None:
@@ -51,7 +55,13 @@ class BatchCompose(Transform):
                 if k in sample:
                     sample.pop(k)
 
-        if self.collate_batch:
+        if self.return_list:
+            batch_data = [{
+                k: np.expand_dims(
+                    sample[k], axis=0)
+                for k in sample
+            } for sample in samples]
+        elif self.collate_batch:
             batch_data = default_collate_fn(samples)
         else:
             batch_data = {}

+ 61 - 27
paddlex/deploy.py

@@ -1,4 +1,4 @@
-# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
+# copyright (c) 2021 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.
@@ -24,7 +24,7 @@ from paddlex.utils import logging, Timer
 class Predictor(object):
     def __init__(self,
                  model_dir,
-                 use_gpu=True,
+                 use_gpu=False,
                  gpu_id=0,
                  cpu_thread_num=1,
                  use_mkl=True,
@@ -37,7 +37,7 @@ class Predictor(object):
         """ 创建Paddle Predictor
             Args:
                 model_dir: 模型路径(必须是导出的部署或量化模型)
-                use_gpu: 是否使用gpu,默认True
+                use_gpu: 是否使用gpu,默认False
                 gpu_id: 使用gpu的id,默认0
                 cpu_thread_num=1:使用cpu进行预测时的线程数,默认为1
                 use_mkl: 是否使用mkldnn计算库,CPU情况下使用,默认False
@@ -51,9 +51,9 @@ class Predictor(object):
         self.model_dir = model_dir
         self._model = load_model(model_dir, with_net=False)
 
-        if trt_precision_mode == 'float32':
+        if trt_precision_mode.lower() == 'float32':
             trt_precision_mode = PrecisionType.Float32
-        elif trt_precision_mode == 'float16':
+        elif trt_precision_mode.lower() == 'float16':
             trt_precision_mode = PrecisionType.Float16
         else:
             logging.error(
@@ -158,7 +158,7 @@ class Predictor(object):
                 transforms=transforms.transforms)
             score_map = np.squeeze(score_map)
             label_map = np.squeeze(label_map)
-            if len(score_map.shape) == 3:
+            if score_map.ndim == 3:
                 preds = {'label_map': label_map, 'score_map': score_map}
             else:
                 preds = [{
@@ -166,10 +166,16 @@ class Predictor(object):
                     'score_map': s
                 } for l, s in zip(label_map, score_map)]
         elif self._model.model_type == 'detector':
-            net_outputs = {
-                k: v
-                for k, v in zip(['bbox', 'bbox_num', 'mask'], net_outputs)
-            }
+            if 'RCNN' in self._model.__class__.__name__:
+                net_outputs = [{
+                    k: v
+                    for k, v in zip(['bbox', 'bbox_num', 'mask'], res)
+                } for res in net_outputs]
+            else:
+                net_outputs = {
+                    k: v
+                    for k, v in zip(['bbox', 'bbox_num', 'mask'], net_outputs)
+                }
             preds = self._model._postprocess(net_outputs)
             if len(preds) == 1:
                 preds = preds[0]
@@ -190,7 +196,6 @@ class Predictor(object):
             input_tensor = self.predictor.get_input_handle(name)
             input_tensor.copy_from_cpu(inputs[name])
 
-        self.timer.inference_time_s.start()
         self.predictor.run()
         output_names = self.predictor.get_output_names()
         net_outputs = list()
@@ -200,14 +205,51 @@ class Predictor(object):
 
         return net_outputs
 
-    def predict(self, img_file, topk=1, transforms=None):
+    def _run(self, images, topk=1, transforms=None):
+        self.timer.preprocess_time_s.start()
+        preprocessed_input = self.preprocess(images, transforms)
+        self.timer.preprocess_time_s.end(iter_num=len(images))
+
+        ori_shape = None
+        self.timer.inference_time_s.start()
+        if 'RCNN' in self._model.__class__.__name__:
+            if len(preprocessed_input) > 1:
+                logging.warning(
+                    "{} only supports inference with batch size equal to 1."
+                    .format(self._model.__class__.__name__))
+            net_outputs = [
+                self.raw_predict(sample) for sample in preprocessed_input
+            ]
+            self.timer.inference_time_s.end(iter_num=len(images))
+        else:
+            net_outputs = self.raw_predict(preprocessed_input)
+            self.timer.inference_time_s.end(iter_num=1)
+            ori_shape = preprocessed_input.get('ori_shape', None)
+
+        self.timer.postprocess_time_s.start()
+        results = self.postprocess(
+            net_outputs, topk, ori_shape=ori_shape, transforms=transforms)
+        self.timer.postprocess_time_s.end(iter_num=len(images))
+
+        return results
+
+    def predict(self,
+                img_file,
+                topk=1,
+                transforms=None,
+                warmup_iters=0,
+                repeats=1):
         """ 图片预测
             Args:
                 img_file(List[np.ndarray or str], str or np.ndarray):
                     图像路径;或者是解码后的排列格式为(H, W, C)且类型为float32且为BGR格式的数组。
                 topk(int): 分类预测时使用,表示预测前topk的结果。
                 transforms (paddlex.transforms): 数据预处理操作。
+                warmup_iters (int): 预热轮数,默认为0。
+                repeats (int): 重复次数,用于评估模型推理以及前后处理速度。若大于1,会预测repeats次取时间平均值。
         """
+        if repeats < 1:
+            logging.error("`repeats` must be greater than 1.", exit=True)
         if transforms is None and not hasattr(self._model, 'test_transforms'):
             raise Exception("Transforms need to be defined, now is None.")
         if transforms is None:
@@ -217,23 +259,15 @@ class Predictor(object):
         else:
             images = img_file
 
-        self.timer.preprocess_time_s.start()
-        preprocessed_input = self.preprocess(images, transforms)
-        self.timer.preprocess_time_s.end()
-
-        self.timer.inference_time_s.start()
-        net_outputs = self.raw_predict(preprocessed_input)
-        self.timer.inference_time_s.end()
+        for step in range(warmup_iters):
+            self._run(images=images, topk=topk, transforms=transforms)
+        self.timer.reset()
 
-        self.timer.postprocess_time_s.start()
-        results = self.postprocess(
-            net_outputs,
-            topk,
-            ori_shape=preprocessed_input.get('ori_shape', None),
-            transforms=transforms)
-        self.timer.postprocess_time_s.end()
+        for step in range(repeats):
+            results = self._run(
+                images=images, topk=topk, transforms=transforms)
 
-        self.timer.img_num = len(images)
+        self.timer.repeats = repeats
         self.timer.info(average=True)
 
         return results

+ 26 - 16
paddlex/utils/utils.py

@@ -152,12 +152,12 @@ class Times(object):
     def start(self):
         self.st = time.time()
 
-    def end(self, repeats=1, accumulative=True):
+    def end(self, iter_num=1, accumulative=True):
         self.et = time.time()
         if accumulative:
-            self.time += (self.et - self.st) / repeats
+            self.time += (self.et - self.st) / iter_num
         else:
-            self.time = (self.et - self.st) / repeats
+            self.time = (self.et - self.st) / iter_num
 
     def reset(self):
         self.time = 0.
@@ -175,44 +175,54 @@ class Timer(Times):
         self.inference_time_s = Times()
         self.postprocess_time_s = Times()
         self.img_num = 0
+        self.repeats = 0
 
     def info(self, average=False):
         total_time = self.preprocess_time_s.value(
         ) + self.inference_time_s.value() + self.postprocess_time_s.value()
         total_time = round(total_time, 4)
         print("------------------ Inference Time Info ----------------------")
-        print("total_time(ms): {}, img_num: {}".format(total_time * 1000,
-                                                       self.img_num))
+        print("total_time(ms): {}, img_num: {}, batch_size: {}".format(
+            total_time * 1000, self.img_num, self.img_num / self.repeats))
         preprocess_time = round(
-            self.preprocess_time_s.value() / self.img_num,
+            self.preprocess_time_s.value() / self.repeats,
             4) if average else self.preprocess_time_s.value()
         postprocess_time = round(
-            self.postprocess_time_s.value() / self.img_num,
+            self.postprocess_time_s.value() / self.repeats,
             4) if average else self.postprocess_time_s.value()
-        inference_time = round(self.inference_time_s.value() / self.img_num,
+        inference_time = round(self.inference_time_s.value() / self.repeats,
                                4) if average else self.inference_time_s.value()
 
-        average_latency = total_time / self.img_num
+        average_latency = total_time / self.repeats
         print("average latency time(ms): {:.2f}, QPS: {:2f}".format(
             average_latency * 1000, 1 / average_latency))
-        print(
-            "preprocess_time(ms): {:.2f}, inference_time(ms): {:.2f}, postprocess_time(ms): {:.2f}".
-            format(preprocess_time * 1000, inference_time * 1000,
-                   postprocess_time * 1000))
+        print("preprocess_time_per_im(ms): {:.2f}, "
+              "inference_time_per_batch(ms): {:.2f}, "
+              "postprocess_time_per_im(ms): {:.2f}".format(
+                  preprocess_time * 1000, inference_time * 1000,
+                  postprocess_time * 1000))
 
     def report(self, average=False):
         dic = {}
         dic['preprocess_time_s'] = round(
-            self.preprocess_time_s.value() / self.img_num,
+            self.preprocess_time_s.value() / self.repeats,
             4) if average else self.preprocess_time_s.value()
         dic['postprocess_time_s'] = round(
-            self.postprocess_time_s.value() / self.img_num,
+            self.postprocess_time_s.value() / self.repeats,
             4) if average else self.postprocess_time_s.value()
         dic['inference_time_s'] = round(
-            self.inference_time_s.value() / self.img_num,
+            self.inference_time_s.value() / self.repeats,
             4) if average else self.inference_time_s.value()
         dic['img_num'] = self.img_num
         total_time = self.preprocess_time_s.value(
         ) + self.inference_time_s.value() + self.postprocess_time_s.value()
         dic['total_time_s'] = round(total_time, 4)
+        dic['batch_size'] = self.img_num / self.repeats
         return dic
+
+    def reset(self):
+        self.preprocess_time_s.reset()
+        self.inference_time_s.reset()
+        self.postprocess_time_s.reset()
+        self.img_num = 0
+        self.repeats = 0

+ 1 - 97
static/docs/examples/industrial_quality_inspection/README.md

@@ -1,99 +1,3 @@
 # 工业质检
 
-本案例面向工业质检场景里的铝材表面缺陷检测,提供了针对GPU端和CPU端两种部署场景下基于PaddleX的解决方案,希望通过梳理优化模型精度和性能的思路能帮助用户更高效地解决实际质检应用中的问题。
-
-## 1. GPU端解决方案
-
-### 1.1 数据集介绍
-
-本案例使用[天池铝材表面缺陷检测初赛](https://tianchi.aliyun.com/competition/entrance/231682/introduction)数据集,共有3005张图片,分别检测擦花、杂色、漏底、不导电、桔皮、喷流、漆泡、起坑、脏点和角位漏底10种缺陷,这10种缺陷的定义和示例可点击文档[天池铝材表面缺陷检测初赛数据集示例](./dataset.md)查看。
-
-将这3005张图片按9:1随机切分成2713张图片的训练集和292张图片的验证集。
-
-### 1.2 精度优化
-
-本小节侧重展示在模型迭代过程中优化精度的思路,在本案例中,有些优化策略获得了精度收益,而有些没有。在其他质检场景中,可根据实际情况尝试这些优化策略。点击文档[精度优化](./accuracy_improvement.md)查看。
-
-### 1.3 性能优化
-
-在完成模型精度优化之后,从以下两个方面对模型进行加速:
-
-#### (1) 减少FPN部分的通道数量
-
-将FPN部分的通道数量由原本的256减少至64,使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时设置参数`fpn_num_channels`为64即可,需要重新对模型进行训练。
-
-#### (2) 减少测试阶段的候选框数量
-
-将测试阶段RPN部分做非极大值抑制计算的候选框数量由原本的6000减少至500,将RPN部分做完非极大值抑制后保留的候选框数量由原本的1000减少至300。使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时设置参数`test_pre_nms_top_n`为500,`test_post_nms_top_n`为300。
-
-采用Fluid C++预测引擎在Tesla P40上测试模型的推理时间(输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间),输入大小设置为800x1333,加速前后推理时间如下表所示:
-
-| 模型 | 推理时间 (ms/image)| VOC mAP (%) |
-| -- | -- | -- |
-| baseline | 66.51 | 88.87 |
-| + fpn channel=64 + test proposal=pre/post topk 500/300 | 46.08 | 87.72 |
-
-### 1.4 最终方案
-
-本案例面向GPU端的最终方案是选择二阶段检测模型FasterRCNN,其骨干网络选择加入了可变形卷积(DCN)的ResNet50_vd,训练时使用SSLD蒸馏方案训练得到的ResNet50_vd预训练模型,FPN部分的通道数量设置为64。使用复核过的数据集,训练阶段数据增强策略采用RandomHorizontalFlip、RandomDistort、RandomCrop,并加入背景图片。测试阶段的RPN部分做非极大值抑制计算的候选框数量由原本的6000减少至500、做完非极大值抑制后保留的候选框数量由原本的1000减少至300。模型在验证集上的VOC mAP为87.72%。
-
-在Tesla P40的Linux系统下,对于输入大小是800 x 1333的模型,图像预处理时长为30ms/image,模型的推理时间为46.08ms/image,包括输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间。
-
-| 模型 | VOC mAP (%) | 推理时间 (ms/image)
-| -- | -- | -- |
-| FasterRCNN-ResNet50_vd_ssld | 81.05 | 48.62 |
-| + dcn | 88.09 | 66.51 |
-| + RandomHorizontalFlip/RandomDistort/RandomCrop | 90.23| 66.51 |
-| + background images | 88.87 | 66.51 |
-| + fpn channel=64 | 87.79 | 48.65 |
-| + test proposal=pre/post topk 500/300 | 87.72 | 46.08 |
-
-具体的训练和部署流程点击文档[GPU端最终解决方案](./gpu_solution.md)进行查看。
-
-## 2. CPU端解决方案
-
-为了实现高效的模型推理,面向CPU端的模型选择精度和效率皆优的单阶段检测模型YOLOv3,骨干网络选择基于PaddleClas中SSLD蒸馏方案训练得到的MobileNetv3_large。训练完成后,对模型做剪裁操作,以提升模型的性能。模型在验证集上的VOC mAP为79.02%。
-
-部署阶段,借助OpenVINO预测引擎完成在Intel(R) Core(TM) i9-9820X CPU @ 3.30GHz Windows系统下高效推理。对于输入大小是608 x 608的模型,图像预处理时长为38.69 ms/image,模型的推理时间为34.50ms/image,
-
-| 模型 | VOC mAP (%) | Inference Speed (ms/image)
-| -- | -- | -- |
-| YOLOv3-MobileNetv3_ssld | 78.52 | 56.71 |
-| pruned YOLOv3-MobileNetv3_ssld | 79.02 | 34.50 |
-
-### 模型训练
-
-[环境前置依赖](./gpu_solution.html#id1)、[下载PaddleX源码](./gpu_solution.html#paddlex)、[下载数据集](./gpu_solution.html#id3)与GPU端是一样的,可点击文档[GPU端最终解决方案](./gpu_solution.md)查看,在此不做赘述。
-
-如果不想再次训练模型,可以直接下载已经训练好的模型完成后面的模型测试和部署推理:
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/models/yolov3_mobilenetv3_large_pruned.tar.gz
-tar xvf yolov3_mobilenetv3_large_pruned.tar.gz
-```
-
-运行以下代码进行模型训练,代码会自动下载数据集,如若事先下载了数据集,需将下载和解压铝材缺陷检测数据集的相关行注释掉。代码中默认使用0,1,2,3,4,5,6,7号GPU训练,可根据实际情况设置卡号并调整`batch_size`和`learning_rate`。
-
-```
-python train_yolov3.py
-```
-
-### 模型剪裁
-
-运行以下代码,分析在不同的精度损失下模型各层的剪裁比例:
-
-```
-python params_analysis.py
-```
-
-设置可允许的精度损失为0.05,对模型进行剪裁,剪裁后需要重新训练模型:
-
-```
-python train_pruned_yolov3.py
-```
-
-[分析预测错误的原因](./gpu_solution.html#id6)、[统计图片级召回率和误检率](./gpu_solution.html#id7)、[模型测试](./gpu_solution.html#id8)这些步骤与GPU端是一样的,可点击文档[GPU端最终解决方案](./gpu_solution.md)查看,在此不做赘述。
-
-### 推理部署
-
-本案例采用C++部署方式,通过OpenVINO将模型部署在Intel(R) Core(TM) i9-9820X CPU @ 3.30GHz的Windows系统下,具体的部署流程请参考文档[PaddleX模型多端安全部署/OpenVINO部署](https://paddlex.readthedocs.io/zh_CN/develop/deploy/openvino/index.html)。
+案例重构中

+ 0 - 97
static/docs/examples/industrial_quality_inspection/accuracy_improvement.md

@@ -1,97 +0,0 @@
-# 精度优化
-
-本小节侧重展示在模型迭代过程中优化精度的思路,在本案例中,有些优化策略获得了精度收益,而有些没有。在其他质检场景中,可根据实际情况尝试这些优化策略。
-
-## (1) 基线模型选择
-
-相较于单阶段检测模型,二阶段检测模型的精度更高但是速度更慢。考虑到是部署到GPU端,本案例选择二阶段检测模型FasterRCNN作为基线模型,其骨干网络选择ResNet50_vd,并使用基于PaddleClas中SSLD蒸馏方案训练得到的ResNet50_vd预训练模型(ImageNet1k验证集上Top1 Acc为82.39%)。训练完成后,模型在验证集上的精度VOC mAP为73.36%。
-
-## (2) 模型效果分析
-
-使用PaddleX提供的[paddlex.det.coco_error_analysis](https://paddlex.readthedocs.io/zh_CN/develop/apis/visualize.html#paddlex-det-coco-error-analysis)接口对模型在验证集上预测错误的原因进行分析,分析结果以图表的形式展示如下:
-
-| all classes| 擦花 | 杂色 | 漏底 | 不导电 | 桔皮 | 喷流 | 漆泡 | 起坑 | 脏点 | 角位漏底 |
-| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
-| ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/allclasses_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/cahua_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/zase_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/loudi_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/budaodian_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/jupi_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/penliu_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/qipao_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/qikeng_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/zangdian_analysis_example.png) | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/jiaoweiloudi_analysis_example.png) |
-
-分析图表展示了7条Precision-Recall(PR)曲线,每一条曲线表示的Average Precision (AP)比它左边那条高,原因是逐步放宽了评估要求。以擦花类为例,各条PR曲线的评估要求解释如下:
-
-* C75: 在IoU设置为0.75时的PR曲线, AP为0.001。
-* C50: 在IoU设置为0.5时的PR曲线,AP为0.622。C50与C75之间的白色区域面积代表将IoU从0.75放宽至0.5带来的AP增益。
-* Loc: 在IoU设置为0.1时的PR曲线,AP为0.740。Loc与C50之间的蓝色区域面积代表将IoU从0.5放宽至0.1带来的AP增益。蓝色区域面积越大,表示越多的检测框位置不够精准。
-* Sim: 在Loc的基础上,如果检测框与真值框的类别不相同,但两者同属于一个亚类,则不认为该检测框是错误的,在这种评估要求下的PR曲线, AP为0.742。Sim与Loc之间的红色区域面积越大,表示子类间的混淆程度越高。VOC格式的数据集所有的类别都属于同一个亚类。
-* Oth: 在Sim的基础上,如果检测框与真值框的亚类不相同,则不认为该检测框是错误的,在这种评估要求下的PR曲线,AP为0.742。Oth与Sim之间的绿色区域面积越大,表示亚类间的混淆程度越高。VOC格式的数据集中所有的类别都属于同一个亚类,故不存在亚类间的混淆。
-* BG: 在Oth的基础上,背景区域上的检测框不认为是错误的,在这种评估要求下的PR曲线,AP为92.1。BG与Oth之间的紫色区域面积越大,表示背景区域被误检的数量越多。
-* FN: 在BG的基础上,漏检的真值框不认为是错误的,在这种评估要求下的PR曲线,AP为1.00。FN与BG之间的橙色区域面积越大,表示漏检的真值框数量越多。
-
-从分析图表中可以看出,杂色、桔皮、起坑三类检测效果较好,角位漏底存在少许检测框没有达到IoU 0.5的情况,问题较多的是擦花、不导电、喷流、漆泡、脏点。擦花类最严重的问题是误检、位置不精准、漏检,不导电类最严重的问题是漏检、位置不精准,喷流类和漆泡类最严重的问题是位置不精准、误检,脏点类最严重的问题是误检、漏检。为进一步理解造成这些问题的原因,将验证集上的预测结果进行了可视化,然后发现数据集标注存在以下问题:
-
-* 轻微的缺陷不视为缺陷,但轻微的界定不明确,有些轻微的缺陷被标注了,造成误检较多
-* 不导电、漏底、角位漏底外观极其相似,肉眼难以区分,导致这三类极其容易混淆而使得评估时误检或漏检的产生
-* 有些轻微的擦花和脏点被标注了,有些明显的反而没有被标注,造成了这两类误检和漏检情况都较为严重
-* 喷流和漆泡多为连续性的缺陷,一处喷流,其水平线上还会有其他喷流,一个气泡,其水平线上还会有一排气泡。但有时候把这些连续性的缺陷标注成一个目标,有时候又单独地标注不同的部分。导致模型有时候检测单独部分,有时候又检测整体,造成这两类位置不精准、误检较多。
-
-## (3) 数据复核
-
-为了减少原始数据标注的诸多问题对模型优化的影响,需要对数据进行复核。复核准则示例如下:
-
-* 擦花:不明显的擦花不标注,面状擦花以同一个框表示,条状擦花一条擦花以一个框表示
-* 漏底、角位漏底、不导电:三者过于相似,归为一类
-* 桔皮:忽略不是大颗粒状的表面
-* 喷流:明显是一条喷流的就用一个框表示,不是的话用多个框表示
-* 漆泡:不要单独标一个点,一连串点标一个框
-* 脏点:忽略轻微脏点
-
-对数据集复核并重新标注后,将FasterRCNN-ResNet50_vd_ssld重新在训练集上进行训练,模型在验证集上的VOC mAP为81.05%。
-
-## (4) 可变形卷积加入
-
-由于喷流、漆泡的形态不规则,导致这两类的很多预测框位置不精准。为了解决该问题,选择在骨干网络ResNet50_vd中使用可变形卷积(DCN)。重新训练后,模型在验证集上的VOC mAP为88.09%,喷流的VOC AP由57.3%提升至78.7%,漆泡的VOC AP由74.7%提升至96.7%。
-
-## (5) 数据增强选择
-
-在(4)的基础上,选择加入一些数据增强策略来进一步提升模型的精度。本案例选择同时使用[RandomHorizontalFlip](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomhorizontalflip)、[RandomDistort](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomdistort)、[RandomCrop](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomcrop)这三种数据增强方法,重新训练后的模型在验证集上的VOC mAP为90.23%。
-
-除此之外,还可以尝试的数据增强方式有[MultiScaleTraining](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#resizebyshort)、[RandomExpand](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomexpand)。本案例使用的铝材表面缺陷检测数据集中,同一个类别的尺度变化不大,使用MultiScaleTraining或者RandomExpand反而使得原始数据分布发生改变。对此,本案例也进行了实验验证,使用RandomHorizontalFlip + RandomDistort + RandomCrop + MultiScaleTraining数据增强方式训练得到的模型在验证集上的VOC mAP为87.15%,使用RandomHorizontalFlip + RandomDistort + RandomCrop + RandomExpand数据增强方式训练得到的模型在验证集上的的VOC mAP为88.56%。
-
-## (6) 背景图片加入
-
-本案例将数据集中提供的背景图片按9:1切分成了1116张、135张两部分,并使用(5)中训练好的模型在135张背景图片上进行测试,发现图片级误检率高达21.5%。为了降低模型的误检率,使用[paddlex.datasets.VOCDetection.add_negative_samples](https://paddlex.readthedocs.io/zh_CN/develop/apis/datasets.html#add-negative-samples)接口将1116张背景图片加入到原本的训练集中,重新训练后图片级误检率降低至4%。为了不让训练被背景图片主导,本案例通过将`train_list.txt`中的文件路径多写了一遍,从而增加有目标图片的占比。
-
-| 模型 | VOC mAP (%) | 有缺陷图片级召回率 | 背景图片级误检率 |
-| -- | -- | -- | -- |
-| FasterRCNN-ResNet50_vd_ssld + DCN + RandomHorizontalFlip + RandomDistort + RandomCrop | 90.23 | 95.5 | 21.5 |
-| FasterRCNN-ResNet50_vd_ssld + DCN + RandomHorizontalFlip + RandomDistort + RandomCrop + 背景图片 | 88.87 | 95.2 | 4 |
-
-【名词解释】
-
-* 图片级别的召回率:只要在有目标的图片上检测出目标(不论框的个数),该图片被认为召回。批量有目标图片中被召回图片所占的比例,即为图片级别的召回率。
-* 图片级别的误检率:只要在无目标的图片上检测出目标(不论框的个数),该图片被认为误检。批量无目标图片中被误检图片所占的比例,即为图片级别的误检率。
-
-## (7) 分类损失函数选择
-
-降低误检率的措施除了(6)中提到的将背景图片加入训练,还可以将RPN部分的分类损失函数选择为`SigmoidFocalLoss`,将更多的anchors加入训练,增加难分样本的在损失函数的比重进而降低误检率。在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时将参数`rpn_cls_loss`设置为'SigmoidFocalLoss',同时需要调整参数`rpn_focal_loss_alpha`、`rpn_focal_loss_gamma`、`rpn_batch_size_per_im`、`rpn_fg_fraction`的设置。
-
-## (8) 位置回归损失函数选择
-
-RCNN部分的位置回归损失函数除了'SmoothL1Loss'以外,还可以选择'CIoULoss',使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时设置参数`rcnn_bbox_loss`即可。在本案例中,选择'CIoULoss'并没有带来精度收益,故还是选择'SmoothL1Loss'。其他质检场景下,也可尝试使用'CIoULoss'。
-
-## (9) 正负样本采样方式选择
-
-当目标物体的区域只占图像的一小部分时,可以考虑采用[LibraRCNN](https://arxiv.org/abs/1904.02701)中提出的IoU-balanced Sampling采样方式来获取更多的难分负样本。使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时将参数`bbox_assigner`设置为'LibraBBoxAssigner'即可。
-
-## (10) 预处理对比度增强
-
-工业界常用灰度相机采集图片,会存在目标与周围背景对比度不明显而无法被检测出的情况。在这种情况下,可以在定义预处理的时候使用[paddlex.det.transforms.CLAHE](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#clahe)对灰度图像的对比度进行增强。
-
-灰度图:
-
-![](../../../examples/industrial_quality_inspection/image/before_clahe.png)
-
-对比度增加后的灰度图:
-
-![](../../../examples/industrial_quality_inspection/image/after_clahe.png) |
-
-## (11) 样本生成
-
-对于数量较少的类别或者小目标,可以通过把这些目标物体粘贴在背景图片上来生成新的图片和标注文件,并把这些新的样本加入到训练中从而提升模型精度。目前PaddleX提供了实现该功能的接口,详细见[paddlex.det.paste_objects](https://paddlex.readthedocs.io/zh_CN/develop/apis/tools.html#paddlex-det-paste-objects),需要注意的是,前景目标颜色与背景颜色差异较大时生成的新图片才会比较逼真。

+ 0 - 14
static/docs/examples/industrial_quality_inspection/dataset.md

@@ -1,14 +0,0 @@
-# 天池铝材表面缺陷检测初赛数据集示例
-
-| 序号 | 瑕疵名称 | 瑕疵成因 | 瑕疵图片示例 | 图片数量 |
-| -- | -- | -- | -- | -- |
-| 1 | 擦花(擦伤)| 表面处理(喷涂)后有轻微擦到其它的东西,造成痕迹 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/ca_hua_example.png) | 128 |
-| 2 | 杂色 | 喷涂换颜料的时候,装颜料的容器未清洗干净,造成喷涂时有少量其它颜色掺入 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/za_se_example.png) |365 |
-| 3 | 漏底 | 喷粉效果不好,铝材大量底色露出 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/lou_di_example.png) | 538 |
-| 4 | 不导电 | 直接喷不到铝材表面上去 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/bu_dao_dian_example.png) | 390 |
-|5 | 桔皮 | 表面处理后涂层表面粗糙,大颗粒 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/ju_pi_example.png) | 173 |
-| 6 | 喷流| 喷涂时油漆稀从上流下来,有流动痕迹 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/pen_liu_example.png) | 86 |
-| 7 |漆泡 | 喷涂后表面起泡,小而多| ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/qi_pao_example.png) | 82 |
-| 8 | 起坑 | 型材模具问题,做出来的型材一整条都有一条凹下去的部分 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/qi_keng_example.png.png) | 407 |
-| 9 | 脏点 | 表面处理时,有灰尘或一些脏东西未能擦掉,导致涂层有颗粒比较突出 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/zang_dian_example.png) | 261 |
-| 10 | 角位漏底 | 在型材角落出现的露底 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/jiao_wei_lou_di_example.png) | 346 |

+ 0 - 116
static/docs/examples/industrial_quality_inspection/gpu_solution.md

@@ -1,116 +0,0 @@
-# GPU端最终解决方案
-
-本案例面向GPU端的最终方案是选择二阶段检测模型FasterRCNN,其骨干网络选择加入了可变形卷积(DCN)的ResNet50_vd,训练时使用SSLD蒸馏方案训练得到的ResNet50_vd预训练模型,FPN部分的通道数量设置为64,训练阶段数据增强策略采用RandomHorizontalFlip、RandomDistort、RandomCrop,并加入背景图片,测试阶段的RPN部分做非极大值抑制计算的候选框数量由原本的6000减少至500、做完非极大值抑制后保留的候选框数量由原本的1000减少至300。
-
-在Tesla P40的Linux系统下,对于输入大小是800 x 1333的模型,图像预处理时长为30ms/image,模型的推理时间为46.08ms/image,包括输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间。
-
-| 模型 | VOC mAP (%) | 推理时间 (ms/image)
-| -- | -- | -- |
-| FasterRCNN-ResNet50_vd_ssld | 81.05 | 48.62 |
-| + dcn | 88.09 | 66.51 |
-| + RandomHorizontalFlip/RandomDistort/RandomCrop | 90.23| 66.51 |
-| + background images | 88.87 | 66.51 |
-| + fpn channel=64 | 87.79 | 48.65 |
-| + test proposal=pre/post topk 500/300 | 87.72 | 46.08 |
-
-## 前置依赖
-
-* Paddle paddle >= 1.8.0
-* Python >= 3.5
-* PaddleX >= 1.3.0
-
-## 模型训练
-
-### (1) 下载PaddleX源码
-
-```
-git clone https://github.com/PaddlePaddle/PaddleX
-
-cd PaddleX/examples/industrial_quality_inspection
-```
-
-### (2) 下载数据集
-
-因数据集较大,可运行以下代码提前将数据集下载并解压。训练代码中也会自动下载数据集,所以这一步不是必须的。
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/aluminum_inspection.tar.gz
-tar xvf aluminum_inspection.tar.gz
-```
-### (3) 下载预先训练好的模型
-
-如果不想再次训练模型,可以直接下载已经训练好的模型完成后面的模型测试和部署推理:
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/models/faster_rcnn_r50_vd_dcn.tar.gz
-tar xvf faster_rcnn_r50_vd_dcn.tar.gz
-```
-### (4) 训练
-
-运行以下代码进行模型训练,代码会自动下载数据集,如若事先下载了数据集,需将下载和解压铝材缺陷检测数据集的相关行注释掉。代码中默认使用0,1,2,3,4号GPU训练,可根据实际情况设置卡号并调整`batch_size`和`learning_rate`。
-
-```
-python train_rcnn.py
-```
-
-### (5) 分析预测错误的原因
-
-在模型迭代过程中,运行以下代码可完成模型效果的分析并生成分析结果图表:
-
-```
-python error_analysis.py
-```
-
-可参考[性能优化部分的模型效果分析](./accuracy_improvement.html#id3)来理解当前模型预测错误的原因。
-
-运行以下代码,生成可视化真值和预测结果的对比图以进一步理解模型效果,代码中的置信度阈值可根据实际情况进行调整。
-
-```
-python compare.py
-```
-
-![](../../../examples/industrial_quality_inspection/image/compare_budaodian-116.jpg)
-
-左边是可视化真值,右边是可视化预测结果。
-
-### (6) 统计图片级召回率和误检率
-
-模型迭代完成后,计算不同置信度阈值下[图片级召回率](./accuracy_improvement.html#id7)和[图片级误检率](./accuracy_improvement.html#id7),找到符合要求的召回率和误检率,对应的置信度阈值用于后续模型预测阶段。
-
-```
-python cal_tp_fp.py
-```
-
-执行后会生成图表`image-level_tp_fp.png`和文件`tp_fp_list.txt`,示意如下:
-
-图表`image-level_tp_fp.png`:
-
-![](../../../examples/industrial_quality_inspection/image/image-level_tp_fp.png)
-
-文件[tp_fp_list.txt](tp_fp_list.md)
-
-图表`image-level_tp_fp.png`中左边子图,横坐标表示不同置信度阈值下计算得到的图片级召回率,纵坐标表示各图片级召回率对应的图片级误检率。右边子图横坐标表示图片级召回率,纵坐标表示各图片级召回率对应的置信度阈值。从图表中可较为直观地看出当前模型的图片级召回率和误检率的量级,从文件`tp_fp_list.txt`可以查到具体数值,例如在图片级召回率/图片级误检率为[0.9589,0.0074]这一组符合要求,就将对应的置信度阈值0.90选取为后续预测推理的阈值。
-
-### (7) 模型测试
-
-测试集因没有标注文件,这里单独下载测试集图片:
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/aluminum_inspection_test.tar.gz
-tar xvf aluminum_inspection_test.tar.gz
-```
-
-加载训练好的模型,使用(5)选取的置信度阈值0.90对验证集图片或者测试集图片进行预测:
-
-```
-python predict.py
-```
-可视化预测结果示例如下:
-
-![](../../../examples/industrial_quality_inspection/image/visualize_budaodian-116.jpg)
-
-## 推理部署
-
-本案例采用C++部署方式将模型部署在Tesla P40的Linux系统下,具体的C++部署流程请参考文档[PaddleX模型多端安全部署/C++部署](https://paddlex.readthedocs.io/zh_CN/develop/deploy/server/cpp/index.html)。
-
-对于输入大小是800 x 1333的模型,图像预处理时长为30ms/image。值得一提的是预处理中的Normalize操作比较耗时,因此在设置预处理操作时,可以先进行Resize操作再做Normalize。模型的推理时间为46.08ms/image,包括输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间。

+ 0 - 102
static/docs/examples/industrial_quality_inspection/tp_fp_list.md

@@ -1,102 +0,0 @@
-| score | recall rate | false-positive rate |
-| -- | -- | -- |
-| 0.000000 | 0.982877 | 0.022222 |
-| 0.010000 | 0.982877 | 0.022222 |
-| 0.020000 | 0.982877 | 0.022222 |
-| 0.030000 | 0.982877 | 0.022222 |
-| 0.040000 | 0.982877 | 0.022222 |
-| 0.050000 | 0.982877 | 0.022222 |
-| 0.060000 | 0.982877 | 0.022222 |
-| 0.070000 | 0.979452 | 0.022222 |
-| 0.080000 | 0.979452 | 0.022222 |
-| 0.090000 | 0.979452 | 0.022222 |
-| 0.100000 | 0.979452 | 0.022222 |
-| 0.110000 | 0.979452 | 0.022222 |
-| 0.120000 | 0.979452 | 0.022222 |
-| 0.130000 | 0.979452 | 0.022222 |
-| 0.140000 | 0.976027 | 0.022222 |
-| 0.150000 | 0.976027 | 0.022222 |
-| 0.160000 | 0.976027 | 0.022222 |
-| 0.170000 | 0.976027 | 0.022222 |
-| 0.180000 | 0.976027 | 0.022222 |
-| 0.190000 | 0.976027 | 0.022222 |
-| 0.200000 | 0.976027 | 0.022222 |
-| 0.210000 | 0.976027 | 0.022222 |
-| 0.220000 | 0.976027 | 0.022222 |
-| 0.230000 | 0.976027 | 0.022222 |
-| 0.240000 | 0.976027 | 0.022222 |
-| 0.250000 | 0.976027 | 0.022222 |
-| 0.260000 | 0.976027 | 0.014815 |
-| 0.270000 | 0.976027 | 0.014815 |
-| 0.280000 | 0.976027 | 0.014815 |
-| 0.290000 | 0.976027 | 0.014815 |
-| 0.300000 | 0.976027 | 0.014815 |
-| 0.310000 | 0.976027 | 0.014815 |
-| 0.320000 | 0.976027 | 0.014815 |
-| 0.330000 | 0.976027 | 0.014815 |
-| 0.340000 | 0.976027 | 0.014815 |
-| 0.350000 | 0.976027 | 0.014815 |
-| 0.360000 | 0.976027 | 0.014815 |
-| 0.370000 | 0.976027 | 0.014815 |
-| 0.380000 | 0.976027 | 0.014815 |
-| 0.390000 | 0.976027 | 0.014815 |
-| 0.400000 | 0.976027 | 0.014815 |
-| 0.410000 | 0.976027 | 0.014815 |
-| 0.420000 | 0.976027 | 0.014815 |
-| 0.430000 | 0.976027 | 0.014815 |
-| 0.440000 | 0.972603 | 0.014815 |
-| 0.450000 | 0.972603 | 0.014815 |
-| 0.460000 | 0.972603 | 0.014815 |
-| 0.470000 | 0.972603 | 0.014815 |
-| 0.480000 | 0.972603 | 0.014815 |
-| 0.490000 | 0.972603 | 0.014815 |
-| 0.500000 | 0.972603 | 0.014815 |
-| 0.510000 | 0.972603 | 0.014815 |
-| 0.520000 | 0.972603 | 0.014815 |
-| 0.530000 | 0.972603 | 0.014815 |
-| 0.540000 | 0.972603 | 0.014815 |
-| 0.550000 | 0.972603 | 0.014815 |
-| 0.560000 | 0.969178 | 0.014815 |
-| 0.570000 | 0.969178 | 0.014815 |
-| 0.580000 | 0.969178 | 0.014815 |
-| 0.590000 | 0.969178 | 0.014815 |
-| 0.600000 | 0.969178 | 0.014815 |
-| 0.610000 | 0.969178 | 0.014815 |
-| 0.620000 | 0.969178 | 0.014815 |
-| 0.630000 | 0.969178 | 0.014815 |
-| 0.640000 | 0.969178 | 0.014815 |
-| 0.650000 | 0.969178 | 0.014815 |
-| 0.660000 | 0.969178 | 0.014815 |
-| 0.670000 | 0.969178 | 0.014815 |
-| 0.680000 | 0.969178 | 0.014815 |
-| 0.690000 | 0.969178 | 0.014815 |
-| 0.700000 | 0.969178 | 0.014815 |
-| 0.710000 | 0.969178 | 0.014815 |
-| 0.720000 | 0.969178 | 0.014815 |
-| 0.730000 | 0.969178 | 0.014815 |
-| 0.740000 | 0.969178 | 0.014815 |
-| 0.750000 | 0.969178 | 0.014815 |
-| 0.760000 | 0.969178 | 0.014815 |
-| 0.770000 | 0.965753 | 0.014815 |
-| 0.780000 | 0.965753 | 0.014815 |
-| 0.790000 | 0.965753 | 0.014815 |
-| 0.800000 | 0.962329 | 0.014815 |
-| 0.810000 | 0.962329 | 0.014815 |
-| 0.820000 | 0.962329 | 0.014815 |
-| 0.830000 | 0.962329 | 0.014815 |
-| 0.840000 | 0.962329 | 0.014815 |
-| 0.850000 | 0.958904 | 0.014815 |
-| 0.860000 | 0.958904 | 0.014815 |
-| 0.870000 | 0.958904 | 0.014815 |
-| 0.880000 | 0.958904 | 0.014815 |
-| 0.890000 | 0.958904 | 0.014815 |
-| 0.900000 | 0.958904 | 0.007407 |
-| 0.910000 | 0.958904 | 0.007407 |
-| 0.920000 | 0.958904 | 0.007407 |
-| 0.930000 | 0.955479 | 0.007407 |
-| 0.940000 | 0.955479 | 0.007407 |
-| 0.950000 | 0.955479 | 0.007407 |
-| 0.960000 | 0.955479 | 0.007407 |
-| 0.970000 | 0.955479 | 0.007407 |
-| 0.980000 | 0.941781 | 0.000000 |
-| 0.990000 | 0.893836 | 0.000000 |

+ 1 - 97
static/examples/industrial_quality_inspection/README.md

@@ -1,99 +1,3 @@
 # 工业质检
 
-本案例面向工业质检场景里的铝材表面缺陷检测,提供了针对GPU端和CPU端两种部署场景下基于PaddleX的解决方案,希望通过梳理优化模型精度和性能的思路能帮助用户更高效地解决实际质检应用中的问题。
-
-## 1. GPU端解决方案
-
-### 1.1 数据集介绍
-
-本案例使用[天池铝材表面缺陷检测初赛](https://tianchi.aliyun.com/competition/entrance/231682/introduction)数据集,共有3005张图片,分别检测擦花、杂色、漏底、不导电、桔皮、喷流、漆泡、起坑、脏点和角位漏底10种缺陷,这10种缺陷的定义和示例可点击文档[天池铝材表面缺陷检测初赛数据集示例](./dataset.md)查看。
-
-将这3005张图片按9:1随机切分成2713张图片的训练集和292张图片的验证集。
-
-### 1.2 精度优化
-
-本小节侧重展示在模型迭代过程中优化精度的思路,在本案例中,有些优化策略获得了精度收益,而有些没有。在其他质检场景中,可根据实际情况尝试这些优化策略。点击文档[精度优化](./accuracy_improvement.md)查看。
-
-### 1.3 性能优化
-
-在完成模型精度优化之后,从以下两个方面对模型进行加速:
-
-#### (1) 减少FPN部分的通道数量
-
-将FPN部分的通道数量由原本的256减少至64,使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时设置参数`fpn_num_channels`为64即可,需要重新对模型进行训练。
-
-#### (2) 减少测试阶段的候选框数量
-
-将测试阶段RPN部分做非极大值抑制计算的候选框数量由原本的6000减少至500,将RPN部分做完非极大值抑制后保留的候选框数量由原本的1000减少至300。使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时设置参数`test_pre_nms_top_n`为500,`test_post_nms_top_n`为300。
-
-采用Fluid C++预测引擎在Tesla P40上测试模型的推理时间(输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间),输入大小设置为800x1333,加速前后推理时间如下表所示:
-
-| 模型 | 推理时间 (ms/image)| VOC mAP (%) |
-| -- | -- | -- |
-| baseline | 66.51 | 88.87 |
-| + fpn channel=64 + test proposal=pre/post topk 500/300 | 46.08 | 87.72 |
-
-### 1.4 最终方案
-
-本案例面向GPU端的最终方案是选择二阶段检测模型FasterRCNN,其骨干网络选择加入了可变形卷积(DCN)的ResNet50_vd,训练时使用SSLD蒸馏方案训练得到的ResNet50_vd预训练模型,FPN部分的通道数量设置为64。使用复核过的数据集,训练阶段数据增强策略采用RandomHorizontalFlip、RandomDistort、RandomCrop,并加入背景图片。测试阶段的RPN部分做非极大值抑制计算的候选框数量由原本的6000减少至500、做完非极大值抑制后保留的候选框数量由原本的1000减少至300。模型在验证集上的VOC mAP为87.72%。
-
-在Tesla P40的Linux系统下,对于输入大小是800 x 1333的模型,图像预处理时长为30ms/image,模型的推理时间为46.08ms/image,包括输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间。
-
-| 模型 | VOC mAP (%) | 推理时间 (ms/image)
-| -- | -- | -- |
-| FasterRCNN-ResNet50_vd_ssld | 81.05 | 48.62 |
-| + dcn | 88.09 | 66.51 |
-| + RandomHorizontalFlip/RandomDistort/RandomCrop | 90.23| 66.51 |
-| + background images | 88.87 | 66.51 |
-| + fpn channel=64 | 87.79 | 48.65 |
-| + test proposal=pre/post topk 500/300 | 87.72 | 46.08 |
-
-具体的训练和部署流程点击文档[GPU端最终解决方案](./gpu_solution.md)进行查看。
-
-## 2. CPU端解决方案
-
-为了实现高效的模型推理,面向CPU端的模型选择精度和效率皆优的单阶段检测模型YOLOv3,骨干网络选择基于PaddleClas中SSLD蒸馏方案训练得到的MobileNetv3_large。训练完成后,对模型做剪裁操作,以提升模型的性能。模型在验证集上的VOC mAP为79.02%。
-
-部署阶段,借助OpenVINO预测引擎完成在Intel(R) Core(TM) i9-9820X CPU @ 3.30GHz Windows系统下高效推理。对于输入大小是608 x 608的模型,图像预处理时长为38.69 ms/image,模型的推理时间为34.50ms/image。
-
-| 模型 | VOC mAP (%) | Inference Speed (ms/image)
-| -- | -- | -- |
-| YOLOv3-MobileNetv3_ssld | 81.31 | 56.71 |
-| pruned YOLOv3-MobileNetv3_ssld | 78.60 | 34.50 |
-
-### 模型训练
-
-[环境前置依赖](./gpu_solution.md#%E5%89%8D%E7%BD%AE%E4%BE%9D%E8%B5%96)、[下载PaddleX源码](./gpu_solution.md#1-%E4%B8%8B%E8%BD%BDpaddlex%E6%BA%90%E7%A0%81)、[下载数据集](./gpu_solution.md#2-%E4%B8%8B%E8%BD%BD%E6%95%B0%E6%8D%AE%E9%9B%86)与GPU端是一样的,可点击文档[GPU端最终解决方案](./gpu_solution.md)查看,在此不做赘述。
-
-如果不想再次训练模型,可以直接下载已经训练好的模型完成后面的模型测试和部署推理:
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/models/yolov3_mobilenetv3_large_pruned.tar.gz
-tar xvf yolov3_mobilenetv3_large_pruned.tar.gz
-```
-
-运行以下代码进行模型训练,代码会自动下载数据集,如若事先下载了数据集,需将下载和解压铝材缺陷检测数据集的相关行注释掉。代码中默认使用0,1,2,3,4,5,6,7号GPU训练,可根据实际情况设置卡号并调整`batch_size`和`learning_rate`。
-
-```
-python train_yolov3.py
-```
-
-### 模型剪裁
-
-运行以下代码,分析在不同的精度损失下模型各层的剪裁比例:
-
-```
-python params_analysis.py
-```
-
-设置可允许的精度损失为0.05,对模型进行剪裁,剪裁后需要重新训练模型:
-
-```
-python train_pruned_yolov3.py
-```
-
-[分析预测错误的原因](./gpu_solution.md#4-%E5%88%86%E6%9E%90%E9%A2%84%E6%B5%8B%E9%94%99%E8%AF%AF%E7%9A%84%E5%8E%9F%E5%9B%A0)、[统计图片级召回率和误检率](./gpu_solution.md#5-%E7%BB%9F%E8%AE%A1%E5%9B%BE%E7%89%87%E7%BA%A7%E5%8F%AC%E5%9B%9E%E7%8E%87%E5%92%8C%E8%AF%AF%E6%A3%80%E7%8E%87)、[模型测试](./gpu_solution.md#6-%E6%A8%A1%E5%9E%8B%E6%B5%8B%E8%AF%95)这些步骤与GPU端是一样的,可点击文档[GPU端最终解决方案](./gpu_solution.md)查看,在此不做赘述。
-
-### 推理部署
-
-本案例采用C++部署方式,通过OpenVINO将模型部署在Intel(R) Core(TM) i9-9820X CPU @ 3.30GHz的Windows系统下,具体的部署流程请参考文档[PaddleX模型多端安全部署/OpenVINO部署](https://paddlex.readthedocs.io/zh_CN/develop/deploy/openvino/index.html)。
+案例重构中

+ 0 - 93
static/examples/industrial_quality_inspection/accuracy_improvement.md

@@ -1,93 +0,0 @@
-# 精度优化
-
-本小节侧重展示在模型迭代过程中优化精度的思路,在本案例中,有些优化策略获得了精度收益,而有些没有。在其他质检场景中,可根据实际情况尝试这些优化策略。
-
-## (1) 基线模型选择
-
-相较于单阶段检测模型,二阶段检测模型的精度更高但是速度更慢。考虑到是部署到GPU端,本案例选择二阶段检测模型FasterRCNN作为基线模型,其骨干网络选择ResNet50_vd,并使用基于PaddleClas中SSLD蒸馏方案训练得到的ResNet50_vd预训练模型(ImageNet1k验证集上Top1 Acc为82.39%)。训练完成后,模型在验证集上的精度VOC mAP为73.36%。
-
-## (2) 模型效果分析
-
-使用PaddleX提供的[paddlex.det.coco_error_analysis](https://paddlex.readthedocs.io/zh_CN/develop/apis/visualize.html#paddlex-det-coco-error-analysis)接口对模型在验证集上预测错误的原因进行分析,分析结果以图表的形式展示如下:
-
-| all classes| 擦花 | 杂色 | 漏底 | 不导电 | 桔皮 | 喷流 | 漆泡 | 起坑 | 脏点 | 角位漏底 |
-| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
-| ![](image/allclasses_analysis_example.png) | ![](image/cahua_analysis_example.png) | ![](image/zase_analysis_example.png) | ![](image/loudi_analysis_example.png) | ![](image/budaodian_analysis_example.png) | ![](image/jupi_analysis_example.png) | ![](image/penliu_analysis_example.png) | ![](image/qipao_analysis_example.png) | ![](image/qikeng_analysis_example.png) | ![](image/zangdian_analysis_example.png) | ![](image/jiaoweiloudi_analysis_example.png) |
-
-分析图表展示了7条Precision-Recall(PR)曲线,每一条曲线表示的Average Precision (AP)比它左边那条高,原因是逐步放宽了评估要求。以擦花类为例,各条PR曲线的评估要求解释如下:
-
-* C75: 在IoU设置为0.75时的PR曲线, AP为0.001。
-* C50: 在IoU设置为0.5时的PR曲线,AP为0.622。C50与C75之间的白色区域面积代表将IoU从0.75放宽至0.5带来的AP增益。
-* Loc: 在IoU设置为0.1时的PR曲线,AP为0.740。Loc与C50之间的蓝色区域面积代表将IoU从0.5放宽至0.1带来的AP增益。蓝色区域面积越大,表示越多的检测框位置不够精准。
-* Sim: 在Loc的基础上,如果检测框与真值框的类别不相同,但两者同属于一个亚类,则不认为该检测框是错误的,在这种评估要求下的PR曲线, AP为0.742。Sim与Loc之间的红色区域面积越大,表示子类间的混淆程度越高。VOC格式的数据集所有的类别都属于同一个亚类。
-* Oth: 在Sim的基础上,如果检测框与真值框的亚类不相同,则不认为该检测框是错误的,在这种评估要求下的PR曲线,AP为0.742。Oth与Sim之间的绿色区域面积越大,表示亚类间的混淆程度越高。VOC格式的数据集中所有的类别都属于同一个亚类,故不存在亚类间的混淆。
-* BG: 在Oth的基础上,背景区域上的检测框不认为是错误的,在这种评估要求下的PR曲线,AP为92.1。BG与Oth之间的紫色区域面积越大,表示背景区域被误检的数量越多。
-* FN: 在BG的基础上,漏检的真值框不认为是错误的,在这种评估要求下的PR曲线,AP为1.00。FN与BG之间的橙色区域面积越大,表示漏检的真值框数量越多。
-
-从分析图表中可以看出,杂色、桔皮、起坑三类检测效果较好,角位漏底存在少许检测框没有达到IoU 0.5的情况,问题较多的是擦花、不导电、喷流、漆泡、脏点。擦花类最严重的问题是误检、位置不精准、漏检,不导电类最严重的问题是漏检、位置不精准,喷流类和漆泡类最严重的问题是位置不精准、误检,脏点类最严重的问题是误检、漏检。为进一步理解造成这些问题的原因,将验证集上的预测结果进行了可视化,然后发现数据集标注存在以下问题:
-
-* 轻微的缺陷不视为缺陷,但轻微的界定不明确,有些轻微的缺陷被标注了,造成误检较多
-* 不导电、漏底、角位漏底外观极其相似,肉眼难以区分,导致这三类极其容易混淆而使得评估时误检或漏检的产生
-* 有些轻微的擦花和脏点被标注了,有些明显的反而没有被标注,造成了这两类误检和漏检情况都较为严重
-* 喷流和漆泡多为连续性的缺陷,一处喷流,其水平线上还会有其他喷流,一个气泡,其水平线上还会有一排气泡。但有时候把这些连续性的缺陷标注成一个目标,有时候又单独地标注不同的部分。导致模型有时候检测单独部分,有时候又检测整体,造成这两类位置不精准、误检较多。
-
-## (3) 数据复核
-
-为了减少原始数据标注的诸多问题对模型优化的影响,需要对数据进行复核。复核准则示例如下:
-
-* 擦花:不明显的擦花不标注,面状擦花以同一个框表示,条状擦花一条擦花以一个框表示
-* 漏底、角位漏底、不导电:三者过于相似,归为一类
-* 桔皮:忽略不是大颗粒状的表面
-* 喷流:明显是一条喷流的就用一个框表示,不是的话用多个框表示
-* 漆泡:不要单独标一个点,一连串点标一个框
-* 脏点:忽略轻微脏点
-
-对数据集复核并重新标注后,将FasterRCNN-ResNet50_vd_ssld重新在训练集上进行训练,模型在验证集上的VOC mAP为81.05%。
-
-## (4) 可变形卷积加入
-
-由于喷流、漆泡的形态不规则,导致这两类的很多预测框位置不精准。为了解决该问题,选择在骨干网络ResNet50_vd中使用可变形卷积(DCN)。重新训练后,模型在验证集上的VOC mAP为88.09%,喷流的VOC AP由57.3%提升至78.7%,漆泡的VOC AP由74.7%提升至96.7%。
-
-## (5) 数据增强选择
-
-在(4)的基础上,选择加入一些数据增强策略来进一步提升模型的精度。本案例选择同时使用[RandomHorizontalFlip](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomhorizontalflip)、[RandomDistort](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomdistort)、[RandomCrop](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomcrop)这三种数据增强方法,重新训练后的模型在验证集上的VOC mAP为90.23%。
-
-除此之外,还可以尝试的数据增强方式有[MultiScaleTraining](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#resizebyshort)、[RandomExpand](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#randomexpand)。本案例使用的铝材表面缺陷检测数据集中,同一个类别的尺度变化不大,使用MultiScaleTraining或者RandomExpand反而使得原始数据分布发生改变。对此,本案例也进行了实验验证,使用RandomHorizontalFlip + RandomDistort + RandomCrop + MultiScaleTraining数据增强方式训练得到的模型在验证集上的VOC mAP为87.15%,使用RandomHorizontalFlip + RandomDistort + RandomCrop + RandomExpand数据增强方式训练得到的模型在验证集上的的VOC mAP为88.56%。
-
-## (6) 背景图片加入
-
-本案例将数据集中提供的背景图片按9:1切分成了1116张、135张两部分,并使用(5)中训练好的模型在135张背景图片上进行测试,发现图片级误检率高达21.5%。为了降低模型的误检率,使用[paddlex.datasets.VOCDetection.add_negative_samples](https://paddlex.readthedocs.io/zh_CN/develop/apis/datasets.html#add-negative-samples)接口将1116张背景图片加入到原本的训练集中,重新训练后图片级误检率降低至4%。为了不让训练被背景图片主导,本案例通过将`train_list.txt`中的文件路径多写了一遍,从而增加有目标图片的占比。
-
-| 模型 | VOC mAP (%) | 有缺陷图片级召回率 | 背景图片级误检率 |
-| -- | -- | -- | -- |
-| FasterRCNN-ResNet50_vd_ssld + DCN + RandomHorizontalFlip + RandomDistort + RandomCrop | 90.23 | 95.5 | 21.5 |
-| FasterRCNN-ResNet50_vd_ssld + DCN + RandomHorizontalFlip + RandomDistort + RandomCrop + 背景图片 | 88.87 | 95.2 | 4 |
-
-【名词解释】
-
-* 图片级别的召回率:只要在有目标的图片上检测出目标(不论框的个数),该图片被认为召回。批量有目标图片中被召回图片所占的比例,即为图片级别的召回率。
-* 图片级别的误检率:只要在无目标的图片上检测出目标(不论框的个数),该图片被认为误检。批量无目标图片中被误检图片所占的比例,即为图片级别的误检率。
-
-## (7) 分类损失函数选择
-
-降低误检率的措施除了(6)中提到的将背景图片加入训练,还可以将RPN部分的分类损失函数选择为`SigmoidFocalLoss`,将更多的anchors加入训练,增加难分样本的在损失函数的比重进而降低误检率。在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时将参数`rpn_cls_loss`设置为'SigmoidFocalLoss',同时需要调整参数`rpn_focal_loss_alpha`、`rpn_focal_loss_gamma`、`rpn_batch_size_per_im`、`rpn_fg_fraction`的设置。
-
-## (8) 位置回归损失函数选择
-
-RCNN部分的位置回归损失函数除了'SmoothL1Loss'以外,还可以选择'CIoULoss',使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时设置参数`rcnn_bbox_loss`即可。在本案例中,选择'CIoULoss'并没有带来精度收益,故还是选择'SmoothL1Loss'。其他质检场景下,也可尝试使用'CIoULoss'。
-
-## (9) 正负样本采样方式选择
-
-当目标物体的区域只占图像的一小部分时,可以考虑采用[LibraRCNN](https://arxiv.org/abs/1904.02701)中提出的IoU-balanced Sampling采样方式来获取更多的难分负样本。使用方式在定义模型[FasterRCNN](https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn)类时将参数`bbox_assigner`设置为'LibraBBoxAssigner'即可。
-
-## (10) 预处理对比度增强
-
-工业界常用灰度相机采集图片,会存在目标与周围背景对比度不明显而无法被检测出的情况。在这种情况下,可以在定义预处理的时候使用[paddlex.det.transforms.CLAHE](https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html#clahe)对灰度图像的对比度进行增强。
-
-| 灰度图 | 对比度增加后的灰度图 |
-| -- | -- |
-| ![](./image/before_clahe.png) | ![](./image/after_clahe.png) |
-
-## (11) 样本生成
-
-对于数量较少的类别或者小目标,可以通过把这些目标物体粘贴在背景图片上来生成新的图片和标注文件,并把这些新的样本加入到训练中从而提升模型精度。目前PaddleX提供了实现该功能的接口,详细见[paddlex.det.paste_objects](https://paddlex.readthedocs.io/zh_CN/develop/apis/tools.html#paddlex-det-paste-objects),需要注意的是,前景目标颜色与背景颜色差异较大时生成的新图片才会比较逼真。

+ 0 - 214
static/examples/industrial_quality_inspection/cal_tp_fp.py

@@ -1,214 +0,0 @@
-# coding: utf8
-# Copyright (c) 2020 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.
-
-# 环境变量配置,用于控制是否使用GPU
-# 说明文档:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html#gpu
-import argparse
-import os
-os.environ['CUDA_VISIBLE_DEVICES'] = '0'
-
-import os.path as osp
-import numpy as np
-import matplotlib
-matplotlib.use('Agg')
-import matplotlib.pyplot as plt
-import paddlex as pdx
-
-
-def cal_image_level_recall_rate(model, dataset_dir):
-    """计算置信度(Score)在[0, 1]内以间隔0.01递增取值时,模型在有目标的图像上的图片级召回率。
-
-    图片级召回率:只要在有目标的图片上检测出目标(不论框的个数),该图片被认为召回,
-       批量有目标图片中被召回的图片所占的比例,即为图片级别的召回率。
-
-    Args:
-        model (PaddleX model object): 已加载的PaddleX模型。
-        dataset_dir (str):数据集路径。
-
-    Returns:
-        numpy.array: 形状为101x1的数组,对应置信度从0到1按0.01递增取值时,计算所得图片级别的召回率。
-    """
-
-    print(
-        "Begin to calculate image-level recall rate of positive images. Please wait for a moment..."
-    )
-    file_list = osp.join(dataset_dir, 'val_list.txt')
-    tp = np.zeros((101, 1))
-    positive_num = 0
-    with open(file_list, 'r') as fr:
-        while True:
-            line = fr.readline()
-            if not line:
-                break
-            img_file, xml_file = [osp.join(dataset_dir, x) \
-                    for x in line.strip().split()[:2]]
-            if not osp.exists(img_file):
-                continue
-            if not osp.exists(xml_file):
-                continue
-
-            positive_num += 1
-            results = model.predict(img_file)
-            scores = list()
-            for res in results:
-                scores.append(res['score'])
-            if len(scores) > 0:
-                tp[0:int(np.round(max(scores) / 0.01)), 0] += 1
-    tp = tp / positive_num
-    return tp
-
-
-def cal_image_level_false_positive_rate(model, negative_dir):
-    """计算置信度(Score)在[0, 1]内以间隔0.01递增取值时,模型在无目标的图像上的图片级误检率。
-
-    图片级误检率:只要在无目标的图片上检测出目标(不论框的个数),该图片被认为误检,
-       批量无目标图片中被误检的图片所占的比例,即为图片级别的误检率。
-
-    Args:
-        model (PaddleX model object): 已加载的PaddleX模型。
-        negative_dir (str):无目标图片的文件夹路径。
-
-    Returns:
-        numpy.array: 形状为101x1的数组,对应置信度从0到1按0.01递增取值时,计算所得图片级别的误检率。
-    """
-
-    print(
-        "Begin to calculate image-level false positive rate of negative(background) images. Please wait for a moment..."
-    )
-    fp = np.zeros((101, 1))
-    negative_num = 0
-    for file in os.listdir(negative_dir):
-        file = osp.join(negative_dir, file)
-        results = model.predict(file)
-        negative_num += 1
-        scores = list()
-        for res in results:
-            scores.append(res['score'])
-        if len(scores) > 0:
-            fp[0:int(np.round(max(scores) / 0.01)), 0] += 1
-    fp = fp / negative_num
-    return fp
-
-
-def result2textfile(tp_list, fp_list, save_dir):
-    """将不同置信度阈值下的图片级召回率和图片级误检率保存为文本文件。
-
-    文本文件中内容按照| 置信度阈值 | 图片级召回率 | 图片级误检率 |的格式保存。
-
-    Args:
-        tp_list (numpy.array): 形状为101x1的数组,对应置信度从0到1按0.01递增取值时,计算所得图片级别的召回率。
-        fp_list (numpy.array): 形状为101x1的数组,对应置信度从0到1按0.01递增取值时,计算所得图片级别的误检率。
-        save_dir (str): 文本文件的保存路径。
-
-    """
-
-    tp_fp_list_file = osp.join(save_dir, 'tp_fp_list.txt')
-    with open(tp_fp_list_file, 'w') as f:
-        f.write("| score | recall rate | false-positive rate |\n")
-        f.write("| -- | -- | -- |\n")
-        for i in range(100):
-            f.write("| {:2f} | {:2f} | {:2f} |\n".format(0.01 * i, tp_list[
-                i, 0], fp_list[i, 0]))
-    print("The numerical score-recall_rate-false_positive_rate is saved as {}".
-          format(tp_fp_list_file))
-
-
-def result2imagefile(tp_list, fp_list, save_dir):
-    """将不同置信度阈值下的图片级召回率和图片级误检率保存为图片。
-
-    图片中左子图横坐标表示不同置信度阈值下计算得到的图片级召回率,纵坐标表示各图片级召回率对应的图片级误检率。
-        右边子图横坐标表示图片级召回率,纵坐标表示各图片级召回率对应的置信度阈值。
-
-    Args:
-        tp_list (numpy.array): 形状为101x1的数组,对应置信度从0到1按0.01递增取值时,计算所得图片级别的召回率。
-        fp_list (numpy.array): 形状为101x1的数组,对应置信度从0到1按0.01递增取值时,计算所得图片级别的误检率。
-        save_dir (str): 文本文件的保存路径。
-
-    """
-
-    plt.subplot(1, 2, 1)
-    plt.title("image-level false_positive-recall")
-    plt.xlabel("recall")
-    plt.ylabel("false_positive")
-    plt.xlim(0, 1)
-    plt.ylim(0, 1)
-    plt.grid(linestyle='--', linewidth=1)
-    plt.plot([0, 1], [0, 1], 'r--', linewidth=1)
-    my_x_ticks = np.arange(0, 1, 0.1)
-    my_y_ticks = np.arange(0, 1, 0.1)
-    plt.xticks(my_x_ticks, fontsize=5)
-    plt.yticks(my_y_ticks, fontsize=5)
-    plt.plot(tp_list, fp_list, color='b', label="image level", linewidth=1)
-    plt.legend(loc="lower left", fontsize=5)
-
-    plt.subplot(1, 2, 2)
-    plt.title("score-recall")
-    plt.xlabel('recall')
-    plt.ylabel('score')
-    plt.xlim(0, 1)
-    plt.ylim(0, 1)
-    plt.grid(linestyle='--', linewidth=1)
-    plt.xticks(my_x_ticks, fontsize=5)
-    plt.yticks(my_y_ticks, fontsize=5)
-    plt.plot(
-        tp_list,
-        np.arange(0, 1.01, 0.01),
-        color='b',
-        label="image level",
-        linewidth=1)
-    plt.legend(loc="lower left", fontsize=5)
-    tp_fp_chart_file = os.path.join(save_dir, "image-level_tp_fp.png")
-    plt.savefig(tp_fp_chart_file, dpi=800)
-    plt.close()
-    print(
-        "The diagrammatic score-recall_rate-false_positive_rate is saved as {}".
-        format(tp_fp_chart_file))
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument(
-        "--model_dir",
-        default="./output/faster_rcnn_r50_vd_dcn/best_model/",
-        type=str,
-        help="The model directory path.")
-    parser.add_argument(
-        "--dataset_dir",
-        default="./aluminum_inspection",
-        type=str,
-        help="The VOC-format dataset directory path.")
-    parser.add_argument(
-        "--background_image_dir",
-        default="./aluminum_inspection/val_wu_xia_ci",
-        type=str,
-        help="The directory path of background images.")
-    parser.add_argument(
-        "--save_dir",
-        default="./visualize/faster_rcnn_r50_vd_dcn",
-        type=str,
-        help="The directory path of result.")
-
-    args = parser.parse_args()
-
-    if not osp.exists(args.save_dir):
-        os.makedirs(args.save_dir)
-
-    model = pdx.load_model(args.model_dir)
-
-    tp_list = cal_image_level_recall_rate(model, args.dataset_dir)
-    fp_list = cal_image_level_false_positive_rate(model,
-                                                  args.background_image_dir)
-    result2textfile(tp_list, fp_list, args.save_dir)
-    result2imagefile(tp_list, fp_list, args.save_dir)

+ 0 - 160
static/examples/industrial_quality_inspection/compare.py

@@ -1,160 +0,0 @@
-# coding: utf8
-# Copyright (c) 2020 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.
-
-# 环境变量配置,用于控制是否使用GPU
-# 说明文档:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html#gpu
-import argparse
-import os
-os.environ['CUDA_VISIBLE_DEVICES'] = '0'
-
-import os.path as osp
-import cv2
-import re
-import xml.etree.ElementTree as ET
-import paddlex as pdx
-
-
-def parse_xml_file(xml_file):
-    tree = ET.parse(xml_file)
-    pattern = re.compile('<object>', re.IGNORECASE)
-    obj_match = pattern.findall(str(ET.tostringlist(tree.getroot())))
-    if len(obj_match) == 0:
-        return False
-    obj_tag = obj_match[0][1:-1]
-    objs = tree.findall(obj_tag)
-    pattern = re.compile('<size>', re.IGNORECASE)
-    size_tag = pattern.findall(str(ET.tostringlist(tree.getroot())))[0][1:-1]
-    size_element = tree.find(size_tag)
-    pattern = re.compile('<width>', re.IGNORECASE)
-    width_tag = pattern.findall(str(ET.tostringlist(size_element)))[0][1:-1]
-    im_w = float(size_element.find(width_tag).text)
-    pattern = re.compile('<height>', re.IGNORECASE)
-    height_tag = pattern.findall(str(ET.tostringlist(size_element)))[0][1:-1]
-    im_h = float(size_element.find(height_tag).text)
-    gt_bbox = []
-    gt_class = []
-    for i, obj in enumerate(objs):
-        pattern = re.compile('<name>', re.IGNORECASE)
-        name_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1:-1]
-        cname = obj.find(name_tag).text.strip()
-        gt_class.append(cname)
-        pattern = re.compile('<difficult>', re.IGNORECASE)
-        diff_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1:-1]
-        try:
-            _difficult = int(obj.find(diff_tag).text)
-        except Exception:
-            _difficult = 0
-        pattern = re.compile('<bndbox>', re.IGNORECASE)
-        box_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1:-1]
-        box_element = obj.find(box_tag)
-        pattern = re.compile('<xmin>', re.IGNORECASE)
-        xmin_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1]
-        x1 = float(box_element.find(xmin_tag).text)
-        pattern = re.compile('<ymin>', re.IGNORECASE)
-        ymin_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1]
-        y1 = float(box_element.find(ymin_tag).text)
-        pattern = re.compile('<xmax>', re.IGNORECASE)
-        xmax_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1]
-        x2 = float(box_element.find(xmax_tag).text)
-        pattern = re.compile('<ymax>', re.IGNORECASE)
-        ymax_tag = pattern.findall(str(ET.tostringlist(box_element)))[0][1:-1]
-        y2 = float(box_element.find(ymax_tag).text)
-        x1 = max(0, x1)
-        y1 = max(0, y1)
-        if im_w > 0.5 and im_h > 0.5:
-            x2 = min(im_w - 1, x2)
-            y2 = min(im_h - 1, y2)
-        gt_bbox.append([x1, y1, x2, y2])
-    gts = []
-    for bbox, name in zip(gt_bbox, gt_class):
-        x1, y1, x2, y2 = bbox
-        w = x2 - x1 + 1
-        h = y2 - y1 + 1
-        gt = {
-            'category_id': 0,
-            'category': name,
-            'bbox': [x1, y1, w, h],
-            'score': 1
-        }
-        gts.append(gt)
-
-    return gts
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument(
-        "--model_dir",
-        default="./output/faster_rcnn_r50_vd_dcn/best_model/",
-        type=str,
-        help="The model directory path.")
-    parser.add_argument(
-        "--dataset_dir",
-        default="./aluminum_inspection",
-        type=str,
-        help="The VOC-format dataset directory path.")
-    parser.add_argument(
-        "--save_dir",
-        default="./visualize/compare",
-        type=str,
-        help="The directory path of result.")
-    parser.add_argument(
-        "--score_threshold",
-        default=0.1,
-        type=float,
-        help="The predicted bbox whose score is lower than score_threshold is filtered."
-    )
-
-    args = parser.parse_args()
-
-    if not os.path.exists(args.save_dir):
-        os.makedirs(args.save_dir)
-    file_list = osp.join(args.dataset_dir, 'val_list.txt')
-
-    model = pdx.load_model(args.model_dir)
-
-    with open(file_list, 'r') as fr:
-        while True:
-            line = fr.readline()
-            if not line:
-                break
-            img_file, xml_file = [osp.join(args.dataset_dir, x) \
-                    for x in line.strip().split()[:2]]
-            if not osp.exists(img_file):
-                continue
-            if not osp.exists(xml_file):
-                continue
-
-            res = model.predict(img_file)
-            gts = parse_xml_file(xml_file)
-
-            det_vis = pdx.det.visualize(
-                img_file, res, threshold=args.score_threshold, save_dir=None)
-            if gts == False:
-                gts = cv2.imread(img_file)
-            else:
-                gt_vis = pdx.det.visualize(
-                    img_file,
-                    gts,
-                    threshold=args.score_threshold,
-                    save_dir=None)
-            vis = cv2.hconcat([gt_vis, det_vis])
-            cv2.imwrite(
-                os.path.join(args.save_dir, os.path.split(img_file)[-1]), vis)
-            print('The comparison has been made for {}'.format(img_file))
-
-    print(
-        "The visualized ground-truths and predictions are saved in {}. Ground-truth is on the left, prediciton is on the right".
-        format(args.save_dir))

+ 0 - 14
static/examples/industrial_quality_inspection/dataset.md

@@ -1,14 +0,0 @@
-# 天池铝材表面缺陷检测初赛数据集示例
-
-| 序号 | 瑕疵名称 | 瑕疵成因 | 瑕疵图片示例 | 图片数量 |
-| -- | -- | -- | -- | -- |
-| 1 | 擦花(擦伤)| 表面处理(喷涂)后有轻微擦到其它的东西,造成痕迹 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/ca_hua_example.png) | 128 |
-| 2 | 杂色 | 喷涂换颜料的时候,装颜料的容器未清洗干净,造成喷涂时有少量其它颜色掺入 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/za_se_example.png) |365 |
-| 3 | 漏底 | 喷粉效果不好,铝材大量底色露出 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/lou_di_example.png) | 538 |
-| 4 | 不导电 | 直接喷不到铝材表面上去 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/bu_dao_dian_example.png) | 390 |
-|5 | 桔皮 | 表面处理后涂层表面粗糙,大颗粒 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/ju_pi_example.png) | 173 |
-| 6 | 喷流| 喷涂时油漆稀从上流下来,有流动痕迹 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/pen_liu_example.png) | 86 |
-| 7 |漆泡 | 喷涂后表面起泡,小而多| ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/qi_pao_example.png) | 82 |
-| 8 | 起坑 | 型材模具问题,做出来的型材一整条都有一条凹下去的部分 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/qi_keng_example.png.png) | 407 |
-| 9 | 脏点 | 表面处理时,有灰尘或一些脏东西未能擦掉,导致涂层有颗粒比较突出 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/zang_dian_example.png) | 261 |
-| 10 | 角位漏底 | 在型材角落出现的露底 | ![](https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/jiao_wei_lou_di_example.png) | 346 |

+ 0 - 26
static/examples/industrial_quality_inspection/error_analysis.py

@@ -1,26 +0,0 @@
-# coding: utf8
-# Copyright (c) 2020 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 os.path as osp
-import paddlex as pdx
-
-model_dir = 'output/faster_rcnn_r50_vd_dcn/best_model/'
-save_dir = 'visualize/faster_rcnn_r50_vd_dcn'
-if not osp.exists(save_dir):
-    os.makedirs(save_dir)
-
-eval_details_file = osp.join(model_dir, 'eval_details.json')
-pdx.det.coco_error_analysis(eval_details_file, save_dir=save_dir)

+ 0 - 114
static/examples/industrial_quality_inspection/gpu_solution.md

@@ -1,114 +0,0 @@
-# GPU端最终解决方案
-
-本案例面向GPU端的最终方案是选择二阶段检测模型FasterRCNN,其骨干网络选择加入了可变形卷积(DCN)的ResNet50_vd,训练时使用SSLD蒸馏方案训练得到的ResNet50_vd预训练模型,FPN部分的通道数量设置为64,训练阶段数据增强策略采用RandomHorizontalFlip、RandomDistort、RandomCrop,并加入背景图片,测试阶段的RPN部分做非极大值抑制计算的候选框数量由原本的6000减少至500、做完非极大值抑制后保留的候选框数量由原本的1000减少至300。
-
-在Tesla P40的Linux系统下,对于输入大小是800 x 1333的模型,图像预处理时长为30ms/image,模型的推理时间为46.08ms/image,包括输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间。
-
-| 模型 | VOC mAP (%) | 推理时间 (ms/image)
-| -- | -- | -- |
-| FasterRCNN-ResNet50_vd_ssld | 81.05 | 48.62 |
-| + dcn | 88.09 | 66.51 |
-| + RandomHorizontalFlip/RandomDistort/RandomCrop | 90.23| 66.51 |
-| + background images | 88.87 | 66.51 |
-| + fpn channel=64 | 87.79 | 48.65 |
-| + test proposal=pre/post topk 500/300 | 87.72 | 46.08 |
-
-## 前置依赖
-
-* Paddle paddle >= 1.8.0
-* Python >= 3.5
-* PaddleX >= 1.3.0
-
-## 模型训练
-
-### (1) 下载PaddleX源码
-
-```
-git clone https://github.com/PaddlePaddle/PaddleX
-
-cd PaddleX/examples/industrial_quality_inspection
-```
-
-### (2) 下载数据集
-
-因数据集较大,可运行以下代码提前将数据集下载并解压。训练代码中也会自动下载数据集,所以这一步不是必须的。
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/aluminum_inspection.tar.gz
-tar xvf aluminum_inspection.tar.gz
-```
-### (3) 下载预先训练好的模型
-
-如果不想再次训练模型,可以直接下载已经训练好的模型完成后面的模型测试和部署推理:
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/models/faster_rcnn_r50_vd_dcn.tar.gz
-tar xvf faster_rcnn_r50_vd_dcn.tar.gz
-```
-### (4) 训练
-
-运行以下代码进行模型训练,代码会自动下载数据集,如若事先下载了数据集,需将下载和解压铝材缺陷检测数据集的相关行注释掉。代码中默认使用0,1,2,3,4号GPU训练,可根据实际情况设置卡号并调整`batch_size`和`learning_rate`。
-
-```
-python train_rcnn.py
-```
-
-### (5) 分析预测错误的原因
-
-在模型迭代过程中,运行以下代码可完成模型效果的分析并生成分析结果图表:
-
-```
-python error_analysis.py
-```
-
-可参考[性能优化部分的模型效果分析](./accuracy_improvement.md#2-%E6%A8%A1%E5%9E%8B%E6%95%88%E6%9E%9C%E5%88%86%E6%9E%90)来理解当前模型预测错误的原因。
-
-运行以下代码,生成可视化真值和预测结果的对比图以进一步理解模型效果,代码中的置信度阈值可根据实际情况进行调整。
-
-```
-python compare.py
-```
-
-![](image/compare_budaodian-116.jpg)
-
-左边是可视化真值,右边是可视化预测结果。
-
-### (6) 统计图片级召回率和误检率
-
-模型迭代完成后,计算不同置信度阈值下[图片级召回率](./accuracy_improvement.md#6-%E8%83%8C%E6%99%AF%E5%9B%BE%E7%89%87%E5%8A%A0%E5%85%A5)和[图片级误检率](./accuracy_improvement.md#6-%E8%83%8C%E6%99%AF%E5%9B%BE%E7%89%87%E5%8A%A0%E5%85%A5),找到符合要求的召回率和误检率,对应的置信度阈值用于后续模型预测阶段。
-
-```
-python cal_tp_fp.py
-```
-
-执行后会生成图表`image-level_tp_fp.png`和文件`tp_fp_list.txt`,示意如下:
-
-| 图表`image-level_tp_fp.png` | 文件`tp_fp_list.txt` |
-| -- | -- |
-| ![](./image/image-level_tp_fp.png) | [tp_fp_list.txt](tp_fp_list.md) |
-
-图表`image-level_tp_fp.png`中左边子图,横坐标表示不同置信度阈值下计算得到的图片级召回率,纵坐标表示各图片级召回率对应的图片级误检率。右边子图横坐标表示图片级召回率,纵坐标表示各图片级召回率对应的置信度阈值。从图表中可较为直观地看出当前模型的图片级召回率和误检率的量级,从文件`tp_fp_list.txt`可以查到具体数值,例如在图片级召回率/图片级误检率为[0.9589,0.0074]这一组符合要求,就将对应的置信度阈值0.90选取为后续预测推理的阈值。
-
-### (7) 模型测试
-
-测试集因没有标注文件,这里单独下载测试集图片:
-
-```
-wget https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/aluminum_inspection_test.tar.gz
-tar xvf aluminum_inspection_test.tar.gz
-```
-
-加载训练好的模型,使用(5)选取的置信度阈值0.90对验证集图片或者测试集图片进行预测:
-
-```
-python predict.py
-```
-可视化预测结果示例如下:
-
-![](image/visualize_budaodian-116.jpg)
-
-## 推理部署
-
-本案例采用C++部署方式将模型部署在Tesla P40的Linux系统下,具体的C++部署流程请参考文档[PaddleX模型多端安全部署/C++部署](https://paddlex.readthedocs.io/zh_CN/develop/deploy/server/cpp/index.html)。
-
-对于输入大小是800 x 1333的模型,图像预处理时长为30ms/image。值得一提的是预处理中的Normalize操作比较耗时,因此在设置预处理操作时,可以先进行Resize操作再做Normalize。模型的推理时间为46.08ms/image,包括输入数据拷贝至GPU的时间、计算时间、数据拷贝至CPU的时间。

BIN
static/examples/industrial_quality_inspection/image/after_clahe.png


BIN
static/examples/industrial_quality_inspection/image/allclasses_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/before_clahe.png


BIN
static/examples/industrial_quality_inspection/image/budaodian_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/cahua_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/compare_budaodian-116.jpg


BIN
static/examples/industrial_quality_inspection/image/image-level_tp_fp.png


BIN
static/examples/industrial_quality_inspection/image/jiaoweiloudi_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/jupi_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/loudi_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/penliu_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/qikeng_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/qipao_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/visualize_budaodian-116.jpg


BIN
static/examples/industrial_quality_inspection/image/zangdian_analysis_example.png


BIN
static/examples/industrial_quality_inspection/image/zase_analysis_example.png


+ 0 - 61
static/examples/industrial_quality_inspection/params_analysis.py

@@ -1,61 +0,0 @@
-#copyright (c) 2020 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 argparse
-import os
-# 选择使用0号卡
-os.environ['CUDA_VISIBLE_DEVICES'] = '0'
-import paddlex as pdx
-
-
-def params_analysis(model_dir, dataset, batch_size, save_file):
-    # 加载模型
-    model = pdx.load_model(model_dir)
-
-    # 定义验证所用的数据集
-    eval_dataset = pdx.datasets.VOCDetection(
-        data_dir=dataset,
-        file_list=os.path.join(dataset, 'val_list.txt'),
-        label_list=os.path.join(dataset, 'labels.txt'),
-        transforms=model.eval_transforms)
-
-    pdx.slim.prune.analysis(
-        model,
-        dataset=eval_dataset,
-        batch_size=batch_size,
-        save_file=save_file)
-
-
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument(
-        "--model_dir",
-        default="./output/yolov3_mobilenetv3/best_model",
-        type=str,
-        help="The model path.")
-    parser.add_argument(
-        "--dataset",
-        default="./aluminum_inspection",
-        type=str,
-        help="The model path.")
-    parser.add_argument("--batch_size", default=8, type=int, help="Batch size")
-    parser.add_argument(
-        "--save_file",
-        default="./sensitivities.data",
-        type=str,
-        help="The sensitivities file path.")
-
-    args = parser.parse_args()
-    params_analysis(args.model_dir, args.dataset, args.batch_size,
-                    args.save_file)

+ 0 - 35
static/examples/industrial_quality_inspection/predict.py

@@ -1,35 +0,0 @@
-# coding: utf8
-# Copyright (c) 2020 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.
-
-# 环境变量配置,用于控制是否使用GPU
-# 说明文档:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html#gpu
-import os
-os.environ['CUDA_VISIBLE_DEVICES'] = '0'
-
-import os.path as osp
-import paddlex as pdx
-
-img_file = 'aluminum_inspection/JPEGImages/budaodian-116.jpg'
-model_dir = 'output/faster_rcnn_r50_vd_dcn/best_model/'
-save_dir = './visualize/predict'
-# 设置置信度阈值
-score_threshold = 0.9
-
-if not os.path.exists(save_dir):
-    os.makedirs(save_dir)
-
-model = pdx.load_model(model_dir)
-res = model.predict(img_file)
-pdx.det.visualize(img_file, res, threshold=score_threshold, save_dir=save_dir)

+ 0 - 102
static/examples/industrial_quality_inspection/tp_fp_list.md

@@ -1,102 +0,0 @@
-| score | recall rate | false-positive rate |
-| -- | -- | -- |
-| 0.000000 | 0.982877 | 0.022222 |
-| 0.010000 | 0.982877 | 0.022222 |
-| 0.020000 | 0.982877 | 0.022222 |
-| 0.030000 | 0.982877 | 0.022222 |
-| 0.040000 | 0.982877 | 0.022222 |
-| 0.050000 | 0.982877 | 0.022222 |
-| 0.060000 | 0.982877 | 0.022222 |
-| 0.070000 | 0.979452 | 0.022222 |
-| 0.080000 | 0.979452 | 0.022222 |
-| 0.090000 | 0.979452 | 0.022222 |
-| 0.100000 | 0.979452 | 0.022222 |
-| 0.110000 | 0.979452 | 0.022222 |
-| 0.120000 | 0.979452 | 0.022222 |
-| 0.130000 | 0.979452 | 0.022222 |
-| 0.140000 | 0.976027 | 0.022222 |
-| 0.150000 | 0.976027 | 0.022222 |
-| 0.160000 | 0.976027 | 0.022222 |
-| 0.170000 | 0.976027 | 0.022222 |
-| 0.180000 | 0.976027 | 0.022222 |
-| 0.190000 | 0.976027 | 0.022222 |
-| 0.200000 | 0.976027 | 0.022222 |
-| 0.210000 | 0.976027 | 0.022222 |
-| 0.220000 | 0.976027 | 0.022222 |
-| 0.230000 | 0.976027 | 0.022222 |
-| 0.240000 | 0.976027 | 0.022222 |
-| 0.250000 | 0.976027 | 0.022222 |
-| 0.260000 | 0.976027 | 0.014815 |
-| 0.270000 | 0.976027 | 0.014815 |
-| 0.280000 | 0.976027 | 0.014815 |
-| 0.290000 | 0.976027 | 0.014815 |
-| 0.300000 | 0.976027 | 0.014815 |
-| 0.310000 | 0.976027 | 0.014815 |
-| 0.320000 | 0.976027 | 0.014815 |
-| 0.330000 | 0.976027 | 0.014815 |
-| 0.340000 | 0.976027 | 0.014815 |
-| 0.350000 | 0.976027 | 0.014815 |
-| 0.360000 | 0.976027 | 0.014815 |
-| 0.370000 | 0.976027 | 0.014815 |
-| 0.380000 | 0.976027 | 0.014815 |
-| 0.390000 | 0.976027 | 0.014815 |
-| 0.400000 | 0.976027 | 0.014815 |
-| 0.410000 | 0.976027 | 0.014815 |
-| 0.420000 | 0.976027 | 0.014815 |
-| 0.430000 | 0.976027 | 0.014815 |
-| 0.440000 | 0.972603 | 0.014815 |
-| 0.450000 | 0.972603 | 0.014815 |
-| 0.460000 | 0.972603 | 0.014815 |
-| 0.470000 | 0.972603 | 0.014815 |
-| 0.480000 | 0.972603 | 0.014815 |
-| 0.490000 | 0.972603 | 0.014815 |
-| 0.500000 | 0.972603 | 0.014815 |
-| 0.510000 | 0.972603 | 0.014815 |
-| 0.520000 | 0.972603 | 0.014815 |
-| 0.530000 | 0.972603 | 0.014815 |
-| 0.540000 | 0.972603 | 0.014815 |
-| 0.550000 | 0.972603 | 0.014815 |
-| 0.560000 | 0.969178 | 0.014815 |
-| 0.570000 | 0.969178 | 0.014815 |
-| 0.580000 | 0.969178 | 0.014815 |
-| 0.590000 | 0.969178 | 0.014815 |
-| 0.600000 | 0.969178 | 0.014815 |
-| 0.610000 | 0.969178 | 0.014815 |
-| 0.620000 | 0.969178 | 0.014815 |
-| 0.630000 | 0.969178 | 0.014815 |
-| 0.640000 | 0.969178 | 0.014815 |
-| 0.650000 | 0.969178 | 0.014815 |
-| 0.660000 | 0.969178 | 0.014815 |
-| 0.670000 | 0.969178 | 0.014815 |
-| 0.680000 | 0.969178 | 0.014815 |
-| 0.690000 | 0.969178 | 0.014815 |
-| 0.700000 | 0.969178 | 0.014815 |
-| 0.710000 | 0.969178 | 0.014815 |
-| 0.720000 | 0.969178 | 0.014815 |
-| 0.730000 | 0.969178 | 0.014815 |
-| 0.740000 | 0.969178 | 0.014815 |
-| 0.750000 | 0.969178 | 0.014815 |
-| 0.760000 | 0.969178 | 0.014815 |
-| 0.770000 | 0.965753 | 0.014815 |
-| 0.780000 | 0.965753 | 0.014815 |
-| 0.790000 | 0.965753 | 0.014815 |
-| 0.800000 | 0.962329 | 0.014815 |
-| 0.810000 | 0.962329 | 0.014815 |
-| 0.820000 | 0.962329 | 0.014815 |
-| 0.830000 | 0.962329 | 0.014815 |
-| 0.840000 | 0.962329 | 0.014815 |
-| 0.850000 | 0.958904 | 0.014815 |
-| 0.860000 | 0.958904 | 0.014815 |
-| 0.870000 | 0.958904 | 0.014815 |
-| 0.880000 | 0.958904 | 0.014815 |
-| 0.890000 | 0.958904 | 0.014815 |
-| 0.900000 | 0.958904 | 0.007407 |
-| 0.910000 | 0.958904 | 0.007407 |
-| 0.920000 | 0.958904 | 0.007407 |
-| 0.930000 | 0.955479 | 0.007407 |
-| 0.940000 | 0.955479 | 0.007407 |
-| 0.950000 | 0.955479 | 0.007407 |
-| 0.960000 | 0.955479 | 0.007407 |
-| 0.970000 | 0.955479 | 0.007407 |
-| 0.980000 | 0.941781 | 0.000000 |
-| 0.990000 | 0.893836 | 0.000000 |

+ 0 - 58
static/examples/industrial_quality_inspection/train_pruned_yolov3.py

@@ -1,58 +0,0 @@
-# 环境变量配置,用于控制是否使用GPU
-# 说明文档:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html#gpu
-import os
-os.environ['CUDA_VISIBLE_DEVICES'] = '0'
-
-from paddlex.det import transforms
-import paddlex as pdx
-
-# 定义训练和验证时的transforms
-# API说明 https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html
-train_transforms = transforms.Compose([
-    transforms.MixupImage(mixup_epoch=250), transforms.RandomDistort(),
-    transforms.RandomExpand(), transforms.RandomCrop(), transforms.Resize(
-        target_size=608, interp='RANDOM'), transforms.RandomHorizontalFlip(),
-    transforms.Normalize()
-])
-
-eval_transforms = transforms.Compose([
-    transforms.Resize(
-        target_size=608, interp='CUBIC'), transforms.Normalize()
-])
-
-# 定义训练和验证所用的数据集
-# API说明:https://paddlex.readthedocs.io/zh_CN/develop/apis/datasets.html#paddlex-datasets-vocdetection
-train_dataset = pdx.datasets.VOCDetection(
-    data_dir='aluminum_inspection',
-    file_list='aluminum_inspection/train_list.txt',
-    label_list='aluminum_inspection/labels.txt',
-    transforms=train_transforms,
-    shuffle=True)
-eval_dataset = pdx.datasets.VOCDetection(
-    data_dir='aluminum_inspection',
-    file_list='aluminum_inspection/val_list.txt',
-    label_list='aluminum_inspection/labels.txt',
-    transforms=eval_transforms)
-
-# 初始化模型,并进行训练
-# 可使用VisualDL查看训练指标,参考https://paddlex.readthedocs.io/zh_CN/develop/train/visualdl.html
-num_classes = len(train_dataset.labels)
-
-# API说明: https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-yolov3
-model = pdx.det.YOLOv3(num_classes=num_classes, backbone='MobileNetV3_large')
-
-# API说明: https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#train
-# 各参数介绍与调整说明:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html
-model.train(
-    num_epochs=400,
-    train_dataset=train_dataset,
-    train_batch_size=8,
-    eval_dataset=eval_dataset,
-    warmup_steps=4000,
-    learning_rate=0.000125,
-    lr_decay_epochs=[240, 320],
-    pretrain_weights='output/yolov3_mobilenetv3/best_model',
-    save_dir='output/yolov3_mobilenetv3_prune',
-    use_vdl=True,
-    sensitivities_file='./sensitivities.data',
-    eval_metric_loss=0.05)

+ 0 - 74
static/examples/industrial_quality_inspection/train_rcnn.py

@@ -1,74 +0,0 @@
-# 环境变量配置,用于控制是否使用GPU
-# 说明文档:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html#gpu
-import os
-os.environ['CUDA_VISIBLE_DEVICES'] = '0'
-
-from paddlex.det import transforms
-import paddlex as pdx
-
-# 下载和解压铝材缺陷检测数据集
-aluminum_dataset = 'https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/aluminum_inspection.tar.gz'
-pdx.utils.download_and_decompress(aluminum_dataset, path='./')
-
-# API说明 https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html
-train_transforms = transforms.Compose([
-    transforms.RandomDistort(), transforms.RandomCrop(),
-    transforms.RandomHorizontalFlip(), transforms.ResizeByShort(
-        short_size=[800], max_size=1333), transforms.Normalize(
-            mean=[0.5], std=[0.5]), transforms.Padding(coarsest_stride=32)
-])
-
-eval_transforms = transforms.Compose([
-    transforms.ResizeByShort(
-        short_size=800, max_size=1333),
-    transforms.Normalize(),
-    transforms.Padding(coarsest_stride=32),
-])
-
-# 定义训练和验证所用的数据集
-# API说明:https://paddlex.readthedocs.io/zh_CN/develop/apis/datasets.html#paddlex-datasets-vocdetection
-train_dataset = pdx.datasets.VOCDetection(
-    data_dir='aluminum_inspection',
-    file_list='aluminum_inspection/train_list.txt',
-    label_list='aluminum_inspection/labels.txt',
-    transforms=train_transforms,
-    num_workers=8,
-    shuffle=True)
-eval_dataset = pdx.datasets.VOCDetection(
-    data_dir='aluminum_inspection',
-    file_list='aluminum_inspection/val_list.txt',
-    label_list='aluminum_inspection/labels.txt',
-    num_workers=8,
-    transforms=eval_transforms)
-
-# 把背景图片加入训练集中
-train_dataset.add_negative_samples(
-    image_dir='./aluminum_inspection/train_wu_xia_ci')
-
-# 初始化模型,并进行训练
-# 可使用VisualDL查看训练指标,参考https://paddlex.readthedocs.io/zh_CN/develop/train/visualdl.html
-# num_classes 需要设置为包含背景类的类别数,即: 目标类别数量 + 1
-num_classes = len(train_dataset.labels) + 1
-
-# API说明: https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-fasterrcnn
-model = pdx.det.FasterRCNN(
-    num_classes=num_classes,
-    backbone='ResNet50_vd_ssld',
-    with_dcn=True,
-    fpn_num_channels=64,
-    with_fpn=True,
-    test_pre_nms_top_n=500,
-    test_post_nms_top_n=300)
-
-# API说明: https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#id1
-# 各参数介绍与调整说明:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html
-model.train(
-    num_epochs=80,
-    train_dataset=train_dataset,
-    train_batch_size=2,
-    eval_dataset=eval_dataset,
-    learning_rate=0.0025,
-    lr_decay_epochs=[60, 70],
-    warmup_steps=5000,
-    save_dir='output/faster_rcnn_r50_vd_dcn',
-    use_vdl=True)

+ 0 - 59
static/examples/industrial_quality_inspection/train_yolov3.py

@@ -1,59 +0,0 @@
-# 环境变量配置,用于控制是否使用GPU
-# 说明文档:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html#gpu
-import os
-os.environ['CUDA_VISIBLE_DEVICES'] = '0'
-
-from paddlex.det import transforms
-import paddlex as pdx
-
-# 下载和解压铝材缺陷检测数据集
-aluminum_dataset = 'https://bj.bcebos.com/paddlex/examples/industrial_quality_inspection/datasets/aluminum_inspection.tar.gz'
-pdx.utils.download_and_decompress(aluminum_dataset, path='./')
-
-# 定义训练和验证时的transforms
-# API说明 https://paddlex.readthedocs.io/zh_CN/develop/apis/transforms/det_transforms.html
-train_transforms = transforms.Compose([
-    transforms.MixupImage(mixup_epoch=250), transforms.RandomDistort(),
-    transforms.RandomExpand(), transforms.RandomCrop(), transforms.Resize(
-        target_size=608, interp='RANDOM'), transforms.RandomHorizontalFlip(),
-    transforms.Normalize()
-])
-
-eval_transforms = transforms.Compose([
-    transforms.Resize(
-        target_size=608, interp='CUBIC'), transforms.Normalize()
-])
-
-# 定义训练和验证所用的数据集
-# API说明:https://paddlex.readthedocs.io/zh_CN/develop/apis/datasets.html#paddlex-datasets-vocdetection
-train_dataset = pdx.datasets.VOCDetection(
-    data_dir='aluminum_inspection',
-    file_list='aluminum_inspection/train_list.txt',
-    label_list='aluminum_inspection/labels.txt',
-    transforms=train_transforms,
-    shuffle=True)
-eval_dataset = pdx.datasets.VOCDetection(
-    data_dir='aluminum_inspection',
-    file_list='aluminum_inspection/val_list.txt',
-    label_list='aluminum_inspection/labels.txt',
-    transforms=eval_transforms)
-
-# 初始化模型,并进行训练
-# 可使用VisualDL查看训练指标,参考https://paddlex.readthedocs.io/zh_CN/develop/train/visualdl.html
-num_classes = len(train_dataset.labels)
-
-# API说明: https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#paddlex-det-yolov3
-model = pdx.det.YOLOv3(num_classes=num_classes, backbone='MobileNetV3_large')
-
-# API说明: https://paddlex.readthedocs.io/zh_CN/develop/apis/models/detection.html#id1
-# 各参数介绍与调整说明:https://paddlex.readthedocs.io/zh_CN/develop/appendix/parameters.html
-model.train(
-    num_epochs=400,
-    train_dataset=train_dataset,
-    train_batch_size=8,
-    eval_dataset=eval_dataset,
-    warmup_steps=4000,
-    learning_rate=0.000125,
-    lr_decay_epochs=[240, 320],
-    save_dir='output/yolov3_mobilenetv3',
-    use_vdl=True)