草庐IT

【计算机视觉】新冠肺炎COVID-19 CT影片阳性检测,感染区域分割,肺部分割,智慧医疗实践,医疗影像处理示例

Kim‘s blog 2023-09-06 原文

引言

新型冠状病毒肺炎(Corona Virus Disease 2019,COVID-19),简称“新冠肺炎”,世界卫生组织命名为“2019冠状病毒病”。截止至2021年12月5日,全球累计确诊病例264047110例,累计死亡5240683例,并且这个数字还在继续高速攀升。

基于肺部CT(computed tomography)影像的人工智能诊断是针对新型冠状病毒肺炎的有效辅助诊断方法之一。

本次实验基于COVID-19 CT scans数据集,根据患者肺部的CT扫描分析,对患者COVID阳性还是阴性进行分类。如果患者Covid阳性,则这行肺部和感染区域的分割。实验还实现了交互性良好的可视化界面,更有助于医护人员对病情的快速筛查。

数据集概述

COVID-19 CT scans数据集包含20例诊断为COVID-19的患者的CT扫描以及专家对肺部和感染的分割。具体参数如下:
· 平均每类162图 大小有630630,512512,401*630;
· 有病区域占总体面积比:11170073 / 993762820 = 1.12%;
· 有病区域占有病图像面积比: 11170073 / 525924858 = 2.12%;
· 有病图占正常图比例: 1718 / 3250 = 52.86%。
数据集包含的四个文件分别是original image,lung mask,infection mask, lung and infection mask,为了更好的理解里面是什么东西,我们找到数据集给出的样例图片:

实验方法

为了系统的完善,本次实验切分为五个小任务:图像预处理、新冠肺炎阴阳性识别、肺部分割、感染区域分割、可视化界面搭建。

图像预处理主要应用本学期所学的图像增强的一些方法,比如直方图均衡,轮廓检测等,然后还引入了形态学的腐蚀和膨胀操作。

阴阳性识别是一个经典的二分类任务,我们使用较为简单的CNN网络进行分类任务的训练,最终的准确率达到0.96以上。

肺部分割与感染区域分割是语义分割任务,前者的语义相对简单,而后者的语义相对比较复杂。这两个任务都基于U-net网络进行训练,不同的是,用于训练感染区域分割任务的U-net网络要更为复杂一些。此外,使用dice loss作为损失函数(后续具体介绍),最终得到的dice系数分别为0.8353、0.7821。

可视化界面的搭建使用pyqt5依赖包,搭建较为简易的交互界面,并将训练好的网络模型应用到该界面,从而得到一个具有实际应用价值的新冠肺炎识别系统。

结果预览

实验步骤与过程

1. 图像预处理

1.1 加载metadata

metadata文件存放着COVID-19 CT scans数据集的文件目录,我们可以通过read_scv函数加载这个文件,从而获得所有数据文件的目录,并在后续能够访问他们。

# 加载文件目录
metadata = pd.read_csv('../input/metadata.csv')
# print(metadata.shape)
# print(metadata.head())

1.2 图像增强

为了后续更好的识别与预测,我们需要对图像进行增强,最简单的做法便是增强图像的对比度。这里我们使用OpenCV的createCLAHE函数以统一的方式进行直方图均衡化,使得暗区可见,而亮区以动态格式清晰可见,从而增强图像的对比度。

1.3 图像分割(ROI提取)

观察CT图片,除了我们感兴趣的肺部区域之外,还有一些黑色的背景,当然为了后续更好的识别与预测,我们只提取感兴趣的肺部区域,所以我们需要对图像进行一个剪裁。因为这个数据集有lung mask已经为我们标注了大概的位置,所以具体的做法如下:

(1)检测轮廓
首先使用cv.findContours函数,选择提取所有轮廓,并简单地保存拐点位置,然后我们画出对应的图像,关键代码如图3-1-5所示,检测出来的轮廓如图3-1-4(Binarization)所示。cv.findContours具体原理参见博客https://www.cnblogs.com/wojianxin/p/12602490.html。

(2)检测肺部的位置
正如我们在图3-1-4所看到的,轮廓提取之后只有左右两侧的肺叶,并没有完全提取出整个我们感兴趣的肺部区域,因此还需要做进一步的处理。

为了更好地提取出整个肺部区域,经过探索分析,发现肺部的像素值都比较接近,可以和其他背景很好地分开,换言之我们可以使用聚类的方法,将肺部检测出来。

做完聚类之后,可以使用形态学技术里面的腐蚀和膨胀(参考:https://zhuanlan.zhihu.com/p/110330329)对聚类得到的肺部区域先进行腐蚀,然后接着进行膨胀(即开操作),这样做的目的是使对象的轮廓变得光滑,断开狭窄的间断和消除细的突出物。

(3)图像剪裁
根据前面的分析,我们已经提取到了边界的信息,这时候图像剪裁只需要根据这个边界信息,创建一个矩形框,然后进行切割即可。

这样我们就完成了肺部的ROI提取,下面我们挑取10张图片,分别打印出它们对应的原图、增强图、以及提取到的ROI。

1.4 推广到infection和lung mask两个数据集

前面是基于ct_scan这一组数据做的处理,现在我们需要将这一处理方法推广到其他两个数据集,处理的流程与前面的大致相同,都是先图像增强然后再做ROI的提取,详细的代码可以参见提交的附件中的DataPreprocessing中的print_all函数。这个函数里面,我们挑取了5张图片,分别打印出其对应的原图、增强之后的图像、原来的lung mask、处理之后的lung mask、原来的infection mask以及处理之后的infection mask。

2. 阴阳性分类

2.1 网络架构

新冠肺炎的阴阳性分类属于一个二分类任务,标签‘1’代表阳性,‘0’代阴性。由于上面对数据做了预处理,并保存在文件中,所以做分类任务的时候只需要加载出来就可以了。

二分类任务有很多的方法可以做,比如经典机器学习的SVM、LDA、线性回归等模型,或者深度学习 的U-net网络、CNN、AlexNet等网络模型。在本任务中,我们选择自己设计一个简单的CNN网络,该网络有4个卷积层,每个卷积层之后有一个最大池化层和normalization,其网络架构如图3-2-1所示。我们可以看到该网络的输入是1281281大小的图像,经过神经网络处理之后,输出一个一维特征。

其中,卷积层的卷积核为3*3,其主要作用是提取局部特征,以利于分类;最大池化层大小为2,其主要作用是通过消除非极大值,降低了上层的计算复杂度;而normalization的主要作用是平衡数据的分布,防止出现无穷大无穷小的值,另外还可以平滑特征,使得分类更加精准。

2.2 网络搭建

本次实验使用TensorFlow框架搭建2DCNN网络,网络搭建分为两步,首先是定义网络框架,然后是定义网络的学习率等参数然后编译网络。

(1)定义网络框架
TensorFlow搭建网络比较简单,我们只需要定义好输入大小和输出大小,然后用搭积木的方式搭建起来就可以了。其关键代码如图3-2-2-所示,我们首先定义一个2维卷积层,随后定义一个MaxPooliing2D,然后加一个normalization,按照此架构重复4次,但是每次的特征维度有所不同,这是为了更好地进行非线性变化,提取更有利于分类的特征。最后,我们需要做一个平均池化层,然后dense之后dropout随机去掉一些特征,最后再dense成一维特征输出。


(2)编译网络
网络框架定义之后,我们需要去引用它,这里可以理解你定义了一个类,然后你要去实例化这个类,你才能去调用这个类相关的函数(train,predicct等)。在网络编译的时候,最重要的是要定义好学习率、损失函数和优化器。

在本实验中,我们定义学习率为0.0001,学习率的定义不宜太大(这样学不到很好的特征,容易跳来跳去),同时也不宜太小(训练太慢,消耗算力资源)。同时由于是二分类任务,因此我们选择交叉熵损失作为我们网络的损失函数,然后选择Adam作为网络的优化器。

2.3 数据集准备

我们都知道神经网络需要大量的数据进行训练,因此我们定义好神经网络之后,要先准备好数据,然后再进行训练。

数据集只需要从前面的数据预处理保存的文件中加载出来即可,然后我们需要将数据集划分为训练集/验证集/测试集,其比例分别为6:2:2。注意,这里比经典机器学习多了一个验证集,其作用是验证网络的好坏,计算loss作为梯度反向传播的依据,这样做有利于防止网络过拟合。

2.4 训练网络

准备好网络框架和数据集之后,我们接下里就是训练网络了,训练的过程中,网络会进行梯度反向传播,不断优化各个参数,然后我们只取其中loss最小的一个模型。

3. 阳性肺部区域分割

3.1 肺部区域分割的必要性与做法

为什么要做肺部区域的预测分割呢?虽然在数据集上已经标记好了肺部的区域,但是这是人工所标记的,后续我们搭建的可视化界面,作为一个应用平台,用户不会告诉你肺部在哪个位置。这个时候就要求我们的系统可以自动地对肺部区域进行分割,然后再出感染的部位(这将在第四个部分进行叙述)。因此,肺部区域的分割不仅有利于提高系统的交互性,更有利于后续感染区域的分割。

同时,因为我们这个时候不是分类任务了,而是图像分割任务,因此我们需要定义新的网络框架进行训练,当然也可以使用其他粗糙的方法,但是往往并不鲁棒。这里我们选择使用U-net网络(后续会详细介绍)进行特征的提取,然后使用Dice Loss作为损失函数,其中dice看理解为是两个轮廓区域的相似程度。

3.2 网络架构

(1)关于语义分割
语义分割(Semantic Segmentation)是图像处理和机器视觉一个重要分支。与分类任务不同,语义分割需要判断图像每个像素点的类别,进行精确分割。语义分割目前在自动驾驶、自动抠图、医疗影像等领域有着比较广泛的应用。Unet可以说是最常用、最简单的一种分割模型了,它简单、高效、易懂、容易构建、可以从小数据集中训练。

(2)关于U-net
U-net最早出现与2015年,是《U-Net: Convolutional Networks for Biomedical Image Segmentation》一文中提出的模型。

这个结构就是先对图片进行卷积和池化,在Unet论文中是池化4次,比方说一开始的图片是224x224的,那么就会变成112x112,56x56,28x28,14x14四个不同尺寸的特征。然后我们对14x14的特征图做上采样或者反卷积,得到28x28的特征图,这个28x28的特征图与之前的28x28的特征图进行通道伤的拼接concat,然后再对拼接之后的特征图做卷积和上采样,得到56x56的特征图,再与之前的56x56的特征拼接,卷积,再上采样,经过四次上采样可以得到一个与输入图像尺寸相同的224x224的预测结果。

Unet网络非常的简单,前半部分就是特征提取,后半部分是上采样。在一些文献中把这种结构叫做编码器-解码器结构,由于网络的整体结构是一个大些的英文字母U,所以叫做U-net。

由于网络层越深得到的特征图,有着更大的视野域,浅层卷积关注纹理特征,深层网络关注本质的那种特征,所以深层浅层特征都是有格子的意义的;另外一点是通过反卷积得到的更大的尺寸的特征图的边缘,是缺少信息的,毕竟每一次下采样提炼特征的同时,也必然会损失一些边缘特征,而失去的特征并不能从上采样中找回,因此通过特征的拼接,来实现边缘特征的一个找回。

而医疗影像语义较为简单、结构固定。因此语义信息相比自动驾驶等较为单一,因此并不需要去筛选过滤无用的信息。医疗影像的所有特征都很重要,因此低级特征和高级语义特征都很重要,所以U型结构的skip connection结构(特征拼接)更好派上用场。

3.3 网路搭建

跟前面一样,我们依然使用TensorFlow框架搭建网络,这里代码比较长,就不截图了,详细的可以参考附件的代码。

3.4 Dice Loss

dice loss 的提出是在V-net中,其中的一段原因描述是在感兴趣的解剖结构仅占据扫描的非常小的区域,从而使学习过程陷入损失函数的局部最小值。所以要加大前景区域的权重。
Dice 可以理解为是两个轮廓区域的相似程度,用A、B表示两个轮廓区域所包含的点集,定义为:

设TP,FP,FN分别是真阳性、假阳性、假阴性的个数。Dice也可以表示为:

dice coefficient取值范围在0到1之间,取值越大表示越相似,dice loss也可以由dice coefficient表出:

由于1是常数,所以在网络训练的过程中,将其省略(提高运算速度),只用-dice coeff代替dice loss。

4. 阳性感染区域分割

4.1 肺部区域分割的必要性与做法

当我们预测出新冠肺炎阳性之后,工作做得还是不够的,一个好的系统除了能帮助医生诊断之外,更理想的效果是同时预测出感染的区域。阳性感染区域的预测正是在做这个工作。 我们用数据集中标记好的infection mask作为目标,使用更加复杂的U-net网络去提取拟合特征,使用dice loss损失函数,最终我们的dice系数达到了0.78219,已经可以很好的预测出感染区域了。

4.2 网络架构

阳性感染区域的分割说到底还是一个语义分割的任务,只不过这个语义相对于肺部分割来说要更加困难和复杂。具体体现在:1)感染区域的位置不稳定,分布方差很大,2)感染区域的大小也有很大的差别,3)感染区域的形状不像肺部那样有潜在的规律。

因此,为了达到阳性感染区域分割的目的,我们必须使用更加复杂的网络来训练。但我们还是在U-net的基础上进行设计,多加了一些卷积层和池化层,使得网络的非线性特征提取能力更强,使网络语义分割能力更加鲁棒。

在损失函数上,我们依然使用衡量两个区域相似度的dice系数,并由dice系数表达出dice loss。

除此之外,在学习率的设置上,这次我们区别于之前固定的学习率,让网络自适应去调整学习率,具体怎么做的呢?TensorFlow框架开放了相关的接口,我们只需要调用就可以了。

4.3 网络搭建

这一次为了实现更加复杂的网络结构,区别于上两个任务的网络搭建,这次采用block的形式来搭建网络。Block的好处是将网络相同结构的块进行包装形成一个新的block,这样既方便代码的编写,也有助于理清网络架构。

4.4 训练

训练和之前也类似,但是这一次因为数据量大,网络复杂,我们需要自己定义batch size(实际上最好是自己定义),防止笔记本吃不消,这里选择的是8。训练过程如图3-4-3所示,可以看到最好的dice是0.78219,已经达到了初步应用的基础。

和之前一样我们打印出loss曲线,判断收敛性和是否过拟合,如图3-4-4所示,可以看到尽管曲线有所波动,但是总体上趋于收敛,并且没有过拟合的现象,有一点点的欠拟合(计算机算力不行,epoch=100训练了6个小时,所以就没继续炼丹)。

5. 可视化系统搭建

5.1 预览


四个图分别有其具体含义:第一个图是CT原图,用于作为系统的输入;第二个图是使用原来的CT图进行图像增强和cropped之后的结果,用于作为网络的输入;第三个图是像素分布直方图,用于验证图像增强之后的效果;第四个图是预测得到的结果,如果没有干扰不会有任何标记,如果是阳性的,就标记出感染的区域。

然后上方最左侧是文件路径,可以用于我们选择输入的CT,往右是开始诊断按钮,再往右用于显示识别结果,黄色表示阴性,红色表示阳性。

5.2 操作说明

进入界面之后,首先点击“…”选择CT图像,然后会看到加载出来原CT图像,CT增强之后的图像和相应的像素分布直方图。然后点击开始诊断按钮,即可进行诊断,等待3s左右,便可看到结果。由于有一定的误差,建议多次识别以获得更加准确的结果。

5.3 原理介绍

怎么实现的呢?界面的设计我使用的是pyqt5,这要是粘贴图片和显示文字,然后识别的过程是用前几个任务训练好的模型(已经保存)进行识别。

首先需要注意的是pyqt5不能直接显示pyplot画的图,这里采用的做法比较粗暴,就是先保存下来,然后再用图片的方式给pyqt5获取.

另外一个比较关键的是,如何加载模型并进行识别,我的做法是保存训练好的模型参数,然后load进来之外,以处理后的CT图作为输入,先做阴阳性识别任务,如果识别的是阳性,再进行infection区域的分割,然后将分割之后的图像和处理后的CT图像进行图层复合。

参考文献

[1]. 唐迁, 杜博, 恽爽, 高莉, 吴爽, 张超, 兰猛, 陈紫业, 李亮, 查云飞, 张良培, 李平湘. COVID-19 CT影像智能诊断系统[J]. 武汉大学学报·信息科学版, 2020, 45(6): 846-853. doi: 10.13203/j.whugis20200225. http://ch.whu.edu.cn/cn/article/doi/10.13203/j.whugis20200225.

[2]. 百度百科·新型冠状病毒肺炎.
https://baike.baidu.com/item/%E6%96%B0%E5%9E%8B%E5%86%A0%E7%8A%B6%E7%97%85%E6%AF%92%E8%82%BA%E7%82%8E/24282529.

[3]. Kaggle COVID-19 CT scans. https://www.kaggle.com/andrewmvd/covid19-ct-scans.

[4]. 知乎·图像分割必备知识点 | Unet详解 理论+ 代码:
https://zhuanlan.zhihu.com/p/313283141

[5]. 知乎·最全综述 | 图像分割算法:https://zhuanlan.zhihu.com/p/70758906.

[6]. 知乎·U-Net:https://zhuanlan.zhihu.com/p/311519960.

[7]. 博客园·python+opencv+dlib+pyqt5人脸识别实践:
https://www.cnblogs.com/tonasy/p/9664181.html。

附加说明

实验环境说明:操作系统为Windows10,python版本为3.8,pycharm为2021专业版,TensorFlow=v2.4.0,keras=v2.2.5,nibabel=3.2.1,可视化界面使用pyqt5,网络模型3D作图使用PlotNeuralNet。

如需源码,或者有学术交流的需要,请私信联系我。需要源码的请在评论区留下您的邮箱。

有关【计算机视觉】新冠肺炎COVID-19 CT影片阳性检测,感染区域分割,肺部分割,智慧医疗实践,医疗影像处理示例的更多相关文章

  1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  2. ruby-on-rails - 缺失区域;使用 :region option or export region name to ENV ['AWS_REGION' ] - 2

    我知道还有其他相同的问题,但他们没有解决我的问题。我不断收到错误:Aws::Errors::MissingRegionErrorinBooksController#create,缺少区域;使用:region选项或将区域名称导出到ENV['AWS_REGION']。但是,这是我的配置开发.rb:config.paperclip_defaults={storage::s3,s3_host_name:"s3-us-west-2.amazonaws.com",s3_credentials:{bucket:ENV['AWS_BUCKET'],access_key_id:ENV['AWS_ACCE

  3. ruby - 检测由 RSpec、Ruby 运行的代码 - 2

    我想知道我的代码是否在rspec下运行。这可能吗?原因是我正在加载一些错误记录器,这些记录器在测试期间会被故意错误(expect{x}.toraise_error)弄得乱七八糟。我查看了我的ENV变量,没有(明显的)测试环境变量的迹象。 最佳答案 在spec_helper.rb的开头添加:ENV['RACK_ENV']='test'现在您可以在代码中检查RACK_ENV是否经过测试。 关于ruby-检测由RSpec、Ruby运行的代码,我们在StackOverflow上找到一个类似的问题

  4. ruby - 使用 Ruby Daemons gem 检测停止 - 2

    我正在使用rubydaemongem。想知道如何向停止操作添加一些额外的步骤?希望我能检测到停止被调用,并向其添加一些额外的代码。任何人都知道我如何才能做到这一点? 最佳答案 查看守护程序gem代码,它似乎没有用于此目的的明显扩展点。但是,我想知道(在守护进程中)您是否可以捕获守护进程在发生“停止”时发送的KILL/TERM信号...?trap("TERM")do#executeyourextracodehereend或者你可以安装一个at_exit钩子(Hook):-at_exitdo#executeyourextracodehe

  5. ruby - Ruby 脚本如何检测到它正在 irb 中运行? - 2

    我有一个定义类的Ruby脚本。我希望脚本执行语句BoolParser.generate:file_base=>'bool_parser'仅当脚本作为可执行文件被调用时,而不是当它被irbrequire(或通过-r在命令行上传递)时。我可以用什么来包装上面的语句,以防止它在我的Ruby文件加载时执行? 最佳答案 条件$0==__FILE__...!/usr/bin/ruby1.8classBoolParserdefself.generate(args)p['BoolParser.generate',args]endendif$0==_

  6. Ruby 无法检测字符串中的换行符 - 2

    我有以下字符串,我想检测那里的换行符。但是Ruby的字符串方法include?检测不到它。我正在运行Ruby1.9.2p290。我哪里出错了?"/'ædres/\nYour".include?('\n')=>false 最佳答案 \n需要在双引号内,否则无法转义。>>"\n".include?'\n'=>false>>"\n".include?"\n"=>true 关于Ruby无法检测字符串中的换行符,我们在StackOverflow上找到一个类似的问题: h

  7. 【自动驾驶环境感知项目】——基于Paddle3D的点云障碍物检测 - 2

    文章目录1.自动驾驶实战:基于Paddle3D的点云障碍物检测1.1环境信息1.2准备点云数据1.3安装Paddle3D1.4模型训练1.5模型评估1.6模型导出1.7模型部署效果附录show_lidar_pred_on_image.py1.自动驾驶实战:基于Paddle3D的点云障碍物检测项目地址——自动驾驶实战:基于Paddle3D的点云障碍物检测课程地址——自动驾驶感知系统揭秘1.1环境信息硬件信息CPU:2核AI加速卡:v100总显存:16GB总内存:16GB总硬盘:100GB环境配置Python:3.7.4框架信息框架版本:PaddlePaddle2.4.0(项目默认框架版本为2.3

  8. ruby - 重新连接 tcpsocket(或如何检测已关闭的套接字) - 2

    我有一个连接到服务器的ruby​​tcpsocket客户端。在发送数据之前如何检查套接字是否已连接?我是否尝试“拯救”断开连接的tcpsocket,重新连接然后重新发送?如果是这样,有没有人有一个简单的代码示例,因为我不知道从哪里开始:(我很自豪我设法在rails中获得了一个持久连接的客户端tcpsocket。然后服务器决定杀死客户端,一切都崩溃了;)编辑我已经使用此代码解决了一些问题-如果未连接,它将尝试重新连接,但如果服务器已关闭则不会处理这种情况(它将继续重试)。这是正确方法的开始吗?谢谢defself.write(data)begin@@my_connection.write(

  9. css - 检测到 Sass 更改但 style.css 仅在我保存时每 5 到 7 次被覆盖 - 2

    我在一台Windows764位机器上使用Sass和Ruby(最新版本),我正在我的家庭服务器上处理一个共享文件夹。(但是,我不得不承认问题本身也出现在服务器上,因为我试图安装Ruby并直接-watch服务器上的文件)。问题如下:如果我第一次保存,检测到变化,我的style.css被直接覆盖。之后,我总是需要保存多达7次才能覆盖style.css。每次都会检测到更改,但不会编译任何内容。这是一个屏幕:>>>Sassiswatchingforchanges.PressCtrl-Ctostop.overwritestyle.css>>>Changedetectedto:E:/Websites

  10. ruby-on-rails - 检测 Rails 是否正在运行站点 - 2

    我所在的团队负责管理公司面向公众的云平台。我们拥有大量运行面向互联网的VM的用户群。我想对我们的地址空间进行自动扫描,看看是否有人在运行Rails应用程序,这样我就可以通知他们升级他们的Rails版本,以避免本周出现的严重安全漏洞。我注意到在某些Apache部署中,有一个有用的PassengerHeader:X-Powered-By:PhusionPassenger(mod_rails/mod_rack)2.0.3然而,这并不可靠。我想知道是否有一种可靠的方法来检测在Web服务器后面运行的Rails,无论是使用响应header还是某种可以确定的GET/POST。谢谢!

随机推荐