摘要:本实践是基于Windows版MindStudio 5.0.RC3,远程连接ECS服务器使用,ECS是基于官方分享的CANN6.0.RC1_MindX_Vision3.0.RC3镜像创建的。
本文分享自华为云社区《【MindStudio训练营第一季】基于U-Net网络的图像分割的MindStudio实践》,作者:Tianyi_Li 。
1.U-Net网络介绍:
U-Net模型基于二维图像分割。在2015年ISBI细胞跟踪竞赛中,U-Net获得了许多最佳奖项。论文中提出了一种用于医学图像分割的网络模型和数据增强方法,有效利用标注数据来解决医学领域标注数据不足的问题。U型网络结构也用于提取上下文和位置信息。
[U-Net 论文]: Olaf Ronneberger, Philipp Fischer, Thomas Brox. “U-Net: Convolutional Networks for Biomedical Image Segmentation.” conditionally accepted at MICCAI 2015. 2015.
2.ECS运行说明
我们的操作基本都在root用户下执行。
首先,修改bash,具体命令和结果如下。
本项目支持MindStudio运行和终端运行。
(1)下载项目代码
下载链接:https://alexed.obs.cn-north-4.myhuaweicloud.com/unet_sdk.zip
将项目文件unet_sdk.zip上传至华为云ECS弹性云服务器/root/目录下,并解压;或者下载到本地电脑,用MindStudio打开。
将之前unet_hw960_bs1.air模型放到/unet_sdk/model/目录下。
项目文件结构
├── unet_sdk
├── README.md
├── data //数据集
│ ├── 1
│ │ ├──image.png //图片
│ │ ├──mask.png //标签
│ ...
├── model
│ ├──air2om.sh // air模型转om脚本
│ ├──xxx.air //air模型
│ ├──xxx.om //om模型
│ ├──aipp_unet_simple_opencv.cfg // aipp文件
├── pipeline
│ ├──unet_simple_opencv.pipeline // pipeline文件
├── main.py // 推理文件
├── run.sh // 执行文件
├── requirements.txt // 需要的三方库
(2) 模型转换
将unet_hw960_bs1.air模型转为昇腾AI处理器支持的.om格式离线模型,此处模型转换需要用到ATC工具。
昇腾张量编译器(Ascend Tensor Compiler,简称ATC)是昇腾CANN架构体系下的模型转换工具,它可以将开源框架的网络模型或Ascend IR定义的单算子描述文件(json格式)转换为昇腾AI处理器支持的.om格式离线模型。模型转换过程中可以实现算子调度的优化、权值数据重排、内存使用优化等,可以脱离设备完成模型的预处理。
ATC参数概览:
(3)运行脚本
运行脚本:
cd unet_sdk/model/ # 切换至模型存储目录
atc --framework=1 --model=unet_hw960_bs1.air --output=unet_hw960_bs1 --input_format=NCHW --soc_version=Ascend310 --log=error --insert_op_conf=aipp_unet_simple_opencv.cfg
- 注意air模型转om只支持静态batch,这里batchsize=1。
参数说明:
framework:原始框架类型。
model:原始模型文件路径与文件名。
output:转换后的离线模型的路径以及文件名。
input_format:输入数据格式。
soc_version:模型转换时指定芯片版本。
log:显示日志的级别。
insert_op_conf:插入算子的配置文件路径与文件名,这里使用AIPP预处理配置文件,用于图像数据预处理。
输出结果:
ATC run success,表示模型转换成功,得到unet_hw960_bs1.om模型。
模型转换成功之后,可以使用MindX SDK mxVision运行脚本,在Ascend 310上进行推理。
(4) MindX SDK mxVision 执行推理
MindX SDK文档请参考:https://support.huaweicloud.com/ug-vis-mindxsdk203/atlasmx_02_0051.html
MindX SDK执行推理的业务流程:
通过stream配置文件,Stream manager可识别需要构建的element以及element之间的连接关系,并启动业务流程。Stream manager对外提供接口,用于向stream发送数据和获取结果,帮助用户实现业务对接。
plugin表示业务流程中的基础模块,通过element的串接构建成一个stream。buffer用于内部挂载解码前后的视频、图像数据,是element之间传递的数据结构,同时也允许用户挂载元数据(Metadata),用于存放结构化数据(如目标检测结果)或过程数据(如缩放后的图像)。
MindX SDK基础概念介绍:
MindX SDK基础插件:
MindX SDK业务流程编排:
Stream配置文件以json格式编写,用户必须指定业务流名称、元件名称和插件名称,并根据需要,补充元件属性和下游元件名称信息。
以下表格为本实验pipeline/unet_simple_opencv.pipeline文件及其对应的名称及描述:
pipeline/unet_simple_opencv.pipeline文件内容如下,可根据实际开发情况进行修改。
{
"unet_mindspore": {
"stream_config": {
"deviceId": "0"
},
"appsrc0": {
"props": {
"blocksize": "4096000"
},
"factory": "appsrc",
"next": "mxpi_imagedecoder0"
},
"mxpi_imagedecoder0": {
"props": {
"cvProcessor": "opencv",
"outputDataFormat": "BGR"
},
"factory": "mxpi_imagedecoder",
"next": "mxpi_imagecrop0"
},
"mxpi_imagecrop0": {
"props": {
"cvProcessor": "opencv",
"dataSource": "ExternalObjects"
},
"factory": "mxpi_imagecrop",
"next": "mxpi_imageresize0"
},
"mxpi_imageresize0": {
"props": {
"handleMethod": "opencv",
"resizeType": "Resizer_Stretch",
"resizeHeight": "960",
"resizeWidth": "960"
},
"factory": "mxpi_imageresize",
"next": "mxpi_tensorinfer0"
},
"mxpi_tensorinfer0": {
"props": {
"dataSource": "mxpi_imageresize0",
"modelPath": "model/unet_hw960_bs1_AIPP.om"
},
"factory": "mxpi_tensorinfer",
"next": "mxpi_dumpdata0"
},
"mxpi_dumpdata0": {
"props": {
"requiredMetaDataKeys": "mxpi_tensorinfer0"
},
"factory": "mxpi_dumpdata",
"next": "appsink0"
},
"appsink0": {
"props": {
"blocksize": "4096000"
},
"factory": "appsink"
}
}
}
(5) 修改modelPath
打开pipeline/unet_simple_opencv.pipeline文件,将"mxpi_tensorinfer0"元件的属性"modelPath"(模型导入路径)修改为模型转换后保存的om模型"model/unet_hw960_bs1.om"。
修改结果:
"modelPath": "model/unet_hw960_bs1.om"
modelPath修改完成之后,保存pipeline/unet_simple_opencv.pipeline文件。
StreamManagerApi:StreamManagerApi文档请参考:
https://support.huaweicloud.com/ug-vis-mindxsdk203/atlasmx_02_0320.html
StreamManagerApi用于对Stream流程的基本管理:加载流程配置、创建流程、向流程发送数据、获得执行结果、销毁流程。
这里用到的StreamManagerApi有:
- InitManager:初始化一个StreamManagerApi。
- CreateMultipleStreams:根据指定的配置创建多个Stream。
- SendData:向指定Stream上的输入元件发送数据(appsrc)。
- GetResult:获得Stream上的输出元件的结果(appsink)
- DestroyAllStreams:销毁所有的流数据。
main.py文件内容如下,可根据实际开发情况进行修改。
import argparse
import base64
import json
import os
import cv2
import numpy as np
from StreamManagerApi import *
import MxpiDataType_pb2 as MxpiDataType
x0 = 2200 # w:2200~4000; h:1000~2800
y0 = 1000
x1 = 4000
y1 = 2800
ori_w = x1 - x0
ori_h = y1 - y0
def _parse_arg():
parser = argparse.ArgumentParser(description="SDK infer")
parser.add_argument("-d", "--dataset", type=str, default="data/",
help="Specify the directory of dataset")
parser.add_argument("-p", "--pipeline", type=str,
default="pipeline/unet_simple_opencv.pipeline",
help="Specify the path of pipeline file")
return parser.parse_args()
def _get_dataset(dataset_dir):
img_ids = sorted(next(os.walk(dataset_dir))[1])
for img_id in img_ids:
img_path = os.path.join(dataset_dir, img_id)
yield img_path
def _process_mask(mask_path):
# 手动裁剪
mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)[y0:y1, x0:x1]
return mask
def _get_stream_manager(pipeline_path):
stream_mgr_api = StreamManagerApi()
ret = stream_mgr_api.InitManager() #初始化stream
if ret != 0:
print(f"Failed to init Stream manager, ret={ret}")
exit(1)
with open(pipeline_path, 'rb') as f:
pipeline_content = f.read()
ret = stream_mgr_api.CreateMultipleStreams(pipeline_content) # 创建stream
if ret != 0:
print(f"Failed to create stream, ret={ret}")
exit(1)
return stream_mgr_api
def _do_infer_image(stream_mgr_api, image_path):
stream_name = b'unet_mindspore' # 与pipeline中stream name一致
data_input = MxDataInput()
with open(image_path, 'rb') as f:
data_input.data = f.read()
# 插入抠图的功能,扣1800*1800大小
roiVector = RoiBoxVector()
roi = RoiBox()
roi.x0 = x0
roi.y0 = y0
roi.x1 = x1
roi.y1 = y1
roiVector.push_back(roi)
data_input.roiBoxs = roiVector
unique_id = stream_mgr_api.SendData(stream_name, 0, data_input) # 向指定Stream上的输入元件发送数据(appsrc)
if unique_id 0.5).astype(np.int)
mask_image = (np.arange(2) == mask_image[..., None]).astype(np.int)
infer_image = np.squeeze(infer_image, axis=0)
inter = np.dot(infer_image.flatten(), mask_image.flatten())
union = np.dot(infer_image.flatten(), infer_image.flatten()) +
np.dot(mask_image.flatten(), mask_image.flatten())
single_dice = 2 * float(inter) / float(union + 1e-6)
single_iou = single_dice / (2 - single_dice)
return single_dice, single_iou
def main(_args):
dice_sum = 0.0
iou_sum = 0.0
cnt = 0
stream_mgr_api = _get_stream_manager(_args.pipeline)
for image_path in _get_dataset(_args.dataset):
infer_image = _do_infer_image(stream_mgr_api, os.path.join(image_path, 'image.png')) # 抠图并且reshape后的shape,1hw
mask_image = _process_mask(os.path.join(image_path, 'mask.png')) # 抠图后的shape, hw
dice, iou = _calculate_accuracy(infer_image, mask_image)
dice_sum += dice
iou_sum += iou
cnt += 1
print(f"image: {image_path}, dice: {dice}, iou: {iou}")
print(f"========== Cross Valid dice coeff is: {dice_sum / cnt}")
print(f"========== Cross Valid IOU is: {iou_sum / cnt}")
stream_mgr_api.DestroyAllStreams() # 销毁stream
if __name__ == "__main__":
args = _parse_arg()
main(args)
run.sh文件内容如下,可根据实际开发情况进行修改。参考SDK软件包sample脚本,需要按照实际路径修改各个环境变量路径。
set -e
CUR_PATH=$(cd "$(dirname "$0")" || { warn "Failed to check path/to/run.sh" ; exit ; } ; pwd)
# Simple log helper functions
info() { echo -e "