基于PaddleSeg实现MR头部矢状位上快速自动定位

2023-10-30 19:59

本文主要是介绍基于PaddleSeg实现MR头部矢状位上快速自动定位,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

点击左上方蓝字关注我们

项目背景

在日常放射科工作中对患者进行磁共振头部检查时,扫描头部轴位,规范标准要求扫描轴位像,需要在矢、冠、轴位定位像上进行定位。冠、轴位像只需要左右对称即可。但是在矢状位定位像上,需要平行胼胝体的前后缘连线(如图)。目前一部分品牌的磁共振机器已经实现自动定位的功能。

磁共振成像,有个特点,时间长。假如确定好扫描范围和参数后,要扫描结束后才能阅览图像。阅览图像时发现范围定位错误或者参数设置错误,那只能重新扫描。而磁共振成像,每一个成像序列都需要1到几分钟不等。因上述错误导致重新扫描,需要的时间成本是很大的,也增加了患者的负担。因此自动定位可以减少人为的错误并加快检查速度,节约时间。

因此本项目是基于PaddleSeg分割开发套件实现类似的自动定位功能。

图1 磁共振颅脑扫描规范

这个项目主要分割颅脑中的胼胝体器官。大脑主要由皮质(灰质)和白质组成,而胼胝体作为最大的连合纤维,连接着左右大脑半球的皮质。胼胝体位于大脑正中矢状切面上,形态上类似弓形,如图2所示。

图2 胼胝体的解剖位置

本项目基于PaddleSeg开发。PaddleSeg是基于飞桨PaddlePaddle开发的端到端图像分割开发套件,涵盖了高精度轻量级等不同方向的大量高质量分割模型。通过模块化的设计,提供了配置化驱动API调用两种应用方式,帮助开发者更便捷地完成从训练到部署的全流程图像分割应用。这个项目主要用到的是API调用的方式。

项目使用PaddleSeg实现的BiSeNetV2网络结构,分割MR头部矢状位上的胼胝体。最终,胼胝体的分割结果miou达到了0.8696。然后,我们将通过OpenCV获取胼胝体的轮廓,得到最左、右的像素点位置,并计算旋转的角度。根据角度得到旋转的定位范围框。

项目在AI Studio上已经公开,并提供了数据集在内的完整路径。Fork之后可以直接运行,相关链接为:

https://aistudio.baidu.com/aistudio/projectdetail/1993321

https://github.com/paddlepaddle/paddleseg

最终效果如图3所示。

图3 最终效果

网络结构介绍

此项目使用PaddleSeg 的API接口,进行数据增强、Loss、和训练和推理,本文主要针对以上步骤进行介绍。

因为自动定位需要速度够快,并且有一定的准确性。因此,选择使用BiSeNetV2 网络结构。BiSeNetV2 网络结构在Cityscapes测试集上获得了72.6%的平均IoU,在一张NVIDIA GeForce GTX 1080Ti卡上获得了156 FPS的速度,如图4所示。使用此分割网络很适合“实时”定位任务。

目前提高语义分割的运行速度一般有以下方法:

  1. 可以通过缩小输入图像的大小来实现,但是会失去部分空间信息。

  2. 通过对减少网络通道数量加快处理速度,但是弱化网络提取特征的能力。

这些提速的方法会丢失很多空间信息,从而导致精准度下降。为了防止空间信息的丢失,有些网络会采用U型结构来恢复空间信息。但是U型结构会降低速度。因此BiSeNetV2提出“双边分割”。在速度和准确性之间进行了很好的权衡。

BiSeNetV2结构设计上,实现细节分支和语义分支,并设计一个引导聚合层来融合两个分支的特征。还设计了一种增强训练策略。只在训练中使用,在推理过程中不增加任何代价,BiSeNetV2整体网络结构,如图E所示。

BiSeNetV2的细节分支,使用宽通道和进行三次下采样卷积,来提取低语义空间信息生成高分辨率特征。在语义分支,使用窄通道和深度卷积来增强感受野和获取丰富的上下文信息。由于采用“双边”分支,需要把两支分支进行融合。但是细节分支和语义分支的特征大小不一致,还有细节分支是低级语义信息,语义分支是高级语义信息,简单的组合这两者信息,得不到有效利用。

因此BiSeNetV2提出双边引导的聚合层融合两个分支的信息,如图6。作者为了进一步不影响推理速度的情况下,提出一种增强训练策略(SegHead),使用在语义分支的不同位置上,如图5的Booster。最终在Cityscapes测试集上获得了72.6%的平均IoU,在一张NVIDIA GeForce GTX 1080Ti卡上获得了156 FPS的速度。

图4 BiSeNetV2 在Cityscapes测试集上推理精度和速度

图5 BiSeNet V2的整体网络结构

图6 BiSeNetV2的双边引导聚合层(Bilateral Guided Aggregation)

使用PaddleSeg套件搭建BiSeNetV2 只需要两行代码。代码如下:

from paddleseg.models import BiSeNetV2
model = BiSeNetV2(num_classes=2,lambd=0.25,align_corners=False,pretrained=None)

lambd这个参数用来控制语义分支中前两者的通道数,使语义分支是轻量的。作者论文中提到,加大lambda可以提高精度,但是会损失一定推理时间。align_corners这个参数控制使用插值时是否将输入和输出张量的4个角落像素的中心对齐,并保留角点像素的值。

数据处理和增强

从约600个健康的患者的头部MRT1轴位NiFit格式数据,随机挑选120例。使SimpleITK读取数据,获取120张正中位置的矢状位MR图像,并保存成jpg格式图像。并使Labelme对胼胝体器官的进行标注。把数据集分成8:2,作为训练集和验证集并生成train.txt和val.txt搭建Dataset时使用,如图7。

注:数据来源https://brain-development.org/ixi-dataset/

图7 数据的处理流程

结合实际情况,不同的矢状定位图的旋转角度相差巨大。除了增加了随机水平翻转和随机模糊、随机对比度、明亮度等数据增强策略。重要的使用较大的角度旋转来增加数据的多样性。通过PaddleSeg的API(paddleseg.transform),可以快速实现数据增强策略。预览通过数据增强后的图片数据,如图8所示。

import paddleseg.transforms as T
from paddleseg.datasets import Dataset#数据增强部分
train_transforms = [T.RandomHorizontalFlip(),#水平翻转T.RandomDistort(),#随机对比度,颜色等变化T.RandomRotation(max_rotation = 25,im_padding_value=(0,0,0), label_padding_value = 0),#随机旋转T.Resize(target_size=(512, 512)),T.Normalize()
]

训练之前,需要通过API(paddleseg.datasets)快速构建可迭代的训练数据装载器和验证集/测试集数据装载器。只需要传入包含origin和label文件路径的train.txt文件、数据增强策略、分类类别和数据存放路径,即可构建高效的数据装载器。

train_dataset = Dataset(transforms = train_transforms,dataset_root = dataset_root,num_classes = 2,train_path  = train_path,mode = 'train')

图8经过数据增强的原图和Mask

模型训练

开始训练前,还需要定义损失函数,PaddleSeg提供多种损失函数的选择。只需要通过API(paddleseg.models.losses)调用就能实现。因为BiSeNetV2  采用增强训练策略(SegHead)。一共使用了5个(SegHead),所以配置损失函数的时候,数量为5。

#构建优化器,和loss
from paddleseg.models.losses import CrossEntropyLoss
import paddle
losses = {}
losses['types'] = [CrossEntropyLoss()] * 5
losses['coef'] = [1]* 5

因为PaddleSeg动态图版本开始训练模型只需要一行代码,调用API(paddleseg.core.train)即可开始训练,训练期间还可以通过VisualDL可视化训练期间的Loss、ACC、IOU等结果,如图9所示。比起静态图组网,此种方式大幅度降低了代码量。

#开始训练
from paddleseg.core import train
train(model=model,train_dataset=train_dataset,val_dataset=val_dataset,optimizer=optimizer,save_dir='Myoutput',iters=4800,batch_size=8,save_interval=120,log_iters=24,num_workers=0,losses=losses,
use_vdl=True)

图9 VisualDL可视化训练期间的Loss

处理推理结果实现快速定位

我们可以调用PaddleSeg的API接口(paddleseg.core.predict)进行推理。同样地,我们也可以自定义推理接口,只需要导入PaddleSeg模型,加载最优模型训练结果,设置模型为eval模式,传入需要预测的胸片数据,即可进行推理。

model = BiSeNetV2(num_classes=2)
model_path = '/home/aistudio/Myoutput/best_model/model.pdparams'
para_state_dict = paddle.load(model_path)
model.set_dict(para_state_dict)
model.eval()
data = np.expand_dims(data,axis=0).repeat(3,axis=0)
data = np.transpose(data, (1,2,0))
data,_ = transforms(data)
data = data[np.newaxis, ...]
data = paddle.to_tensor(data)
output = model(data)[0].numpy()
output = np.argmax(output,axis=1)
output = np.squeeze(output)

得到推理结果,通过OpenCV对结果进一步处理。先提取轮廓,然后根据提取出来的轮廓得到胼胝体的最左边和最右边的像素点坐标。根据左、右两点坐标求得两点的中心坐标。通过反正切函数求得最左边与中心点坐标连线与水平方向(X轴)的夹角,整体流程如图10所示。

points = list() #用来保存坐标点
#对预测的结果进行阈值分割
ret, binary = cv2.threshold(output,0,255,cv2.THRESH_BINARY)
#获取轮廓的所有坐标点
contours, hierarchy = cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
for p in contours[0]:points.append(p.tolist()[0])
#根据坐标x轴从小到大排序
points = sorted(points)
#得到最左边的坐标点和最右边的坐标点
left_point = points[0] 
right_point = points[-1]
#计算最左和最右两个坐标点之间的中间坐标
center_point = [int((right_point[0] - left_point[0])/2)+left_point[0],int((right_point[1] - left_point[1])/2)+left_point[1]]
#计算最左边的坐标点和中间坐标点连线  与 X轴之间的夹角
dx1 = left_point[0] - center_point[0]
dy1 = left_point[1] - center_point[1]
#atan2函数计算线段与X轴夹角(弧度)
angle = math.atan2(dy1, dx1)

然后根据上述的中心坐标为中心创建一个矩形框。用来模拟磁共振在定位图上的扫描范围框。根据已得的角度,通过以下代码计算矩形框每个坐标经过旋转后的坐标。在原图上进行绘制旋转后的矩形框。即可快速自动实现平行胼胝体的前后缘的定位。具体实现代码这里不做详细展示,读者可以访问AI Studio项目查看。

rotationx = (x  - centerx)* math.cos(angle) - (y - centery)* math.sin(angle) + centerx
rotationy = (x  - centerx)* math.sin(angle) + (y - centery)* math.cos(angle) + centery

图10 项目流程图

总结

从观察到需要解决的问题,到思考如何解决问题,开始收集数据并标记数据,搭建Dataset、构建网络、训练、参数调优,到最后通过对推理结果进行处理。整个项目只要准备好数据和知道怎样处理推理结果,中间的分割部分,例如网络的搭建和训练代码。只需要结合PaddleSeg API很容易搭建一个完整的分割任务,实现想要的效果。

参考文献:

Yu, Changqian, et al. "BiSeNetV2: Bilateral Network with Guided Aggregation for Real-time Semantic Segmentation"(https://arxiv.org/abs/2004.02147)

飞桨开发者说直播

主题:基于PaddleSeg实现MR头部矢状位上快速自动定位

分享人:冯嘉骏,飞桨开发者技术专家 PPDE

如在使用过程中有问题,可加入官方QQ群进行交流:793866180。

如果您想详细了解更多飞桨的相关内容,请参阅以下文档。

·飞桨官网地址·

https://www.paddlepaddle.org.cn/

·飞桨开源框架项目地址·

GitHub: https://github.com/PaddlePaddle/Paddle 

Gitee: https://gitee.com/paddlepaddle/Paddle

????长按上方二维码立即star!????

飞桨(PaddlePaddle)以百度多年的深度学习技术研究和业务应用为基础,集深度学习核心训练和推理框架、基础模型库、端到端开发套件和丰富的工具组件于一体,是中国首个开源最早、技术领先的产业级深度学习平台。飞桨企业版针对企业级需求增强了相应特性,包含零门槛AI开发平台EasyDL和全功能AI开发平台BML。EasyDL主要面向中小企业,提供零门槛、预置丰富网络和模型、便捷高效的开发平台;BML是为大型企业提供的功能全面、可灵活定制和被深度集成的开发平台。

END

这篇关于基于PaddleSeg实现MR头部矢状位上快速自动定位的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/310265

相关文章

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

golang版本升级如何实现

《golang版本升级如何实现》:本文主要介绍golang版本升级如何实现问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录golanwww.chinasem.cng版本升级linux上golang版本升级删除golang旧版本安装golang最新版本总结gola