软件说明

程序演示

观看下列程序演示示例:

注:新版本增加了线粒体分支节点检测功能。

功能介绍

该程序的主要功能实现如下:

  1. 完成细胞的分割和长宽的测量
  2. 完成线粒体形态参数(总长度、各段长度、段数)的统计
  3. 选择某一行细胞参数值可以查看其图像处理的具体过程,如图三所示。
  4. 自动过滤边缘的细胞
  5. 对于线粒体未染色或者染色不明显的细胞,仅测量其细胞形态参数
  6. 支持图像的批处理

软件用法

主界面介绍

程序主窗口如下:

主要分为以下几个步骤:

  1. 点击浏览按钮打开单/多张图像
  2. 在左侧列表栏中选择特定图像并进行预览,通过右侧的直方图可调节图像的对比度
  3. 点击分析按钮,可进行当前细胞的检测以及形态参数的测量 / 点击导出所有结果按钮可以将列表中的所有图片进行检测和分析。

副界面介绍

点击主界面分析按钮弹出的结果窗口如下:

 

主要作如下说明:

  1. 细胞分析结果中的细胞ID 与右侧图像检测标记的数字相对应,可以根据ID快速查找对应细胞

  2. 细胞节点个数与各段长度中元素一一对应,例如,ID为0的细胞第一段线粒体长度为13.01, 对应的节点个数为3个。

  3. 选中进行预览,通过右侧的直方图可调节图像的对比度

  4. 选中细胞分析结果中的某一行数值,可以查看当前细胞的具体处理过程如下图所示。

    2020-09-05 09-59-07 的屏幕截图

  5. 点击导出表格和图像可以保存当前细胞的 原始图片, 检测图片, 检测结果列表

 

 

 

 

 

项目介绍

目标

 

利用上述任意图像,实现:

 

技术路线:

按照项目的要求,可以将它分为两步走:

  1. 实例分割各个细胞
  2. 利用图像处理的形态学方法解决细胞形态参数和线粒体参数的测量

1. 实例分割

实例分割是一个具有挑战性的计算机视觉任务,需要预测的对象实例及其每像素分割掩码。这使得它混合了语义分割和目标检测。

自从 Mask R-CNN 发明以来,最先进的实例分割方法主要是 Mask R-CNN 及其变体(如 PANet、 Mask Score RCNN 等)。该算法采用先检测后分段的方法,首先执行目标检测提取每个对象实例周围的包围盒,然后在每个包围盒内执行二进制分割,分离前景(对象)和背景。

但是,Mask R-CNN 速度很慢,妨碍了许多实时应用程序的使用。此外,Mask R-CNN 预测的掩模具有固定的分辨率,因此对于具有复杂形状的大型物体没有足够的精度。随着无锚目标检测方法(如 CenterNet 和 FCOS)的进步,对单级实例分割的研究已经出现了一波研究热潮。如下图所示,这些方法中的许多比 Mask RCNN 更快更准确,但同时带来的问题是算法原理更加复杂。

在本项目中,由于细胞的形态比较单一,用最早的Mask R-CNN网络足以胜任,我先将整个项目的功能进行实现,在后续的工作完善中看情况可以将网络调整为更快的SOLO或者YOLACT,从而获得更快更准确的处理速度。

2. 图像处理形态学分析

细胞的形态学分析主要由Python的两个模块完成,分别是:python-opencvscikit-image。具体的分析过程可参见第 4 章 细胞参数提取

 

 

 

 

 

Mask R-CNN 原理

何凯明和他的团队在2018年提出的Mask R-CNN是实力分割领域最具有影响力的深度学习算法之一,近年来,我们利用Mask R-CNN框架解决了许多实际的问题。正确理解Mask R-CNN对于实际优化它的参数有着重要的意义,本文将阐述Mask R-CNN的基本原理。

基本框架

Mask-RCNN 大体框架还是 Faster-RCNN 的框架,可以说在基础特征网络之后又加入了全连接的分割子网,由原来的两个任务(分类+回归)变为了三个任务(分类+回归+分割)。Mask R-CNN 采用和Faster R-CNN相同的两个阶段:

总体流程如下:

  1. 首先,输入一张预处理后的照片。
  2. 然后, 输入到一个预训练好的神经网络中(ResNet等)获得对应的特征映射(Feature map)
  3. 接着,对这个Feature map中的每一个点设定预定的ROI(Region of Interest),从而获得多个候选的ROI。
  4. 接着,对这些剩下的ROI进行ROI Align操作
  5. 最后,对这些ROI进行分类、回归以及MASK生成(在每个ROI里面进行FCN操作)

 

mask r-cnn framework

图1 Mask R-CNN 总体框图

架构分解

maskrcnn details

图2 Mask R-CNN展开图

图像预处理

为了便于训练,图像在输入网络前需要进行一定的预处理。

img preprocessing

图像处理流程

注意: 用于生成锚点和过滤步骤的图像高度和宽度将作为缩放后的图像,而不是填充后的图像。


Feature pyramid networks (FPN) backbone

Backbone

backbone是一系列的卷积层用于提取图像的feature maps,比如可以是VGG16,VGG19,GooLeNet,ResNet50,ResNet101等,这里主要讲解的是ResNet101的结构。

ResNet(深度残差网络)实际上就是为了能够训练更加深层的网络提供了有利的思路,毕竟之前一段时间里面一直相信深度学习中网络越深得到的效果会更加的好,但是在构建了太深层之后又会使得网络退化。ResNet使用了跨层连接,使得训练更加容易。

残差网络结构

网络试图让一个block的输出为f(x) + x,其中的f(x)为残差,当网络特别深的时候残差f(x)会趋近于0,从而f(x) + x就等于了x,即实现了恒等变换,不管训练多深性能起码不会变差。

在网络中只存在两种类型的block,在构建ResNet中一直是这两种block在交替或者循环的使用,所有接下来介绍一下这两种类型的block(indetity block, conv block):

跳过三个卷积的identity block

图中可以看出该block中直接把开端的x接入到第三个卷积层的输出,所以该x也被称为shortcut,相当于捷径似得。注意主路上第三个卷积层使用激活层,在相加之后才进行了ReLU的激活。

跳过三个卷积并在shortcut上存在的卷积的conv block

与identity block其实是差不多的,只是在shortcut上加了一个卷积层再进行相加。注意主路上的第三个卷积层和shortcut上的卷积层都没激活,而是先相加再进行激活的。

其实在作者的代码中,主路中的第一个和第三个卷积都是1*1的卷积(改变的只有feature maps的通道大小,不改变长和宽),为了降维从而实现卷积运算的加速;注意需要保持shortcut和主路最后一个卷积层的channel要相同才能够进行相加。下面展示Res-net101的完整框架:

从图中可以得知ResNet分为了5个stage,C1-C5分别为每个Stage的输出,这些输出在后面的FPN中会使用到。你可以数数,看看是不是总共101层,数的时候除去BatchNorm层。注:stage4中是由一个conv_block和22个identity_block,如果要改成ResNet50网络的话只需要调整为5个identity_block.


Feature Pyramid Map

概念

FPN的提出是为了实现更好的feature maps融合,一般的网络都是直接使用最后一层的feature maps,虽然最后一层的feature maps 语义强,但是位置和分辨率都比较低,容易检测不到比较小的物体。FPN的功能就是融合了底层到高层的feature maps ,从而充分的利用了提取到的各个阶段的特征(ResNet中的C2-C5 )。简单来说,就是把底层的特征和高层的特征进行融合,便于细致检测

FPN是为了自然地利用CNN层级特征的金字塔形式,同时生成在所有尺度上都具有强语义信息的特征金字塔。所以FPN的结构设计了top-down结构和横向连接,以此融合具有高分辨率的浅层layer和具有丰富语义信息的深层layer。*这样就实现了*从单尺度的单张输入图像,快速构建在所有尺度上都具有强语义信息的特征金字塔,同时不产生明显的代价。

如下图所示: Top: 一个带有skip connection的网络结构在预测的时候是在finest level(自顶向下的最后一层)进行的,简单讲就是经过多次上采样并融合特征到最后一步,拿最后一步生成的特征做预测。Bottom: FPN网络结构和上面的类似,区别在于预测是在每一层中独立进行的。后面的实验证明finest level的效果不如FPN好,原因在于FPN网络是一个窗口大小固定的滑动窗口检测器,因此在金字塔的不同层滑动可以增加其对尺度变化的鲁棒性。另外虽然finest level有更多的anchor,但仍然效果不如FPN好,说明增加anchor的数量并不能有效提高准确率。

特征融合图

特征金字塔特征提取过程

特征金字塔提取图像特征可以分为两个部分: 自下而上(down-to-top pathway)和自上而下(top-to-down pathway)两条路径,或者将自下而上路径称为编码过程,自上而下路径称为解码过程。

从图中可以看出+的意义为:左边的底层特征层通过1×1的卷积得到与上一层特征层相同的通道数;上层的特征层通过上采样得到与下一层特征层一样的长和宽再进行相加,从而得到了一个融合好的新的特征层。举个例子说就是:C4层经过1×1卷积得到与P5相同的通道,P5经过上采样后得到与C4相同的长和宽,最终两者进行相加,得到了融合层P4,其他的以此类推。

注:P2-P5是将来用于预测物体的bbox,box-regression,mask的,而P2-P6是用于训练RPN的,即P6只用于RPN网络中。


输出

经过高层特征与底层特征的融合之后,每层进行特征输出,分别得到[P2, P3, P4, P5, P6]五层。其中RPN网络将用到[P2, P3, P4, P5, P6], 而目标检测网络部分用到[P2, P3, P4, P5],也就是说,P6 16×16的特征张量只用于RPN(感兴趣区域生成网络中),用途后面具体再聊。

特征层形状
P2256*256 @ 256
P3128*128 @ 256
P464*64 @ 256
P532*32 @ 256
P6*16*16 @ 256

Proposal Region Of Interest

第二部分主要介绍感兴趣区域(ROI)的生成规则,他主要有三个部分组成:锚框(anchor)生成、区域提议网络(Region Proposal Network,RPN)、区域生成网络(Proposal Layer)。如下图所示,最左侧的anchors是生成锚框流程,中间的build_rpn_model->rpn_graph是构建RPN网络流程,最右侧的ProposalLayer是筛选ROIs的生成建议框流程。

特征金字塔特征提取过程

锚框生成

首先,需要理解锚框的概念,在这篇博文中对锚框有着很好的说明。笼统的说,P2~P6的特征图相当于将原来1024×1024的图像进行了分块,将每一块都压缩成一个像素点,然后,以该像素点为中心进行框选周边区域,为了检测多种形状的物体,同一个像素区域利用多个锚框进行框选。具体参数如下:


RPN 网络

FPN输出的特征映射张量[P2, P3, P4, P5, P6]将用于RPN网络,P[?]代表的张量形状各不相同,因此需要分开处理,RPN网络的结构如下图所示,以P2为例:

注意:以上均是以一张图片的处理过程为例,在实际的操作中,还有batchsize一个维度加在张量的最前面。

最终输出形式:


Proposal Layer

Proposal Layer的输入来自于前两部分:Anchors生成和RPN输出。在Proposal Layer中,并没有需要进行训练的参数,只是利用算法进行anchors的选择与修正工作。


Network Heads

利用rpn_feature_map和anchors提取出rois,rois和maskrcnn_feature_map一起输入到网络的第三部分:Network Heads。


RoIAlign layer

RoIAlign的作用是根据rois的坐标,在feature_map中将对应的区域框选出来,然后利用插值法则,生成固定尺寸的张量输入到分类网络和分割网络。这里有两个概念需要理清楚。

其中, 代表的是基准值,设置为5,代表P5层的输出, 代表的是roi的长度和宽度, 224是ImageNet的标准输入大小。例如,若, 那么 的取值为4,rois需要截取P4输出的特征值


结构

整体FPN heads分为两个分支,一是用于分类回归目标框偏移fpn_classifier_graph,一是用于像素分割build_fpn_mask_graph,两者都是将rois在对应mrcnn_feature_maps特征层进行roialign特征提取,然后再经过各自的卷积操作预测最终结果。

PyramidROIAlign层不展开讨论,可认为将pool_size划分的77区域(对于mask_pool_size则为1414)取若干采样点后,进行双线性插值得到f(x,y),这个版本的代码中取采样点为1。

keras.layers.TimeDistributed作为封装器可将一个层应用于输入的每个时间片,要求输入的第一个维度为时间步。该封装器在搭建需要独立连接结构时用到,例如mask rcnn heads结构,进行类别判断、box框回归和mask划分时,需要对num_rois个感兴趣区域ROIs进行分类回归分割处理,每一个区域的处理是相对独立的,此时等价于时间步为num_rois,

fpn_classifier_graph

首先利用PyramidROIAlign提取rois区域的特征,再利用TimeDistributed封装器针对num_rois依次进行77->11卷积操作,再分出两个次分支,分别用于预测分类和回归框。解释如下:

build_fpn_mask_graph

首先利用PyramidROIAlign提取rois区域的特征,再利用TimeDistributed封装器针对num_rois依次进行33->33->33->33卷积操作,再经过2*2的转置卷积操作,得到像素分割结果。解释如下:

 

 

 

 

Mask R-CNN 实现

Mask R-CNN 核心模型代码在 GitHub开源, 该项目在Matterport/Mask_RCNN的基础上进行开发。可直接将该项目克隆到本地。

数据集制作

上图是利用Mask-RCNN网络以及已经训练好的模型进行人物分割的一个演示,分割的结果不仅将每个人物进行的框选,并且对人物进行了语义的分割。如果将以上图片中的人物换做是显微镜下的细胞,经过网络同样形式的训练,可想而知,也是可以达到同样效果的。Mask-RCNN的核心代码部分已经实现,现在只需要将当前测试的人物数据集换成细胞数据集,下面开始介绍细胞数据集标注的具体方式。

相应软件

为了方便不同平台的标注,标注软件采用的是:VGG Image Annotator (VIA),这是一个网页版的应用,无需安装,简洁,轻量。

图像标记

网络输入图像将采用RFP+DIC处理后的JPG图,其中细胞的轮廓以及线粒体的形态都比较清晰,有利于以后细胞形态以及线粒体参数的提取。由于细胞标注的工作量较大,所以最好可以将现有的数据图片分成几组分别进行标注。

由于目前没有获得RFP+DIC的完整数据集,所以下面将以matlab处理的伪彩色图数据集加以演示:

导入图像集

首先通过Remove健将演示图片删除,然后点击Add Files将图片集导入到VIA中。为了便于右侧图片的精细标注,利用Ctrl+鼠标滚轮将图片放大到合适的大小。

 

开始多边形标注

首先,在Region Shape菜单中选中多边形状工具,然后进行细胞样本的标注,如下图所示。

 

导出文件

标注完成之后需要将标注好的文件进行保存,这里将文件保存为json格式,然后与图片集放置在一起,如果分组进行标记工作,那么每个人都会导出一个json文件,只要最后将json文件和图片集汇总然后重新导入VIA生成即可。

 

存储数据集

在Mask R-CNN项目的datasets中新建cell 目录, 然后将VIA汇总得到的json和图片放置在该目录下。其中trainval分目录存储。

代码编写

Mask_RCNN/samples下新建cell目录,仿照nucleus编写主程序。

环境安装

该项目的主要环境依赖如下所示,实际运行时可根据提示进行相应库的安装, 本机(CPU)版本下正常运行的环境配置可见附录。

模型训练

采用迁移学习的方法,基于COCO数据集(千万张图片)训练得到的权重,保持convolutional backbone部分权重不变,导入细胞数据集,调整Head部分的权重,训练60代,得到用于识别酵母细胞的模型。下载[预训练的权重文件][https://github.com/lizhogn/Cell_Analysis/releases].

模型预测

输入测试图像,模型预测输出如下信息:

 

 

附录

本机环境配置文件:

 

细胞参数提取

细胞参数分两方面进行提取:

细胞形态参数

利用Mask R-CNN模型输出结果中的掩膜图像,可进行细胞形态参数(长度、宽度)的提取。以一个细胞的提取进行演示:

图1.细胞掩膜 图2.正外接矩形 图3.最小外接矩形

 

图1便是由Mask R-CNN识别的单个细胞,在掩膜数组中数值只有 0 和 1(非0数即为1)。酵母细胞的形状近似于一个狭长的椭球状,长宽差异明显,并且沿中轴线无明显的弯曲,所以可以用一个最小的外接细胞矩形的长和宽来代替细胞的长和宽,如图3所示,这个外接矩形的学术名叫做连通域最小外接矩形。求该矩形坐标及旋转角度的算法如下:

第一步:正外接矩形的算法

第二步:最小外接矩形的确定

Opencv-python中,最小外接矩形算法由minAreaRect进行封装,可以直接调用。

最终可以输出最小外接矩形的坐标,进而求出细胞的长度和宽度。

 

线粒体形态参数

线粒体参数(线粒体数目、各段长度、总长度)提取流程如上图所示,

 

原理:OTSU算法:

 

线粒体个数-总长度-各段长度

线粒体个数:等于细化二值图像连通域的个数,相关函数:cv2.measure.label

总长度:等于细化二值图像的面积,求和即可。

各段长度:等于细化二值图像各个连通域的面积,相关函数:cv2.measure.regionprops

线粒体分支节点检测

任意一张线粒体骨架图片,其中的分支节点与8-邻域的位置是固定的,通过统计,可以总结为如下40种情况(其中白色像素代表1, 黑色像素代表0):

myplot

因此,只需要遍历线粒体骨架图片中每一个非零的像素点,然后去匹配这40种情况,最后再去除距离较近的分支点,剩下的便是线粒体分支节点的检测结果。

算法流程如下:

相关问题及解决方案

漏检测细胞

细胞漏检测的原因主要有两个流程:

① 首先是Mask R-CNN的网络输出,由于网络训练不充分、非极大值抑制等等的因素,导致有些细胞并不能被检测到。下图中红色箭头所指的位置便是Mask R-CNN网络未检测到的细胞,主要是由于该细胞与左侧的细胞挨得太近导致两个细胞边界框的交并比(IoU)太大而被认同为一个目标,非极大值抑制算法移除了概率较小的那个细胞。

 

② 其次是最小矩形框算法略过的矩形框。最小矩形框算法在针对一些细胞的掩膜时会失效,算法报错从而导致程序的终止,

长宽测量误差

① 在长度和宽度的测量中,依据的原理是细胞掩膜图像的最小矩形框。而利用Mask R-CNN分割细胞得到的掩膜图像有时候并不是那么理想(如下图所示),会导致细胞的宽度测量出现误差。另外,有些细胞生成的掩膜图像无法应用最小矩形框的算法,当前设定的程序是跳过该细胞长宽的检测,在后续需要完善。

 

② 在图像边缘的区域,细胞的长宽测量必然是不准确的。

 

线粒体测量问题

① 未染色线粒体测量误差,如下图所示,由于某些细胞的线粒体并没有被染色,所以当进行绿色通道分离、阈值分割后,并不能很好的提取线粒体的形态掩膜(本来就没有)。当进行图像细化算法的分析时,所细化的线条也不符合预期。在后续的程序中应该能够有效的识别这些未被染色的细胞,跳过对他们的线粒体形态参数统计过程。

error_mito

error_mito1

② 阈值分割以及图像细化过程产生的误差。观察下列线粒体骨架的生成过程,可以发现,由于存在中间的不连续点,程序将它分成了两条线粒体,加上左边小的不连续点,总共得到了四条线粒体。通过过滤,保留了图中长的两条线粒体,像素的个数 × 单位像素的长度 就是线粒体的长度。

 

 

图形界面开发

该项目的图形界面是基于PyQt5,结合QtDesigner界面布局软件和pyqtgraph画图模块进行开发的,通过API调用实现图形与算法的分离。

实现:

尚未实现:


软件发布

为了使软件脱离python特定环境而在计算机上独立运行,需要将py程序打包为二进制可执行文件,现在比较常用的打包工具为PyInstaller

安装

首先需要安装PyInstaller, 安装方式如下:

打包

PyInstaller打包使用语法如下:

各参数意义如下:

参数说明
-F, –onefile打包一个单个文件,如果你的代码都写在一个.py文件的话,可以用这个,如果是多个.py文件就别用
-D, –onedir打包多个文件,在dist中生成很多依赖文件,适合以框架形式编写工具代码,我个人比较推荐这样,代码易于维护
-K, –tk在部署时包含 TCL/TK
-a, –ascii不包含编码.在支持Unicode的python版本上默认包含所有的编码.
-d, –debug产生debug版本的可执行文件
-w,–windowed,–noconsole使用Windows子系统执行.当程序启动的时候不会打开命令行(只对Windows有效)
-c,–nowindowed,–console使用控制台子系统执行(默认)(只对Windows有效)pyinstaller -c xxxx.pypyinstaller xxxx.py --console
-s,–strip可执行文件和共享库将run through strip.注意Cygwin的strip往往使普通的win32 Dll无法使用.
-X, –upx如果有UPX安装(执行Configure.py时检测),会压缩执行文件(Windows系统中的DLL也会)(参见note)
-o DIR, –out=DIR指定spec文件的生成目录,如果没有指定,而且当前目录是PyInstaller的根目录,会自动创建一个用于输出(spec和生成的可执行文件)的目录.如果没有指定,而当前目录不是PyInstaller的根目录,则会输出到当前的目录下.
-p DIR, –path=DIR设置导入路径(和使用PYTHONPATH效果相似).可以用路径分割符(Windows使用分号,Linux使用冒号)分割,指定多个目录.也可以使用多个-p参数来设置多个导入路径,让pyinstaller自己去找程序需要的资源
–icon=<FILE.ICO>将file.ico添加为可执行文件的资源(只对Windows系统有效),改变程序的图标 pyinstaller -i ico路径 xxxxx.py
–icon=<FILE.EXE,N>将file.exe的第n个图标添加为可执行文件的资源(只对Windows系统有效)
-v FILE, –version=FILE将verfile作为可执行文件的版本资源(只对Windows系统有效)
-n NAME, –name=NAME可选的项目(产生的spec的)名字.如果省略,第一个脚本的主文件名将作为spec的名字

其中,scriptname为主程序入口,是必须的参数,在本项目中,代码层级如下,程序的主要入口为main.py

故打包的代码为:

运行完成之后会在对应目录下生成dist目录,里面便存放着可运行的二进制文件。

常见问题

生成的二进制文件并不能直接运行,可通过终端查看问题所在。

UI 文件和模型路径找不到

原因:PyInstaller在打包python程序只能将代码本身和包含的模块进行封装,至于.ui.h5文件需要手动添加。添加方式是按照源代码中相对路径的存放方式,将文件放置到对应的位置。

Matplotlib 版本问题

错误提示如下:

问题在于matplotlib版本太新,PyInstaller(4.0)无法将其打包。解决方式: 降低matplotlib版本。

模块缺失

错误提示如下:

重新打包程序,命令如下:

例如:

 

添加主窗口图标

程序窗口图标

我们程序运行的窗口,需要显示自己的图标,这样才更像一个正式的产品。

通过如下代码,我们可以把一个png图片文件作为 程序窗口图标。

注意:这些图标png文件,在使用PyInstaller创建可执行程序时,也要拷贝到程序所在目录。否则可执行程序运行后不会显示图标。

应用程序图标

应用程序图标是放在可执行程序里面的资源。可以在PyInstaller创建可执行程序时,通过参数 --icon="logo.ico" 指定。比如

注意参数一定是存在的ico文件,不能是png等图片文件。如果你只有png文件,可以通过在线的png转ico文件网站,生成ico,比如下面两个网站网站1网站2

注意:这些应用程序图标ico文件,在使用PyInstaller创建可执行程序时,不需要要拷贝到程序所在目录。因为它已经被嵌入可执行程序了。