提交 06ea68cd 编写于 作者: C chenguowei01
...@@ -22,7 +22,7 @@ CVPR 19 Look into Person (LIP) 单人人像分割比赛冠军模型,详见[ACE ...@@ -22,7 +22,7 @@ CVPR 19 Look into Person (LIP) 单人人像分割比赛冠军模型,详见[ACE
### 4. 运行 ### 4. 运行
**NOTE:** 运行该模型需要需至少2.5G显存 **NOTE:** 运行该模型需要2G左右显存
使用GPU预测 使用GPU预测
``` ```
......
...@@ -118,10 +118,10 @@ def infer(): ...@@ -118,10 +118,10 @@ def infer():
output_im.putpalette(palette) output_im.putpalette(palette)
output_im.save(result_path) output_im.save(result_path)
if idx % 100 == 0: if (idx + 1) % 100 == 0:
print('%d processd' % (idx)) print('%d processd' % (idx + 1))
print('%d processd done' % (idx)) print('%d processd done' % (idx + 1))
return 0 return 0
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
打开终端输入`labelme`会出现LableMe的交互界面,可以先预览`LabelMe`给出的已标注好的图片,再开始标注自定义数据集。 打开终端输入`labelme`会出现LableMe的交互界面,可以先预览`LabelMe`给出的已标注好的图片,再开始标注自定义数据集。
<div align="center"> <div align="center">
<img src="./docs/imgs/annotation/image-1.png" width="600px"/> <img src="../imgs/annotation/image-1.png" width="600px"/>
<p>图1 LableMe交互界面的示意图</p> <p>图1 LableMe交互界面的示意图</p>
</div> </div>
...@@ -24,7 +24,7 @@ git clone https://github.com/wkentaro/labelme ...@@ -24,7 +24,7 @@ git clone https://github.com/wkentaro/labelme
终端输入`labelme`会出现LableMe的交互界面,点击`OpenDir`打开`<path/to/labelme>/examples/semantic_segmentation/data_annotated`,其中`<path/to/labelme>`为克隆下来的`labelme`的路径,打开后示意的是语义分割的真值标注。 终端输入`labelme`会出现LableMe的交互界面,点击`OpenDir`打开`<path/to/labelme>/examples/semantic_segmentation/data_annotated`,其中`<path/to/labelme>`为克隆下来的`labelme`的路径,打开后示意的是语义分割的真值标注。
<div align="center"> <div align="center">
<img src="./docs/imgs/annotation/image-2.png" width="600px"/> <img src="../imgs/annotation/image-2.png" width="600px"/>
<p>图2 已标注图片的示意图</p> <p>图2 已标注图片的示意图</p>
</div> </div>
...@@ -35,15 +35,15 @@ git clone https://github.com/wkentaro/labelme ...@@ -35,15 +35,15 @@ git clone https://github.com/wkentaro/labelme
​ (1) 点击`OpenDir`打开待标注图片所在目录,点击`Create Polygons`,沿着目标的边缘画多边形,完成后输入目标的类别。在标注过程中,如果某个点画错了,可以按撤销快捷键可撤销该点。Mac下的撤销快捷键为`command+Z` ​ (1) 点击`OpenDir`打开待标注图片所在目录,点击`Create Polygons`,沿着目标的边缘画多边形,完成后输入目标的类别。在标注过程中,如果某个点画错了,可以按撤销快捷键可撤销该点。Mac下的撤销快捷键为`command+Z`
<div align="center"> <div align="center">
<img src="./docs/imgs/annotation/image-3.png" width="600px"/> <img src="../imgs/annotation/image-3.png" width="600px"/>
<p>图3 标注单个目标的示意图</p> <p>图3 标注单个目标的示意图</p>
</div> </div>
​ (2) 右击选择`Edit Polygons`可以整体移动多边形的位置,也可以移动某个点的位置;右击选择`Edit Label`可以修改每个目标的类别。请根据自己的需要执行这一步骤,若不需要修改,可跳过。 ​ (2) 右击选择`Edit Polygons`可以整体移动多边形的位置,也可以移动某个点的位置;右击选择`Edit Label`可以修改每个目标的类别。请根据自己的需要执行这一步骤,若不需要修改,可跳过。
<div align="center"> <div align="center">
<img src="./docs/imgs/annotation/image-4-1.png" width="00px" /> <img src="../imgs/annotation/image-4-1.png" width="00px" />
<img src="./docs/imgs/annotation/image-4-2.png" width="600px"/> <img src="../imgs/annotation/image-4-2.png" width="600px"/>
<p>图4 修改标注的示意图</p> <p>图4 修改标注的示意图</p>
</div> </div>
...@@ -52,7 +52,7 @@ git clone https://github.com/wkentaro/labelme ...@@ -52,7 +52,7 @@ git clone https://github.com/wkentaro/labelme
LableMe产出的真值文件可参考我们给出的文件夹`data_annotated` LableMe产出的真值文件可参考我们给出的文件夹`data_annotated`
<div align="center"> <div align="center">
<img src="./docs/imgs/annotation/image-5.png" width="600px"/> <img src="../imgs/annotation/image-5.png" width="600px"/>
<p>图5 LableMe产出的真值文件的示意图</p> <p>图5 LableMe产出的真值文件的示意图</p>
</div> </div>
...@@ -71,7 +71,7 @@ LableMe产出的真值文件可参考我们给出的文件夹`data_annotated`。 ...@@ -71,7 +71,7 @@ LableMe产出的真值文件可参考我们给出的文件夹`data_annotated`。
``` ```
<div align="center"> <div align="center">
<img src="./docs/imgs/annotation/image-6.png" width="600px"/> <img src="../imgs/annotation/image-6.png" width="600px"/>
<p>图6 训练所需的数据集目录的结构示意图</p> <p>图6 训练所需的数据集目录的结构示意图</p>
</div> </div>
...@@ -92,6 +92,6 @@ pip install pillow ...@@ -92,6 +92,6 @@ pip install pillow
转换得到的数据集可参考我们给出的文件夹`my_dataset`。其中,文件`class_names.txt`是数据集中所有标注类别的名称,包含背景类;文件夹`JPEGImages`保存的是数据集的图片;文件夹`SegmentationClassPNG`保存的是各图片的像素级别的真值信息,背景类`_background_`对应为0,其它目标类别从1开始递增,至多为255。 转换得到的数据集可参考我们给出的文件夹`my_dataset`。其中,文件`class_names.txt`是数据集中所有标注类别的名称,包含背景类;文件夹`JPEGImages`保存的是数据集的图片;文件夹`SegmentationClassPNG`保存的是各图片的像素级别的真值信息,背景类`_background_`对应为0,其它目标类别从1开始递增,至多为255。
<div align="center"> <div align="center">
<img src="./docs/imgs/annotation/image-7.png" width="600px"/> <img src="../imgs/annotation/image-7.png" width="600px"/>
<p>图7 训练所需的数据集各目录的内容示意图</p> <p>图7 训练所需的数据集各目录的内容示意图</p>
</div> </div>
...@@ -13,7 +13,7 @@ DATALOADER Group存放所有与数据加载相关的配置 ...@@ -13,7 +13,7 @@ DATALOADER Group存放所有与数据加载相关的配置
### 注意事项 ### 注意事项
* 该选项只在`pdseg/train.py``pdseg/eval.py`中使用到 * 该选项只在`pdseg/train.py``pdseg/eval.py`中使用到
* 当使用多线程时,该字段表示线程适量,使用多进程时,该字段表示进程数量。一般该字段使用默认值即可 * 该字段表示数据预处理时的进程数量,只有在`pdseg/train.py`或者`pdseg/eval.py`中打开了`--use_mpio`开关有效。一般该字段使用默认值即可
<br/> <br/>
<br/> <br/>
...@@ -27,4 +27,4 @@ DATALOADER Group存放所有与数据加载相关的配置 ...@@ -27,4 +27,4 @@ DATALOADER Group存放所有与数据加载相关的配置
256 256
<br/> <br/>
<br/> <br/>
\ No newline at end of file
...@@ -45,7 +45,7 @@ PaddleSeg采用通用的文件列表方式组织训练集、验证集和测试 ...@@ -45,7 +45,7 @@ PaddleSeg采用通用的文件列表方式组织训练集、验证集和测试
``` ```
其中`[SEP]`是文件路径分割符,可以在`DATASET.SEPRATOR`配置项中修改, 默认为空格。 其中`[SEP]`是文件路径分割符,可以在`DATASET.SEPARATOR`配置项中修改, 默认为空格。
**注意事项** **注意事项**
......
...@@ -40,5 +40,5 @@ train数据集合为Cityscapes 训练集合,测试为Cityscapes的验证集合 ...@@ -40,5 +40,5 @@ train数据集合为Cityscapes 训练集合,测试为Cityscapes的验证集合
|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|
| DeepLabv3+/MobileNetv2/bn | Cityscapes |MODEL.MODEL_NAME: deeplabv3p <br> MODEL.DEEPLAB.BACKBONE: mobilenet <br> MODEL.DEEPLAB.DEPTH_MULTIPLIER: 1.0 <br> MODEL.DEEPLAB.ENCODER_WITH_ASPP: False <br> MODEL.DEEPLAB.ENABLE_DECODER: False <br> MODEL.DEFAULT_NORM_TYPE: bn|[mobilenet_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/mobilenet_cityscapes.tgz) |16|false| 0.698| | DeepLabv3+/MobileNetv2/bn | Cityscapes |MODEL.MODEL_NAME: deeplabv3p <br> MODEL.DEEPLAB.BACKBONE: mobilenet <br> MODEL.DEEPLAB.DEPTH_MULTIPLIER: 1.0 <br> MODEL.DEEPLAB.ENCODER_WITH_ASPP: False <br> MODEL.DEEPLAB.ENABLE_DECODER: False <br> MODEL.DEFAULT_NORM_TYPE: bn|[mobilenet_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/mobilenet_cityscapes.tgz) |16|false| 0.698|
| DeepLabv3+/Xception65/gn | Cityscapes |MODEL.MODEL_NAME: deeplabv3p <br> MODEL.DEEPLAB.BACKBONE: xception_65 <br> MODEL.DEFAULT_NORM_TYPE: gn | [deeplabv3p_xception65_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/deeplabv3p_xception65_cityscapes.tgz) |16|false| 0.7804 | | DeepLabv3+/Xception65/gn | Cityscapes |MODEL.MODEL_NAME: deeplabv3p <br> MODEL.DEEPLAB.BACKBONE: xception_65 <br> MODEL.DEFAULT_NORM_TYPE: gn | [deeplabv3p_xception65_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/deeplabv3p_xception65_cityscapes.tgz) |16|false| 0.7804 |
| DeepLabv3+/Xception65/bn | Cityscapes | MODEL.MODEL_NAME: deeplabv3p <br> MODEL.DEEPLAB.BACKBONE: xception_65 <br> MODEL.DEFAULT_NORM_TYPE: bn| [Xception65_deeplab_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/Xception65_deeplab_cityscapes.tgz) | 16 | false | 0.7715 | | DeepLabv3+/Xception65/bn | Cityscapes | MODEL.MODEL_NAME: deeplabv3p <br> MODEL.DEEPLAB.BACKBONE: xception_65 <br> MODEL.DEFAULT_NORM_TYPE: bn| [Xception65_deeplab_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/xception65_bn_cityscapes.tgz) | 16 | false | 0.7715 |
| ICNet/bn | Cityscapes | MODEL.MODEL_NAME: icnet <br> MODEL.DEFAULT_NORM_TYPE: bn | [icnet_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/icnet_cityscapes.tgz) |16|false| 0.6854 | | ICNet/bn | Cityscapes | MODEL.MODEL_NAME: icnet <br> MODEL.DEFAULT_NORM_TYPE: bn | [icnet_cityscapes.tgz](https://paddleseg.bj.bcebos.com/models/icnet6831.tar.gz) |16|false| 0.6831 |
...@@ -36,7 +36,7 @@ if (NOT DEFINED OPENCV_DIR OR ${OPENCV_DIR} STREQUAL "") ...@@ -36,7 +36,7 @@ if (NOT DEFINED OPENCV_DIR OR ${OPENCV_DIR} STREQUAL "")
endif() endif()
include_directories("${CMAKE_SOURCE_DIR}/") include_directories("${CMAKE_SOURCE_DIR}/")
include_directories("${CMAKE_CURRENT_BINARY_DIR}/ext/yaml-cpp/src/yaml-cpp/include") include_directories("${CMAKE_CURRENT_BINARY_DIR}/ext/yaml-cpp/src/ext-yaml-cpp/include")
include_directories("${PADDLE_DIR}/") include_directories("${PADDLE_DIR}/")
include_directories("${PADDLE_DIR}/third_party/install/protobuf/include") include_directories("${PADDLE_DIR}/third_party/install/protobuf/include")
include_directories("${PADDLE_DIR}/third_party/install/glog/include") include_directories("${PADDLE_DIR}/third_party/install/glog/include")
...@@ -82,7 +82,7 @@ if (WIN32) ...@@ -82,7 +82,7 @@ if (WIN32)
add_definitions(-DSTATIC_LIB) add_definitions(-DSTATIC_LIB)
endif() endif()
else() else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -o2 -std=c++11")
set(CMAKE_STATIC_LIBRARY_PREFIX "") set(CMAKE_STATIC_LIBRARY_PREFIX "")
endif() endif()
...@@ -160,14 +160,13 @@ if (NOT WIN32) ...@@ -160,14 +160,13 @@ if (NOT WIN32)
set(EXTERNAL_LIB "-lrt -ldl -lpthread") set(EXTERNAL_LIB "-lrt -ldl -lpthread")
set(DEPS ${DEPS} set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB} ${MATH_LIB} ${MKLDNN_LIB}
glog gflags protobuf snappystream snappy z xxhash glog gflags protobuf yaml-cpp snappystream snappy z xxhash
${EXTERNAL_LIB}) ${EXTERNAL_LIB})
else() else()
set(DEPS ${DEPS} set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB} ${MATH_LIB} ${MKLDNN_LIB}
opencv_world346 glog libyaml-cppmt gflags_static libprotobuf snappy zlibstatic xxhash snappystream ${EXTERNAL_LIB}) opencv_world346 glog libyaml-cppmt gflags_static libprotobuf snappy zlibstatic xxhash snappystream ${EXTERNAL_LIB})
set(DEPS ${DEPS} libcmt shlwapi) set(DEPS ${DEPS} libcmt shlwapi)
set(DEPS ${DEPS} ${YAML_CPP_LIBRARY})
endif(NOT WIN32) endif(NOT WIN32)
if(WITH_GPU) if(WITH_GPU)
...@@ -206,13 +205,17 @@ ADD_LIBRARY(libpaddleseg_inference STATIC ${PADDLESEG_INFERENCE_SRCS}) ...@@ -206,13 +205,17 @@ ADD_LIBRARY(libpaddleseg_inference STATIC ${PADDLESEG_INFERENCE_SRCS})
target_link_libraries(libpaddleseg_inference ${DEPS}) target_link_libraries(libpaddleseg_inference ${DEPS})
add_executable(demo demo.cpp) add_executable(demo demo.cpp)
ADD_DEPENDENCIES(libpaddleseg_inference yaml-cpp) ADD_DEPENDENCIES(libpaddleseg_inference ext-yaml-cpp)
ADD_DEPENDENCIES(demo yaml-cpp libpaddleseg_inference) ADD_DEPENDENCIES(demo ext-yaml-cpp libpaddleseg_inference)
target_link_libraries(demo ${DEPS} libpaddleseg_inference) target_link_libraries(demo ${DEPS} libpaddleseg_inference)
if (WIN32)
add_custom_command(TARGET demo POST_BUILD add_custom_command(TARGET demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/mklml.dll ./mklml.dll COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/mklml.dll ./mklml.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/libiomp5md.dll ./libiomp5md.dll COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/libiomp5md.dll ./libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mkldnn/bin/mkldnn.dll ./mkldnn.dll COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mkldnn/lib/mkldnn.dll ./mkldnn.dll
) COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/mklml.dll ./release/mklml.dll
\ No newline at end of file COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mklml/lib/libiomp5md.dll ./release/libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_DIR}/third_party/install/mkldnn/lib/mkldnn.dll ./mkldnn.dll
)
endif()
...@@ -4,132 +4,133 @@ ...@@ -4,132 +4,133 @@
本目录提供一个跨平台的图像分割模型的C++预测部署方案,用户通过一定的配置,加上少量的代码,即可把模型集成到自己的服务中,完成图像分割的任务。 本目录提供一个跨平台的图像分割模型的C++预测部署方案,用户通过一定的配置,加上少量的代码,即可把模型集成到自己的服务中,完成图像分割的任务。
主要设计的目标包括以下点: 主要设计的目标包括以下点:
- 跨平台,支持在 windows和Linux完成编译、开发和部署 - 跨平台,支持在 windows 和 Linux 完成编译、开发和部署
- 支持主流图像分割任务,用户通过少量配置即可加载模型完成常见预测任务,比如人像分割等 - 支持主流图像分割任务,用户通过少量配置即可加载模型完成常见预测任务,比如人像分割等
- 可扩展性,支持用户针对新模型开发自己特殊的数据预处理、后处理等逻辑 - 可扩展性,支持用户针对新模型开发自己特殊的数据预处理、后处理等逻辑
- 高性能,除了`PaddlePaddle`自身带来的性能优势,我们还针对图像分割的特点对关键步骤进行了性能优化
## 主要目录和文件 ## 主要目录和文件
| 文件 | 作用 |
|-------|----------|
| CMakeList.txt | cmake 编译配置文件 |
| external-cmake| 依赖的外部项目 cmake (目前仅有yaml-cpp)|
| demo.cpp | 示例C++代码,演示加载模型完成预测任务 |
| predictor | 加载模型并预测的类代码|
| preprocess |数据预处理相关的类代码|
| utils | 一些基础公共函数|
| images/humanseg | 样例人像分割模型的测试图片目录|
| conf/humanseg.yaml | 示例人像分割模型配置|
| tools/visualize.py | 预测结果彩色可视化脚本 |
## Windows平台编译
### 前置条件
* Visual Studio 2015+
* CUDA 8.0 / CUDA 9.0 + CuDNN 7
* CMake 3.0+
我们分别在 `Visual Studio 2015``Visual Studio 2019 Community` 两个版本下做了测试.
**下面所有示例,以根目录为 `D:\`演示**
### Step1: 下载代码
1. `git clone http://gitlab.baidu.com/Paddle/PaddleSeg.git`
2. 拷贝 `D:\PaddleSeg\inference\` 目录到 `D:\PaddleDeploy`下
目录`D:\PaddleDeploy\inference` 目录包含了`CMakelist.txt`以及代码等项目文件.
### Step2: 下载PaddlePaddle预测库fluid_inference
根据Windows环境,下载相应版本的PaddlePaddle预测库,并解压到`D:\PaddleDeploy\`目录
| CUDA | GPU | 下载地址 |
|------|------|--------|
| 8.0 | Yes | [fluid_inference.zip](https://bj.bcebos.com/v1/paddleseg/fluid_inference_win.zip) |
| 9.0 | Yes | [fluid_inference_cuda90.zip](https://paddleseg.bj.bcebos.com/fluid_inference_cuda9_cudnn7.zip) |
`D:\PaddleDeploy\fluid_inference`目录包含内容为:
```bash
paddle # paddle核心目录
third_party # paddle 第三方依赖
version.txt # 编译的版本信息
``` ```
inference
├── demo.cpp # 演示加载模型、读入数据、完成预测任务C++代码
|
├── conf
│ └── humanseg.yaml # 示例人像分割模型配置
├── images
│ └── humanseg # 示例人像分割模型测试图片目录
├── tools
│ └── visualize.py # 示例人像分割模型结果可视化脚本
├── docs
| ├── linux_build.md # Linux 编译指南
| ├── windows_vs2015_build.md # windows VS2015编译指南
│ └── windows_vs2019_build.md # Windows VS2019编译指南
|
├── utils # 一些基础公共函数
|
├── preprocess # 数据预处理相关代码
|
├── predictor # 模型加载和预测相关代码
|
├── CMakeList.txt # cmake编译入口文件
|
└── external-cmake # 依赖的外部项目cmake(目前仅有yaml-cpp)
```
### Step3: 安装配置OpenCV ## 编译
支持在`Windows``Linux`平台编译和使用:
- [Linux 编译指南](./docs/linux_build.md)
- [Windows 使用 Visual Studio 2019 Community 编译指南](./docs/windows_vs2019_build.md)
- [Windows 使用 Visual Studio 2015 编译指南](./docs/windows_vs2015_build.md)
1. 在OpenCV官网下载适用于Windows平台的3.4.6版本, [下载地址](https://sourceforge.net/projects/opencvlibrary/files/3.4.6/opencv-3.4.6-vc14_vc15.exe/download) `Windows`上推荐使用最新的`Visual Studio 2019 Community`直接编译`CMake`项目。
2. 运行下载的可执行文件,将OpenCV解压至指定目录,如`D:\PaddleDeploy\opencv`
3. 配置环境变量,如下流程所示
1. 我的电脑->属性->高级系统设置->环境变量
2. 在系统变量中找到Path(如没有,自行创建),并双击编辑
3. 新建,将opencv路径填入并保存,如`D:\PaddleDeploy\opencv\build\x64\vc14\bin`
### Step4: 以VS2015为例编译代码 ## 预测并可视化结果
以下命令需根据自己系统中各相关依赖的路径进行修改 完成编译后,便生成了需要的可执行文件和链接库,然后执行以下步骤:
* 调用VS2015, 请根据实际VS安装路径进行调整,打开cmd命令行工具执行以下命令 ### 1. 下载模型文件
* 其他vs版本,请查找到对应版本的`vcvarsall.bat`路径,替换本命令即可 我们提供了一个人像分割模型示例用于测试,点击右侧地址下载:[示例模型下载地址](https://paddleseg.bj.bcebos.com/inference_model/deeplabv3p_xception65_humanseg.tgz)
下载并解压,解压后目录结构如下:
``` ```
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 deeplabv3p_xception65_humanseg
├── __model__ # 模型文件
|
└── __params__ # 参数文件
``` ```
解压后把上述目录拷贝到合适的路径:
* CMAKE编译工程
* PADDLE_DIR: fluid_inference预测库目录 **假设**`Windows`系统上,我们模型和参数文件所在路径为`D:\projects\models\deeplabv3p_xception65_humanseg`
* CUDA_LIB: CUDA动态库目录, 请根据实际安装情况调整
* OPENCV_DIR: OpenCV解压目录 **假设**`Linux`上对应的路径则为`/root/projects/models/deeplabv3p_xception65_humanseg`
```
# 创建CMake的build目录 ### 2. 修改配置
D: 源代码的`conf`目录下提供了示例人像分割模型的配置文件`humanseg.yaml`, 相关的字段含义和说明如下:
cd PaddleDeploy\inference ```yaml
mkdir build DEPLOY:
cd build # 是否使用GPU预测
D:\PaddleDeploy\inference\build> cmake .. -G "Visual Studio 14 2015 Win64" -DWITH_GPU=ON -DPADDLE_DIR=D:\PaddleDeploy\fluid_inference -DCUDA_LIB=D:\PaddleDeploy\cudalib\v8.0\lib\x64 -DOPENCV_DIR=D:\PaddleDeploy\opencv -T host=x64 USE_GPU: 1
# 模型和参数文件所在目录路径
MODEL_PATH: "/root/projects/models/deeplabv3p_xception65_humanseg"
# 模型文件名
MODEL_FILENAME: "__model__"
# 参数文件名
PARAMS_FILENAME: "__params__"
# 预测图片的的标准输入尺寸,输入尺寸不一致会做resize
EVAL_CROP_SIZE: (513, 513)
# 均值
MEAN: [104.008, 116.669, 122.675]
# 方差
STD: [1.0, 1.0, 1.0]
# 图片类型, rgb 或者 rgba
IMAGE_TYPE: "rgb"
# 分类类型数
NUM_CLASSES: 2
# 图片通道数
CHANNELS : 3
# 预处理方式,目前提供图像分割的通用处理类SegPreProcessor
PRE_PROCESSOR: "SegPreProcessor"
# 预测模式,支持 NATIVE 和 ANALYSIS
PREDICTOR_MODE: "ANALYSIS"
# 每次预测的 batch_size
BATCH_SIZE : 3
``` ```
修改字段`MODEL_PATH`的值为你在**上一步**下载并解压的模型文件所放置的目录即可。
这里的`cmake`参数`-G`, 可以根据自己的`VS`版本调整,具体请参考[cmake文档](https://cmake.org/cmake/help/v3.15/manual/cmake-generators.7.html)
* 生成可执行文件 ### 3. 执行预测
``` 在终端中切换到生成的可执行文件所在目录为当前目录(Windows系统为`cmd`)。
D:\PaddleDeploy\inference\build> msbuild /m /p:Configuration=Release cpp_inference_demo.sln
```
### Step5: 预测及可视化
上步骤中编译生成的可执行文件和相关动态链接库并保存在build/Release目录下,可通过Windows命令行直接调用。
可下载并解压示例模型进行测试,点击下载示例的人像分割模型[下载地址](https://paddleseg.bj.bcebos.com/inference_model/deeplabv3p_xception65_humanseg.tgz)
假设解压至 `D:\PaddleDeploy\models\deeplabv3p_xception65_humanseg` ,执行以下命令:
`Linux` 系统中执行以下命令:
```shell
./demo --conf=/root/projects/PaddleSeg/inference/conf/humanseg.yaml --input_dir=/root/projects/PaddleSeg/inference/images/humanseg/
``` ```
cd Release `Windows` 中执行以下命令:
D:\PaddleDeploy\inference\build\Release> demo.exe --conf=D:\\PaddleDeploy\\inference\\conf\\humanseg.yaml --input_dir=D:\\PaddleDeploy\\inference\\images\humanseg\\ ```shell
D:\projects\PaddleSeg\inference\build\Release>demo.exe --conf=D:\\projects\\PaddleSeg\\inference\\conf\\humanseg.yaml --input_dir=D:\\projects\\PaddleSeg\\inference\\images\humanseg\\
``` ```
预测使用的两个命令参数说明如下: 预测使用的两个命令参数说明如下:
| 参数 | 含义 | | 参数 | 含义 |
|-------|----------| |-------|----------|
| conf | 模型配置的yaml文件路径 | | conf | 模型配置的Yaml文件路径 |
| input_dir | 需要预测的图片目录 | | input_dir | 需要预测的图片目录 |
**配置文件**的样例以及字段注释说明请参考: [conf/humanseg.yaml](./conf/humanseg.yaml)
样例程序会扫描input_dir目录下的所有图片,并生成对应的预测结果图片。 配置文件说明请参考上一步,样例程序会扫描input_dir目录下的所有图片,并生成对应的预测结果图片:
文件`demo.jpg`预测的结果存储在`demo_jpg.png`中,可视化结果在`demo_jpg_scoremap.png`中, 原始尺寸的预测结果在`demo_jpg_recover.png`中。 文件`demo.jpg`预测的结果存储在`demo_jpg.png`中,可视化结果在`demo_jpg_scoremap.png`中, 原始尺寸的预测结果在`demo_jpg_recover.png`中。
输入原图 输入原图
![avatar](images/humanseg/demo.jpg) ![avatar](images/humanseg/demo2.jpeg)
输出预测结果 输出预测结果
![avatar](images/humanseg/demo_jpg_recover.png) ![avatar](images/humanseg/demo2_jpeg_recover.png)
DEPLOY: DEPLOY:
USE_GPU: 1 USE_GPU: 1
MODEL_PATH: "C:\\PaddleDeploy\\models\\deeplabv3p_xception65_humanseg" MODEL_PATH: "/root/projects/models/deeplabv3p_xception65_humanseg"
MODEL_NAME: "unet" MODEL_NAME: "unet"
MODEL_FILENAME: "__model__" MODEL_FILENAME: "__model__"
PARAMS_FILENAME: "__params__" PARAMS_FILENAME: "__params__"
...@@ -11,5 +11,5 @@ DEPLOY: ...@@ -11,5 +11,5 @@ DEPLOY:
NUM_CLASSES: 2 NUM_CLASSES: 2
CHANNELS : 3 CHANNELS : 3
PRE_PROCESSOR: "SegPreProcessor" PRE_PROCESSOR: "SegPreProcessor"
PREDICTOR_MODE: "ANALYSIS" PREDICTOR_MODE: "NATIVE"
BATCH_SIZE : 3 BATCH_SIZE : 3
\ No newline at end of file
...@@ -21,7 +21,6 @@ int main(int argc, char** argv) { ...@@ -21,7 +21,6 @@ int main(int argc, char** argv) {
// 2. get all the images with extension '.jpeg' at input_dir // 2. get all the images with extension '.jpeg' at input_dir
auto imgs = PaddleSolution::utils::get_directory_images(FLAGS_input_dir, ".jpeg|.jpg"); auto imgs = PaddleSolution::utils::get_directory_images(FLAGS_input_dir, ".jpeg|.jpg");
// 3. predict // 3. predict
predictor.predict(imgs); predictor.predict(imgs);
return 0; return 0;
......
# Linux平台 编译指南
## 说明
本文档在 `Linux`平台使用`GCC 4.8.5``GCC 4.9.4`测试过,如果需要使用更高G++版本编译使用,则需要重新编译Paddle预测库,请参考: [从源码编译Paddle预测库](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/advanced_usage/deploy/inference/build_and_install_lib_cn.html#id15)
## 前置条件
* G++ 4.8.2 ~ 4.9.4
* CUDA 8.0/ CUDA 9.0
* CMake 3.0+
请确保系统已经安装好上述基本软件,**下面所有示例以工作目录为 `/root/projects/`演示**
### Step1: 下载代码
1. `mkdir -p /root/projects/ && cd /root/projects`
2. `git clone http://gitlab.baidu.com/Paddle/PaddleSeg.git`
`C++`预测代码在`/root/projects/projects/PaddleSeg/inference` 目录,该目录不依赖任何`PaddleSeg`下其他目录。
### Step2: 下载PaddlePaddle C++ 预测库 fluid_inference
目前仅支持`CUDA 8``CUDA 9`,请点击 [PaddlePaddle预测库下载地址](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/advanced_usage/deploy/inference/build_and_install_lib_cn.html)下载对应的版本。
下载并解压后`/root/projects/fluid_inference`目录包含内容为:
```
fluid_inference
├── paddle # paddle核心库和头文件
|
├── third_party # 第三方依赖库和头文件
|
└── version.txt # 版本和编译信息
```
### Step3: 安装配置OpenCV
```shell
# 0. 切换到/root/projects目录
cd /root/projects
# 1. 下载OpenCV3.4.6版本源代码
wget -c https://paddleseg.bj.bcebos.com/inference/opencv-3.4.6.zip
# 2. 解压
unzip opencv-3.4.6.zip && cd opencv-3.4.6
# 3. 创建build目录并编译, 这里安装到/usr/local/opencv3目录
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local/opencv3
make -j4
make install
```
### Step4: 编译
```shell
cd /root/projects/PaddleSeg/inference
mkdir build && cd build
cmake .. -DWITH_GPU=ON -DPADDLE_DIR=/root/projects/fluid_inference -DCUDA_LIB=/usr/local/cuda/lib64/ -DOPENCV_DIR=/usr/local/opencv3/ -DCUDNN_LIB=/usr/local/cuda/lib64/
make
```
### Step5: 预测及可视化
执行命令:
```
./demo --conf=/path/to/your/conf --input_dir=/path/to/your/input/data/directory
```
更详细说明请参考ReadMe文档: [预测和可视化部分](../ReadMe.md)
# Windows平台使用 Visual Studio 2015 编译指南
本文档步骤,我们同时在`Visual Studio 2015``Visual Studio 2019 Community` 两个版本进行了测试,我们推荐使用[`Visual Studio 2019`直接编译`CMake`项目](./windows_vs2019_build.md)
## 前置条件
* Visual Studio 2015
* CUDA 8.0/ CUDA 9.0
* CMake 3.0+
请确保系统已经安装好上述基本软件,**下面所有示例以工作目录为 `D:\projects`演示**
### Step1: 下载代码
1. 打开`cmd`, 执行 `cd /d D:\projects`
2. `git clone http://gitlab.baidu.com/Paddle/PaddleSeg.git`
`C++`预测库代码在`D:\projects\PaddleSeg\inference` 目录,该目录不依赖任何`PaddleSeg`下其他目录。
### Step2: 下载PaddlePaddle C++ 预测库 fluid_inference
根据Windows环境,下载相应版本的PaddlePaddle预测库,并解压到`D:\projects\`目录
| CUDA | GPU | 下载地址 |
|------|------|--------|
| 8.0 | Yes | [fluid_inference.zip](https://bj.bcebos.com/v1/paddleseg/fluid_inference_win.zip) |
| 9.0 | Yes | [fluid_inference_cuda90.zip](https://paddleseg.bj.bcebos.com/fluid_inference_cuda9_cudnn7.zip) |
解压后`D:\projects\fluid_inference`目录包含内容为:
```
fluid_inference
├── paddle # paddle核心库和头文件
|
├── third_party # 第三方依赖库和头文件
|
└── version.txt # 版本和编译信息
```
### Step3: 安装配置OpenCV
1. 在OpenCV官网下载适用于Windows平台的3.4.6版本, [下载地址](https://sourceforge.net/projects/opencvlibrary/files/3.4.6/opencv-3.4.6-vc14_vc15.exe/download)
2. 运行下载的可执行文件,将OpenCV解压至指定目录,如`D:\PaddleDeploy\opencv`
3. 配置环境变量,如下流程所示
- 我的电脑->属性->高级系统设置->环境变量
- 在系统变量中找到Path(如没有,自行创建),并双击编辑
- 新建,将opencv路径填入并保存,如`D:\PaddleDeploy\opencv\build\x64\vc14\bin`
### Step4: 以VS2015为例编译代码
以下命令需根据自己系统中各相关依赖的路径进行修改
* 调用VS2015, 请根据实际VS安装路径进行调整,打开cmd命令行工具执行以下命令
* 其他vs版本(比如vs2019),请查找到对应版本的`vcvarsall.bat`路径,替换本命令即可
```
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
```
* CMAKE编译工程
* PADDLE_DIR: fluid_inference预测库路径
* CUDA_LIB: CUDA动态库目录, 请根据实际安装情况调整
* OPENCV_DIR: OpenCV解压目录
```
# 切换到预测库所在目录
cd /d D:\projects\PaddleSeg\inference\
# 创建构建目录, 重新构建只需要删除该目录即可
mkdir build
cd build
# cmake构建VS项目
D:\projects\PaddleSeg\inference\build> cmake .. -G "Visual Studio 14 2015 Win64" -DWITH_GPU=ON -DPADDLE_DIR=D:\projects\fluid_inference -DCUDA_LIB=D:\projects\cudalib\v8.0\lib\x64 -DOPENCV_DIR=D:\projects\opencv -T host=x64
```
这里的`cmake`参数`-G`, 表示生成对应的VS版本的工程,可以根据自己的`VS`版本调整,具体请参考[cmake文档](https://cmake.org/cmake/help/v3.15/manual/cmake-generators.7.html)
* 生成可执行文件
```
D:\projects\PaddleSeg\inference\build> msbuild /m /p:Configuration=Release cpp_inference_demo.sln
```
### Step5: 预测及可视化
上述`Visual Studio 2015`编译产出的可执行文件在`build\release`目录下,切换到该目录:
```
cd /d D:\projects\PaddleSeg\inference\build\release
```
之后执行命令:
```
demo.exe --conf=/path/to/your/conf --input_dir=/path/to/your/input/data/directory
```
更详细说明请参考ReadMe文档: [预测和可视化部分](../README.md)
# Visual Studio 2019 Community CMake 编译指南
Windows 平台下,我们使用`Visual Studio 2015``Visual Studio 2019 Community` 进行了测试。微软从`Visual Studio 2017`开始即支持直接管理`CMake`跨平台编译项目,但是直到`2019`才提供了稳定和完全的支持,所以如果你想使用CMake管理项目编译构建,我们推荐你使用`Visual Studio 2019`环境下构建。
你也可以使用和`VS2015`一样,通过把`CMake`项目转化成`VS`项目来编译,其中**有差别的部分**在文档中我们有说明,请参考:[使用Visual Studio 2015 编译指南](./windows_vs2015_build.md)
## 前置条件
* Visual Studio 2019
* CUDA 8.0/ CUDA 9.0
* CMake 3.0+
请确保系统已经安装好上述基本软件,我们使用的是`VS2019`的社区版。
**下面所有示例以工作目录为 `D:\projects`演示**
### Step1: 下载代码
1. 点击下载源代码:[下载地址](https://github.com/PaddlePaddle/PaddleSeg/archive/master.zip)
2. 解压,解压后目录重命名为`PaddleSeg`
以下代码目录路径为`D:\projects\PaddleSeg` 为例。
### Step2: 下载PaddlePaddle C++ 预测库 fluid_inference
根据Windows环境,下载相应版本的PaddlePaddle预测库,并解压到`D:\projects\`目录
| CUDA | GPU | 下载地址 |
|------|------|--------|
| 8.0 | Yes | [fluid_inference.zip](https://bj.bcebos.com/v1/paddleseg/fluid_inference_win.zip) |
| 9.0 | Yes | [fluid_inference_cuda90.zip](https://paddleseg.bj.bcebos.com/fluid_inference_cuda9_cudnn7.zip) |
解压后`D:\projects\fluid_inference`目录包含内容为:
```
fluid_inference
├── paddle # paddle核心库和头文件
|
├── third_party # 第三方依赖库和头文件
|
└── version.txt # 版本和编译信息
```
**注意:** `CUDA90`版本解压后目录名称为`fluid_inference_cuda90`。
### Step3: 安装配置OpenCV
1. 在OpenCV官网下载适用于Windows平台的3.4.6版本, [下载地址](https://sourceforge.net/projects/opencvlibrary/files/3.4.6/opencv-3.4.6-vc14_vc15.exe/download)
2. 运行下载的可执行文件,将OpenCV解压至指定目录,如`D:\projects\opencv`
3. 配置环境变量,如下流程所示
- 我的电脑->属性->高级系统设置->环境变量
- 在系统变量中找到Path(如没有,自行创建),并双击编辑
- 新建,将opencv路径填入并保存,如`D:\projects\opencv\build\x64\vc14\bin`
### Step4: 使用Visual Studio 2019直接编译CMake
1. 打开Visual Studio 2019 Community,点击`继续但无需代码`
![step2](https://paddleseg.bj.bcebos.com/inference/vs2019_step1.png)
2. 点击: `文件`->`打开`->`CMake`
![step2.1](https://paddleseg.bj.bcebos.com/inference/vs2019_step2.png)
选择项目代码所在路径,并打开`CMakeList.txt`:
![step2.2](https://paddleseg.bj.bcebos.com/inference/vs2019_step3.png)
3. 点击:`项目`->`cpp_inference_demo的CMake设置`
![step3](https://paddleseg.bj.bcebos.com/inference/vs2019_step4.png)
4. 点击`浏览`,分别设置编译选项指定`CUDA`、`OpenCV`、`Paddle预测库`的路径
![step4](https://paddleseg.bj.bcebos.com/inference/vs2019_step5.png)
三个编译参数的含义说明如下:
| 参数名 | 含义 |
| ---- | ---- |
| CUDA_LIB | cuda的库路径 |
| OPENCV_DIR | OpenCV的安装路径, |
| PADDLE_DIR | Paddle预测库的路径 |
**设置完成后**, 点击上图中`保存并生成CMake缓存以加载变量`。
5. 点击`生成`->`全部生成`
![step6](https://paddleseg.bj.bcebos.com/inference/vs2019_step6.png)
### Step5: 预测及可视化
上述`Visual Studio 2019`编译产出的可执行文件在`out\build\x64-Release`目录下,打开`cmd`,并切换到该目录:
```
cd /d D:\projects\PaddleSeg\inference\out\x64-Release
```
之后执行命令:
```
demo.exe --conf=/path/to/your/conf --input_dir=/path/to/your/input/data/directory
```
更详细说明请参考ReadMe文档: [预测和可视化部分](../ReadMe.md)
...@@ -6,7 +6,7 @@ include(ExternalProject) ...@@ -6,7 +6,7 @@ include(ExternalProject)
message("${CMAKE_BUILD_TYPE}") message("${CMAKE_BUILD_TYPE}")
ExternalProject_Add( ExternalProject_Add(
yaml-cpp ext-yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
GIT_TAG e0e01d53c27ffee6c86153fa41e7f5e57d3e5c90 GIT_TAG e0e01d53c27ffee6c86153fa41e7f5e57d3e5c90
CMAKE_ARGS CMAKE_ARGS
...@@ -26,4 +26,4 @@ ExternalProject_Add( ...@@ -26,4 +26,4 @@ ExternalProject_Add(
# Disable install step # Disable install step
INSTALL_COMMAND "" INSTALL_COMMAND ""
LOG_DOWNLOAD ON LOG_DOWNLOAD ON
) )
\ No newline at end of file
...@@ -125,6 +125,10 @@ namespace PaddleSolution { ...@@ -125,6 +125,10 @@ namespace PaddleSolution {
int Predictor::native_predict(const std::vector<std::string>& imgs) int Predictor::native_predict(const std::vector<std::string>& imgs)
{ {
if (imgs.size() == 0) {
LOG(ERROR) << "No image found";
return -1;
}
int config_batch_size = _model_config._batch_size; int config_batch_size = _model_config._batch_size;
int channels = _model_config._channels; int channels = _model_config._channels;
...@@ -205,6 +209,11 @@ namespace PaddleSolution { ...@@ -205,6 +209,11 @@ namespace PaddleSolution {
int Predictor::analysis_predict(const std::vector<std::string>& imgs) { int Predictor::analysis_predict(const std::vector<std::string>& imgs) {
if (imgs.size() == 0) {
LOG(ERROR) << "No image found";
return -1;
}
int config_batch_size = _model_config._batch_size; int config_batch_size = _model_config._batch_size;
int channels = _model_config._channels; int channels = _model_config._channels;
int eval_width = _model_config._resize[0]; int eval_width = _model_config._resize[0];
......
...@@ -28,7 +28,6 @@ namespace PaddleSolution { ...@@ -28,7 +28,6 @@ namespace PaddleSolution {
_channels = 0; _channels = 0;
_use_gpu = 0; _use_gpu = 0;
_batch_size = 1; _batch_size = 1;
_model_name.clear();
_model_file_name.clear(); _model_file_name.clear();
_model_path.clear(); _model_path.clear();
_param_file_name.clear(); _param_file_name.clear();
...@@ -57,7 +56,7 @@ namespace PaddleSolution { ...@@ -57,7 +56,7 @@ namespace PaddleSolution {
} }
bool load_config(const std::string& conf_file) { bool load_config(const std::string& conf_file) {
reset(); reset();
YAML::Node config = YAML::LoadFile(conf_file); YAML::Node config = YAML::LoadFile(conf_file);
...@@ -79,8 +78,6 @@ namespace PaddleSolution { ...@@ -79,8 +78,6 @@ namespace PaddleSolution {
_img_type = config["DEPLOY"]["IMAGE_TYPE"].as<std::string>(); _img_type = config["DEPLOY"]["IMAGE_TYPE"].as<std::string>();
// 5. get class number // 5. get class number
_class_num = config["DEPLOY"]["NUM_CLASSES"].as<int>(); _class_num = config["DEPLOY"]["NUM_CLASSES"].as<int>();
// 6. get model_name
_model_name = config["DEPLOY"]["MODEL_NAME"].as<std::string>();
// 7. set model path // 7. set model path
_model_path = config["DEPLOY"]["MODEL_PATH"].as<std::string>(); _model_path = config["DEPLOY"]["MODEL_PATH"].as<std::string>();
// 8. get model file_name // 8. get model file_name
...@@ -101,7 +98,7 @@ namespace PaddleSolution { ...@@ -101,7 +98,7 @@ namespace PaddleSolution {
} }
void debug() const { void debug() const {
std::cout << "EVAL_CROP_SIZE: (" << _resize[0] << ", " << _resize[1] << ")" << std::endl; std::cout << "EVAL_CROP_SIZE: (" << _resize[0] << ", " << _resize[1] << ")" << std::endl;
std::cout << "MEAN: ["; std::cout << "MEAN: [";
...@@ -129,7 +126,6 @@ namespace PaddleSolution { ...@@ -129,7 +126,6 @@ namespace PaddleSolution {
std::cout << "DEPLOY.NUM_CLASSES: " << _class_num << std::endl; std::cout << "DEPLOY.NUM_CLASSES: " << _class_num << std::endl;
std::cout << "DEPLOY.CHANNELS: " << _channels << std::endl; std::cout << "DEPLOY.CHANNELS: " << _channels << std::endl;
std::cout << "DEPLOY.MODEL_PATH: " << _model_path << std::endl; std::cout << "DEPLOY.MODEL_PATH: " << _model_path << std::endl;
std::cout << "DEPLOY.MODEL_NAME: " << _model_name << std::endl;
std::cout << "DEPLOY.MODEL_FILENAME: " << _model_file_name << std::endl; std::cout << "DEPLOY.MODEL_FILENAME: " << _model_file_name << std::endl;
std::cout << "DEPLOY.PARAMS_FILENAME: " << _param_file_name << std::endl; std::cout << "DEPLOY.PARAMS_FILENAME: " << _param_file_name << std::endl;
std::cout << "DEPLOY.PRE_PROCESSOR: " << _pre_processor << std::endl; std::cout << "DEPLOY.PRE_PROCESSOR: " << _pre_processor << std::endl;
...@@ -152,8 +148,6 @@ namespace PaddleSolution { ...@@ -152,8 +148,6 @@ namespace PaddleSolution {
int _channels; int _channels;
// DEPLOY.MODEL_PATH // DEPLOY.MODEL_PATH
std::string _model_path; std::string _model_path;
// DEPLOY.MODEL_NAME
std::string _model_name;
// DEPLOY.MODEL_FILENAME // DEPLOY.MODEL_FILENAME
std::string _model_file_name; std::string _model_file_name;
// DEPLOY.PARAMS_FILENAME // DEPLOY.PARAMS_FILENAME
......
...@@ -3,7 +3,13 @@ ...@@ -3,7 +3,13 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <string> #include <string>
#ifdef _WIN32
#include <filesystem> #include <filesystem>
#else
#include <dirent.h>
#include <sys/types.h>
#endif
namespace PaddleSolution { namespace PaddleSolution {
namespace utils { namespace utils {
...@@ -14,7 +20,31 @@ namespace PaddleSolution { ...@@ -14,7 +20,31 @@ namespace PaddleSolution {
#endif #endif
return dir + seperator + path; return dir + seperator + path;
} }
#ifndef _WIN32
// scan a directory and get all files with input extensions
inline std::vector<std::string> get_directory_images(const std::string& path, const std::string& exts)
{
std::vector<std::string> imgs;
struct dirent *entry;
DIR *dir = opendir(path.c_str());
if (dir == NULL) {
closedir(dir);
return imgs;
}
while ((entry = readdir(dir)) != NULL) {
std::string item = entry->d_name;
auto ext = strrchr(entry->d_name, '.');
if (!ext || std::string(ext) == "." || std::string(ext) == "..") {
continue;
}
if (exts.find(ext) != std::string::npos) {
imgs.push_back(path_join(path, entry->d_name));
}
}
return imgs;
}
#else
// scan a directory and get all files with input extensions // scan a directory and get all files with input extensions
inline std::vector<std::string> get_directory_images(const std::string& path, const std::string& exts) inline std::vector<std::string> get_directory_images(const std::string& path, const std::string& exts)
{ {
...@@ -28,5 +58,6 @@ namespace PaddleSolution { ...@@ -28,5 +58,6 @@ namespace PaddleSolution {
} }
return imgs; return imgs;
} }
#endif
} }
} }
...@@ -106,19 +106,21 @@ class SegDataset(object): ...@@ -106,19 +106,21 @@ class SegDataset(object):
def batch(self, reader, batch_size, is_test=False, drop_last=False): def batch(self, reader, batch_size, is_test=False, drop_last=False):
def batch_reader(is_test=False, drop_last=drop_last): def batch_reader(is_test=False, drop_last=drop_last):
if is_test: if is_test:
imgs, img_names, valid_shapes, org_shapes = [], [], [], [] imgs, grts, img_names, valid_shapes, org_shapes = [], [], [], [], []
for img, img_name, valid_shape, org_shape in reader(): for img, grt, img_name, valid_shape, org_shape in reader():
imgs.append(img) imgs.append(img)
grts.append(grt)
img_names.append(img_name) img_names.append(img_name)
valid_shapes.append(valid_shape) valid_shapes.append(valid_shape)
org_shapes.append(org_shape) org_shapes.append(org_shape)
if len(imgs) == batch_size: if len(imgs) == batch_size:
yield np.array(imgs), img_names, np.array( yield np.array(imgs), np.array(
valid_shapes), np.array(org_shapes) grts), img_names, np.array(valid_shapes), np.array(
imgs, img_names, valid_shapes, org_shapes = [], [], [], [] org_shapes)
imgs, grts, img_names, valid_shapes, org_shapes = [], [], [], [], []
if not drop_last and len(imgs) > 0: if not drop_last and len(imgs) > 0:
yield np.array(imgs), img_names, np.array( yield np.array(imgs), np.array(grts), img_names, np.array(
valid_shapes), np.array(org_shapes) valid_shapes), np.array(org_shapes)
else: else:
imgs, labs, ignore = [], [], [] imgs, labs, ignore = [], [], []
...@@ -146,93 +148,64 @@ class SegDataset(object): ...@@ -146,93 +148,64 @@ class SegDataset(object):
# reserver alpha channel # reserver alpha channel
cv2_imread_flag = cv2.IMREAD_UNCHANGED cv2_imread_flag = cv2.IMREAD_UNCHANGED
if mode == ModelPhase.TRAIN or mode == ModelPhase.EVAL: parts = line.strip().split(cfg.DATASET.SEPARATOR)
parts = line.strip().split(cfg.DATASET.SEPARATOR) if len(parts) != 2:
if len(parts) != 2: if mode == ModelPhase.TRAIN or mode == ModelPhase.EVAL:
raise Exception("File list format incorrect! It should be" raise Exception("File list format incorrect! It should be"
" image_name{}label_name\\n".format( " image_name{}label_name\\n".format(
cfg.DATASET.SEPARATOR)) cfg.DATASET.SEPARATOR))
img_name, grt_name = parts[0], None
else:
img_name, grt_name = parts[0], parts[1] img_name, grt_name = parts[0], parts[1]
img_path = os.path.join(src_dir, img_name)
grt_path = os.path.join(src_dir, grt_name)
img = cv2_imread(img_path, cv2_imread_flag) img_path = os.path.join(src_dir, img_name)
img = cv2_imread(img_path, cv2_imread_flag)
if grt_name is not None:
grt_path = os.path.join(src_dir, grt_name)
grt = cv2_imread(grt_path, cv2.IMREAD_GRAYSCALE) grt = cv2_imread(grt_path, cv2.IMREAD_GRAYSCALE)
else:
grt = None
if img is None or grt is None: if img is None:
raise Exception( raise Exception(
"Empty image, src_dir: {}, img: {} & lab: {}".format( "Empty image, src_dir: {}, img: {} & lab: {}".format(
src_dir, img_path, grt_path)) src_dir, img_path, grt_path))
img_height = img.shape[0] img_height = img.shape[0]
img_width = img.shape[1] img_width = img.shape[1]
if grt is not None:
grt_height = grt.shape[0] grt_height = grt.shape[0]
grt_width = grt.shape[1] grt_width = grt.shape[1]
if img_height != grt_height or img_width != grt_width: if img_height != grt_height or img_width != grt_width:
raise Exception( raise Exception(
"source img and label img must has the same size") "source img and label img must has the same size")
else:
if len(img.shape) < 3: if mode == ModelPhase.TRAIN or mode == ModelPhase.EVAL:
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
img_channels = img.shape[2]
if img_channels < 3:
raise Exception(
"PaddleSeg only supports gray, rgb or rgba image")
if img_channels != cfg.DATASET.DATA_DIM:
raise Exception(
"Input image channel({}) is not match cfg.DATASET.DATA_DIM({}), img_name={}"
.format(img_channels, cfg.DATASET.DATADIM, img_name))
if img_channels != len(cfg.MEAN):
raise Exception(
"img name {}, img chns {} mean size {}, size unequal".
format(img_name, img_channels, len(cfg.MEAN)))
if img_channels != len(cfg.STD):
raise Exception(
"img name {}, img chns {} std size {}, size unequal".format(
img_name, img_channels, len(cfg.STD)))
# visualization mode
elif mode == ModelPhase.VISUAL:
if cfg.DATASET.SEPARATOR in line:
parts = line.strip().split(cfg.DATASET.SEPARATOR)
img_name = parts[0]
else:
img_name = line.strip()
img_path = os.path.join(src_dir, img_name)
img = cv2_imread(img_path, cv2_imread_flag)
if img is None:
raise Exception("empty image, src_dir:{}, img: {}".format(
src_dir, img_name))
# Convert grayscale image to BGR 3 channel image
if len(img.shape) < 3:
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
img_height = img.shape[0]
img_width = img.shape[1]
img_channels = img.shape[2]
if img_channels < 3:
raise Exception("this repo only recept gray, rgb or rgba image")
if img_channels != cfg.DATASET.DATA_DIM:
raise Exception("data dim must equal to image channels")
if img_channels != len(cfg.MEAN):
raise Exception(
"img name {}, img chns {} mean size {}, size unequal".
format(img_name, img_channels, len(cfg.MEAN)))
if img_channels != len(cfg.STD):
raise Exception( raise Exception(
"img name {}, img chns {} std size {}, size unequal".format( "Empty image, src_dir: {}, img: {} & lab: {}".format(
img_name, img_channels, len(cfg.STD))) src_dir, img_path, grt_path))
grt = None if len(img.shape) < 3:
grt_name = None img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
else:
raise ValueError("mode error: {}".format(mode)) img_channels = img.shape[2]
if img_channels < 3:
raise Exception("PaddleSeg only supports gray, rgb or rgba image")
if img_channels != cfg.DATASET.DATA_DIM:
raise Exception(
"Input image channel({}) is not match cfg.DATASET.DATA_DIM({}), img_name={}"
.format(img_channels, cfg.DATASET.DATADIM, img_name))
if img_channels != len(cfg.MEAN):
raise Exception(
"img name {}, img chns {} mean size {}, size unequal".format(
img_name, img_channels, len(cfg.MEAN)))
if img_channels != len(cfg.STD):
raise Exception(
"img name {}, img chns {} std size {}, size unequal".format(
img_name, img_channels, len(cfg.STD)))
return img, grt, img_name, grt_name return img, grt, img_name, grt_name
...@@ -329,4 +302,4 @@ class SegDataset(object): ...@@ -329,4 +302,4 @@ class SegDataset(object):
elif ModelPhase.is_eval(mode): elif ModelPhase.is_eval(mode):
return (img, grt, ignore) return (img, grt, ignore)
elif ModelPhase.is_visual(mode): elif ModelPhase.is_visual(mode):
return (img, img_name, valid_shape, org_shape) return (img, grt, img_name, valid_shape, org_shape)
...@@ -171,7 +171,7 @@ def visualize(cfg, ...@@ -171,7 +171,7 @@ def visualize(cfg,
fetch_list = [pred.name] fetch_list = [pred.name]
test_reader = dataset.batch(dataset.generator, batch_size=1, is_test=True) test_reader = dataset.batch(dataset.generator, batch_size=1, is_test=True)
img_cnt = 0 img_cnt = 0
for imgs, img_names, valid_shapes, org_shapes in test_reader: for imgs, grts, img_names, valid_shapes, org_shapes in test_reader:
pred_shape = (imgs.shape[2], imgs.shape[3]) pred_shape = (imgs.shape[2], imgs.shape[3])
pred, = exe.run( pred, = exe.run(
program=test_prog, program=test_prog,
...@@ -185,6 +185,7 @@ def visualize(cfg, ...@@ -185,6 +185,7 @@ def visualize(cfg,
# Add more comments # Add more comments
res_map = np.squeeze(pred[i, :, :, :]).astype(np.uint8) res_map = np.squeeze(pred[i, :, :, :]).astype(np.uint8)
img_name = img_names[i] img_name = img_names[i]
grt = grts[i]
res_shape = (res_map.shape[0], res_map.shape[1]) res_shape = (res_map.shape[0], res_map.shape[1])
if res_shape[0] != pred_shape[0] or res_shape[1] != pred_shape[1]: if res_shape[0] != pred_shape[0] or res_shape[1] != pred_shape[1]:
res_map = cv2.resize( res_map = cv2.resize(
...@@ -196,6 +197,11 @@ def visualize(cfg, ...@@ -196,6 +197,11 @@ def visualize(cfg,
res_map, (org_shape[1], org_shape[0]), res_map, (org_shape[1], org_shape[0]),
interpolation=cv2.INTER_NEAREST) interpolation=cv2.INTER_NEAREST)
if grt is not None:
grt = cv2.resize(
grt, (org_shape[1], org_shape[0]),
interpolation=cv2.INTER_NEAREST)
png_fn = to_png_fn(img_names[i]) png_fn = to_png_fn(img_names[i])
if also_save_raw_results: if also_save_raw_results:
raw_fn = os.path.join(raw_save_dir, png_fn) raw_fn = os.path.join(raw_save_dir, png_fn)
...@@ -209,6 +215,8 @@ def visualize(cfg, ...@@ -209,6 +215,8 @@ def visualize(cfg,
makedirs(dirname) makedirs(dirname)
pred_mask = colorize(res_map, org_shapes[i], color_map) pred_mask = colorize(res_map, org_shapes[i], color_map)
if grt is not None:
grt = colorize(grt, org_shapes[i], color_map)
cv2.imwrite(vis_fn, pred_mask) cv2.imwrite(vis_fn, pred_mask)
img_cnt += 1 img_cnt += 1
...@@ -233,7 +241,13 @@ def visualize(cfg, ...@@ -233,7 +241,13 @@ def visualize(cfg,
img, img,
epoch, epoch,
dataformats='HWC') dataformats='HWC')
#TODO: add ground truth (label) images #add ground truth (label) images
if grt is not None:
log_writer.add_image(
"Label/{}".format(img_names[i]),
grt[..., ::-1],
epoch,
dataformats='HWC')
# If in local_test mode, only visualize 5 images just for testing # If in local_test mode, only visualize 5 images just for testing
# procedure # procedure
......
# 源码编译安装及搭建服务流程 # 源码编译安装及搭建服务流程
本文将介绍源码编译安装以及在服务搭建流程。 本文将介绍源码编译安装以及在服务搭建流程。编译前确保PaddleServing的依赖项安装完毕。依赖安装教程请前往[PaddleSegServing 依赖安装](./README.md).
## 1. 系统依赖项 ## 1. 编译安装PaddleServing
依赖项 | 验证过的版本
-- | --
Linux | Centos 6.10 / 7
CMake | 3.0+
GCC | 4.8.2/5.4.0
Python| 2.7
GO编译器| 1.9.2
openssl| 1.0.1+
bzip2 | 1.0.6+
如果需要使用GPU预测,还需安装以下几个依赖库
GPU库 | 验证过的版本
-- | --
CUDA | 9.2
cuDNN | 7.1.4
nccl | 2.4.7
## 2. 安装依赖项
以下流程在百度云CentOS7.5+CUDA9.2环境下进行。
### 2.1. 安装openssl、Go编译器以及bzip2
```bash
yum -y install openssl openssl-devel golang bzip2-libs bzip2-devel
```
### 2.2. 安装GPU预测的依赖项(如果需要使用GPU预测,必须执行此步骤)
#### 2.2.1. 安装配置CUDA9.2以及cuDNN 7.1.4
该百度云机器已经安装CUDA以及cuDNN,仅需复制相关头文件与链接库
```bash
# 看情况确定是否需要安装 cudnn
# 进入 cudnn 根目录
cd /home/work/cudnn/cudnn7.1.4
# 拷贝头文件
cp include/cudnn.h /usr/local/cuda/include/
# 拷贝链接库
cp lib64/libcudnn* /usr/local/cuda/lib64/
# 修改头文件、链接库访问权限
chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn*
```
#### 2.2.2. 安装nccl库
```bash
# 下载文件 nccl-repo-rhel7-2.4.7-ga-cuda9.2-1-1.x86_64.rpm
wget -c https://paddlehub.bj.bcebos.com/serving/nccl-repo-rhel7-2.4.7-ga-cuda9.2-1-1.x86_64.rpm
# 安装nccl的repo
rpm -i nccl-repo-rhel7-2.4.7-ga-cuda9.2-1-1.x86_64.rpm
# 更新索引
yum -y update
# 安装包
yum -y install libnccl-2.4.7-1+cuda9.2 libnccl-devel-2.4.7-1+cuda9.2 libnccl-static-2.4.7-1+cuda9.2
```
### 2.3. 安装 cmake 3.15
如果机器没有安装cmake或者已安装cmake的版本低于3.0,请执行以下步骤
```bash
# 如果原来的已经安装低于3.0版本的cmake,请先卸载原有低版本 cmake
yum -y remove cmake
# 下载源代码并解压
wget -c https://github.com/Kitware/CMake/releases/download/v3.15.0/cmake-3.15.0.tar.gz
tar xvfz cmake-3.15.0.tar.gz
# 编译cmake
cd cmake-3.15.0
./configure
make -j4
# 安装并检查cmake版本
make install
cmake --version
# 在cmake-3.15.0目录中,将相应的头文件目录(curl目录,为PaddleServing的依赖头文件目录)拷贝到系统include目录下
cp -r Utilities/cmcurl/include/curl/ /usr/include/
```
### 2.4. 为依赖库增加相应的软连接
现在Linux系统中大部分链接库的名称都以版本号作为后缀,如libcurl.so.4.3.0。这种命名方式最大的问题是,CMakeList.txt中find_library命令是无法识别使用这种命名方式的链接库,会导致CMake时候出错。由于本项目是用CMake构建,所以务必保证相应的链接库以 .so 或 .a为后缀命名。解决这个问题最简单的方式就是用创建一个软连接指向相应的链接库。在百度云的机器中,只有curl库的命名方式有问题。所以命令如下:(如果是其他库,解决方法也类似):
```bash
ln -s /usr/lib64/libcurl.so.4.3.0 /usr/lib64/libcurl.so
```
### 2.5. 编译安装PaddleServing
下列步骤介绍CPU版本以及GPU版本的PaddleServing编译安装过程。 下列步骤介绍CPU版本以及GPU版本的PaddleServing编译安装过程。
```bash ```bash
...@@ -134,7 +46,7 @@ serving ...@@ -134,7 +46,7 @@ serving
└── tools └── tools
``` ```
### 2.6. 安装PaddleSegServing ## 2. 安装PaddleSegServing
```bash ```bash
# Step 1. 在~目录下下载PaddleSeg代码 # Step 1. 在~目录下下载PaddleSeg代码
...@@ -243,4 +155,4 @@ make install ...@@ -243,4 +155,4 @@ make install
可参考[预编译安装流程](./README.md)中2.2.2节。可执行文件在该目录下:~/serving/build/output/demo/seg-serving/bin/。 可参考[预编译安装流程](./README.md)中2.2.2节。可执行文件在该目录下:~/serving/build/output/demo/seg-serving/bin/。
### 3.4 运行客户端程序进行测试。 ### 3.4 运行客户端程序进行测试。
可参考[预编译安装流程](./README.md)中2.2.3节。 可参考[预编译安装流程](./README.md)中2.2.3节。
\ No newline at end of file
# PaddleSeg Serving # PaddleSegServing
## 1.简介 ## 1.简介
PaddleSegServing是基于PaddleSeg开发的实时图像分割服务的企业级解决方案。用户仅需关注模型本身,无需理解模型模型的加载、预测以及GPU/CPU资源的并发调度等细节操作,通过设置不同的参数配置,即可根据自身的业务需求定制化不同图像分割服务。目前,PaddleSegServing支持人脸分割、城市道路分割、宠物外形分割模型。本文将通过一个人脸分割服务的搭建示例,展示PaddleSeg服务通用的搭建流程。 PaddleSegServing是基于PaddleSeg开发的实时图像分割服务的企业级解决方案。用户仅需关注模型本身,无需理解模型模型的加载、预测以及GPU/CPU资源的并发调度等细节操作,通过设置不同的参数配置,即可根据自身的业务需求定制化不同图像分割服务。目前,PaddleSegServing支持人脸分割、城市道路分割、宠物外形分割模型。本文将通过一个人脸分割服务的搭建示例,展示PaddleSeg服务通用的搭建流程。
## 2.预编译版本安装及搭建服务流程 ## 2.预编译版本安装及搭建服务流程
### 2.1. 下载预编译的PaddleSegServing 运行PaddleSegServing需要依赖其他的链接库,请保证在下载安装前系统环境已经具有相应的依赖项。
安装以及搭建服务的流程均在Centos和Ubuntu系统上验证。以下是Centos系统上的搭建流程,Ubuntu版本的依赖项安装流程介绍在[Ubuntu系统下依赖项的安装教程](UBUNTU.md)
### 2.1. 系统依赖项
依赖项 | 验证过的版本
-- | --
Linux | Centos 6.10 / 7, Ubuntu16.07
CMake | 3.0+
GCC | 4.8.2
Python| 2.7
GO编译器| 1.9.2
openssl| 1.0.1+
bzip2 | 1.0.6+
如果需要使用GPU预测,还需安装以下几个依赖库
GPU库 | 验证过的版本
-- | --
CUDA | 9.2
cuDNN | 7.1.4
nccl | 2.4.7
### 2.2. 安装依赖项
#### 2.2.1. 安装openssl、Go编译器以及bzip2
```bash
yum -y install openssl openssl-devel golang bzip2-libs bzip2-devel
```
#### 2.2.2. 安装GPU预测的依赖项(如果需要使用GPU预测,必须执行此步骤)
#### 2.2.2.1. 安装配置CUDA 9.2以及cuDNN 7.1.4
请确保正确安装CUDA 9.2以及cuDNN 7.1.4. 以下为安装CUDA和cuDNN的官方教程。
```bash
安装CUDA教程: https://developer.nvidia.com/cuda-90-download-archive?target_os=Linux&target_arch=x86_64&target_distro=CentOS&target_version=7&target_type=rpmnetwork
安装cuDNN教程: https://docs.nvidia.com/deeplearning/sdk/cudnn-install/index.html
```
#### 2.2.2.2. 安装nccl库(如果已安装nccl 2.4.7请忽略该步骤)
```bash
# 下载文件 nccl-repo-rhel7-2.4.7-ga-cuda9.2-1-1.x86_64.rpm
wget -c https://paddlehub.bj.bcebos.com/serving/nccl-repo-rhel7-2.4.7-ga-cuda9.2-1-1.x86_64.rpm
# 安装nccl的repo
rpm -i nccl-repo-rhel7-2.4.7-ga-cuda9.2-1-1.x86_64.rpm
# 更新索引
yum -y update
# 安装包
yum -y install libnccl-2.4.7-1+cuda9.2 libnccl-devel-2.4.7-1+cuda9.2 libnccl-static-2.4.7-1+cuda9.2
```
### 2.2.3. 安装 cmake 3.15
如果机器没有安装cmake或者已安装cmake的版本低于3.0,请执行以下步骤
```bash
# 如果原来的已经安装低于3.0版本的cmake,请先卸载原有低版本 cmake
yum -y remove cmake
# 下载源代码并解压
wget -c https://github.com/Kitware/CMake/releases/download/v3.15.0/cmake-3.15.0.tar.gz
tar xvfz cmake-3.15.0.tar.gz
# 编译cmake
cd cmake-3.15.0
./configure
make -j4
# 安装并检查cmake版本
make install
cmake --version
# 在cmake-3.15.0目录中,将相应的头文件目录(curl目录,为PaddleServing的依赖头文件目录)拷贝到系统include目录下
cp -r Utilities/cmcurl/include/curl/ /usr/include/
```
### 2.2.4. 为依赖库增加相应的软连接
现在Linux系统中大部分链接库的名称都以版本号作为后缀,如libcurl.so.4.3.0。这种命名方式最大的问题是,CMakeList.txt中find_library命令是无法识别使用这种命名方式的链接库,会导致CMake时候出错。由于本项目是用CMake构建,所以务必保证相应的链接库以 .so 或 .a为后缀命名。解决这个问题最简单的方式就是用创建一个软连接指向相应的链接库。在百度云的机器中,只有curl库的命名方式有问题。所以命令如下:(如果是其他库,解决方法也类似):
```bash
ln -s /usr/lib64/libcurl.so.4.3.0 /usr/lib64/libcurl.so
```
### 2.3. 下载预编译的PaddleSegServing
预编译版本在Centos7.6系统下编译,如果想快速体验PaddleSegServing,可在此系统下下载预编译版本进行安装。预编译版本有两个,一个是针对有GPU的机器,推荐安装GPU版本PaddleSegServing。另一个是CPU版本PaddleServing,针对无GPU的机器。 预编译版本在Centos7.6系统下编译,如果想快速体验PaddleSegServing,可在此系统下下载预编译版本进行安装。预编译版本有两个,一个是针对有GPU的机器,推荐安装GPU版本PaddleSegServing。另一个是CPU版本PaddleServing,针对无GPU的机器。
#### 2.1.1. 下载并解压GPU版本PaddleSegServing #### 2.3.1. 下载并解压GPU版本PaddleSegServing
```bash ```bash
cd ~ cd ~
wget -c XXXX/PaddleSegServing.centos7.6_cuda9.2_gpu.tar.gz wget -c --no-check-certificate https://paddleseg.bj.bcebos.com/serving/paddle_seg_serving_centos7.6_gpu_cuda9.2.tar.gz
tar xvfz PaddleSegServing.centos7.6_cuda9.2_gpu.tar.gz tar xvfz PaddleSegServing.centos7.6_cuda9.2_gpu.tar.gz seg-serving
``` ```
#### 2.1.2. 下载并解压CPU版本PaddleSegServing #### 2.3.2. 下载并解压CPU版本PaddleSegServing
```bash ```bash
cd ~ cd ~
wget -c XXXX/PaddleSegServing.centos7.6_cuda9.2_cpu.tar.gz wget -c --no-check-certificate https://paddleseg.bj.bcebos.com/serving/paddle_seg_serving_centos7.6_cpu.tar.gz
tar xvfz PaddleSegServing.centos7.6_cuda9.2_gpu.tar.gz tar xvfz PaddleSegServing.centos7.6_cuda9.2_gpu.tar.gz seg-serving
``` ```
解压后的PaddleSegServing目录如下。 解压后的PaddleSegServing目录如下。
...@@ -36,13 +116,22 @@ tar xvfz PaddleSegServing.centos7.6_cuda9.2_gpu.tar.gz ...@@ -36,13 +116,22 @@ tar xvfz PaddleSegServing.centos7.6_cuda9.2_gpu.tar.gz
└── log └── log
``` ```
### 2.2. 运行PaddleSegServing ### 2.4 安装动态库
把 libiomp5.so, libmklml_gnu.so, libmklml_intel.so拷贝到/usr/lib。
```bash
cd seg-serving/bin/
cp libiomp5.so libmklml_gnu.so libmklml_intel.so /usr/lib
```
### 2.5. 运行PaddleSegServing
本节将介绍如何运行以及测试PaddleSegServing。 本节将介绍如何运行以及测试PaddleSegServing。
#### 2.2.1. 搭建人脸分割服务 #### 2.5.1. 搭建人脸分割服务
搭建人脸分割服务只需完成一些配置文件的编写即可,其他分割服务的搭建流程类似。 搭建人脸分割服务只需完成一些配置文件的编写即可,其他分割服务的搭建流程类似。
##### 2.2.1.1. 下载人脸分割模型文件,并将其复制到相应目录。 #### 2.5.1.1. 下载人脸分割模型文件,并将其复制到相应目录。
```bash ```bash
# 下载人脸分割模型 # 下载人脸分割模型
wget -c https://paddleseg.bj.bcebos.com/inference_model/deeplabv3p_xception65_humanseg.tgz wget -c https://paddleseg.bj.bcebos.com/inference_model/deeplabv3p_xception65_humanseg.tgz
...@@ -52,11 +141,7 @@ cp -r deeplabv3p_xception65_humanseg seg-serving/bin/data/model/paddle/fluid ...@@ -52,11 +141,7 @@ cp -r deeplabv3p_xception65_humanseg seg-serving/bin/data/model/paddle/fluid
``` ```
##### 2.2.1.2. 配置参数文件 #### 2.5.1.2. 配置参数文件。参数文件如下。PaddleSegServing仅新增一个配置文件seg_conf.yaml,用来指定具体分割模型的一些参数,如均值、方差、图像尺寸等。该配置文件可在gflags.conf中通过--seg_conf_file指定。其他配置文件的字段解释可参考以下链接:https://github.com/PaddlePaddle/Serving/blob/develop/doc/SERVING_CONFIGURE.md
参数文件如,PaddleSegServing仅新增一个配置文件seg_conf.yaml,用来指定具体分割模型的一些参数,如均值、方差、图像尺寸等。该配置文件可在gflags.conf中通过--seg_conf_file指定。
其他配置文件的字段解释可参考以下链接:https://github.com/PaddlePaddle/Serving/blob/develop/doc/SERVING_CONFIGURE.md (TODO:介绍seg_conf.yaml中每个字段的含义)
```bash ```bash
conf/ conf/
...@@ -68,7 +153,25 @@ conf/ ...@@ -68,7 +153,25 @@ conf/
└── workflow.prototxt └── workflow.prototxt
``` ```
#### 2.2.2 运行服务端程序 以下为seg_conf.yaml文件内容以及每一个配置项的内容。
```bash
%YAML:1.0
# 输入到模型的图像的尺寸。会将任意图片resize到513*513尺寸的图像,再放入模型进行推测。
SIZE: [513, 513]
# 均值
MEAN: [104.008, 116.669, 122.675]
# 方差
STD: [1.0, 1.0, 1.0]
# 通道数
CHANNELS: 3
# 类别数量
CLASS_NUM: 2
# 加载的模型的名称,需要与model_toolkit.prototxt中对应模型的名称保持一致。
MODEL_NAME: "human_segmentation"
```
#### 2.5.2 运行服务端程序
```bash ```bash
# 1. 设置环境变量 # 1. 设置环境变量
...@@ -77,17 +180,44 @@ export LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/lib64:$LD_LIBRARY_PATH ...@@ -77,17 +180,44 @@ export LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/lib64:$LD_LIBRARY_PATH
cd ~/serving/build/output/demo/seg-serving/bin/ cd ~/serving/build/output/demo/seg-serving/bin/
./seg-serving ./seg-serving
``` ```
#### 2.2.3.运行客户端程序进行测试 (建议在windows、mac测试,可直接查看分割后的图像) #### 2.5.3.运行客户端程序
以下为PaddleSeg的目录结构,客户端在PaddleSeg/serving/tools目录。
客户端程序是用Python3编写的,代码简洁易懂,可以通过运行客户端验证服务的正确性以及性能表现。 ```bash
PaddleSeg
├── configs
├── contrib
├── dataset
├── docs
├── inference
├── pdseg
├── README.md
├── requirements.txt
├── scripts
├── serving
│ ├── COMPILE_GUIDE.md
│ ├── imgs
│ ├── README.md
│ ├── requirements.txt # 客户端程序依赖的包
│ ├── seg-serving
│ ├── tools # 客户端目录
│ │ ├── images # 测试的图像目录,可放置jpg格式或其他三通道格式的图像,以jpg或jpeg作为文件后缀名
│ │ │  ├── 1.jpg
│ │ │ ├── 2.jpg
│ │ │ └── 3.jpg
│ │ └── image_seg_client.py # 客户端测试代码
│ └── UBUNTU.md
├── test
└── test.md
```
客户端程序使用Python3编写,通过下载requirements.txt中的python依赖包(`pip3 install -r requirements.txt`),用户可以在Windows、Mac、Linux等平台上正常运行该客户端,测试的图像放在PaddleSeg/serving/tools/images目录,用户可以根据自己需要把其他三通道格式的图片放置到该目录下进行测试。从服务端返回的结果图像保存在PaddleSeg/serving/tools目录下。
```bash ```bash
# 使用Python3.6,需要安装opencv-python、requests、numpy包(建议安装anaconda)
cd tools cd tools
vim image_seg_client.py (修改IMAGE_SEG_URL变量,改成服务端的ip地址) vim image_seg_client.py (修改IMAGE_SEG_URL变量,改成服务端的ip地址)
python3.6 image_seg_client.py python3.6 image_seg_client.py
# 当前目录可以看到生成出分割结果的图片。 # 当前目录可以看到生成出分割结果的图片。
``` ```
## 3. 源码编译安装及搭建服务流程 (可选) ## 3. 源码编译安装及搭建服务流程 (可选)
源码编译安装时间较长,一般推荐在centos7.6下安装预编译版本进行使用。如果您系统版本非centos7.6或者您想进行二次开发,请点击以下链接查看[源码编译安装流程](./COMPILE_GUIDE.md) 源码编译安装时间较长,一般推荐在centos7.6下安装预编译版本进行使用。如果您系统版本非centos7.6或者您想进行二次开发,请点击以下链接查看[源码编译安装流程](./COMPILE_GUIDE.md)
\ No newline at end of file
# Ubuntu系统下依赖项的安装教程
运行PaddleSegServing需要系统安装一些依赖库。在不同发行版本的Linux系统下,安装依赖项的具体命令略有不同,以下介绍在Ubuntu 16.07下安装依赖项的方法。
## 1. 安装ssl、go、python、bzip2、crypto.
```bash
sudo apt-get install golang-1.10 python2.7 libssl1.0.0 libssl-dev libssl-doc libcrypto++-dev libcrypto++-doc libcrypto++-utils libbz2-1.0 libbz2-dev
```
## 2. 为ssl、crypto、curl链接库添加软连接
```bash
ln -s /lib/x86_64-linux-gnu/libssl.so.1.0.0 /usr/lib/x86_64-linux-gnu/libssl.so
ln -s /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 /usr/lib/x86_64-linux-gnu/libcrypto.so.10
ln -s /usr/lib/x86_64-linux-gnu/libcurl.so.4.4.0 /usr/lib/x86_64-linux-gnu/libcurl.so
```
## 3. 安装GPU依赖项(如果需要使用GPU预测,必须执行此步骤)
### 3.1. 安装配置CUDA 9.2以及cuDNN 7.1.4
方法与[预编译安装流程](README.md) 2.2.2.1节一样。
### 3.2. 安装nccl库(如果已安装nccl 2.4.7请忽略该步骤)
```bash
# 下载nccl相关的deb包
wget -c --no-check-certificate https://paddleseg.bj.bcebos.com/serving/nccl-repo-ubuntu1604-2.4.8-ga-cuda9.2_1-1_amd64.deb
sudo apt-key add /var/nccl-repo-2.4.8-ga-cuda9.2/7fa2af80.pub
# 安装deb包
sudo dpkg -i nccl-repo-ubuntu1604-2.4.8-ga-cuda9.2_1-1_amd64.deb
# 更新索引
sudo apt update
# 安装nccl库
sudo apt-get install libnccl2 libnccl-dev
```
## 4. 安装cmake 3.15
如果机器没有安装cmake或者已安装cmake的版本低于3.0,请执行以下步骤
```bash
# 如果原来的已经安装低于3.0版本的cmake,请先卸载原有低版本 cmake
sudo apt-get autoremove cmake
```
其余安装cmake的流程请参考以下链接[预编译安装流程](README.md) 2.2.3节。
## 5. 安装PaddleSegServing
### 5.1. 下载并解压GPU版本PaddleSegServing
```bash
cd ~
wget -c --no-check-certificate https://paddleseg.bj.bcebos.com/serving/paddle_seg_serving_ubuntu16.07_gpu_cuda9.2.tar.gz
tar xvfz PaddleSegServing.ubuntu16.07_cuda9.2_gpu.tar.gz seg-serving
```
### 5.2. 下载并解压CPU版本PaddleSegServing
```bash
cd ~
wget -c --no-check-certificate https://paddleseg.bj.bcebos.com/serving%2Fpaddle_seg_serving_ubuntu16.07_cpu.tar.gz
tar xvfz PaddleSegServing.ubuntu16.07_cuda9.2_gpu.tar.gz seg-serving
```
## 6. gcc版本问题
在Ubuntu 16.07系统中,默认的gcc版本为5.4.0。而目前PaddleSegServing仅支持gcc 4.8编译,所以如果测试的机器gcc版本为5.4,请先进行降级(无需卸载原有的gcc)。
```bash
# 安装gcc 4.8
sudo apt-get install gcc-4.8
# 查看是否成功安装gcc4.8
ls /usr/bin/gcc*
# 设置gcc4.8的优先级,使其能被gcc命令优先连接gcc4.8
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
# 查看设置结果(非必须)
sudo update-alternatives --config gcc
```
--enable_model_toolkit --enable_model_toolkit
--seg_conf_file=./conf/seg_conf.yaml --seg_conf_file=./conf/seg_conf.yaml
--num_threads=1
--bthread_min_concurrency=4
--bthread_concurrency=4
engines { engines {
name: "human_segmentation" name: "human_segmentation"
type: "FLUID_GPU_NATIVE" type: "FLUID_GPU_ANALYSIS"
reloadable_meta: "./data/model/paddle/fluid_time_file" reloadable_meta: "./data/model/paddle/fluid_time_file"
reloadable_type: "timestamp_ne" reloadable_type: "timestamp_ne"
model_data_path: "./data/model/paddle/fluid/deeplabv3p_xception65_humanseg" model_data_path: "./data/model/paddle/fluid/deeplabv3p_xception65_humanseg"
......
...@@ -128,16 +128,30 @@ int ImageSegOp::inference() { ...@@ -128,16 +128,30 @@ int ImageSegOp::inference() {
mask_raw[di] = label; mask_raw[di] = label;
} }
//cv::Mat mask_mat = cv::Mat(height, width, CV_32FC1);
cv::Mat mask_mat = cv::Mat(height, width, CV_8UC1); cv::Mat mask_mat = cv::Mat(height, width, CV_8UC1);
mask_mat.data = mask_raw.data(); //scoremap
// mask_mat.data = reinterpret_cast<uchar *>(data + out_size);
//mask_mat.data = mask_raw.data();
std::vector<uchar> temp_mat(out_size, 0);
for(int i = 0; i < out_size; ++i){
temp_mat[i] = 255 * data[i + out_size];
}
mask_mat.data = temp_mat.data();
cv::Mat mask_temp_mat((*height_vec)[si], (*width_vec)[si], mask_mat.type()); cv::Mat mask_temp_mat((*height_vec)[si], (*width_vec)[si], mask_mat.type());
//Size(cols, rows) //Size(cols, rows)
cv::resize(mask_mat, mask_temp_mat, mask_temp_mat.size()); cv::resize(mask_mat, mask_temp_mat, mask_temp_mat.size());
// cv::resize(mask_mat, mask_temp_mat, cv::Size((*width_vec)[si], (*height_vec)[si])); //debug
//for(int i = 0; i < (*height_vec)[si]; ++i){
// for(int j = 0; j < (*width_vec)[si]; ++j) {
// std::cout << mask_temp_mat.at<float>(i, j) << " ";
// }
// std::cout << std::endl;
//}
std::vector<uchar> mat_buff; std::vector<uchar> mat_buff;
cv::imencode(".png", mask_temp_mat, mat_buff); cv::imencode(".png", mask_temp_mat, mat_buff);
ins->set_mask(mat_buff.data(), mat_buff.size()); ins->set_mask(reinterpret_cast<char *>(mat_buff.data()), mat_buff.size());
} }
// release out tensor object resource // release out tensor object resource
......
...@@ -103,7 +103,8 @@ int ReaderOp::inference() { ...@@ -103,7 +103,8 @@ int ReaderOp::inference() {
const ImageSegReqItem& ins = req->instances(si); const ImageSegReqItem& ins = req->instances(si);
// read dense image from request bytes // read dense image from request bytes
const char* binary = ins.image_binary().c_str(); const char* binary = ins.image_binary().c_str();
size_t length = ins.image_length(); //size_t length = ins.image_length();
size_t length = ins.image_binary().length();
if (length == 0) { if (length == 0) {
LOG(ERROR) << "Empty image, length is 0"; LOG(ERROR) << "Empty image, length is 0";
return -1; return -1;
......
...@@ -50,7 +50,7 @@ int WriteJsonOp::inference() { ...@@ -50,7 +50,7 @@ int WriteJsonOp::inference() {
std::string err_string; std::string err_string;
uint32_t batch_size = seg_out->item_size(); uint32_t batch_size = seg_out->item_size();
LOG(INFO) << "batch_size = " << batch_size; LOG(INFO) << "batch_size = " << batch_size;
LOG(INFO) << seg_out->ShortDebugString(); // LOG(INFO) << seg_out->ShortDebugString();
for (uint32_t si = 0; si < batch_size; si++) { for (uint32_t si = 0; si < batch_size; si++) {
ResponseItem* ins = res->add_prediction(); ResponseItem* ins = res->add_prediction();
//LOG(INFO) << "Original image width = " << seg_out->width(si) << ", height = " << seg_out->height(si); //LOG(INFO) << "Original image width = " << seg_out->width(si) << ", height = " << seg_out->height(si);
...@@ -59,6 +59,7 @@ int WriteJsonOp::inference() { ...@@ -59,6 +59,7 @@ int WriteJsonOp::inference() {
return -1; return -1;
} }
std::string* text = ins->mutable_info(); std::string* text = ins->mutable_info();
LOG(INFO) << seg_out->item(si).ShortDebugString();
if (!ProtoMessageToJson(seg_out->item(si), text, &err_string)) { if (!ProtoMessageToJson(seg_out->item(si), text, &err_string)) {
LOG(ERROR) << "Failed convert message[" LOG(ERROR) << "Failed convert message["
<< seg_out->item(si).ShortDebugString() << seg_out->item(si).ShortDebugString()
......
# coding: utf-8 # coding: utf-8
import sys import os
import cv2 import cv2
import requests import requests
import json import json
...@@ -8,115 +7,96 @@ import base64 ...@@ -8,115 +7,96 @@ import base64
import numpy as np import numpy as np
import time import time
import threading import threading
import re
#分割服务的地址 #分割服务的地址
#IMAGE_SEG_URL = 'http://yq01-gpu-151-23-00.epc:8010/ImageSegService/inference' IMAGE_SEG_URL = 'http://xxx.xxx.xxx.xxx:8010/ImageSegService/inference'
#IMAGE_SEG_URL = 'http://106.12.25.202:8010/ImageSegService/inference'
IMAGE_SEG_URL = 'http://180.76.118.53:8010/ImageSegService/inference'
# 请求预测服务
# input_img 要预测的图片列表
def get_item_json(input_img):
with open(input_img, mode="rb") as fp:
# 使用 http 协议请求服务时, 请使用 base64 编码发送图片
item_binary_b64 = str(base64.b64encode(fp.read()), 'utf-8')
item_size = len(item_binary_b64)
item_json = {
"image_length": item_size,
"image_binary": item_binary_b64
}
return item_json
def request_predictor_server(input_img_list, dir_name):
data = {"instances" : [get_item_json(dir_name + input_img) for input_img in input_img_list]}
response = requests.post(IMAGE_SEG_URL, data=json.dumps(data))
try:
response = json.loads(response.text)
prediction_list = response["prediction"]
mask_response_list = [mask_response["info"] for mask_response in prediction_list]
mask_raw_list = [json.loads(mask_response)["mask"] for mask_response in mask_response_list]
except Exception as err:
print ("Exception[%s], server_message[%s]" % (str(err), response.text))
return None
# 使用 json 协议回复的包也是 base64 编码过的
mask_binary_list = [base64.b64decode(mask_raw) for mask_raw in mask_raw_list]
m = [np.fromstring(mask_binary, np.uint8) for mask_binary in mask_binary_list]
return m
# 对预测结果进行可视化
# input_raw_mask 是server返回的预测结果
# output_img 是可视化结果存储路径
def visualization(mask_mat, output_img):
# ColorMap for visualization more clearly
color_map = [[128, 64, 128],
[244, 35, 231],
[69, 69, 69],
[102, 102, 156],
[190, 153, 153],
[153, 153, 153],
[250, 170, 29],
[219, 219, 0],
[106, 142, 35],
[152, 250, 152],
[69, 129, 180],
[219, 19, 60],
[255, 0, 0],
[0, 0, 142],
[0, 0, 69],
[0, 60, 100],
[0, 79, 100],
[0, 0, 230],
[119, 10, 32]]
im = cv2.imdecode(mask_mat, 1)
w, h, c = im.shape
im2 = cv2.resize(im, (w, h))
im = im2
for i in range(0, h):
for j in range(0, w):
im[i, j] = color_map[im[i, j, 0]]
cv2.imwrite(output_img, im)
#benchmark test
def benchmark_test(batch_size, img_list):
start = time.time()
total_size = len(img_list)
for i in range(0, total_size, batch_size):
mask_mat_list = request_predictor_server(img_list[i : np.min([i + batch_size, total_size])], "images/")
# 将获得的mask matrix转换成可视化图像,并在当前目录下保存为图像文件
# 如果进行压测,可以把这句话注释掉
# for j in range(len(mask_mat_list)):
# visualization(mask_mat_list[j], img_list[j + i])
latency = time.time() - start
print("batch size = %d, total latency = %f s" % (batch_size, latency))
class ClientThread(threading.Thread): class ClientThread(threading.Thread):
def __init__(self, thread_id, batch_size): def __init__(self, thread_id, image_data_repo):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.__thread_id = thread_id self.__thread_id = thread_id
self.__batch_size = batch_size self.__image_data_repo = image_data_repo
def run(self): def run(self):
self.request_image_seg_service(3) self.__request_image_seg_service()
def request_image_seg_service(self, imgs_num): def __request_image_seg_service(self):
total_size = imgs_num
img_list = [str(i + 1) + ".jpg" for i in range(total_size)]
# batch_size_list = [2**i for i in range(0, 4)]
# 持续发送150个请求 # 持续发送150个请求
batch_size_list = [self.__batch_size] * 150 for i in range(1, 151):
i = 1
for batch_size in batch_size_list:
print("Epoch %d, thread %d" % (i, self.__thread_id)) print("Epoch %d, thread %d" % (i, self.__thread_id))
i += 1 self.__benchmark_test()
benchmark_test(batch_size, img_list)
# benchmark test
def __benchmark_test(self):
start = time.time()
for image_filename in self.__image_data_repo:
mask_mat_list = self.__request_predictor_server(image_filename)
input_img = self.__image_data_repo.get_image_matrix(image_filename)
# 将获得的mask matrix转换成可视化图像,并在当前目录下保存为图像文件
# 如果进行压测,可以把这句话注释掉
for j in range(len(mask_mat_list)):
self.__visualization(mask_mat_list[j], image_filename, 2, input_img)
latency = time.time() - start
print("total latency = %f s" % (latency))
# 对预测结果进行可视化
# input_raw_mask 是server返回的预测结果
# output_img 是可视化结果存储路径
def __visualization(self, mask_mat, output_img, num_cls, input_img):
# ColorMap for visualization more clearly
n = num_cls
color_map = []
for j in range(n):
lab = j
a = b = c = 0
color_map.append([a, b, c])
i = 0
while lab:
color_map[j][0] |= (((lab >> 0) & 1) << (7 - i))
color_map[j][1] |= (((lab >> 1) & 1) << (7 - i))
color_map[j][2] |= (((lab >> 2) & 1) << (7 - i))
i += 1
lab >>= 3
im = cv2.imdecode(mask_mat, 1)
w, h, c = im.shape
im2 = cv2.resize(im, (w, h))
im = im2
# I = aF + (1-a)B
a = im / 255.0
im = a * input_img + (1 - a) * [255, 255, 255]
cv2.imwrite(output_img, im)
def __request_predictor_server(self, input_img):
data = {"instances": [self.__get_item_json(input_img)]}
response = requests.post(IMAGE_SEG_URL, data=json.dumps(data))
try:
response = json.loads(response.text)
prediction_list = response["prediction"]
mask_response_list = [mask_response["info"] for mask_response in prediction_list]
mask_raw_list = [json.loads(mask_response)["mask"] for mask_response in mask_response_list]
except Exception as err:
print("Exception[%s], server_message[%s]" % (str(err), response.text))
return None
# 使用 json 协议回复的包也是 base64 编码过的
mask_binary_list = [base64.b64decode(mask_raw) for mask_raw in mask_raw_list]
m = [np.fromstring(mask_binary, np.uint8) for mask_binary in mask_binary_list]
return m
# 请求预测服务
# input_img 要预测的图片列表
def __get_item_json(self, input_img):
# 使用 http 协议请求服务时, 请使用 base64 编码发送图片
item_binary_b64 = str(base64.b64encode(self.__image_data_repo.get_image_binary(input_img)), 'utf-8')
item_size = len(item_binary_b64)
item_json = {
"image_length": item_size,
"image_binary": item_binary_b64
}
return item_json
def create_thread_pool(thread_num, batch_size): def create_thread_pool(thread_num, image_data_repo):
return [ClientThread(i + 1, batch_size) for i in range(thread_num)] return [ClientThread(i + 1, image_data_repo) for i in range(thread_num)]
def run_threads(thread_pool): def run_threads(thread_pool):
...@@ -126,7 +106,35 @@ def run_threads(thread_pool): ...@@ -126,7 +106,35 @@ def run_threads(thread_pool):
for thread in thread_pool: for thread in thread_pool:
thread.join() thread.join()
class ImageDataRepo:
def __init__(self, dir_name):
print("Loading images data...")
self.__data = {}
pattern = re.compile(".+\.(jpg|jpeg)", re.I)
if os.path.isdir(dir_name):
for image_filename in os.listdir(dir_name):
if pattern.match(image_filename):
full_path = os.path.join(dir_name, image_filename)
fp = open(full_path, mode="rb")
image_binary_data = fp.read()
image_mat_data = cv2.imread(full_path)
self.__data[image_filename] = (image_binary_data, image_mat_data)
else:
raise Exception("Please use directory to initialize");
print("Finish loading.")
def __iter__(self):
for filename in self.__data:
yield filename
def get_image_binary(self, image_name):
return self.__data[image_name][0]
def get_image_matrix(self, image_name):
return self.__data[image_name][1]
if __name__ == "__main__": if __name__ == "__main__":
thread_pool = create_thread_pool(thread_num=2, batch_size=1) #preprocess
IDR = ImageDataRepo("images")
thread_pool = create_thread_pool(thread_num=1, image_data_repo=IDR)
run_threads(thread_pool) run_threads(thread_pool)
...@@ -20,6 +20,7 @@ import sys ...@@ -20,6 +20,7 @@ import sys
import tarfile import tarfile
import zipfile import zipfile
import platform import platform
import functools
lasttime = time.time() lasttime = time.time()
FLUSH_INTERVAL = 0.1 FLUSH_INTERVAL = 0.1
...@@ -78,8 +79,10 @@ def _uncompress_file(filepath, extrapath, delete_file, print_progress): ...@@ -78,8 +79,10 @@ def _uncompress_file(filepath, extrapath, delete_file, print_progress):
if filepath.endswith("zip"): if filepath.endswith("zip"):
handler = _uncompress_file_zip handler = _uncompress_file_zip
else: elif filepath.endswith("tgz"):
handler = _uncompress_file_tar handler = _uncompress_file_tar
else:
handler = functools.partial(_uncompress_file_tar, mode="r")
for total_num, index in handler(filepath, extrapath): for total_num, index in handler(filepath, extrapath):
if print_progress: if print_progress:
...@@ -104,8 +107,8 @@ def _uncompress_file_zip(filepath, extrapath): ...@@ -104,8 +107,8 @@ def _uncompress_file_zip(filepath, extrapath):
yield total_num, index yield total_num, index
def _uncompress_file_tar(filepath, extrapath): def _uncompress_file_tar(filepath, extrapath, mode="r:gz"):
files = tarfile.open(filepath, "r:gz") files = tarfile.open(filepath, mode)
filelist = files.getnames() filelist = files.getnames()
total_num = len(filelist) total_num = len(filelist)
for index, file in enumerate(filelist): for index, file in enumerate(filelist):
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册