语义分割学习笔记(五)U-net网络

news2024/10/7 7:25:06

推荐课程:U-Net网络结构讲解(语义分割)_哔哩哔哩_bilibili

感谢博主霹雳吧啦Wz  提供视频讲解和源码支持,真乃神人也!

目录

1. U-net网络模型

2. 分割效果

3. U-Net源码解析(Pytorch版)

4. 测试结果


1. U-net网络模型

U-Net网络由两部分构成,contracting path(收缩路径) 和 expanding path(扩展路径)。

U-Net网络训练过程:

1. contracting path(收缩路径):由4组 { 两个3x3卷积层 + 一个池化层(下采样) } 构成。

输入特征图(572 x 572 x 1) --conv(3x3卷积)--> 长、宽、通道数(570 x 570 x 64)--conv(3x3卷积)--> (568 x 568 x 64)--max_pooling(池化)(减半)--> (284 x 284 x 64),  两个卷积层 + 一个池化层……最后到特征图(32 x 32 x 512)。

2. 中间又经过两个3x3卷积层:特征图(32 x 32 x 512) --conv(3x3卷积)--> (30 x 30 x 1024)--conv(3x3卷积)--> (28 x 28 x 1024)

3. expanding path(扩展路径):由4组 { 中心裁剪和拼接 + 一个上采样层(转置卷积) + 两个3x3卷积层 } 构成。

注意:copy and cope 中心裁剪和拼接,先进行裁剪 (64 x 64 x 512)--crop(中心裁剪)--> (56 x 56 x 512) 。这里裁剪的是contracting path(收缩路径)中的一个特征图。再在 expanding path(扩展路径)中进行拼接。

特征图(28 x 28 x 1024) --up-conv(上采样,转置卷积)--> (56 x 56 x 512)--cope(拼接,上面中心裁剪得到的特征图)-->(56 x 56 x 1024)--conv(3x3卷积)--> (54 x 54 x 512)--conv(3x3卷积)--> (52 x 52 x 512)  ,一次中心裁剪 + 一个上采样层(转置卷积) + 两个卷积层……最后得到特征图(388 x 388 x 64)。

4. 最后进行一次1x1卷积:特征图(388 x 388 x 64)--conv(1x1卷积)--> 特征图(388 x 388 x 2)。最后输出一个388 x 388 x 2的分割图。

U-Net网络模型改进:在步骤2和步骤3中的卷积层改为大小为3x3,填充为1的卷积层,这样 expanding path(扩展路径)中的特征图经过上采样后的大小与contracting path(收缩路径)中对应的特征图大小一致,可以省去中心裁剪这一步直接拼接。

2. 分割效果

3. U-Net源码解析(Pytorch版)

unet源码:https://github.com/WZMIAOMIAO/deep-learning-for-image-processing/tree/master/pytorch_segmentation/unet

DRIVE数据集下载地址 :百度云链接: https://pan.baidu.com/s/1Tjkrx2B9FgoJk0KviA-rDw 密码: 8no8

unet源码:

  ├── src: 搭建U-Net模型代码
  ├── train_utils: 训练、验证以及多GPU训练相关模块
  ├── my_dataset.py: 自定义dataset用于读取DRIVE数据集(视网膜血管分割)
  ├── train.py: 以单GPU为例进行训练
  ├── train_multi_GPU.py: 针对使用多GPU的用户使用
  ├── predict.py: 简易的预测脚本,使用训练好的权重进行预测测试
  └── compute_mean_std.py: 统计数据集各通道的均值和标准差

DRIVE数据集:

test:
        1st_manual目录:标注图片,金标准
        2nd_manual目录:标注图片,验证
        images目录:用于分割的原图片
        mask目录:分割区域,
training:
        1st_manual目录:标注图片
        images目录:用于分割的原图片
        mask目录:分割区域

改进的U-Net网络模型:

 (1) U-Net网络模型代码

unet.py

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

# 在uent中卷积一般成对使用
class DoubleConv(nn.Sequential):
    # 输入通道数, 输出通道数, mid_channels为成对卷积中第一个卷积层的输出通道数
    def __init__(self, in_channels, out_channels, mid_channels=None):
        if mid_channels is None:
            mid_channels = out_channels
        super(DoubleConv, self).__init__(
            # 3*3卷积,填充为1,卷积之后输入输出的特征图大小一致
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

# 下采样
class Down(nn.Sequential):
    def __init__(self, in_channels, out_channels):
        super(Down, self).__init__(
            # 1.最大池化的窗口大小为2, 步长为2
            nn.MaxPool2d(2, stride=2),
            # 2.两个卷积
            DoubleConv(in_channels, out_channels)
        )


# 上采样
class Up(nn.Module):
    # bilinear是否采用双线性插值
    def __init__(self, in_channels, out_channels, bilinear=True):
        super(Up, self).__init__()
        if bilinear:
            # 使用双线性插值上采样
            # 上采样率为2,双线性插值模式
            self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
            self.conv = DoubleConv(in_channels, out_channels, in_channels // 2)
        else:
            # 使用转置卷积上采样
            self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2)
            self.conv = DoubleConv(in_channels, out_channels)

    def forward(self, x1: torch.Tensor, x2: torch.Tensor) -> torch.Tensor:
        x1 = self.up(x1)
        # [N, C, H, W]
        # 上采样之后的特征图与要拼接的特征图,高度方向的差值
        diff_y = x2.size()[2] - x1.size()[2]
        # 上采样之后的特征图与要拼接的特征图,宽度方向的差值
        diff_x = x2.size()[3] - x1.size()[3]

        # padding_left, padding_right, padding_top, padding_bottom
        # 1.填充差值
        x1 = F.pad(x1, [diff_x // 2, diff_x - diff_x // 2,
                        diff_y // 2, diff_y - diff_y // 2])

        # 2.拼接
        x = torch.cat([x2, x1], dim=1)
        # 3.两个卷积
        x = self.conv(x)
        return x

# 最后的1*1输出卷积
class OutConv(nn.Sequential):
    def __init__(self, in_channels, num_classes):
        super(OutConv, self).__init__(
            nn.Conv2d(in_channels, num_classes, kernel_size=1)
        )


# unet网络模型
class UNet(nn.Module):
    # 参数: 输入通道数, 分割任务个数, 是否使用双线插值, 网络中第一个卷积通道个数
    def __init__(self,
                 in_channels: int = 1,
                 num_classes: int = 2,
                 bilinear: bool = True,
                 base_c: int = 64):
        super(UNet, self).__init__()
        self.in_channels = in_channels
        self.num_classes = num_classes
        self.bilinear = bilinear

        self.in_conv = DoubleConv(in_channels, base_c)
        # 下采样,参数:输入通道,输出通道
        self.down1 = Down(base_c, base_c * 2)
        self.down2 = Down(base_c * 2, base_c * 4)
        self.down3 = Down(base_c * 4, base_c * 8)
        # 如果采用双线插值上采样为 2,采用转置矩阵上采样为 1
        factor = 2 if bilinear else 1
        # 最后一个下采样,如果是双线插值则输出通道为512,否则为1024
        self.down4 = Down(base_c * 8, base_c * 16 // factor)
        # 上采样,参数:输入通道,输出通道
        self.up1 = Up(base_c * 16, base_c * 8 // factor, bilinear)
        self.up2 = Up(base_c * 8, base_c * 4 // factor, bilinear)
        self.up3 = Up(base_c * 4, base_c * 2 // factor, bilinear)
        self.up4 = Up(base_c * 2, base_c, bilinear)
        # 最后的1*1输出卷积
        self.out_conv = OutConv(base_c, num_classes)

    # 正向传播过程
    def forward(self, x: torch.Tensor) -> Dict[str, torch.Tensor]:
        # 1. 定义最开始的两个卷积层
        x1 = self.in_conv(x)
        # 2. contracting path(收缩路径)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)
        # 3. expanding path(扩展路径)
        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)
        # 4. 最后1*1输出卷积
        logits = self.out_conv(x)

        return {"out": logits}

(2)加载数据集

my_dataset.py

import os
from PIL import Image
import numpy as np
from torch.utils.data import Dataset

# 载入数据集,继承Dataset类
class DriveDataset(Dataset):
    # 获取文件路劲。参数:根目录, T 载入训练集/F 测试集, 数据预处理方式
    def __init__(self, root: str, train: bool, transforms=None):
        super(DriveDataset, self).__init__()
        # train为 T,flag为training,否则为test
        self.flag = "training" if train else "test"
        data_root = os.path.join(root, "DRIVE", self.flag)
        # 判断路径是否存在
        assert os.path.exists(data_root), f"path '{data_root}' does not exists."
        self.transforms = transforms
        # 遍历data_root下的images目录,得到以.tif结尾的文件名称
        img_names = [i for i in os.listdir(os.path.join(data_root, "images")) if i.endswith(".tif")]
        # 获取文件路径
        self.img_list = [os.path.join(data_root, "images", i) for i in img_names]
        self.manual = [os.path.join(data_root, "1st_manual", i.split("_")[0] + "_manual1.gif")
                       for i in img_names]
        # check files
        for i in self.manual:
            if os.path.exists(i) is False:
                raise FileNotFoundError(f"file {i} does not exists.")

        self.roi_mask = [os.path.join(data_root, "mask", i.split("_")[0] + f"_{self.flag}_mask.gif")
                         for i in img_names]
        # check files
        for i in self.roi_mask:
            if os.path.exists(i) is False:
                raise FileNotFoundError(f"file {i} does not exists.")

    #
    def __getitem__(self, idx):
        # 转化为RGB灰度图片
        img = Image.open(self.img_list[idx]).convert('RGB')
        manual = Image.open(self.manual[idx]).convert('L')
        # 前景区域像素值变为1,背景区域像素值变为0
        manual = np.array(manual) / 255
        roi_mask = Image.open(self.roi_mask[idx]).convert('L')
        # 感兴趣的区域像素值变为0,不感兴趣的区域像素值变为255
        roi_mask = 255 - np.array(roi_mask)
        mask = np.clip(manual + roi_mask, a_min=0, a_max=255)

        # 这里转回PIL的原因是,transforms中是对PIL数据进行处理
        mask = Image.fromarray(mask)

        if self.transforms is not None:
            # 进行图片预处理
            img, mask = self.transforms(img, mask)

        return img, mask

    def __len__(self):
        # 返回用于分割的原图片个数
        return len(self.img_list)

    @staticmethod
    def collate_fn(batch):
        images, targets = list(zip(*batch))
        batched_imgs = cat_list(images, fill_value=0)
        batched_targets = cat_list(targets, fill_value=255)
        return batched_imgs, batched_targets


def cat_list(images, fill_value=0):
    max_size = tuple(max(s) for s in zip(*[img.shape for img in images]))
    batch_shape = (len(images),) + max_size
    batched_imgs = images[0].new(*batch_shape).fill_(fill_value)
    for img, pad_img in zip(images, batched_imgs):
        pad_img[..., :img.shape[-2], :img.shape[-1]].copy_(img)
    return batched_imgs

(3)训练和评估

Dice similarity coefficient用于衡量两个集合的相似性,是分割网络中最常用的评价指标之一。

计算公式:

Dice=\frac{2\left | X\bigcap Y \right |}{ |X|+|Y| }

Dice\ Loss=1-\frac{2\left | X\bigcap Y \right |}{ |X|+|Y| }

Dice计算过程:

预测前景gailv矩阵X和前景标签矩阵进行数乘,再除以两个矩阵所有元素之和。如下图:

构建前景和背景GT标签过程:

我们在计算dice,应该分别根据前景和背景分别计算一个dice系数。因此需要分别构建前景和背景GT标签

在GT标签中元素0为背景区域,1为前景区域,255为应该被忽略的区域(不感兴趣的区域)。将首先,所有的255元素变为0,然后进行one-hot操作,通道为0的矩阵所有为0的元素变为1,所有为1的元素变为0,得到background GT。通道为1的矩阵,元素不变,得到foreground GT。

前景和背景GT标签构建 + Dice计算 + Dice_Loss计算代码实现:

dice_coefficient_loss.py(前景和背景GT标签构建 + Dice计算 + Dice_Loss计算)

import torch
import torch.nn as nn

# 构建前景和背景GT标签
def build_target(target: torch.Tensor, num_classes: int = 2, ignore_index: int = -100):
    """build target for dice coefficient"""
    dice_target = target.clone()
    # 是否有255元素
    if ignore_index >= 0:
        ignore_mask = torch.eq(target, ignore_index)
        # 将所有的255元素变为0
        dice_target[ignore_mask] = 0
        # [N, H, W] -> [N, H, W, C]
        # 2个通道,通道为0的矩阵所有0变1,1变0。通道为1的矩阵元素不变
        dice_target = nn.functional.one_hot(dice_target, num_classes).float()
        # 将255元素复原
        dice_target[ignore_mask] = ignore_index
    else:
        dice_target = nn.functional.one_hot(dice_target, num_classes).float()

    return dice_target.permute(0, 3, 1, 2)


def dice_coeff(x: torch.Tensor, target: torch.Tensor, ignore_index: int = -100, epsilon=1e-6):
    # Average of Dice coefficient for all batches, or for a single mask
    # 计算一个batch中所有图片某个类别的dice_coefficient
    d = 0.
    batch_size = x.shape[0]
    for i in range(batch_size):
        x_i = x[i].reshape(-1)
        t_i = target[i].reshape(-1)
        if ignore_index >= 0:
            # 找出mask中不为ignore_index的区域
            roi_mask = torch.ne(t_i, ignore_index)
            x_i = x_i[roi_mask]
            t_i = t_i[roi_mask]
        inter = torch.dot(x_i, t_i)
        sets_sum = torch.sum(x_i) + torch.sum(t_i)
        if sets_sum == 0:
            sets_sum = 2 * inter

        d += (2 * inter + epsilon) / (sets_sum + epsilon)

    return d / batch_size


def multiclass_dice_coeff(x: torch.Tensor, target: torch.Tensor, ignore_index: int = -100, epsilon=1e-6):
    """Average of Dice coefficient for all classes"""
    dice = 0.
    for channel in range(x.shape[1]):
        dice += dice_coeff(x[:, channel, ...], target[:, channel, ...], ignore_index, epsilon)

    return dice / x.shape[1]

# 计算dice_loss
def dice_loss(x: torch.Tensor, target: torch.Tensor, multiclass: bool = False, ignore_index: int = -100):
    # Dice loss (objective to minimize) between 0 and 1
    x = nn.functional.softmax(x, dim=1)
    fn = multiclass_dice_coeff if multiclass else dice_coeff
    return 1 - fn(x, target, ignore_index=ignore_index)

train_and_eval.py(训练 + 评估)

import torch
from torch import nn
import train_utils.distributed_utils as utils
from .dice_coefficient_loss import dice_loss, build_target

# dice计算 + dice_loss计算
def criterion(inputs, target, loss_weight=None, num_classes: int = 2, dice: bool = True, ignore_index: int = -100):
    losses = {}
    for name, x in inputs.items():
        # 忽略target中值为255的像素,255的像素是目标边缘或者padding填充
        loss = nn.functional.cross_entropy(x, target, ignore_index=ignore_index, weight=loss_weight)
        if dice is True:
            dice_target = build_target(target, num_classes, ignore_index)
            # dice_loss
            loss += dice_loss(x, dice_target, multiclass=True, ignore_index=ignore_index)
        losses[name] = loss

    if len(losses) == 1:
        return losses['out']

    return losses['out'] + 0.5 * losses['aux']

# 评估
def evaluate(model, data_loader, device, num_classes):
    model.eval()
    confmat = utils.ConfusionMatrix(num_classes)
    dice = utils.DiceCoefficient(num_classes=num_classes, ignore_index=255)
    metric_logger = utils.MetricLogger(delimiter="  ")
    header = 'Test:'
    with torch.no_grad():
        for image, target in metric_logger.log_every(data_loader, 100, header):
            image, target = image.to(device), target.to(device)
            output = model(image)
            output = output['out']

            confmat.update(target.flatten(), output.argmax(1).flatten())
            # dice验证指标
            dice.update(output, target)

        confmat.reduce_from_all_processes()
        dice.reduce_from_all_processes()

    return confmat, dice.value.item()

# 训练一个轮回
def train_one_epoch(model, optimizer, data_loader, device, epoch, num_classes,
                    lr_scheduler, print_freq=10, scaler=None):
    model.train()
    metric_logger = utils.MetricLogger(delimiter="  ")
    metric_logger.add_meter('lr', utils.SmoothedValue(window_size=1, fmt='{value:.6f}'))
    header = 'Epoch: [{}]'.format(epoch)

    if num_classes == 2:
        # 设置cross_entropy中背景和前景的loss权重(根据自己的数据集进行设置)
        loss_weight = torch.as_tensor([1.0, 2.0], device=device)
    else:
        loss_weight = None

    for image, target in metric_logger.log_every(data_loader, print_freq, header):
        image, target = image.to(device), target.to(device)
        with torch.cuda.amp.autocast(enabled=scaler is not None):
            output = model(image)
            loss = criterion(output, target, loss_weight, num_classes=num_classes, ignore_index=255)

        optimizer.zero_grad()
        if scaler is not None:
            scaler.scale(loss).backward()
            scaler.step(optimizer)
            scaler.update()
        else:
            loss.backward()
            optimizer.step()

        lr_scheduler.step()

        lr = optimizer.param_groups[0]["lr"]
        metric_logger.update(loss=loss.item(), lr=lr)

    return metric_logger.meters["loss"].global_avg, lr


def create_lr_scheduler(optimizer,
                        num_step: int,
                        epochs: int,
                        warmup=True,
                        warmup_epochs=1,
                        warmup_factor=1e-3):
    assert num_step > 0 and epochs > 0
    if warmup is False:
        warmup_epochs = 0

    def f(x):
        """
        根据step数返回一个学习率倍率因子,
        注意在训练开始之前,pytorch会提前调用一次lr_scheduler.step()方法
        """
        if warmup is True and x <= (warmup_epochs * num_step):
            alpha = float(x) / (warmup_epochs * num_step)
            # warmup过程中lr倍率因子从warmup_factor -> 1
            return warmup_factor * (1 - alpha) + alpha
        else:
            # warmup后lr倍率因子从1 -> 0
            # 参考deeplab_v2: Learning rate policy
            return (1 - (x - warmup_epochs * num_step) / ((epochs - warmup_epochs) * num_step)) ** 0.9

    return torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=f)

4. 测试结果

n_{ij}:类别 i 被预测成类别 j 的像素个数(预测正确的部分)

n_{cls}:目标类别个数(包含背景)

t_{i}=\Sigma _{j}n_{ij}:目标类别 i 的总像素个数(真实标签)

使用DIRVE数据集进行训练和测试结果:

通过测试预测的分割结果图片:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/476826.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Leetcode -147.对链表进行插入排序 -237.删除链表中的节点】

Leetcode Leetcode -147.对链表进行插入排序Leetcode - 237.删除链表中的节点 Leetcode -147.对链表进行插入排序 题目: 给定单个链表的头 head &#xff0c;使用 插入排序 对链表进行排序&#xff0c;并返回 排序后链表的头 。 插入排序 算法的步骤 : 插入排序是迭代的&…

设计模式-三种工厂模式的优缺点和使用场景

本文参考&#xff1a; 常见设计模式解析,应用场景以及优点(一)单例,工厂,抽象工厂,构造者_口怪物口的博客-CSDN博客_简述常见的工厂模式以及单例模式的使用场景 轻松理解工厂模式&#xff01;就等面试官问了&#xff01;_牛客网 (nowcoder.com) 工厂模式 工厂模式&#xff08;…

4.29补题记录

昨天做了俩道补题&#xff0c;感觉很自己有点无脑了 一、镜像最短距离 思路 首先这道题我最开始想的是bfs记录步数来做的&#xff0c;当时测试的时候没写出来&#xff0c;后来&#xff0c;看补题的时候&#xff0c;才发现这个就是简单的数学问题&#xff0c;我们只要计算出初…

项目管理软件可以用来做什么?这篇文章说清楚了

项目管理软件是用来干嘛的&#xff0c;就得看对项目的理解。项目是为创造独特的产品、服务或成果而进行的临时性工作。建造一座大楼可以是一个项目&#xff0c;进行一次旅游活动、日常办公活动、期末考试复习等也都可以看成一个项目。 项目管理不善会导致项目超时、超支、返工、…

javascript-算法基础-01

时间复杂度 O(1) O(n) O(n) O(2ⁿ) 记得有次面试, 让我求1 … n, 我说用for循环. 当时竟然都忘了等差数列公式了… 一个简单的求和 let res 0 // 1 for(let i; i < arr.length; i){ // 1res arr[i] // n }let res 0 for(const …

肝一肝设计模式【四】-- 建造者模式

系列文章目录 肝一肝设计模式【一】-- 单例模式 传送门 肝一肝设计模式【二】-- 工厂模式 传送门 肝一肝设计模式【三】-- 原型模式 传送门 肝一肝设计模式【四】-- 建造者模式 传送门 文章目录 系列文章目录前言一、什么是建造者模式二、举个栗子三、静态内部类写法四、开源框…

Golang-常见数据结构Map

Map map 是一种特殊的数据结构&#xff1a;一种元素对&#xff08;pair&#xff09;的无序集合&#xff0c;pair 的一个元素是 key&#xff0c;对应的另一个元素是 value&#xff0c;所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构&#xff1a;给定 key&…

5 款 AI 老照片修复工具的横向比较

在大语言模型和各类 AI 应用日新月异的今天&#xff0c;我终于下定决心&#xff0c;趁着老照片们还没有完全发黄褪色、受潮粘连抑或损坏遗失&#xff0c;将上一代人实体相册里的纸质胶卷照片全部数字化&#xff0c;并进行一次彻底的 AI 修复&#xff0c;好让这些珍贵的记忆能更…

宽带IPTV单线复用

宽带IPTV单线复用 中国联通类&#xff1a; 1、前言 为了解决家里电视墙只预留了一个网口&#xff0c;IPTV无法与路由器共存的问题。 网络环境&#xff1a;中国联通 作者使用的路由器&#xff1a;云易家AX18C 2、光猫获取超管密码 黑龙江&#xff1a;hljcuadmin 重庆&…

HTML基础 + 实例解析

我们的目标: 认识HTML语言学会常用的HTML标签 目录 HTML的框架结构 HTML常见标签使用 1.注释标签 2. 标题标签 3. 段落标签 4. 换行标签 5. 格式化标签 6. 图片标签 7. 超链接标签 8. 表格标签 9. 表格标签-单元格合并 10. 列表标签 无序列表标签 有序标签 11. 表单标签 inp…

将 Quicker 搜索功能打造成专属于你的 Windows 启动器

在 macOS 平台上&#xff0c;有许多优秀的启动器&#xff0c;如老牌双雄 Alfred、Launchbar 和新秀 Raycast。反观 Windows 平台&#xff0c;则有 uTools、Listary、Wox 等应用&#xff0c;它们在基础功能上表现不错&#xff0c;但受限于应用生态&#xff0c;可拓展性都较弱。Q…

Java——Java面向对象

该系列博文会告诉你如何从入门到进阶&#xff0c;一步步地学习Java基础知识&#xff0c;并上手进行实战&#xff0c;接着了解每个Java知识点背后的实现原理&#xff0c;更完整地了解整个Java技术体系&#xff0c;形成自己的知识框架。 概述&#xff1a; Java是面向对象的程序…

【C++】类与对象(1)

【C】类与对象&#xff08;1&#xff09; 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2023.4.29 本篇博客是有关C类与对象的知识&#xff0c;学习了类与对象就相当于一只脚踏进了C的大门&#xff0c;本篇博客会深入讲解C中的类与对象&#xff08;重点为this指针&#xff…

C++知识点 -- 异常

C知识点 – 异常 文章目录 C知识点 -- 异常一、异常概念二、异常的使用1.异常的抛出和捕获2.异常的重新抛出3.异常安全4.异常规范 三、自定义异常体系四、C标准库的异常体系五、C异常的优缺点 一、异常概念 当一个函数发现自己无法处理错误时&#xff0c;就可以抛出异常&#…

Python超矩形

文章目录 距离函数矩形分割 Rectangle是 scipy.spatial中封装的类&#xff0c;其构造函数只需输入最小值和最大值的数组即可&#xff0c;并且可通过内置的 volume方法计算广义的体积。 from scipy.spatial import Rectanglerec Rectangle((0,0), (5,5)) print(rec.maxes) …

java-会话技术

1.1 会话管理概述 1.1.1 什么是会话 这里的会话&#xff0c;指的是web开发中的一次通话过程&#xff0c;当打开浏览器&#xff0c;访问网站地址后&#xff0c;会话开始&#xff0c;当关闭浏览器&#xff08;或者到了过期时间&#xff09;&#xff0c;会话结束。 举个例子&am…

py_rabbitmq

安装 服务端 https://www.jianshu.com/p/2fb6d5ac17b9 客户端 pip install pika文档 https://rabbitmq.com/tutorials/tutorial-one-python.html 简单示例 生产者 import pika import rabbitmq_study.settings as settingscredentials pika.PlainCredentials(settings…

Python每日一练(20230430)

目录 1. 移除元素 &#x1f31f; 2. 删除排序链表中的重复元素 &#x1f31f; 3. 搜索旋转排序数组 II &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1.…

总结836

学习目标&#xff1a; 4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 学习内容&#xff1a; 暴力英语&#xff1a;背诵《keep your direction》&#xff0c;默写&#xff0c;英语语法 高等数学&#xff1a;刷题&a…

node笔记_安装nvm管理node版本

文章目录 前言下载nvm安装nvmnvm路径node路径查看版本nvm -v查看nvm的node版本列表&#xff08;nvm list available&#xff09;配置nvm的镜像库mirror选择node版本安装 (node install version)使用指定的node版本&#xff08;nvm use&#xff09; node环境变量配置配置NODE_PA…