Commit bd09ccc2 authored by liucong's avatar liucong
Browse files

提交Unet模型python和C++代码示例

parent c80d4455
Pipeline #293 failed with stages
in 0 seconds
#! /bin/sh
############### Ubuntu ###############
# 参考:https://docs.opencv.org/3.4.11/d7/d9f/tutorial_linux_install.html
# apt-get install build-essential -y
# apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev -y
# apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev -y # 处理图像所需的包,可选
############### CentOS ###############
yum install gcc gcc-c++ gtk2-devel gimp-devel gimp-devel-tools gimp-help-browser zlib-devel libtiff-devel libjpeg-devel libpng-devel gstreamer-devel libavc1394-devel libraw1394-devel libdc1394-devel jasper-devel jasper-utils swig python libtool nasm -y
\ No newline at end of file
############################ 在线安装依赖 ###############################
#cd ./3rdParty
#pip install rbuild-master.tar.gz
############################ 离线安装依赖 ###############################
# 安装依赖
cd ./3rdParty/rbuild_depend
pip install click-6.6-py2.py3-none-any.whl
pip install six-1.15.0-py2.py3-none-any.whl
pip install subprocess32-3.5.4.tar.gz
pip install cget-0.1.9.tar.gz
# 安装rbuild
cd ../
pip install rbuild-master.tar.gz
# 设置cmake的最低版本
cmake_minimum_required(VERSION 3.5)
# 设置项目名
project(MIGraphX_Samples)
# 设置编译器
set(CMAKE_CXX_COMPILER g++)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -std=c++17) # 2.2版本以上需要c++17
set(CMAKE_BUILD_TYPE release)
# 添加头文件路径
set(INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Src/
${CMAKE_CURRENT_SOURCE_DIR}/Src/Utility/
${CMAKE_CURRENT_SOURCE_DIR}/Src/Segmentation/
$ENV{DTKROOT}/include/
${CMAKE_CURRENT_SOURCE_DIR}/depend/include/)
include_directories(${INCLUDE_PATH})
# 添加依赖库路径
set(LIBRARY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/depend/lib64/
$ENV{DTKROOT}/lib/)
link_directories(${LIBRARY_PATH})
# 添加依赖库
set(LIBRARY opencv_core
opencv_imgproc
opencv_imgcodecs
opencv_dnn
migraphx_ref
migraphx
migraphx_c
migraphx_device
migraphx_gpu
migraphx_onnx)
link_libraries(${LIBRARY})
# 添加源文件
set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/Sample.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/Segmentation/Unet.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/Utility/CommonUtility.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/Utility/Filesystem.cpp)
# 添加可执行目标
add_executable(MIGraphX_Samples ${SOURCE_FILES})
# 图像分割
本示例主要通过Unet模型说明如何使用MIGraphX C++ API进行图像分割模型的推理,包括模型初始化、预处理、模型推理。
## 模型简介
本示例采用了经典的Unet模型进行图像分割,模型下载地址:https://www.dropbox.com/s/3ntkhyk30x05uuv/unet_13_256.onnx, 将unet_13_256.onnx文件保存在Resource/Models/Segmentation文件夹下。模型结构如下图所示,可以通过netron工具, 链接:https://netron.app/, 查看具体的模型结构,该模型的输入shape为[batch_size,3,256,256],输出shape为[batch_size,1,256,256],数据排布为NCHW。
<img src="../Images/Unet_01.png" style="zoom:80%;" align=middle>
## 模型初始化
在模型初始化的过程中,首先采用parse_onnx()函数根据提供的模型地址加载图像分割Unet的onnx模型,保存在net中。其次,通过net.get_parameter_shapes()获取Unet模型的输入属性,包含inputName和inputShape。最后,完成模型加载后使用migraphx::gpu::target{}设置编译模式为GPU模式,并使用compile()函数编译模型,完成模型的初始化过程。
其中,模型地址设置在/Resource/Configuration.xml文件中的Unet节点中。
```C++
ErrorCode Unet::Initialize(InitializationParameterOfSegmentation initParamOfSegmentationUnet)
{
...
// 加载模型
net = migraphx::parse_onnx(modelPath);
LOG_INFO(logFile,"succeed to load model: %s\n",GetFileName(modelPath).c_str());
// 获取模型输入属性
std::pair<std::string, migraphx::shape> inputAttribute=*(net.get_parameter_shapes().begin());
inputName=inputAttribute.first;
inputShape=inputAttribute.second;
inputSize=cv::Size(inputShape.lens()[3],inputShape.lens()[2]);
// 设置模型为GPU模式
migraphx::target gpuTarget = migraphx::gpu::target{};
// 编译模型
migraphx::compile_options options;
options.device_id=0; // 设置GPU设备,默认为0号设备
options.offload_copy=true; // 设置offload_copy
net.compile(gpuTarget,options);
LOG_INFO(logFile,"succeed to compile model: %s\n",GetFileName(modelPath).c_str());
...
}
```
## 预处理
完成模型初始化后,需要将输入数据进行如下预处理:
​ 1.尺度变换,将图像resize到256x256大小
​ 2.归一化,将数据归一化到[0.0, 1.0]之间
​ 3.数据排布,将数据从HWC转换为NCHW
本示例代码主要通过opencv实现预处理操作:
```C++
ErrorCode Unet::Segmentation(const cv::Mat &srcImage, cv::Mat &maskImage)
{
...
// 图像预处理并转换为NCHW
cv::Mat inputBlob;
cv::dnn::blobFromImage(srcImage, // 输入数据,支持多张图像
inputBlob, // 输出数据
1 / 255.0, // 缩放系数
inputSize, // 模型输入大小,这里为256x256
Scalar(0, 0, 0), // 均值,这里不需要减均值,所以设置为0
true, // 通道转换,B通道与R通道互换,所以为true
false);
...
}
```
1.cv::dnn::blobFromImage()函数支持多个输入图像,首先将输入图像resize到inputSize大小,然后减去均值,其次乘以缩放系数1/255.0并转换为NCHW,最终将转换好的数据保存到inputBlob作为输入数据,执行后续的模型推理。
## 推理
完成图像预处理后,就可以执行模型推理。首先,定义inputData表示Unet模型的输入数据,inputName表示Unet模型的输入节点名,采用migraphx::argument{inputShape, (float*)inputBlob.data}保存前面预处理的数据inputBlob,第一个参数表示输入数据的shape,第二个参数表示输入数据指针。其次,执行net.eval(inputData)获得模型的推理结果,由于这里只有一个输出节点,仅使用results[0]获取输出节点的数据,就可以对输出数据执行相关后处理操作。
```c++
ErrorCode Unet::Segmentation(const cv::Mat &srcImage, cv::Mat &maskImage)
{
...
// 输入数据
migraphx::parameter_map inputData;
inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};
// 推理
std::vector<migraphx::argument> results = net.eval(inputData);
// 获取输出节点的属性
migraphx::argument result = results[0]; // 获取第一个输出节点的数据
migraphx::shape outputShape=result.get_shape(); // 输出节点的shape
std::vector<std::size_t> outputSize=outputShape.lens(); // 每一维大小,维度顺序为(N,C,H,W)
int numberOfOutput=outputShape.elements(); // 输出节点元素的个数
float *data = (float *)result.data(); // 输出节点数据指针
...
}
```
模型得到的推理结果并不能直接作为图像的分割结果,还需要做如下处理:
1.计算sigmoid值,当计算值大于0.996时值为1,小于等于0.996时值为0,保存在数组value_mask中。
2.保存结果,创建一个cv::Mat将value_mask数组中的值按行依次赋值到对应位置,得到最终的分割图像。
```c++
ErrorCode Unet::Segmentation(const cv::Mat &srcImage, cv::Mat &maskImage)
{
...
// 计算sigmoid值,并且当大于0.996时,值为1,当小于0.996时,值为0,存储在value_mask[]数组中
int value_mask[numberOfOutput];
for(int i=0; i<numberOfOutput; ++i)
{
float num = Sigmoid(data[i]);
if (num > 0.996)
{
value_mask[i] = 1;
}
else
{
value_mask[i] = 0;
}
}
// 将对应的value_mask[]数组中的值依次赋值到outputImage对应位置处
cv::Mat outputImage = cv::Mat_<int>(Size(outputShape.lens()[3], outputShape.lens()[2]), CV_32S);
for(int i=0;i<outputShape.lens()[2];++i)
{
for(int j=0;j<outputShape.lens()[3];++j)
{
outputImage.at<int>(i,j)=value_mask[256*i+j]; // 其中,256代表了outputShape.lens()[3]的值
}
}
...
}
```
注:本次采用的模型权重onnx文件是通过使用具有普通背景的汽车图像来训练的。因此,“现实世界“图像的分割结果不完美是意料之中的。为了获得更好的结果,建议对现实世界示例数据集上的模型进行微调。
# 图像分割
本示例主要通过Unet模型说明如何使用MIGraphX Python API进行图像分割模型的推理,包括预处理、模型推理。
## 模型简介
本示例采用了经典的Unet模型进行图像分割,模型下载地址:https://www.dropbox.com/s/3ntkhyk30x05uuv/unet_13_256.onnx, 将unet_13_256.onnx文件保存在Resource/Models/Segmentation文件夹下。模型结构如下图所示, 链接:https://netron.app/, 该模型的输入shape为[batch_size,3,256,256],输出shape为[batch_size,1,256,256],数据排布为NCHW。
<img src="../Images/Unet_01.png" style="zoom:80%;" align=middle>
## 预处理
在将数据输入到模型之前,需要对图像做如下预处理操作:
​ 1.尺度变换,将图像resize到256x256大小
​ 2.归一化,将数据归一化到[0.0, 1.0]之间
​ 3.数据排布,将数据从HWC转换为CHW,再将维度转换为NCHW
本示例代码通过如下方式实现预处理操作:
```python
def Preprocessing(pil_img, newW, newH):
assert newW > 0 and newH > 0, 'Scale is too small'
img_nd = cv2.resize(pil_img, (newW, newH)) # 将图像尺寸修改为256x256
img_nd = cv2.cvtColor(img_nd, cv2.COLOR_BGR2RGB) # BGR转换为RGB
img_trans = img_nd.transpose((2, 0, 1)) # HWC转换为CHW
if img_trans.max() > 1: # 保证数据处于0-1之间的浮点数
img_trans = img_trans / 255.0
img_trans = np.expand_dims(img_trans, 0) # CHW转换为NCHW
img = img_trans.astype(np.float32) # 转换成浮点型数据
return img
```
## 推理
完成图像预处理后,就可以执行推理,得到推理结果。
```python
# 加载模型
model = migraphx.parse_onnx("../../Resource/Models/Segmentation/unet_13_256.onnx")
# 编译模型
model.compile(migraphx.get_target("gpu"), device_id=0) #device_id: 设置GPU设备,默认为0号设备
# 图像预处理
img = cv2.imread("../../Resource/Images/car1.jpeg")
input_img = Preprocessing(img, 256, 256)
# 模型推理
mask = model.run({'inputs':input_img})
output_mask = np.array(mask[0])[0] # 获取推理结果,shape为(1,256,256)
probs = Sigmoid(output_mask) # 计算sigmoid值
# 0/1像素值,当概率值大于0.996时,值为255,小于等于0.996时,值为0
output_mask[probs > 0.996] = 255
output_mask[probs <= 0.996] = 0
output = output_mask.astype(np.uint8)[0] # 将浮点数类型转换为无符号整型,shape为(256,256)
cv2.imwrite("output.jpg", output) # 保存图像分割结果
```
1.Preprocessing函数返回预处理后的数据(numpy类型),然后通过model.run({'inputs':input_img})得到推理结果,因为只有输入一张图片,所以通过mask[0]获取第一个输出节点的数据即可。
2.模型得到的推理结果并不能直接作为分割结果。首先,需要计算sigmoid值,当概率值大于0.996时值为255,小于等于0.996时值为0。其次,将数据类型转换为无符号整形。最终,保存结果得到分割图像。
注:本次采用的模型权重onnx文件是通过使用具有普通背景的汽车图像来训练的。因此,“现实世界“图像的分割结果不完美是意料之中的。为了获得更好的结果,建议对现实世界示例数据集上的模型进行微调。
import numpy as np
from PIL import Image
import cv2
import migraphx
def Preprocess(pil_img, newW, newH):
assert newW > 0 and newH > 0, 'Scale is too small'
pil_img = pil_img.resize((newW, newH))
img_nd = np.array(pil_img)
if len(img_nd.shape) == 2:
img_nd = np.expand_dims(img_nd, axis=2)
# HWC to CHW
img_print = pil_img
img_trans = img_nd.transpose((2, 0, 1))
if img_trans.max() > 1:
img_trans = img_trans / 255
img_trans = np.expand_dims(img_trans, 0)
return img_trans, img_print
def Sigmoid(x):
return 1 / (1 + np.exp(-x))
if __name__ == '__main__':
# 加载模型
model = migraphx.parse_onnx("./model/unet_13_256.onnx")
inputName = model.get_parameter_names()
inputShape = model.get_parameter_shapes()
print("inputName:{0} \ninputShape:{1}".format(inputName, inputShape))
# 编译模型
model.compile(migraphx.get_target("gpu"), device_id=0) # device_id: 设置GPU设备,默认为0号设备
# 图像预处理
img = Image.open("./car.jpeg")
img, imPrint = Preprocess(img, 256, 256)
input_img = np.zeros((1,3,256,256),dtype='float32')
np.lib.stride_tricks.as_strided(input_img, shape=img.shape, strides=input_img.strides)[:] = img
# 模型推理
mask = model.run({'inputs':input_img})
output_mask = np.array(mask[0])[0] # 获取推理结果,shape为(1,256,256)
probs = Sigmoid(output_mask) # 计算sigmoid值
# 0/1像素值,当大于0.996时,值为255,小于等于0.996时,值为0
output_mask[probs > 0.996] = 255
output_mask[probs <= 0.996] = 0
output = output_mask.astype(np.uint8)[0] # 将浮点型转换为uint8整型,shape为(256,256)
cv2.imwrite("output.jpg", output) # 保存图像分割结果
opencv-python
numpy
pillow
\ No newline at end of file
...@@ -6,38 +6,106 @@ ...@@ -6,38 +6,106 @@
## 模型结构 ## 模型结构
U-net模型整体为U型结构,主要分为三部分:下采样、上采样以及跳跃连接。首先,在左半部分进行编码阶段,通过卷积和下采样降低图像尺寸,提取一些浅层特征。其次,在右半部分进行解码阶段,通过卷积和上采样来获取一些深层次的特征。其中卷积采用无填充方式来保证结果都是基于没有缺失上下文特征得到的,因此每次经过卷积后,图像的大小会减小。最后,通过concat的方式,将编码阶段获得的feature map同解码阶段获得的feature map结合在一起,得到更加精细的细节,从而根据feature map进行预测分割。 U-net模型整体为U型结构,主要分为三部分:下采样、上采样以及跳跃连接。首先,在左半部分进行编码阶段,通过卷积和下采样降低图像尺寸,提取一些浅层特征。其次,在右半部分进行解码阶段,通过卷积和上采样来获取一些深层次的特征。其中卷积采用无填充方式来保证结果都是基于没有缺失上下文特征得到的,因此每次经过卷积后,图像的大小会减小。最后,通过concat的方式,将编码阶段获得的feature map同解码阶段获得的feature map结合在一起,得到更加精细的细节,从而根据feature map进行预测分割。
## 推理 ## 构建安装
### 环境配置 在光源可拉取推理的docker镜像,U-net模型推理的镜像如下:
在光源可拉取推理的docker镜像,U-Net模型推理的镜像如下: ```python
docker pull image.sourcefind.cn:5000/dcu/admin/base/custom:ort1.14.0_migraphx3.0.0-dtk22.10.1
```
### 安装Opencv依赖
```python ```python
docker pull image.sourcefind.cn:5000/dcu/admin/base/custom:ort_dcu_1.14.0_migraphx2.5.2_dtk22.10.1 cd <path_to_migraphx_samples>
sh ./3rdParty/InstallOpenCVDependences.sh
```
### 修改CMakeLists.txt
- 如果使用ubuntu系统,需要修改CMakeLists.txt中依赖库路径:
将"${CMAKE_CURRENT_SOURCE_DIR}/depend/lib64/"修改为"${CMAKE_CURRENT_SOURCE_DIR}/depend/lib/"
- **MIGraphX2.3.0及以上版本需要c++17**
### 安装OpenCV并构建工程
```
rbuild build -d depend
```
### 设置环境变量
将依赖库依赖加入环境变量LD_LIBRARY_PATH,在~/.bashrc中添加如下语句:
**Centos**:
```
export LD_LIBRARY_PATH=<path_to_migraphx_samples>/depend/lib64/:$LD_LIBRARY_PATH
``` ```
在光合开发者社区可下载MIGraphX安装包,python依赖安装: **Ubuntu**:
```
export LD_LIBRARY_PATH=<path_to_migraphx_samples>/depend/lib/:$LD_LIBRARY_PATH
```
然后执行:
```
source ~/.bashrc
```
## 推理
下面介绍如何运行python代码和C++代码示例,具体推理代码解析,在Doc目录中有详细说明。
### python版本推理
1.参考《MIGraphX教程》中的安装方法安装MIGraphX并设置好PYTHONPATH
2.安装依赖:
```Python
# 进入migraphx samples工程根目录
cd <path_to_migraphx_samples>
# 进入示例程序目录
cd Python/Segmentation
# 安装依赖
pip install -r requirements.txt
```
3.在Python/Segmentation目录下执行如下命令运行该示例程序:
```python ```python
pip install -r requirement.txt python Unet.py
``` ```
### 运行示例 输出结果为:
我们提供了基于MIGraphX的推理脚本,版本依赖: <img src="C:/Users/柳聪/Desktop/ni/unet_migraphx/Doc/Images/Unet_03.jpg" style="zoom:100%;" align=middle>
- Migraphx(DCU版本) >= 2.5.2 ### C++版本推理
bert.py是基于Migraphx的推理脚本,使用需安装好MIGraphX。使用方法 切换到build目录中,执行如下命令
```python ```python
# 执行推理 cd ./build/
python unet.py ./MIGraphX_Samples
```
根据提示选择运行U-net模型的示例程序
```c++
./MIGraphX_Samples 0
``` ```
推理结果为: 会在当前目录中生成分割结果图像Result.jpg
<img src="./Sample_picture.jpg" style="zoom:90%;" align=middle> <img src="C:/Users/柳聪/Desktop/ni/unet_migraphx/Doc/Images/Unet_02.jpg" style="zoom:100%;" align=middle>
## 历史版本 ## 历史版本
......
<?xml version="1.0" encoding="GB2312"?>
<opencv_storage>
<!--Unet-->
<Unet>
<ModelPath>"../Resource/Models/Segmentation/unet_13_256.onnx"</ModelPath>
</Unet>
</opencv_storage>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment