使用opencv-dnn+C++部署onnx肺区分割模型

1.环境:

windows + vscdoe + opencv3.4.15

2.流程:

①通过python将训练得到的模型转化为onnx。

②通过C++调用onnx模型实现推理。

3.代码:

① python代码

ResUnet.py

import torch
from torch import nn
import torch.nn.functional as F


class DoubleConv(nn.Module):
    """(convolution => [BN] => ReLU) * 2"""

    def __init__(self, inChannels, outChannels, midChannels=None):
        super().__init__()
        if not midChannels:
            midChannels = outChannels
        self.doubleConv = nn.Sequential(
            nn.Conv2d(inChannels, midChannels, kernel_size=3, padding=1),
            nn.BatchNorm2d(midChannels),
            nn.ReLU(inplace=True),
            nn.Conv2d(midChannels, outChannels, kernel_size=3, padding=1),
            nn.BatchNorm2d(outChannels),
            nn.ReLU(inplace=True)
        )

    def forward(self, inNet):
        return self.doubleConv(inNet)


class ResBlock(nn.Module):
    def __init__(self, inChannels, outChannels):
        super(ResBlock, self).__init__()
        self.down1Sample = nn.Sequential(
            nn.Conv2d(inChannels, outChannels, kernel_size=1, stride=1, bias=False),
            nn.BatchNorm2d(outChannels))
        self.doubleConv = DoubleConv(inChannels, outChannels)
        self.down2Sample = nn.MaxPool2d(2)
        self.relu = nn.ReLU()

    def forward(self, inNet):
        identity = self.down1Sample(inNet)
        outNet = self.doubleConv(inNet)
        outNet = self.relu(outNet + identity)
        return self.down2Sample(outNet), outNet


class UpBlock(nn.Module):
    def __init__(self, inChannels, outChannels):
        super(UpBlock, self).__init__()
        self.upSample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
        self.doubleConv = DoubleConv(inChannels, outChannels)

    def forward(self, downInput, skipInput):
        inNet = self.upSample(downInput)
        # input is CHW
        dify = skipInput.size()[2] - inNet.size()[2]
        difx = skipInput.size()[3] - inNet.size()[3]

        inNet = F.pad(inNet, [difx // 2, difx - difx // 2,
                              dify // 2, dify - dify // 2])
        inNet = torch.cat([inNet, skipInput], dim=1)
        return self.doubleConv(inNet)


class OutConv(nn.Module):
    def __init__(self, inChannels, outChannels):
        super(OutConv, self).__init__()
        self.conv = nn.Conv2d(inChannels, outChannels, kernel_size=1)

    def forward(self, inNet):
        return self.conv(inNet)


class ResUnet(nn.Module):
    """
    Hybrid solution of resnet blocks and double conv blocks
    """
    def __init__(self, inChannels=3, nClasses=1):
        super(ResUnet, self).__init__()

        self.downConv = nn.ModuleList()
        neuronNum = [64, 128, 256, 512, 1024]
        preChannels = inChannels
        for num in neuronNum[0:-1]:
            self.downConv.append(ResBlock(preChannels, num))
            preChannels = num
        self.doubleConv = DoubleConv(preChannels, neuronNum[-1])

        self.upConv = nn.ModuleList()
        for num1, num2 in zip(neuronNum[1::][::-1],  neuronNum[0:-1][::-1]):
            self.upConv.append(UpBlock(num1 + num2, num2))

        self.convFinal = OutConv(num2, nClasses)

    def forward(self, inNet):
        skipOuts = []
        for cnt, down in enumerate(self.downConv):
            inNet, skipOut = down(inNet)
            skipOuts.append(skipOut)
        inNet = self.doubleConv(inNet)

        for cnt, up in enumerate(self.upConv):
            inNet = up(inNet, skipOuts[-1 - cnt])

        outNet = self.convFinal(inNet)
        return outNet

export.py

def export(ckpt):
    model = ResUnet().to(DEVICE)
    stateDict = torch.load(ckpt)['state_dict']
    new_state_dict = OrderedDict()
    for key, val in stateDict.items():
        name = key[7:]  # remove "module."   
        new_state_dict[name] = val
       
    model.load_state_dict(new_state_dict)
    model.eval()

    inNet = torch.rand(1, 3, calSize[0], calSize[1]).to(DEVICE)
    torch.onnx.export(model, inNet, modelOnnx, opset_version=11, verbose=True, export_params=True, do_constant_folding=True)

② C++代码:

#include <iostream>
#include <fstream>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

using namespace std;
using namespace cv;
using namespace cv::dnn;
 

cv::Scalar meanDefault {0.485, 0.456, 0.406};
cv::Scalar stdDefault {0.229, 0.224, 0.225};
std::vector<std::string> extensions {"jpg", "bmp", "png", "jpeg"};
const int topk = 2;
static const string kWinName = "CT lung seg in OpenCV";

typedef struct
{
    int index;
    double value;
}sortArray;

cv::Mat Preprocess(cv::Mat pendImg)
{

    cv::Mat postImg;
    cv::resize(pendImg, postImg, cv::Size(512, 512));
    postImg.convertTo(postImg, CV_32F, 1.0/255.0);
    cv::subtract(postImg, meanDefault, postImg);
    cv::divide(postImg, stdDefault, postImg);
    return postImg;
}


bool cmp(sortArray a, sortArray b)
{
    return a.value>b.value;
}

std::vector<std::vector<cv::Point>> Postprocess(cv::Mat pendMask)
{

    
    cv::Mat bwImg;
    std::vector<std::vector<cv::Point>> contours; 
    std::vector<double> areas;

    cv::threshold(pendMask, bwImg, 1, 255.0, CV_THRESH_BINARY | CV_THRESH_OTSU);  
    cv::findContours(bwImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);  
    
    std::vector <sortArray> sortedArray(contours.size());
    for(int i=0;i<contours.size();++i){
        sortedArray[i].index = i;
        sortedArray[i].value = cv::contourArea(contours[i]);
    }
    std::sort(sortedArray.begin(), sortedArray.end(), cmp);
  
    std::vector<std::vector<cv::Point>> nContours;

    for (int i=0;i<sortedArray.size();i++)
    {

        if (i<topk) nContours.push_back(contours[sortedArray[i].index]);
        else break;

    }

    return nContours;
}

void GetImgFilenames(std::string path, std::vector<std::string>& imgFilenames)
{
    // imgFilenames.clear();
    if (path.find(".") != std::string::npos)
    {
        imgFilenames.push_back(path);
    }
    else
    {
        std::string fpath = path.append("*.*");
        std::vector<cv::String> allfiles;  //cv::String
        cv::glob(fpath, allfiles);
        for (int i = 0; i < allfiles.size(); i++)
        {
            size_t iPos = allfiles[i].rfind('.');
            std::string fileEx = allfiles[i].substr(iPos + 1, allfiles[i].length());
            // cout << fileEx << endl;
            if (std::find(extensions.begin(), extensions.end(), fileEx) != extensions.end())
            {
                imgFilenames.push_back(allfiles[i]);
            }
        }
    }
    // return;
}

cv::Mat Seg(std::string modelPath, std::string imgPath){

    // Net net = cv::dnn::readNetFromONNX(modelPath);
    cv::dnn::Net net = cv::dnn::readNet(modelPath);
    net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
    net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);

    cv::Mat Oimg = imread(imgPath);    
    cv::Mat img = Preprocess(Oimg.clone());
 
    cv::Mat blob = cv::dnn::blobFromImage(img, 1.0, Size(512, 512), cv::Scalar(0, 0, 0), true, false);    
    net.setInput(blob);    
 
    cv::Mat predOut = net.forward();  
    std::vector<cv::Mat> predTmp;
    cv::dnn::imagesFromBlob(predOut, predTmp); 

    cv::Mat predMask;
    predTmp[0] = (predTmp[0]>0);    
    predTmp[0].convertTo(predMask, CV_8UC1);
    return predMask;

}


int main()
{

    std::string modelBin = "weights/ctSeg.onnx";

    std::string path = "imgs/";
    std::vector<std::string> imgfiles;
  
    GetImgFilenames(path, imgfiles);

    for (int i=0; i<imgfiles.size(); i++)
    {
        // std::cout << imgfiles[i] << std::endl;
        cv::Mat predMask = Seg(modelBin, imgfiles[i]);
        cv::Mat Oimg = imread(imgfiles[i]);    
        cv::Mat imgShow = Oimg.clone();
        cv::resize(imgShow, imgShow, cv::Size(512, 512));

        std::vector<std::vector<cv::Point>> nContours = Postprocess(predMask);
        for (int i = 0; i < nContours.size(); i++)
        {
            cv::drawContours(imgShow, nContours, i, Scalar(0, 255, 0), 2, 8); 
        }
        cv::imshow("iShow", imgShow);
        cv::waitKey(0);
    }
    return 0;

}
4.结果:
5.文件下载路径:

链接:https://pan.baidu.com/s/1DDweuwcpSubLotU79c-jFw

提取码:ZDWD

注:刚接触深度学习完成的模型,所以采用了当时比较常见的网络,网络模型偏大。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值