CPP-NET论文阅读

本文提出上下文感知多边形提议网络(CPP-Net)用于细胞核分割。方法分点集采样、网络结构、优化策略三部分,采用U-Net骨干网络、置信度加权模块和形状感知感知损失函数。实验在三个公开数据集表现优异,还介绍了相关技术点及代码运行注意事项。
摘要由CSDN通过智能技术生成

Abstract

本文提出了一种新的细胞核分割方法,名为上下文感知多边形提议网络(Context-aware Polygon Proposal Network,CPP-Net)。该方法主要分为三部分:

  1. 点集采样:传统的距离预测方法通常只利用每个细胞核的质心像素,而忽略了周围环境的信息。因此,本文采用了点集采样的方式,即在每个细胞核内部随机采样一定数量的点作为输入,从而增强了上下文信息。

  2. 网络结构:本文提出的CPP-Net采用了简单的U-Net作为骨干网络,并在其之上添加了三个平行的一维卷积层用于预测距离。同时,为了更好地融合多个点的特征,本文还设计了一个置信度加权模块,对不同点的贡献进行加权平均。

  3. 优化策略:为了避免过拟合,本文引入了一种新颖的形状感知感知损失函数(Shape-Aware Perceptual Loss,SAP Loss),该损失函数能够约束预测出的多边形的形状与真实多边形相似。此外,本文还采用了数据增强等技术来增加训练样本的多样性,从而提高了模型的泛化能力。

实验结果表明,本文提出的方法在三个公开数据集上均取得了优异的表现,证明了其有效性和可靠性。

Related Work

相关工作的部分介绍了当前常用的几种细胞核分割方法,包括基于形状模型的方法、基于图割的方法、基于卷积神经网络的方法等。其中,基于形状模型的方法是最早被应用到细胞核分割中的方法之一,其主要思想是通过拟合形状模型来实现细胞核的分割。然而,这种方法需要预先定义形状模型,对于不同类型的细胞核难以适应。基于图割的方法则是通过对图像进行分割,然后通过优化能量函数来实现细胞核的分割。虽然这种方法能够较好地解决重叠细胞核的问题,但是计算复杂度较高,运行速度较慢。近年来,随着深度学习技术的发展,基于卷积神经网络的方法逐渐成为主流。这些方法通过训练神经网络来学习细胞核的特征,从而实现细胞核的分割。相比于传统方法,基于卷积神经网络的方法具有更高的准确性和鲁棒性。

Methods

overview

总体介绍了网络的架构,如下图:

分三步走,先对输入的图像利用以U-net为backbone的网络进行特征提取和输出,第二步利用本文提出的CEM来对特征进行处理,第三步进行后处理得到分割实例

代码:

# 网络结构
import torch
import torch.nn as nn
import torch.nn.functional as F
​
# 导入 unet_parts_gn.py 中定义的网络层
from .unet_parts_gn import *
# 导入 SamplingFeatures2.py 中定义的类
from .SamplingFeatures2 import SamplingFeatures
​
# 定义 CPPNet 类,继承自 nn.Module
class CPPNet(nn.Module):
​
    # n_channels: 输入图片的通道数
    # n_rays: 光线的数量
    # erosion_factor_list: 腐蚀因子
    # return_conf: 是否返回置信度分布
    # with_seg: 是否进行语义分割
    # n_seg_cls: 语义分割的类别数
    def __init__(self, n_channels, n_rays, erosion_factor_list=[0.2, 0.4, 0.6, 0.8, 1.0], return_conf=False,
                 with_seg=True, n_seg_cls=1):
        super(CPPNet, self).__init__()
        # 定义 U-Net 的 encoder 部分
        self.inc = inconv(n_channels, 32)
        self.down1 = down(32, 64)
        self.down2 = down(64, 128)
        self.down3 = down(128, 128)
        # 定义 U-Net 的 decoder 部分
        self.up1 = up(256, 64, bilinear=True)
        self.up2 = up(128, 32, bilinear=True)
        self.up3 = up(64, 32, bilinear=True)
        # 定义特征提取层
        self.features = nn.Conv2d(32, 128, 3, padding=1)
        # 定义输出层
        self.out_prob = outconv(128, 1)  # outconv is 1*1 conv
        self.out_ray = outconv(128, n_rays) # n_rays个通道
        # 定义置信度分布的输出层
        self.conv_0_confidence = outconv(128, n_rays)
        self.conv_1_confidence = outconv(1 + len(erosion_factor_list), 1 + len(erosion_factor_list))
        nn.init.constant_(self.conv_1_confidence.conv.bias, 1.0)
​
        # 是否进行语义分割
        self.with_seg = with_seg
        self.n_seg_cls = n_seg_cls
        if self.with_seg:
            # 定义语义分割的 decoder 部分
            self.up1_seg = up(256, 64, bilinear=True)
            self.up2_seg = up(128, 32, bilinear=True)
            self.up3_seg = up(64, 32, bilinear=True)
            # 定义输出层,用于预测语义分割结果
            self.out_seg = outconv(32, n_seg_cls)
            if self.n_seg_cls == 1:
                self.final_activation_seg = nn.Sigmoid()
            else:
                self.final_activation_seg = nn.Softmax(dim=1)
​
        # 定义最终输出的激活函数
        self.final_activation_ray = nn.ReLU()
        self.final_activation_prob = nn.Sigmoid()
​
        # cem
        self.sampling_feature = SamplingFeatures(n_rays)
        self.erosion_factor_list = erosion_factor_list
        self.n_rays = n_rays
        self.return_conf = return_conf
​
    # 网络的前向传播
    # img: 输入图片
    # gt_dist: rays方向的真实值(仅在训练时用到)
    def forward(self, img, gt_dist=None):
        # step1:obtain initial D,C,P maps by u-net
        x1 = self.inc(img)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x = self.up1(x4, x3)
        x = self.up2(x, x2)
        x = self.up3(x, x1)
        x = self.features(x)
        out_ray = self.out_ray(x)   # D
        out_confidence = self.conv_0_confidence(x) # c
        out_prob = self.out_prob(x) # P
​
        if gt_dist is not None:
            out_ray_for_sampling = gt_dist
        else:
            out_ray_for_sampling = out_ray
            
        ray_refined = [out_ray_for_sampling]
        # step2:CEM module
        confidence_refined = [out_confidence]
        for erosion_factor in self.erosion_factor_list:
            base_dist = (out_ray_for_sampling - 1.0) * erosion_factor
            # algorithm of sampling
            ray_sampled, _ = self.sampling_feature(out_ray_for_sampling, base_dist, 1)
            conf_sampled, _ = self.sampling_feature(out_confidence, base_dist, 1)
            
            ray_refined.append(ray_sampled + base_dist)
            confidence_refined.append(conf_sampled)
        ray_refined = torch.stack(ray_refined, dim=1)
        b, k, c, h, w = ray_refined.shape
​
        confidence_refined = torch.stack(confidence_refined, dim=1)
        # confidence_refined = torch.cat((confidence_refined, ray_refined), dim=1)
        confidence_refined = confidence_refined.permute([0, 2, 1, 3, 4]).contiguous().view(b * c, k, h, w)
        confidence_refined = self.conv_1_confidence(confidence_refined)
        confidence_refined = confidence_refined.view(b, c, k, h, w).permute([0, 2, 1, 3, 4])
        confidence_refined = F.softmax(confidence_refined, dim=1)
        if self.return_conf:
            out_conf = [out_confidence, confidence_refined]
        else:
            out_conf = None
        ray_refined = (ray_refined * confidence_refined).sum(dim=1)
​
        out_ray = self.final_activation_ray(out_ray)
        ray_refined = self.final_activation_ray(ray_refined)
        out_prob = self.final_activation_prob(out_prob)
​
        if self.with_seg:
            # step3:sap
            x_seg = self.up1_seg(x4, x3)
            x_seg = self.up2_seg(x_seg, x2)
            x_seg = self.up3_seg(x_seg, x1)
            out_seg = self.out_seg(x_seg)
            if self.n_seg_cls == 1:
                out_seg = self.final_activation_seg(out_seg)
            elif not self.training:
                out_seg = self.final_activation_seg(out_seg)
​
        else:
            out_seg = None
​
        return [out_ray, ray_refined], [out_prob], [out_seg, ], [out_conf, ]
​
    # 初始化网络的权重参数
    def init_weight(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if hasattr(m, 'bias'):
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d) or isinstance(m, nn.GroupNorm):
                nn.init.constant_(m.weight, 1.0)
                nn.init.constant_(m.bias, 0.0)
        nn.init.constant_(self.conv_1_confidence.conv.bias, 1.0)
​

context enhancement module

这一节的sampling算法使用特别关键

Confidence-Based Weighting Module

对每一个通道的特征的置信度进行一个线性变换

Loss Functions

公式:

代码:

import torch
import torch.nn.functional as F
​
# 定义了一个Dice Loss函数,用于计算预测结果和目标结果之间的相似度损失。
def dice_loss(pred, target, eps=1e-7):
    b = pred.shape[0]
    n_cls = pred.shape[1]
    loss = 0.0
    for ic in range(n_cls):
        ic_target = (target == ic).float().view(b, -1)
        ic_pred = pred[:, ic, :, :].view(b, -1)
        loss += (2*(ic_pred*ic_target).sum(dim=1)+eps) / (ic_pred.pow(2).sum(dim=1)+ic_target.pow(2).sum(dim=1)+eps)
    loss /= n_cls
    loss = 1.0 - loss.mean()
    return loss
​
class L1Loss_List_withSAP_withSeg(torch.nn.Module):
    def __init__(self, feature_extractor, scale=[1,1,1,1], cls_balance_mode=False):
        super(L1Loss_List_withSAP_withSeg, self).__init__()
        assert len(scale)==4
        self.scale = scale
        self.feature_extractor = feature_extractor
        if self.feature_extractor is not None:
            assert len(scale)==4
        else:
            assert len(scale)==3        
        self.cls_balance_mode = cls_balance_mode
        assert(self.cls_balance_mode in [True, False])
​
    def forward(self, prediction, target_dists, **kwargs):
​
        prob =  kwargs.get('labels', None)
        # 在前向传播过程中,首先从预测结果中提取出距离图(pred_dists)和概率图(pred_probs)。
        pred_dists = prediction[0]
        pred_probs = prediction[1]
​
        l1loss = 0.0 # the weighted L1 loss for pixel-to-boundary distance regression.
        bceloss = 0.0 # the binary cross entropy loss for centroid probability prediction
        segloss = 0.0 # the seg loss to measure the difference between gt and prediction
        # 权重L1损失通过计算每个距离图与目标距离的L1损失,并根据概率图进行加权求平均得到
        for i_dist in pred_dists:
            l1loss_map = F.l1_loss(i_dist, target_dists, reduction='none')
            l1loss += torch.mean(prob*l1loss_map)
        # 二进制交叉熵损失用于计算概率图与标签之间的损失。
        for i_prob in pred_probs:
            bceloss += F.binary_cross_entropy(i_prob, prob)
        # 分割损失根据分类平衡模式进行计算,将交叉熵损失和Dice Loss相结合。
        for i_seg in pred_segs:
            if self.cls_balance_mode:
                segloss_map = F.cross_entropy(i_seg, seg_target, reduction='none')
                cur_segloss = 0.0
                for ic in range(i_seg.shape[1]):
                    icmask = (seg_target==ic).float()
                    cur_segloss += ((icmask * segloss_map).sum()+1e-5) / (icmask.sum()+1e-5)
                segloss += cur_segloss / i_seg.shape[1]
                        + dice_loss(F.softmax(i_seg, dim=1), seg_target)
            else:
                segloss += F.cross_entropy(i_seg, seg_target) \
                        + dice_loss(F.softmax(i_seg, dim=1), seg_target)
        '''
        根据权重参数对三个损失进行加权求和得到总损失(loss)。然后,如果存在特征提取器,则计算特征相似度损失(sap_loss)并加入总损失中。最后,返回总损失和指标(metric)。
        '''
        loss = self.scale[0]*l1loss + self.scale[1]*bceloss
        metric = loss.data.clone().cpu()
        loss += self.scale[2]*segloss
​
        if self.feature_extractor is not None:
            self.feature_extractor.zero_grad()
            sap_loss = 0.0
            target_mask = (target_dists.max(dim=1, keepdim=True)[0]>0).float()
            f_target = self.feature_extractor(torch.cat((prob, target_dists), dim=1))
            for i_dist in pred_dists:
                f_pred = self.feature_extractor(torch.cat((pred_probs[-1], i_dist*target_mask), dim=1))
                sap_loss += F.l1_loss(f_pred, f_target)
            loss += self.scale[3]*sap_loss
        else:
            sap_loss = 0.0
​
        # print('loss: {:.5f}, metric: {:.5f}, l1: {:.5f}, bce: {:.5f}, seg: {:.5f}, sap: {:.5f}'\
        #     .format(loss, metric, l1loss, bceloss, segloss, sap_loss))
​
        return loss, metric
​
关于SAP

SAP损失的目的是惩罚预测和真实核表示之间形状特征的差异。

本文主要考虑两种核表示策略:一是基于边界的方法中的语义分割和边界检测图;二是基于每个核的关联边界框的位置和大小。在转换模型的训练过程中,我们将每个训练图像的真值D gt和P gt c连接起来以创建输入。两种目标表示策略分别采用二值交叉熵损失和L1损失。

所以首先要创建的是一个输出上述两种表示形式的unet网络,然后利用分割后的数据集对该网络进行训练,训练结束后该网络的encoder能够抽象出细胞核的高精度形状特征,取该网络的encoder参数后在训练CPP-net时使用。

Post-Processing

看算法3

主要是用在了预测阶段。

Algorithms

Algorithm1 sampling

# 采样
import torch
import torch.nn.functional as F
import torch.nn as nn
import math
#import time
​
# nd_sampling采样的维度,表示每个采样点采样的特征数量。
# 当 nd_sampling 大于 0 时,表示每个采样点采样的特征数量。当 nd_sampling 等于 0 时,表示每个采样点只采样一个特征。
def feature_sampling(feature_map, coord_map, nd_sampling, sampling_mode='nearest'):
    b, c, h, w = feature_map.shape # (batch_size, channels, height, width)
    # coord_map: b, k, 2, h, w
    # 'k' for k rays in each image
    _, k, _, h, w = coord_map.shape #(batch_size, k_rays, 2, height, width)
    x_ = torch.arange(w).view(1, -1).expand(h, -1) # params are (h,w)-1 represent all
    y_ = torch.arange(h).view(-1, 1).expand(-1, w)
    grid = torch.stack([x_, y_], dim=0).float()
    # grid: b, 1, 2, h, w
    grid = grid.unsqueeze(0).expand(b, -1, -1, -1, -1).cuda()
    # sampling_coord: b, k, 2, h, w
    # 将网格采样点坐标和预定义的偏移量相加,得到最终的采样点坐标。这里的grid代表网格采样点的坐标,coord_map代表预定义的偏移量。
    sampling_coord = grid + coord_map
    # 对采样点坐标进行归一化处理,将x和y方向的坐标值都除以特征图的宽度和高度减1,以将坐标值限制在[0,1]的范围内。
    sampling_coord[:, :, 0, :, :] = sampling_coord[:, :, 0, :, :]/(w-1)
    sampling_coord[:, :, 1, :, :] = sampling_coord[:, :, 1, :, :]/(h-1)
    # 对采样点坐标进行线性变换,将坐标值从[0,1]的范围映射到[-1,1]的范围内。具体来说,该代码将采样点坐标乘以2,再减去1,从而将原始的坐标值[0,1]线性变换为[-1,1]。这个操作可以使得采样点的坐标值符合神经网络的输入要求,并且能够增强模型的表达能力。
    sampling_coord = sampling_coord*2.0-1.0
​
    assert(k*nd_sampling==c)
​
    if nd_sampling > 0:
        sampling_coord = sampling_coord.permute(1, 0, 3, 4, 2).flatten(start_dim=0, end_dim=1) # k, b, h, w, 2
        sampling_features = F.grid_sample(feature_map.view(b, k, nd_sampling, h, w).permute(1, 0, 2, 3, 4).flatten(start_dim=0, end_dim=1), sampling_coord, mode=sampling_mode) # k,b, c', h, w
        sampling_features = sampling_features.view(k, b, nd_sampling, h, w).permute(1, 0, 2, 3, 4) # b, k, c', h, w
    else:
        sampling_coord = sampling_coord.permute(0, 1, 3, 4, 2).flatten(start_dim=1, end_dim=2) # b, kh, w, 2
        sampling_features = F.grid_sample(feature_map, sampling_coord, mode=sampling_mode)
        sampling_features = sampling_features.view(b, c, k, h, w).permute(0, 2, 1, 3, 4) # b, k, c'/c, h, w
​
    sampling_features = sampling_features.flatten(start_dim=1, end_dim=2) # b, k*c', h, w
​
    return sampling_features, sampling_coord
​
class SamplingFeatures(nn.Module):
    def __init__(self, n_rays, sampling_mode='nearest'):
        super(SamplingFeatures, self).__init__()
        self.n_rays = n_rays
        self.angles = torch.arange(n_rays).float()/float(n_rays)*math.pi*2.0 # 0 - 2*pi
        self.sin_angles = torch.sin(self.angles).cuda().view(1, n_rays, 1, 1)
        self.cos_angles = torch.cos(self.angles).cuda().view(1, n_rays, 1, 1)
        self.sampling_mode = sampling_mode
    def forward(self, feature_map, dist, nd_sampling):
        # feature_map: b, c, h, w
        # dist: b, k, h, w
        # sampled_features: b, k*c, h, w
        offset_ih = self.sin_angles * dist
        offset_iw = self.cos_angles * dist
        offsets = torch.stack([offset_iw, offset_ih], dim=2)
        sampled_features, sampling_coord = feature_sampling(feature_map, offsets, nd_sampling, self.sampling_mode)
        return sampled_features, sampling_coord
​
​

Alorithm2 CEM

# CEM
out_ray = self.out_ray(x)   # D
        out_confidence = self.conv_0_confidence(x) # c
        out_prob = self.out_prob(x) # P
​
        if gt_dist is not None:
            out_ray_for_sampling = gt_dist
        else:
            out_ray_for_sampling = out_ray
            
        ray_refined = [out_ray_for_sampling]
        # step2:CEM module
        confidence_refined = [out_confidence]
        for erosion_factor in self.erosion_factor_list:
            base_dist = (out_ray_for_sampling - 1.0) * erosion_factor
            # use of sampling
            ray_sampled, _ = self.sampling_feature(out_ray_for_sampling, base_dist, 1)
            conf_sampled, _ = self.sampling_feature(out_confidence, base_dist, 1)
            
            ray_refined.append(ray_sampled + base_dist)
            confidence_refined.append(conf_sampled)
        ray_refined = torch.stack(ray_refined, dim=1)
        b, k, c, h, w = ray_refined.shape
​
        confidence_refined = torch.stack(confidence_refined, dim=1)
        # confidence_refined = torch.cat((confidence_refined, ray_refined), dim=1)
        confidence_refined = confidence_refined.permute([0, 2, 1, 3, 4]).contiguous().view(b * c, k, h, w)
        confidence_refined = self.conv_1_confidence(confidence_refined)
        confidence_refined = confidence_refined.view(b, c, k, h, w).permute([0, 2, 1, 3, 4])
        confidence_refined = F.softmax(confidence_refined, dim=1)
        if self.return_conf:
            out_conf = [out_confidence, confidence_refined]
        else:
            out_conf = None
        ray_refined = (ray_refined * confidence_refined).sum(dim=1)
​

Algorithm3 fpp

    input = torch.tensor(img)
    input = input.unsqueeze(0).unsqueeze(0)
    preds = model_dist(input.cuda())
    dist_cuda = preds[0][-1][:, :, :h, :w]
    dist = dist_cuda.data.cpu()
    prob = preds[1][-1].data.cpu()[:, :, :h, :w]
    seg = preds[2][-1].data.cpu()[:, :, :h, :w]
​
    dist_numpy = dist.numpy().squeeze()
    prob_numpy = prob.numpy().squeeze()
    seg = seg.numpy().squeeze()
    prob_numpy = prob_numpy*seg# (seg>=seg_prob_thres).astype(np.float32)
    
    dist_numpy = np.transpose(dist_numpy,(1,2,0))
    coord = dist_to_coord(dist_numpy)
    points = non_maximum_suppression(coord, prob_numpy, prob_thresh=center_prob_thres)
    star_label = polygons_to_label(coord, prob_numpy, points)
# 细粒度处理
    if FPP and sin_angles is None:
        if dist_cmp == 'cuda':
            angles = torch.arange(n_rays).float()/float(n_rays)*math.pi*2.0 # 0 - 2*pi
            sin_angles = torch.sin(angles).view(1, n_rays, 1, 1)
            cos_angles = torch.cos(angles).view(1, n_rays, 1, 1)
            sin_angles = sin_angles.cuda()
            cos_angles = cos_angles.cuda()
​
            offset_ih = sin_angles * dist_cuda
            offset_iw = cos_angles * dist_cuda
            # 1, r, h, w, 2
            offsets = torch.stack([offset_iw, offset_ih], dim=-1)
            # h, w, 2
            mean_coord = np.round(offsets.mean(dim=1).data.cpu().squeeze(dim=0).numpy()).astype(np.int16)
            
    # Offset-based Post Processing:
    if FPP:
        seg_remained = np.logical_and(seg>=seg_prob_thres, pred==0)
        while seg_remained.any():
            if seg_remained.any():
                rxs, rys = np.where(seg_remained)
                mean_coord_remained = mean_coord[seg_remained, :]
                pred_0 = pred.copy()
                rxs_a = np.clip((rxs + mean_coord_remained[:, 1]).astype(np.int16), 0, h-1)
                rys_a = np.clip((rys + mean_coord_remained[:, 0]).astype(np.int16), 0, w-1)
                pred[seg_remained] = pred[(rxs_a, rys_a)]
                if not((pred_0 != pred).any()):
                    break
            else:
                break
            seg_remained = np.logical_and(seg>=seg_prob_thres, pred==0)

技术点

bilinear插值

Bilinear插值是一种图像处理中常用的插值方法,用于在已知的像素值之间估算新的像素值。在上下采样等操作中,为了获得非整数坐标位置的像素值,可以使用插值方法。Bilinear插值是其中一种常见的插值方法。

具体来说,对于一个二维平面上的四个已知点(四个相邻像素),Bilinear插值计算新点的值,其计算方式如下:

设目标点为(x, y),四个已知点分别为(x1, y1)(x1, y2)(x2, y1)(x2, y2),则Bilinear插值的计算公式为:

f(x,y)=(1−α)⋅(1−β)⋅f(x1,y1)+α⋅(1−β)⋅f(x2,y1)+(1−α)⋅β⋅f(x1,y2)+α⋅β⋅f(x2,y2)

其中,α 和 β 分别表示目标点在水平和垂直方向上的相对位置(在0到1之间)。

在神经网络中,Bilinear插值通常用于上采样操作,例如在上采样层或者转置卷积层(Transpose Convolution)中。通过Bilinear插值,可以有效地增加图像的分辨率,保留一定的图像特征。在上述代码中,bilinear=True表示在上采样操作中使用Bilinear插值。

形态学操作是一组基于形状的图像处理技术,主要用于改变图像的形状和结构。这些操作通常应用于二值图像,其中图像中的像素被标记为前景(白色)或背景(黑色)。形态学操作主要包括腐蚀(Erosion)、膨胀(Dilation)、开运算(Opening)和闭运算(Closing)等。

F.grid_sample是PyTorch中的一个函数,用于在输入张量上进行二维网格采样。它接受两个输入:特征图和采样点坐标,并返回根据采样点坐标在特征图上进行插值得到的采样结果。

具体来说,F.grid_sample函数使用双线性插值方法,在输入特征图上根据给定的采样点坐标进行采样。该函数的参数和功能如下:

  • input:形状为(batch_size, channels, height, width)的输入特征图。其中,batch_size表示批量大小,channels表示通道数,heightwidth表示特征图的高度和宽度。

  • grid:形状为(batch_size, num_samples, 2)的采样点坐标。其中,num_samples表示采样点的数量,2表示采样坐标的x和y方向。

  • mode:采样模式,可以是"bilinear"或"nearest",决定了采样时使用的插值方法。

F.grid_sample函数通过对输入特征图进行插值计算,根据给定的采样点坐标在特征图上进行采样。它会根据采样点坐标的值在特征图上找到最近的四个像素点,并使用双线性插值方法计算出采样点的值。

具体的插值方法如下:

  1. 首先,根据采样点坐标的值,计算出在特征图上最近的四个像素点的坐标。这是通过向下取整和向上取整来实现的。

  2. 然后,对于每个采样点,计算其在最近的四个像素点上的权重。这是通过计算采样点与最近四个像素点的相对距离来实现的。

  3. 最后,根据权重和最近四个像素点的值,进行加权求和,得到采样点的值。

使用F.grid_sample函数可以方便地在特征图上进行二维网格采样,并得到相应的采样结果。该函数在图像处理、计算机视觉等任务中经常被用于对输入数据进行空间变换和几何变换。

形态学操作

博客:openCV:详解形态学操作(腐蚀、膨胀、开运算,闭运算等)_侵蚀和开运算-CSDN博客

以下是形态学操作的简要说明:

  1. 腐蚀(Erosion): 腐蚀操作用于缩小物体的边界。它通过在图像中滑动一个结构元素(也称为内核)并将其放在像素上,只有当内核完全覆盖物体时,该像素才被保留。

  2. 膨胀(Dilation): 膨胀操作用于扩大物体的边界。它通过在图像中滑动一个结构元素并将其放在像素上,只要内核与物体的边界相交,该像素就会被设置为前景。

  3. 开运算(Opening): 开运算是先进行腐蚀,然后进行膨胀的组合操作。它主要用于消除小的亮点或孤立的像素。

  4. 闭运算(Closing): 闭运算是先进行膨胀,然后进行腐蚀的组合操作。它主要用于填充物体内的小洞或连接相邻的物体。

这些形态学操作通常应用于二值图像,其中前景和背景用不同的颜色表示。这些操作对于图像分割、边缘检测、去噪等任务都是有用的。在细胞核分割的上下文中,形态学操作可以用于优化二值掩码,清理和改进细胞核的边界。

在代码中,你可以使用图像处理库(如OpenCV)来应用这些形态学操作。以下是一个简化的示例:

​
import cv2
import numpy as np
​
# 读取二值图像
binary_image = cv2.imread('binary_image.jpg', cv2.IMREAD_GRAYSCALE)
​
# 定义形态学内核
kernel = np.ones((5, 5), np.uint8)
​
# 腐蚀操作
erosion_result = cv2.erode(binary_image, kernel, iterations=1)
​
# 膨胀操作
dilation_result = cv2.dilate(binary_image, kernel, iterations=1)
​
# 开运算
opening_result = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, kernel)
​
# 闭运算
closing_result = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, kernel)

本文中主要采用了腐蚀操作,详情可参照这篇博客:机器学习进阶-图像形态学操作-腐蚀操作 1.cv2.erode(进行腐蚀操作) - python我的最爱 - 博客园 (cnblogs.com)

采样

彻底分清机器学习中的上采样、下采样、过采样、欠采样【总结】_机器学习 上采样和下采样的区别-CSDN博客

文章:Point-Set Anchors for Object Detection, Instance Segmentation and Pose Estimation

学习率调度器

scheduler = ReduceLROnPlateau(optimizer, 'min', factor=0.5, verbose=True, patience=10, eps=1e-8, threshold=1e-20)

这行代码是使用 PyTorch 中的学习率调度器 ReduceLROnPlateau,并将其实例化为 scheduler 对象。

ReduceLROnPlateau 学习率调度器是一种动态调整学习率的方法。它会在训练过程中监测指定指标(如验证集上的损失函数或准确率),如果该指标不再明显地改善,则会降低学习率。具体来说,当连续 patience 个 epoch 的指标都没有明显改善时,调度器会将学习率乘以一个因子 factor

参数说明:

  • optimizer:优化器对象,即实现了梯度下降算法的优化器(如 torch.optim.SGDtorch.optim.Adam 等)。

  • 'min':指定了优化器优化的指标是最小化的。

  • factor:学习率下降的因子。默认值为 0.1,即将学习率降低为原来的 0.1 倍。

  • verbose:是否打印调度信息。默认为 True

  • patience:当连续指定数量的 epoch 没有指标改善时,降低学习率。默认值为 10。

  • eps:学习率下降的最小值。如果新学习率比旧学习率低的程度小于 eps,则不会更新学习率。默认值为 1e-8。

  • threshold:指标变化的最小值。如果指标变化的程度低于 threshold,则不会被视为明显改善。默认值为 1e-4。

示例:

import torch
from torch.optim import SGD
from torch.optim.lr_scheduler import ReduceLROnPlateau
​
# 创建一个优化器对象
optimizer = SGD(model.parameters(), lr=0.01, momentum=0.9)
​
# 创建学习率调度器对象
scheduler = ReduceLROnPlateau(optimizer, 'min', factor=0.5, verbose=True, patience=10, eps=1e-8, threshold=1e-20)
​
# 在训练过程中使用学习率调度器
for epoch in range(num_epochs):
    # 训练模型并计算损失函数和准确率
    train_loss, train_acc = train(model, train_loader, criterion, optimizer)
    valid_loss, valid_acc = validate(model, valid_loader, criterion)
​
    # 每个 epoch 结束后更新学习率
    scheduler.step(valid_loss)
​
    # 打印学习率和指标信息
    print(f"Epoch {epoch+1}, lr {optimizer.param_groups[0]['lr']:.6f}, train loss: {train_loss:.6f}, train acc: {train_acc:.6f}, valid loss: {valid_loss:.6f}, valid acc: {valid_acc:.6f}")

在这个示例中,我们首先使用 SGD 优化器创建了一个优化器对象,然后使用 ReduceLROnPlateau 学习率调度器创建了一个调度器对象。在每个 epoch 结束后,我们使用验证集上的损失函数更新学习率,并打印相关信息。

需要注意的是,在使用学习率调度器时,通常需要设置一个合适的初始学习率并进行调整。过小的学习率会导致收敛速度过慢,而过大的学习率可能会导致模型无法收敛。因此,需要根据具体任务和数据集选择一个合适的学习率,并通过训练过程中的学习率调整来优化收敛效果。

代码运行

通过os.getcwd()得到的目录中最好不含中文,否则可能会报错,目录尽量用英文。(mainshape.py)

注意训练的轮数是否为None

先进行feature_extractor的训练,得到参数文件

再进行maincppnet的训练

最后进行predict验证。

predict中修改:

   # 源代码
    coord = dist_to_coord(dist_numpy)
    points = non_maximum_suppression(coord, prob_numpy, prob_thresh=center_prob_thres)
    star_label = polygons_to_label(coord, prob_numpy, points)
    
    # 修改后
    points,sc,dis = non_maximum_suppression(dist_numpy, prob_numpy, prob_thresh=center_prob_thres)
    coord = dist_to_coord(dis,points)
    star_label = polygons_to_label(dis, points,img.shape)

### 使用C++实现基于PointNet的点云分割 对于使用C++来实现实现基于PointNet架构的点云分割,目前主要资源集中在研究论文以及开源项目上。PointNet是一种专门针对无序点集设计的神经网络模型,能够处理诸如分类、语义分割等任务[^1]。 #### 论文介绍 一篇重要的基础性工作是由Charles R. Qi等人提出的《PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation》。该文章介绍了如何构建可以直接作用于原始三维坐标数据上的深度学习框架,并展示了其在网络结构设计方面的创新之处[^2]。 #### 开源库与工具包 为了便于开发者快速搭建实验环境并开展进一步的研究,在GitHub平台上存在多个实现了PointNet算法的C++版本仓库。例如`pointnet2_lib`提供了较为完整的解决方案,不仅包含了训练过程中的前向传播计算逻辑,还支持通过CUDA加速提高效率[^3]。 ```cpp // Example of including necessary headers from PCL library which can be used alongside custom implementations. #include <pcl/point_cloud.h> #include <pcl/io/pcd_io.h> int main() { pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // Load point cloud data into the variable 'cloud' if (pcl::io::loadPCDFile<pcl::PointXYZ>("test_pcd.pcd", *cloud) == -1) { std::cerr << "Couldn't read file test_pcd.pcd \n"; return (-1); } } ``` 值得注意的是,虽然上述提到的内容侧重于理论阐述和技术细节描述,但在实际应用开发过程中还需要考虑更多因素如性能优化、跨平台兼容等问题。因此建议读者深入阅读相关文档并与社区保持交流以便获取最新进展和支持信息。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值