本章简要介绍如何如何用C++实现一个语义分割器模型,该模型具有训练和预测的功能。本文的分割模型架构使用简单的U-Net结构,代码结构参考了qubvel segmentation中的U-Net部分,该项目简称SMP,是基于pytorch实现的开源语义分割项目。本文分享的c++模型几乎完美复现了python的版本。
模型简介
简单介绍一下U-Net模型。U-Net模型的提出是在医学图像分割中,相比于当时的其他模型结构,U-Net的分割能力具有明显优势。一个经典的U-Net结构图如下:
U-Net模型采用典型的编码器-解码器结构,左边的编码部分类似VGG模型,是双卷积+下采样的多次堆叠。U-Net模型右边的解码部分同样是双卷积,但是为了得到接近原始输入图像大小的输出图像,针对编码的下采样实施了对应的上采样。最重要的是,U-Net之所以效果突出,重要原因在于其在解码部分利用了编码环节的特征图,拼接编码和解码的特征图,再对拼接后特征图卷积上采样,重复多次得到解码输出。
编码器——ResNet
本文介绍的编码器使用ResNet网络,同时可以像第五章一样加载预训练权重,即骨干网络为ImageNet预训练的ResNet。话不多说,直接上c++的ResNet代码。
Block搭建
建议看本文代码时打开pytorch的torchvision中的resnet.py,对比阅读。
首先是基础模块,pytorch针对resnet18,resne34和resnet50,resnet101,resnet152进行分类,resnet18与resnet34均使用BasicBlock,而更深的网络使用BottleNeck。我不想使用模板类编程,就直接将两个模块合为一体。声明如下:
1 | class BlockImpl : public torch::nn::Module { |
可以发现,其实是直接声明了三个conv结构和一个is_basic标志位判断定义时进行BasicBlock定义还是BottleNeck定义。下面是其定义
1 | BlockImpl::BlockImpl(int64_t inplanes, int64_t planes, int64_t stride_, |
然后不要忘了熟悉的conv_options函数,定义如下:
1 | inline torch::nn::Conv2dOptions conv_options(int64_t in_planes, int64_t out_planes, int64_t kerner_size, |
和之前章节中的相比,增加了groups参数,同时with_bias默认打开,使用需要注意修改。
ResNet主体搭建
定义好Block模块后就可以设计ResNet了,c++中ResNet模型声明类似pytorch中的ResNet。但是初始化参数增加一个model_type,辅助判断采用哪种Block。
1 | class ResNetImpl : public torch::nn::Module { |
在实现初始化函数之前,需要实现_make_layer函数。实现好_make_layer函数后再实现ResNet初始化函数,代码如下:
1 | torch::nn::Sequential ResNetImpl::_make_layer(int64_t planes, int64_t blocks, int64_t stride) { |
前向传播及特征提取
前向传播相对简单,直接根据定义好的层往下传播即可。
1 | torch::Tensor ResNetImpl::forward(torch::Tensor x) { |
但是本文是介绍分割用的,所以需要对不同的特征层进行提取,存储到std::vector<torch::Tensor>中。
1 | std::vector<torch::Tensor> ResNetImpl::features(torch::Tensor x){ |
U-Net解码
上面的ResNet部分其实可以开单章详细讲解,但是参照源码读者应该容易理解,就直接放一起。如果上面的内容是对torchvision在libtorch中的优化,下面的部分可以看成直接对SMP中U-Net解码的c++复制。
直接上声明:
1 | //attention and basic |
直接上定义:
1 | SCSEModuleImpl::SCSEModuleImpl(int in_channels, int reduction, bool _use_attention){ |
不展开说了,内容较多。后续还有U-Net整体和封装…
U-Net整体设计
这是U-Net的声明,分为编码器,解码器和分割头。
1 | class UNetImpl : public torch::nn::Module |
这是实现:
1 | UNetImpl::UNetImpl(int _num_classes, std::string encoder_name, std::string pretrained_path, int encoder_depth, |
分割头:
1 | class SegmentationHeadImpl: public torch::nn::Module{ |
内容过于多,博客写得比较费劲。直接将封装和测试代码放到GitHub上了,在这里。里面集成了包括ResNet,ResNext和可能的ResNest为骨干网络,目前网络架构实现了FPN和U-Net。如果项目内容有帮到你请务必给个star,作者需要这份支持作为动力,实在没钱做推广呀…大家牛年快乐!!!
实际测试U-Net在c++代码执行效率,发现与python在cpu下速度一致,GPU下快35%+。c++真香。
分享不易,如果有用请不吝给我一个👍,转载注明出处:https://allentdan.github.io/
代码见LibtorchTutorials