深度学习 图像分割 PSPNet 论文复现(训练 测试 可视化)

Table of Contents

  • 一、PSPNet 介绍
    • 1、原理阐述
    • 2、论文解释
    • 3、网络模型
  • 二、部署实现
    • 1、PASCAL VOC 2012
    • 2、模型训练
    • 3、度量指标
    • 4、结果分析
    • 5、图像测试

一、PSPNet 介绍

PSPNet(Pyramid Scene Parsing Network)来自于CVPR2017的一篇文章,中文翻译为金字塔场景解析网络,主要用于图像分割。此架构引入了金字塔池化(Pyramid Pooling)模块,以捕捉不同尺度下的上下文信息。Pyramid Pooling可以在不同尺度上提取全局和局部上下文信息,有助于更好地理解图像中的语义内容,从而提高分割性能。

1、原理阐述

PSPNet 框架图

  • (a)输入图像
  • (b)使用预训练的 ResNet 模型获取特征图
  • (c)利用Pyramid Pooling获得不同子区域的表示,通过上采样和concat形成包含局部和全局上下文信息的特征表示
  • (d)将特征送入卷积层,得到像素级预测结果

2、论文解释

金字塔池化模块融合了四种不同金字塔尺度下的特征。红色标注的是全局池化,以生成单个bin输出。下面的金字塔级别将特征图分为不同的子区域,并形成不同位置的池化表示。金字塔池化模块中不同层级的输出包含不同大小的特征图。为了保持全局特征的权重,在每个金字塔层级后使用 1×1 卷积层,将上下文表示的维度降低到原始维度的 1/N(如果金字塔层级大小为 N)。

然后对低维特征图进行上采样,通过双线性插值得到与原始特征图相同大小的特征。最后将不同级别的特征连接起来作为最终的金字塔池化输出的全局特征。

3、网络模型

class PSPNet(BaseModel):def __init__(self, num_classes, in_channels=3, backbone='resnet152', pretrained=True, use_aux=True, freeze_bn=False, freeze_backbone=False):super(PSPNet, self).__init__()norm_layer = nn.BatchNorm2d  # 用于规范化的层类型# 使用getattr根据backbone参数选择合适的骨干网络模型,并可能加载预训练权重model = getattr(resnet, backbone)(pretrained, norm_layer=norm_layer)m_out_sz = model.fc.in_features  # 提取骨干网络的输出特征通道数self.use_aux = use_aux  # 是否使用辅助分割分支# 初始卷积层,根据in_channels来调整输入通道数self.initial = nn.Sequential(*list(model.children())[:4])if in_channels != 3:self.initial[0] = nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False)self.initial = nn.Sequential(*self.initial)# 骨干网络的不同层self.layer1 = model.layer1self.layer2 = model.layer2self.layer3 = model.layer3self.layer4 = model.layer4# 主要分割分支,包括特征融合和分割输出self.master_branch = nn.Sequential(PSPModule(m_out_sz, bin_sizes=[1, 2, 3, 6], norm_layer=norm_layer),  # 特征融合模块nn.Conv2d(m_out_sz // 4, num_classes, kernel_size=1)  # 分割输出卷积层)# 辅助分割分支,可选,用于训练时帮助主分割任务self.auxiliary_branch = nn.Sequential(nn.Conv2d(m_out_sz // 2, m_out_sz // 4, kernel_size=3, padding=1, bias=False),norm_layer(m_out_sz // 4),nn.ReLU(inplace=True),nn.Dropout2d(0.1),nn.Conv2d(m_out_sz // 4, num_classes, kernel_size=1))# 初始化网络权重initialize_weights(self.master_branch, self.auxiliary_branch)def forward(self, x):input_size = (x.size()[2], x.size()[3])  # 记录输入图像的尺寸x = self.initial(x)  # 初始卷积层x = self.layer1(x)  # 第一层x = self.layer2(x)  # 第二层x_aux = self.layer3(x)  # 第三层,用于辅助分割分支x = self.layer4(x)  # 第四层output = self.master_branch(x)  # 主要分割分支output = F.interpolate(output, size=input_size, mode='bilinear')  # 插值操作,将分割输出大小调整为输入大小output = output[:, :, :input_size[0], :input_size[1]]  # 调整输出的尺寸以匹配输入# 如果在训练模式下且使用辅助分割分支,还生成辅助分割输出if self.training and self.use_aux:aux = self.auxiliary_branch(x_aux)aux = F.interpolate(aux, size=input_size, mode='bilinear')  # 调整辅助分割输出大小aux = aux[:, :, :input_size[0], :input_size[1]]  # 调整输出的尺寸以匹配输入return output, aux  # 返回主分割输出和辅助分割输出return output  # 只返回主分割输出
其中,PSPModule类的定义如下
class PSPModule(nn.Module):def __init__(self, in_channels, bin_sizes, norm_layer):super(_PSPModule, self).__init__()# 计算每个池化分支的输出通道数out_channels = in_channels // len(bin_sizes)# 创建池化分支,将它们存储在一个 ModuleList 中self.stages = nn.ModuleList([self._make_stages(in_channels, out_channels, b_s, norm_layer) for b_s in bin_sizes])# 创建特征融合模块(bottleneck)self.bottleneck = nn.Sequential(nn.Conv2d(in_channels + (out_channels * len(bin_sizes)), out_channels, kernel_size=3, padding=1, bias=False),  # 卷积层norm_layer(out_channels),  # 规范化层nn.ReLU(inplace=True),  # ReLU激活函数nn.Dropout2d(0.1)  # 2D Dropout层)def _make_stages(self, in_channels, out_channels, bin_sz, norm_layer):# 创建池化分支的内部结构prior = nn.AdaptiveAvgPool2d(output_size=bin_sz)  # 自适应平均池化层conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)  # 卷积层bn = norm_layer(out_channels)  # 规范化层relu = nn.ReLU(inplace=True)  # ReLU激活函数return nn.Sequential(prior, conv, bn, relu)  # 返回池化分支的Sequential模块def forward(self, features):h, w = features.size()[2], features.size()[3]  # 获取输入特征的高度和宽度pyramids = [features]  # 存储原始特征到金字塔中# 遍历每个池化分支,对特征进行插值操作并存储在金字塔中pyramids.extend([F.interpolate(stage(features), size=(h, w), mode='bilinear', align_corners=True) for stage in self.stages])# 将金字塔中的特征拼接在一起并通过特征融合模块output = self.bottleneck(torch.cat(pyramids, dim=1))return output  # 返回特征融合后的输出

此类用于执行金字塔池化和特征融合操作,并将它们融合成一个具有更丰富语义信息的特征表示。

PSPNet的核心思想是利用4 级金字塔结构,池化核可以覆盖图像的(whole)整体、(half)一半和(small portions)一小部分,即

 self.stages = nn.ModuleList([self._make_stages(in_channels, out_channels, b_s, norm_layer) for b_s in bin_sizes])

以上代码中,self.stages 包含多个池化分支,bin_sizes 为一个包含4个元素的列表,对应于4个不同的池化分支。通过遍历 bin_sizes,使用 make_stages 方法创建了4个池化分支。每个池化分支均由自适应平均池化层、卷积层、归一化层和ReLU激活层组成。由此形成金字塔结构的特征提取部分。

4个池化分支具有不同的感受野大小,以此来捕获不同尺度的图像信息。

换句话说,self.stages 中的每个元素都代表金字塔中的一个级别,体现了4级金字塔结构。forward 方法将遍历这些池化分支,并对输入特征执行插值操作,将它们调整为与原始特征相同的大小,以便进行特征融合。

二、部署实现

我的环境是

  • 操作系统:win11
  • 语言:python3.10
  • IDE:PyCharm 2023
  • GPU:RTX 4060

1、PASCAL VOC 2012

Dataset采用经典的PASCAL VOC 2012,一个用于计算机视觉研究的标准数据集,它提供了多种任务的图像数据和相关标注。其中包含20个不同的物体类别,如飞机、自行车、汽车、狗、猫、椅子等,以及一类"背景"。这些图像均是从真实世界中采集,涵盖了不同场景和角度,代表了常见的日常物体。大小方面,包含1,464张训练图像、1,449张验证图像和1,456张测试图像。其中每张图像都带有详细的标注信息,包括每个物体实例的边界框(目标检测任务)和像素级的语义分割标签(语义分割任务)。

2、模型训练

核心训练部分的代码如下:
def _train_epoch(self, epoch):self.logger.info('\n')  # 打印日志信息self.model.train()  # 设置模型为训练模式if self.config['arch']['args']['freeze_bn']:  if isinstance(self.model, torch.nn.DataParallel):self.model.module.freeze_bn() else:self.model.freeze_bn()  self.wrt_mode = 'train'  # 设置写入模式为'train'tic = time.time()  # 记录当前时间self._reset_metrics()  # 重置度量指标tbar = tqdm(self.train_loader, ncols=130)  # 创建一个进度条以迭代训练数据集for batch_idx, (data, target) in enumerate(tbar):  # 遍历训练数据self.data_time.update(time.time() - tic)  # 更新数据加载时间self.lr_scheduler.step(epoch=epoch - 1)  # 根据当前训练的epoch调整学习率# LOSS & OPTIMIZEself.optimizer.zero_grad()  # 清零梯度output = self.model(data)  # 前向传播,获取模型输出if self.config['arch']['type'][:3] == 'PSP':  assert output[0].size()[2:] == target.size()[1:]  # 检查输出和目标的空间尺寸匹配assert output[0].size()[1] == self.num_classes  # 检查输出通道数与类别数匹配loss = self.loss(output[0], target)  # 计算损失loss += self.loss(output[1], target) * 0.4  # 添加辅助损失,加权为0.4output = output[0]  # 将主要输出作为最终输出else:assert output.size()[2:] == target.size()[1:]  assert output.size()[1] == self.num_classes  loss = self.loss(output, target) if isinstance(self.loss, torch.nn.DataParallel):  loss = loss.mean()  # 计算损失的均值loss.backward()  # 反向传播,计算梯度self.optimizer.step()  # 更新模型参数self.total_loss.update(loss.item())  # 更新总损失# measure elapsed timeself.batch_time.update(time.time() - tic)  # 更新批次处理时间tic = time.time()# LOGGING & TENSORBOARDif batch_idx % self.log_step == 0:  # 每隔一定步数记录一次日志和TensorBoardself.wrt_step = (epoch - 1) * len(self.train_loader) + batch_idx  # 当前步数self.writer.add_scalar(f'{self.wrt_mode}/loss', loss.item(), self.wrt_step)  # 记录损失到TensorBoard# FOR EVALseg_metrics = eval_metrics(output, target, self.num_classes)  # 计算分割度量指标self._update_seg_metrics(*seg_metrics)  # 更新分割度量指标pixAcc, mIoU, _ = self._get_seg_metrics().values()  # 获取分割指标值# PRINT INFOtbar.set_description('TRAIN ({}) | Loss: {:.3f} | Acc {:.2f} mIoU {:.2f} | B {:.2f} D {:.2f} |'.format(epoch, self.total_loss.average, pixAcc, mIoU,self.batch_time.average, self.data_time.average))  # 打印训练信息# METRICS TO TENSORBOARDseg_metrics = self._get_seg_metrics()for k, v in list(seg_metrics.items())[:-1]:  # 遍历分割度量指标并记录self.writer.add_scalar(f'{self.wrt_mode}/{k}', v, self.wrt_step)for i, opt_group in enumerate(self.optimizer.param_groups):  # 记录学习率self.writer.add_scalar(f'{self.wrt_mode}/Learning_rate_{i}', opt_group['lr'], self.wrt_step)# RETURN LOSS & METRICSlog = {'loss': self.total_loss.average,  # 返回平均损失**seg_metrics}  # 返回分割度量指标return log  # 返回日志信息
交叉验证部分,我们进行以下的定义
def _valid_epoch(self, epoch):if self.val_loader is None:self.logger.warning('Not data loader was passed for the validation step, No validation is performed !')return {}  # 如果没有提供验证数据加载器,发出警告并返回一个空字典self.logger.info('\n###### EVALUATION ######')self.model.eval()  # 设置模型为评估(验证)模式self.wrt_mode = 'val'  # 设置写入模式为'val'(用于TensorBoard记录)self._reset_metrics()  # 重置度量指标tbar = tqdm(self.val_loader, ncols=130)  # 创建一个进度条用于遍历验证数据集with torch.no_grad():  # 禁用梯度计算val_visual = []  # 用于可视化的图像列表for batch_idx, (data, target) in enumerate(tbar):#data, target = data.to(self.device), target.to(self.device)  # 将数据和目标移到指定的设备上(通常是GPU)# LOSSoutput = self.model(data)  # 前向传播,获取模型的输出loss = self.loss(output, target)  # 计算损失if isinstance(self.loss, torch.nn.DataParallel):  # 如果损失函数是DataParallel损失函数loss = loss.mean()  # 计算损失的均值self.total_loss.update(loss.item())  # 更新总损失seg_metrics = eval_metrics(output, target, self.num_classes)  # 计算分割度量指标self._update_seg_metrics(*seg_metrics)  # 更新分割度量指标# LIST OF IMAGE TO VIZ (15 images)if len(val_visual) < 15:  # 用于可视化的图像数量限制在15张以内target_np = target.data.cpu().numpy()  # 将目标从GPU移到CPU并转换为NumPy数组output_np = output.data.max(1)[1].cpu().numpy()  # 将模型输出的类别概率最大的类别作为预测结果val_visual.append([data[0].data.cpu(), target_np[0], output_np[0]])  # 添加可视化所需的图像和标签# PRINT INFOpixAcc, mIoU, _ = self._get_seg_metrics().values()  # 获取分割度量指标的值tbar.set_description('EVAL ({}) | Loss: {:.3f}, PixelAcc: {:.2f}, Mean IoU: {:.2f} |'.format( epoch,self.total_loss.average,pixAcc, mIoU))  # 打印验证信息# WRITING & VISUALIZING THE MASKSval_img = []  # 用于可视化的图像列表palette = self.train_loader.dataset.palette  # 获取调色板信息for d, t, o in val_visual:  # 遍历可视化图像列表d = self.restore_transform(d)  # 还原图像的转换(例如,去均值、缩放等)t, o = colorize_mask(t, palette), colorize_mask(o, palette)  # 将标签和模型输出转换为彩色掩码d, t, o = d.convert('RGB'), t.convert('RGB'), o.convert('RGB')  # 将图像转换为RGB格式[d, t, o] = [self.viz_transform(x) for x in [d, t, o]]  # 应用可视化转换val_img.extend([d, t, o])  # 添加可视化图像到列表中val_img = torch.stack(val_img, 0)  # 将可视化图像堆叠成一个张量val_img = make_grid(val_img.cpu(), nrow=3, padding=5)  # 使用Grid方式排列可视化图像self.writer.add_image(f'{self.wrt_mode}/inputs_targets_predictions', val_img, self.wrt_step)  # 将可视化图像写入TensorBoard# METRICS TO TENSORBOARDself.wrt_step = (epoch) * len(self.val_loader)  # 计算当前步数self.writer.add_scalar(f'{self.wrt_mode}/loss', self.total_loss.average, self.wrt_step)  # 记录平均损失到TensorBoardseg_metrics = self._get_seg_metrics()  # 获取分割度量指标for k, v in list(seg_metrics.items())[:-1]:  # 遍历分割度量指标并记录到TensorBoardself.writer.add_scalar(f'{self.wrt_mode}/{k}', v, self.wrt_step)log = {'val_loss': self.total_loss.average,  # 返回平均验证损失**seg_metrics  # 返回分割度量指标}return log  # 返回日志信息# 以下是用于度量指标的辅助函数
def _reset_metrics(self):self.batch_time = AverageMeter()  # 用于记录批次处理时间的平均值self.data_time = AverageMeter()  # 用于记录数据加载时间的平均值self.total_loss = AverageMeter()  # 用于记录总损失的平均值self.total_inter, self.total_union = 0, 0  # 用于记录交集和并集的总和self.total_correct, self.total_label = 0, 0  # 用于记录正确分类和标签的总和def _update_seg_metrics(self, correct, labeled, inter, union):self.total_correct += correct  # 更新正确分类的数量self.total_label += labeled  # 更新标签的数量self.total_inter += inter  # 更新交集的总和self.total_union += union  # 更新并集的总和def _get_seg_metrics(self):pixAcc = 1.0 * self.total_correct / (np.spacing(1) + self.total_label)  # 计算像素准确率IoU = 1.0 * self.total_inter / (np.spacing(1) + self.total_union)  # 计算各类别的IoUmIoU = IoU.mean()  # 计算平均IoUreturn {"Pixel_Accuracy": np.round(pixAcc, 3),  # 返回像素准确率"Mean_IoU": np.round(mIoU, 3),  # 返回平均IoU"Class_IoU": dict(zip(range(self.num_classes), np.round(IoU, 3)))  # 返回各类别的IoU}

3、度量指标

这里我们使用两种指标来评估模型的性能:

Pixel_Accuracy(像素准确率)是用于评估图像分割任务性能的一种度量指标,用于衡量模型在整个图像上正确分类的像素数量占总像素数量的比例。可以简单地表示为以下数学公式:

在这里插入图片描述
其中:

  • “Number of Correctly Classified Pixels” 表示模型在分割图像中正确分类的像素数量。
  • “Total Number of Pixels” 表示整个分割图像的像素总数。

Pixel Accuracy的取值范围在0到1之间,其中1表示模型在整个图像上完全正确分类了所有像素,而0表示模型未正确分类任何像素。

Mean_IoU :IoU(Intersection over Union)是一个表示两个集合重叠程度的指标,通常用于分割任务中。在分割任务中,一个集合代表模型的预测分割区域,另一个集合代表真实的分割区域。IoU 的计算公式如下:

在这里插入图片描述

其中:

  • Area of Intersection" 是模型预测分割区域和真实分割区域的交集面积。
  • Area of Union" 是模型预测分割区域和真实分割区域的并集面积。

或者
在这里插入图片描述
其中:

  • TP(True Positives):表示模型正确预测为正类(目标类别)的像素数量
  • FP(False Positives):表示模型错误地将背景像素预测为正类的像素数量
  • FN(False Negatives):表示模型错误地将正类像素预测为背景的像素数量

Mean Intersection over Union (Mean IoU)是所有类别IoU的平均值

在这里插入图片描述
其中,N是类别的数量,IoU_i是第i个类别的IoU

4、结果分析

主要参数设置部分如下:

"epochs": 80,
"loss": "CrossEntropyLoss2d",
"batch_size": 8,
"base_size": 400,  //图像大小调整为base_size,然后随机裁剪
"crop_size": 380,  //重新缩放后随机裁剪的大小"optimizer": {"type": "SGD","differential_lr": true,"args":{"lr": 0.01,"weight_decay": 1e-4,"momentum": 0.9}},

由于GPU资源有限,这里只运行80个epoch,得到的日志信息如下:
在这里插入图片描述
Tensorboard记录的信息:
train
在这里插入图片描述
validation
在这里插入图片描述
交叉验证集的Input、Ground Truth和Output对比:
在这里插入图片描述
部分细节仍未完全分割,但已经可识别出图像主体。train_loss和val_loss相差不大,并未出现过拟合,增加训练周期可能会达到更好的效果。

5、图像测试

测试部分代码如下:

    args = parse_arguments()  # 解析命令行参数config = json.load(open(args.config))  # 从JSON文件中加载配置信息# 根据配置信息创建数据加载器loader = getattr(dataloaders, config['train_loader']['type'])(**config['train_loader']['args'])to_tensor = transforms.ToTensor()  # 创建图像到张量的转换normalize = transforms.Normalize(loader.MEAN, loader.STD)  # 创建归一化转换num_classes = loader.dataset.num_classes  # 获取数据集中的类别数量palette = loader.dataset.palette  # 获取颜色映射表# 创建模型model = getattr(models, config['arch']['type'])(num_classes, **config['arch']['args'])  # 根据配置创建模型availble_gpus = list(range(torch.cuda.device_count()))  # 获取可用的GPU列表device = torch.device('cuda:0' if len(availble_gpus) > 0 else 'cpu')  # 选择运行设备(GPU或CPU)# 加载模型检查点checkpoint = torch.load(args.model, map_location=device)if isinstance(checkpoint, dict) and 'state_dict' in checkpoint.keys():checkpoint = checkpoint['state_dict']# 如果在训练期间使用了数据并行,需要处理模型if 'module' in list(checkpoint.keys())[0] and not isinstance(model, torch.nn.DataParallel):# 对于GPU推理,使用数据并行if "cuda" in device.type:model = torch.nn.DataParallel(model)else:# 对于CPU推理,移除模型的"module"前缀new_state_dict = OrderedDict()for k, v in checkpoint.items():name = k[7:]new_state_dict[name] = vcheckpoint = new_state_dict# 加载模型权重model.load_state_dict(checkpoint)model.to(device)  # 将模型移动到所选设备model.eval()  # 设置模型为评估模式# 创建输出目录if not os.path.exists('outputs'):os.makedirs('outputs')# 获取图像文件列表image_files = sorted(glob(os.path.join(args.images, f'*.{args.extension}')))with torch.no_grad():tbar = tqdm(image_files, ncols=100)  # 创建进度条for img_file in tbar:image = Image.open(img_file).convert('RGB')  # 打开图像并将其转换为RGB格式input = normalize(to_tensor(image)).unsqueeze(0)  # 转换图像并添加批次维度#预测图像分割结果prediction = multi_scale_predict(model, input, scales, num_classes, device)prediction = F.softmax(torch.from_numpy(prediction), dim=0).argmax(0).cpu().numpy()  # 计算最终的预测结果save_images(image, prediction, args.output, img_file, palette)  # 保存预测结果的图像

其中对多尺度图像预测函数定义为:

def multi_scale_predict(model, image, scales, num_classes, device, flip=False):# 获取输入图像的尺寸input_size = (image.size(2), image.size(3))# 创建上采样层,用于将不同尺度的预测结果恢复到原始尺寸upsample = nn.Upsample(size=input_size, mode='bilinear', align_corners=True)# 初始化用于累计预测结果的数组total_predictions = np.zeros((num_classes, image.size(2), image.size(3)))# 将输入图像转换为NumPy数组,并移动到CPU上image = image.data.data.cpu().numpy()# 遍历不同的尺度for scale in scales:# 缩放图像scaled_img = ndimage.zoom(image, (1.0, 1.0, float(scale), float(scale)), order=1, prefilter=False)# 将缩放后的图像转换为PyTorch张量并移动到指定设备scaled_img = torch.from_numpy(scaled_img).to(device)# 使用模型进行预测并上采样到原始尺寸scaled_prediction = upsample(model(scaled_img).cpu())# 如果启用了翻转,对翻转后的图像进行预测并平均if flip:fliped_img = scaled_img.flip(-1).to(device)fliped_predictions = upsample(model(fliped_img).cpu())scaled_prediction = 0.5 * (fliped_predictions.flip(-1) + scaled_prediction)# 将当前尺度的预测结果累加到总体预测中total_predictions += scaled_prediction.data.cpu().numpy().squeeze(0)# 计算平均预测结果total_predictions /= len(scales)return total_predictions

我们任意指定一张输入图像,测试模型的分割效果
在这里插入图片描述
效果欠佳,如果大家有资源可以增加epoch数量训练,尝试不同的数据集
——————————————————————————————————————————————

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot+Uniapp+redis智能导诊系统源码,支持以公众号、小程序、App 等形式接入

AI医疗的智能导诊系统源码 智慧导诊系统全套源码 什么是智能导诊系统&#xff1f; 智能导诊系统是一种基于人工智能和大数据技术开发的医疗辅助软件&#xff0c;它能够通过对患者的症状、病史等信息进行计算分析&#xff0c;快速推荐科室和医生。通过简单的描述自身症状&#…

go语法入门2

字符串 使用双引号或反引号引起来的任意个字符。它是字面常量。 func main() {var a "abc\n测试" // \n换行fmt.Println(a) } abc 测试func main() {var a "abc\n\t测试" \\换行后在tabfmt.Println(a) } abc测试func main() {var a abc测试 …

火山引擎 ByteHouse 与白鲸开源完成兼容性认证,加速数据价值释放

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 数据作为新型生产要素&#xff0c;已快速融入生产、分配、流通、消费和社会服务管理等各环节&#xff0c;深刻改变着生产方式、生活方式和治理方式。越来越多企业也…

番外12:连续类功率放大器理论-连续类实现带宽拓展的底层原理

连续类功放通解&#xff1a;连续类功率放大器理论-连续类实现带宽拓展的底层原理-基础 本次内容理论性较强&#xff0c;适合对功率放大器理论研究比较感兴趣以及想发论文的小朋友&#xff0c;着重探讨现有的一些带宽拓展模式&#xff08;也就是连续类&#xff09;的基本实现原…

基于SpringBoot的流浪动物管理系

基于SpringBoot的流浪动物管理系的设计与实现&#xff0c;前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 首页 后台登陆界面 管理员界面 摘要 基于Spring Boot的…

Nginx限流熔断

一、Nginx限流熔断 Nginx 是一款流行的反向代理和负载均衡服务器&#xff0c;也可以用于实现服务熔断和限流。通过使用 Nginx 的限流和熔断模块&#xff0c;比如&#xff1a;ngx_http_limit_req_module 和 ngx_http_limit_conn_module&#xff0c;可以在代理层面对服务进行限流…

网络-跨域解决

文章目录 前言一、跨域是什么&#xff1f;二、跨域的解决1.JSONP2.前端代理dev环境3.后端设置请求头CORS4.运维nginx代理 总结 前言 本文主要介绍跨域问题介绍并提供了四种解决办法。 一、跨域是什么&#xff1f; 准确的来说是浏览器存在跨域问题&#xff0c;浏览器为了安全考…

【匠心打造】从0打造uniapp 可视化拖拽设计 c_o 第十篇

一、click one for uniapp置顶&#xff1a; 全部免费开源 (你商业用途也没关系&#xff0c;不过可以告诉我公司名或者项目名&#xff0c;放在官网上好看点。哈哈-_-) 二、写在之前 距离上一篇更新已经大约4个月了&#xff0c;公司的事情&#xff0c;自己的一些琐事一直没时间…

计算机中丢失vcomp140.dll解决方案,可以使用这几个最新方法来修复

今天早上&#xff0c;当我打开电脑时&#xff0c;突然看到一个提示窗口&#xff0c;显示找不到 vcomp140.dll 文件。我一下子懵了&#xff0c;不知道这是怎么回事&#xff0c;也不知道如何解决这个问题。于是&#xff0c;我开始了寻找答案的旅程。 首先&#xff0c;我了解到 v…

【MySQL】表的约束(一)

文章目录 为什么要有约束一. 空属性二. 默认值三. 列描述四. zerofill结束语 为什么要有约束 数据库是用来存放数据的&#xff0c;所以其需要保证数据的完整性和可靠性 数据类型也算是一种约束&#xff0c;比如&#xff0c;整型的数据无法插入字符型。 通过约束&#xff0c;让…

高精度算法模板

1.加法 string a1, b1; int a[5010], b[5010], c[5010]; signed main() {cin >> a1 >> b1;int len1 a1.size();int len2 b1.size();for (int i 1; i < len1; i) {a[i] a1[len1 - i] - 0;}for (int i 1; i < len2; i) {b[i] b1[len2 - i] - 0;}for (in…

java - 七大比较排序 - 详解

前言 本篇介绍了七大比较排序&#xff0c;直接插入排序&#xff0c;希尔排序&#xff0c;冒泡排序&#xff0c;堆排序&#xff0c;选择排序&#xff0c;快速排序&#xff0c;归并排序&#xff0c;一些简单思想代码实现&#xff0c;如有错误&#xff0c;请在评论区指正&#xf…

深入探讨 Presto 中的缓存

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 Presto是一种流行的开源分布式SQL引擎&#xff0c;使组织能够在多个数据源上大规模运行交互式分析查询。缓存是一种典型的提高 Presto 查询性能的优化技术。它为 Prest…

kr 第三阶段(三)调试器

调试框架 特点&#xff1a;事件驱动&#xff0c;事件响应。 Win32 程序是消息驱动响应的基址&#xff0c;而在调试器则是事件驱动响应&#xff0c;有事件则处理&#xff0c;无事件则去做别的事。 事件&#xff1a;整个调试框架是建立在异常的基础之上的基本单位。响应&#xf…

基于ssm的互联网废品回收/基于web的废品资源利用系统

摘 要 本毕业设计的内容是设计并且实现一个基于SSM框架的互联网废品回收。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;Tomcat网络信息服务作为应用服务器。互联网废品回收的功能已基本实现&#xff0c;主要包括用户、回收员、物品分类、回收物品、用户下单…

【技能树笔记】网络篇——练习题解析(四)

目录 前言 一、传输层的作用 1.1 传输层的作用 1.2 传输层的PDU 二、端口号的分类、作用 2.1 传输层的PDU 三、TCP协议的特点及应用 3.1 传输层的PDU 3.2 TCP协议连接的建立 四、UDP协议的特点及应用 4.1 UDP协议的特点 总结 前言 本篇文章给出了CSDN网络技能树中…

操作系统内存管理相关

1. 虚拟内存 1.1 什么是虚拟内存 虚拟内存是计算机系统内存管理的一种技术&#xff0c;我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。虚拟内存的重要意义是它定义了一个连续的虚拟地址空间&#xff0c;并且 把内存扩展到硬…

2023年中国体育赛事行业现状及趋势分析:体育与科技逐步融合,推动产业高质量发展[图]

体育赛事运营是指组织体育赛事或获取赛事版权&#xff0c;并进行赛事推广营销、运营管理等一系列商业运作的运营活动。体育赛事运营相关业务主要包括赛事运营与营销、赛事版权运营两个部分。 体育赛事运营行业分类 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#x…

Java 实现遍历一个文件夹,文件夹有100万数据,获取到修改时间在2天之内的数据

目录 1 需求2 实现1&#xff08;第一种方法&#xff09;2 实现2 &#xff08;推荐使用这个&#xff0c;快&#xff09;3 实现3&#xff08;推荐&#xff09; 1 需求 现在有一个文件夹&#xff0c;里面会一直存数据&#xff0c;动态的存数据&#xff0c;之后可能会达到100万&am…

JVM-满老师

JVM 前言程序计数器&#xff0c;栈&#xff0c;虚拟机栈&#xff1a;本地方法栈&#xff1a;堆&#xff0c;方法区&#xff1a;堆内存溢出方法区运行时常量池 前言 JVM 可以理解的代码就叫做字节码&#xff08;即扩展名为 .class 的文件&#xff09;&#xff0c;它不面向任何特…