PyTorch深度学习实战(24)——从零开始实现Mask R-CNN实例分割

news2024/11/26 5:29:31

PyTorch深度学习实战(24)——从零开始实现Mask R-CNN实例分割

    • 0. 前言
    • 1. Mask R-CNN
      • 1.1 网络架构
      • 1.2 RoI Align
      • 1.3 Mask 检测头
    • 2. 使用 Mask R-CNN 实现实例分割
      • 2.1 数据集分析
      • 2.2 模型构建策略
      • 2.3 模型构建与训练
    • 3. 多类别实例分割
    • 小结
    • 系列链接

0. 前言

Mask R-CNN (Mask Region Convolutional Neural Network) 是基于深度学习的图像分割算法,它是在 Faster R-CNN 目标检测框架的基础上进行扩展和改进的。与传统目标检测方法相比,Mask R-CNN 不仅可以准确地检测图像中的对象,还可以为每个对象生成精确的像素级别的分割掩码。这意味着 Mask R-CNN 能够同时提供对象的边界框和具体的像素级别分割结果,从而更细粒度地理解图像中的结构和语义信息。在本节,将介绍 Mask R-CNN 架构的工作原理,并使用 PyTorch 实现 Mask R-CNN 进行实例分割。

1. Mask R-CNN

1.1 网络架构

Mask R-CNN 是一种用于目标检测和实例分割的深度学习算法,它扩展了 Faster R-CNN 算法,并增加了一个用于预测对象掩码 (mask) 的分支。
Mask R-CNN 架构可以用于在图像中识别/显示给定类别的对象实例,能够分割图像中类别相同的多个对象,Mask 表示由 Mask R-CNN 在像素级别完成的分割。掩码用于标注图像中的不同区域,使用图像分割模型可以将图像分成不同的区域,然后为每个区域分配一个掩码值。
Mask R-CNN 架构是对 Faster R-CNN 网络的扩展:

  • Mask R-CNN 架构修改了 Faster R-CNNRoI Pooling 层,使用更加准确的 RoI Align
  • 除了在最终层中预测对象的类别和边界框偏移量外,还增加了一个 mask head,用于预测对象的掩码
  • 使用全卷积网络 (Fully Convolutional Network, FCN) 实现掩码预测。

Mask R-CNN 整体架构如下:

网络架构

除了用于获取类别和边界框信息的预测头外,在 Mask R-CNN 中还添加了 Mask 预测头 (Mask head) 获取掩码信息:

网络架构

接下来,我们介绍 Mask R-CNN 架构的基本组件。

1.2 RoI Align

在 Faster R-CNN 中,我们了解了 RoI Pooling 的缺点之一是在执行 RoI Pooling 操作时可能会丢失某些信息。例如,在下图 RoI Pooling 示例中:

ROI pooling

在上图中,区域提议形状为 5 x 7,需要将其转换为 2 x 2 的形状,将其转换为 2 x 2 形状时(这一过程也称为量化),由于仅能保留最高值,因此会导致信息丢失,为了解决这一问题,提出了 RoI Align
接下来,我们通过一个简单示例讲解 RoI Align 的工作原理,尝试将以下区域(以虚线表示)转换为 2 x 2 的形状:

RoI Align

该区域并非均匀分布在特征图中的所有单元格中。为了能够使用 2x2 的形状合理表示该区域,我们需要执行以下步骤。

(1) 首先,将该区域划分为形状相等的 2 x 2 网格:

RoI Align

(2) 在每个单元格中定义四个等距的点:

RoI Align

在上图中,两个连续点之间的距离为 0.75

(3) 根据每个点到最近已知值的距离计算其加权平均值:

RoI Align

(4) 对单元格中的所有点重复以上加权均值计算过程:

RoI Align

(5) 对单元格中的点执行平均池化,并根据相同步骤计算所有单元格值:

RoI Align

可以看到,通过以上步骤 RoI Align 能够确保不会丢失信息。

1.3 Mask 检测头

使用 RoI Align,我们可以更准确地表示从区域提议网络 (Region Proposal Network, RPN) 获得的区域提议。然后,对于每个区域提议,根据 RoI Align 输出获取分割(掩码)输出。
在目标检测中,需要将 RoI Align 结果输入到展平层,用于预测目标物体的类别和边界框偏移量。在图像分割中,还需要预测包含目标对象的边界框内的像素。因此,除了类别和边界框偏移量外,还需要预测感兴趣区域内的掩码。
将预测的掩码叠加在原始图像上,将 RoI Align 的输出连接到卷积层上,以获得类似图像的结构,如下图所示:

Mask head
在上图中,使用特征金字塔网络 (feature pyramid network, FPN) 得到形状为 7 x 7 x 2048 的输出,该网络包含 2 个分支:

  • 第一个分支在展平 FPN 输出后返回目标对象的类别和边界框
  • 第二个分支在 FPN 的输出后执行卷积运算以获得掩码

形状为 14 x 14 输出对应的真实标签是区域提议对应的调整大小后图像。如果数据集中有 80 个不同的类别,则区域提议的真实标签形状为 80 x 14 x 14,每个像素值都是 10,表示该像素是否包含对象。因此,在预测像素类别的同时使用二元交叉熵损失函数。
模型训练完成后,可以用于检测区域,获取类别和边界框偏移量,并获取每个区域对应的掩码。在模型推理阶段时,首先检测图像中存在的目标对象并进行边界框校正,然后,将校正后的区域传递给 Mask 检测头,以预测该区域中不同像素对应的掩码。
了解了 Mask R-CNN 架构的工作原理后,我们将使用 PyTorch 实现 Mask R-CNN,以检测图像中的人物实例。

2. 使用 Mask R-CNN 实现实例分割

2.1 数据集分析

为了训练 Mask R-CNN 进行实例分割,我们将使用掩码标注的人物数据集,该数据集是根据 DE20K 数据集的子集创建的,可以在 ADE20K 数据集官方网站中下载该数据集,DE20K 数据集是一个大规模的图像和语义分割数据集,该数据集包含超过 `20,000 张高分辨率图像,覆盖了各种多样的场景和对象类别。本节中,我们只使用包含人物掩码的图像。

2.2 模型构建策略

为了训练 Mask R-CNN 图像分割模型,我们采用以下策略:

  1. 获取数据集,并据此创建数据集和数据加载器
  2. 创建符合 PyTorch 官方的 Mask R-CNN 实现所需格式的目标输出
  3. 下载预训练的 Faster R-CNN 模型,并为其附加一个 Mask R-CNN 预测头
  4. 训练模型 Mask R-CNN 模型
  5. 首先执行非极大值抑制,然后识别与图像中的人物对应的边界框和掩码

2.3 模型构建与训练

(1) 下载相关的数据集和模型训练工具。

为了训练模型,首先下载图像及掩码标注数据集,下载完成后解压文件。同时,为了快速训练模型,在 PyTorch GitHub 存储库中下载 engine.py、utils.py、transforms.py、coco_eval.py 和 coco_utils.py 文件。最后,使用 pip 安装 cocoapi

$ pip install -q -U 'git+https://github.com/cocodataset/cocoapi.git#subdirectory=PythonAPI'

(2) 导入所有必要的库并定义设备:

import torchvision
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
from torchvision.models.detection.mask_rcnn import MaskRCNNPredictor
from glob import glob

from engine import train_one_epoch, evaluate
import utils
import transforms as T
import torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'
import cv2
import numpy as np
from matplotlib import pyplot as plt
import random
from PIL import Image
from torch.utils.data import DataLoader, Dataset

(3) 获取包含人物掩码的图片。

遍历 imagesannotations_instance 文件夹以获取文件名:

all_images = glob('images/training/*.jpg')
all_annots = glob('annotations_instance/training/*.png')

检查原始图像和人物实例的掩码表示:

f = 'ADE_train_00014184'

def find(item, original_list):
    results = []
    for o_i in original_list:
        if item in o_i:
            results.append(o_i)
    if len(results) == 1:
        print(results)
        return results[0]
    else:
        return results

im = cv2.imread(find(f, all_images), 1)
an = cv2.imread(find(f, all_annots), 1).transpose(2,0,1)
r,g,b = an
nzs = np.nonzero(b==4) # 4 stands for person
instances = np.unique(g[nzs])
masks = np.zeros((len(instances), *r.shape))
for ix,_id in enumerate(instances):
    masks[ix] = g ==_id

length = len(masks)+1
plt.subplot(1, length, 1)
plt.imshow(cv2.cvtColor(im, cv2.COLOR_BGR2RGB))
ix = 2
for m in masks:
    plt.subplot(1, length, ix)
    plt.imshow(m, cmap='gray')
    ix += 1
plt.show()

样本可视化
在该数据集中,对象实例以如下方式进行标注,RGB 中的红色通道对应于对象的类别,而绿色通道对应于实例编号(如果图像中有多个相同类别的对象)图像。此外,在数据集中,Person 类别的编码值为 4

循环遍历标注文件并存储至少包含一个人物的文件:

annots = []
for ann in all_annots:
    _ann = cv2.imread(ann, 1).transpose(2,0,1)
    r,g,b = _ann
    if 4 not in np.unique(b):
        continue
    annots.append(ann)

读取图像的 R 通道获取掩码,然后遍历掩码并检查掩码中是否至少有一个像素值为 4 (人物类别),如果存在值为 4 的像素,就将该图像的文件名添加到包含人物的文件列表中。

将数据文件拆分为训练和验证数据:

def stems(split):
    items_new = [item.split('/')[-1] for item in split]
    items = [item.split('.')[0] for item in items_new]
    return items

from sklearn.model_selection import train_test_split
_annots = stems(annots)

trn_items, val_items = train_test_split(_annots, random_state=2)

(4) 定义图像变换方法:

def get_transform(train):
    image_transforms = []
    image_transforms.append(T.PILToTensor())
    return T.Compose(image_transforms)

(5) 创建数据集类 MasksDataset

定义 __init__ 方法,将图像名称 (items)、图像变换方法 (transforms) 和所用文件数 (N) 作为输入:

class MasksDataset(Dataset):
    def __init__(self, items, transforms, N):
        self.items = items
        self.transforms = transforms
        self.N = N

定义 get_mask 方法,获取与图像中存在的实例数量相同的多个掩码:

    def get_mask(self, path):
        an = cv2.imread(path, 1).transpose(2,0,1)
        r,g,b = an
        nzs = np.nonzero(b==4)
        instances = np.unique(g[nzs])
        masks = np.zeros((len(instances), *r.shape))
        for ix,_id in enumerate(instances):
            masks[ix] = g == _id
        return masks

定义 __getitem__ 方法,获取所需返回的图像和相应的目标值。每个人物(实例)都被视为不同的对象类别,也就是说,每个实例都是一个不同的类别。与 Faster R-CNN 模型类似,目标值以张量字典的形式返回:

    def __getitem__(self, ix):
        _id = self.items[ix]
        img_path = f'images/training/{_id}.jpg'
        mask_path = f'annotations_instance/training/{_id}.png'
        masks = self.get_mask(mask_path)
        obj_ids = np.arange(1, len(masks)+1)
        img = Image.open(img_path).convert("RGB")
        num_objs = len(obj_ids)

除了掩码本身,Mask R-CNN 还需要边界框信息:

        boxes = []
        for i in range(num_objs):
            obj_pixels = np.where(masks[i])
            xmin = np.min(obj_pixels[1])
            xmax = np.max(obj_pixels[1])
            ymin = np.min(obj_pixels[0])
            ymax = np.max(obj_pixels[0])
            if (((xmax-xmin)<=10) | (ymax-ymin)<=10):
                xmax = xmin+10
                ymax = ymin+10
            boxes.append([xmin, ymin, xmax, ymax])

在以上代码中,通过将边界框的 xy 坐标的最小值加上 10 个像素来调整存在可疑标注信息的情况(即 Person 类别的高度或宽度小于 10 像素)。

将目标值转换为张量对象:

        boxes = torch.as_tensor(boxes, dtype=torch.float32)
        labels = torch.ones((num_objs,), dtype=torch.int64)
        masks = torch.as_tensor(masks, dtype=torch.uint8)
        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
        iscrowd = torch.zeros((num_objs,), dtype=torch.int64)
        image_id = torch.tensor([ix])

将目标值存储在字典中:

        target = {}
        target["boxes"] = boxes
        target["labels"] = labels
        target["masks"] = masks
        target["image_id"] = image_id
        target["area"] = area
        target["iscrowd"] = iscrowd

指定变换方法并返回图像和目标值:

        if self.transforms is not None:
            img, target = self.transforms(img, target)
        img = img/255.
        return img, target

定义 __len__ 方法:

    def __len__(self):
        return self.N

定义选择随机图像的函数:

    def choose(self):
        return self[random.randint(len(self))]

检查输入输出组合:

x = MasksDataset(trn_items, get_transform(train=True), N=100)
im, targ = x[3]

length = len(targ['masks'])+1
plt.subplot(1, length, 1)
print(type(im))
plt.imshow(im.permute(1,2,0).detach().cpu())
ix = 2
for m in targ['masks']:
    plt.subplot(1, length, ix)
    plt.imshow(m, cmap='gray')
    ix += 1
plt.show()

样本示例
在以上输出中,可以看到掩码的形状为 4 x 512 x 683,表示图像中有 4 个人物。
__getitem__ 方法中,对于图像中存在的每个对象(实例),都有相应的掩码和边界框。此外,由于我们只有两个类别(背景类别和人类类别),因此我们将人类类别指定为 1
总体而言,在目标输出字典中包括对象类别、边界框、掩码、掩码的面积以及蒙版是否对应于人物,这些信息都可以在目标输出词典中找到。为了使用损失函数,需要将数据标准化为 torchvision.models.detection.maskrcnn_resnet50_fpn 类所要求的格式。

(5) 定义实例分割模型 (get_model_instance_segmentation),使用预训练模型,仅重新初始化检测头来预测对象类别(背景类别和人物类别)。

首先,初始化预训练模型并替换 box_predictor 和 mask_predictor 检测头,以便学习最佳权重:

def get_model_instance_segmentation(num_classes):
    # 加载在COCO上预先训练的实例分割模型 
    model = torchvision.models.detection.maskrcnn_resnet50_fpn(pretrained=True)

    in_features = model.roi_heads.box_predictor.cls_score.in_features
    model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)

    # 获取掩码分类器的输入特征数
    in_features_mask = model.roi_heads.mask_predictor.conv5_mask.in_channels
    hidden_layer = 256
    # 掩码预测器
    model.roi_heads.mask_predictor = MaskRCNNPredictor(in_features_mask,
                                                       hidden_layer,num_classes)
    return model

FastRCNNPredictor 需要两个输入——in_features (输入通道数)和 num_classes (类别数)。根据要预测的类别数,计算边界框预测数
MaskRCNNPredictor 需要三个输入——in_features_mask (输入通道数)、hidden_layer (输出通道数)和 num_classes (要预测的类别数)

获取模型的详细信息:

model = get_model_instance_segmentation(2).to(device)
print(model)

Faster R-CNN 网络和 Mask R-CNN 模型之间的主要区别在于 roi_heads 模块,该模块包含多个子模块:

  • box_head:对齐从 FPN 网络获取的输入并创建两个张量
  • box_predictor:使用 box_head 的输出预测每个 RoI 的类别和边界框偏移量
  • mask_roi_pool:对齐来自 FPN 网络的输出
  • mask_head:将 mask_roi_pool 的输出转换为可用于预测掩码的特征图
  • mask_predictor:从 mask_head 获取输出并预测最终的掩码

(6) 获取与训练和验证图像对应的数据集和数据加载器:

dataset = MasksDataset(trn_items, get_transform(train=True), N=len(trn_items))
dataset_test = MasksDataset(val_items, get_transform(train=False), N=len(val_items))

# 定义训练、测试数据管道
data_loader = torch.utils.data.DataLoader(
    dataset, batch_size=2, shuffle=True, num_workers=0,
    collate_fn=utils.collate_fn)

data_loader_test = torch.utils.data.DataLoader(
    dataset_test, batch_size=1, shuffle=False, num_workers=0,
    collate_fn=utils.collate_fn)

(7) 定义模型、超参数和优化器:

num_classes = 2
model = get_model_instance_segmentation(num_classes).to(device)
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005,
                            momentum=0.9, weight_decay=0.0005)
lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer,
                                                step_size=3,
                                                gamma=0.1)

模型将图像和目标字典作为输入,可以通过以下命令查看模型输出示例:

model.eval()
pred = model(dataset[0][0][None].to(device))

输出字典包含边界框 (BOXES)、与边界框相对应的类别 (LABELS)、与类别预测相对应的置信度分数 (SCORES) 以及掩码实例的位置 (MASKS)。该模型返回 100 个预测结果,因为通常图像中的对象数不超过 100 个。

获取检测到的实例数量:

print(pred[0]['masks'].shape)

一张图像通过以上模型最多可以获取 100 个掩码实例(非背景类别)。对于这 100 个实例,返回相应的边界框、类别标签和相应的置信度值。

(8) 训练模型:

trn_history = []
for epoch in range(num_epochs):
    # 模型训练
    res = train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=10)
    trn_history.append(res)
    # 调整学习率
    lr_scheduler.step()

使用训练后的模型,就可以预测图像中的人物实例掩码。记录训练损失随时间的变化情况:

import matplotlib.pyplot as plt
plt.title('Training Loss') 
losses = [np.mean(list(trn_history[i].meters['loss'].deque)) for i in range(len(trn_history))]
plt.plot(losses)
plt.show()

模型监测
(9) 使用训练后的模型预测测试图像:

model.eval()
k=1
im = dataset_test[k][0]
plt.imshow(im.permute(1,2,0))
plt.show()
with torch.no_grad():
    prediction = model([im.to(device)])
    for i in range(len(prediction[0]['masks'])):
        plt.imshow(Image.fromarray(prediction[0]['masks'][i, 0].mul(255).byte().cpu().numpy()), cmap='gray')
        plt.title('Class: '+str(prediction[0]['labels'][i].cpu().numpy())+' Score:'+str(prediction[0]['scores'][i].cpu().numpy()))
        plt.show()

模型预测结果
从上图中可以看出,模型成功识别出图中的人物实例。此外,模型还预测图像中的多个置信度较低的其他分割实例。

3. 多类别实例分割

在上一小节中,我们学习了如何分割 Person 类别的多个实例。在本节中,我们将调整上一节中构建的模型,一次性分割图像中多个对象类别的多个实例。

(1) 获取包含感兴趣类别的图像,类别 ID 4ID 5ID 6ID 6

classes_list = [4,5,6,7]
annots = []
for ann in all_annots[:3000]:
    _ann = cv2.imread(ann, 1).transpose(2,0,1)
    r,g,b = _ann
    if np.array([num in np.unique(b) for num in classes_list]).sum()==0:
        continue
    annots.append(ann)

def stems(split):
    items_new = [item.split('/')[-1] for item in split]
    items = [item.split('.')[0] for item in items_new]
    return items

from sklearn.model_selection import train_test_split
_annots = stems(annots)

trn_items, val_items = train_test_split(_annots, random_state=2)

在以上代码中,获取至少包含一个感兴趣的类别 (classes_list) 的图像。

(2) 修改 MasksDataset 类中的 get_mask 方法,使其返回两个掩码以及与每个掩码对应的类别:

    def get_mask(self,path):
        an = cv2.imread(path, 1).transpose(2,0,1)
        r,g,b = an
        cls = list(set(np.unique(b)).intersection({4,5,6,7}))
        masks = []
        labels = []
        for _cls in cls:
          nzs = np.nonzero(b==_cls)
          instances = np.unique(g[nzs])
          for ix, _id in enumerate(instances):
              masks.append(g==_id)
              labels.append(classes_list.index(_cls)+1)
        return np.array(masks), np.array(labels)

在以上代码中,获取图像中感兴趣的类别,并存储在 cls 中,然后,循环遍历每个已识别的类别 (cls),并将红色通道值对应于类别 (cls) 的位置存储在 nzs 中。接下来,获取这些位置上的实例 ID (instances)。此外,在返回掩码 masks 和标签 lables 数组之前,将实例 instances 追加到掩码数组中,并将实例对应的类别追加到标签数组中。

(3) 修改 __getitem__ 方法中的标签 (labels) 对象,使其包含从 get_mask 方法获得的标签:

    def __getitem__(self, ix):
        _id = self.items[ix]
        img_path = f'images/training/{_id}.jpg'
        mask_path = f'annotations_instance/training/{_id}.png'
        masks, labels = self.get_mask(mask_path)
        obj_ids = np.arange(1, len(masks)+1)
        img = Image.open(img_path).convert("RGB")
        num_objs = len(obj_ids)
        boxes = []
        for i in range(num_objs):
            obj_pixels = np.where(masks[i])
            xmin = np.min(obj_pixels[1])
            xmax = np.max(obj_pixels[1])
            ymin = np.min(obj_pixels[0])
            ymax = np.max(obj_pixels[0])
            if (((xmax-xmin)<=10) | (ymax-ymin)<=10):
                xmax = xmin+10
                ymax = ymin+10
            boxes.append([xmin, ymin, xmax, ymax])
        boxes = torch.as_tensor(boxes, dtype=torch.float32)
        labels = torch.as_tensor(labels, dtype=torch.int64)
        masks = torch.as_tensor(masks, dtype=torch.uint8)
        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])
        iscrowd = torch.zeros((num_objs,), dtype=torch.int64)
        image_id = torch.tensor([ix])
        target = {}
        target["boxes"] = boxes
        target["labels"] = labels
        target["masks"] = masks
        target["image_id"] = image_id
        target["area"] = area
        target["iscrowd"] = iscrowd
        if self.transforms is not None:
            img, target = self.transforms(img, target)
        img = img/255.
        return img, target

4.定义模型时,指定 4 个类别:

num_classes = 5
model = get_model_instance_segmentation(num_classes).to(device)

绘制模型训练期间,训练损失随时间增加的变化情况:

模型监测

使用训练后的模型预测包含目标对象的样本图像:

模型预测

小结

Mask R-CNN 是一种在目标检测任务中引入了语义分割的强大框架,通过在 Faster R-CNN 基础上进行扩展,添加了额外的分支网络,不仅可以准确地检测对象的位置和类别,还可以生成每个实例的精确像素级别的语义分割掩码。其模块化的设计可以轻松地应用于不同的任务和数据集,并且可以通过添加更多的分支进行功能扩展,如实例关键点检测等。

系列链接

PyTorch深度学习实战(1)——神经网络与模型训练过程详解
PyTorch深度学习实战(2)——PyTorch基础
PyTorch深度学习实战(3)——使用PyTorch构建神经网络
PyTorch深度学习实战(4)——常用激活函数和损失函数详解
PyTorch深度学习实战(5)——计算机视觉基础
PyTorch深度学习实战(6)——神经网络性能优化技术
PyTorch深度学习实战(7)——批大小对神经网络训练的影响
PyTorch深度学习实战(8)——批归一化
PyTorch深度学习实战(9)——学习率优化
PyTorch深度学习实战(10)——过拟合及其解决方法
PyTorch深度学习实战(11)——卷积神经网络
PyTorch深度学习实战(12)——数据增强
PyTorch深度学习实战(13)——可视化神经网络中间层输出
PyTorch深度学习实战(14)——类激活图
PyTorch深度学习实战(15)——迁移学习
PyTorch深度学习实战(16)——面部关键点检测
PyTorch深度学习实战(17)——多任务学习
PyTorch深度学习实战(18)——目标检测基础
PyTorch深度学习实战(19)——从零开始实现R-CNN目标检测
PyTorch深度学习实战(20)——从零开始实现Fast R-CNN目标检测
PyTorch深度学习实战(21)——从零开始实现Faster R-CNN目标检测
PyTorch深度学习实战(22)——从零开始实现YOLO目标检测
PyTorch深度学习实战(23)——使用U-Net架构进行图像分割

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

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

相关文章

Zabbix监控nginx状态

文章目录 zabbix监控nginx状态环境前期准备开启nginx状态页面配置监控 zabbix监控nginx状态 环境 主机名IP地址角色安装的软件zabbix192.168.179.100zabbix服务端zabbix_serverzabbix_agentwanf192.168.179.11nginxzabbix客户端nginxzabbix_agent 前期准备 部署nginx请阅读…

删除排序链表中的重复节点II(C++解法)

题目 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;[1,2,5]示例 2&#xff1a; 输入&#xff1a;head [1…

火影忍者游戏攻略大公开!成为忍者大师的秘诀揭秘

大家好&#xff01;作为火影忍者游戏的玩家&#xff0c;我们都希望能够在游戏中成为优秀的忍者大师&#xff0c;战胜强大的对手。为了帮助大家实现这一目标&#xff0c;我想分享一些实用的攻略和技巧。 首先&#xff0c;熟悉忍者技能是成为忍者大师的基础。在火影忍者游戏中&am…

Pytorch 注意力机制解析与代码实现

什么是注意力机制 注意力机制是深度学习常用的一个小技巧&#xff0c;它有多种多样的实现形式&#xff0c;尽管实现方式多样&#xff0c;但是每一种注意力机制的实现的核心都是类似的&#xff0c;就是注意力。 注意力机制的核心重点就是让网络关注到它更需要关注的地方。 当…

Elasticsearch 集群分片出现 unassigned 其中一种原因详细还原

&#x1f3e1; 个人主页&#xff1a;IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 &#x1f6a9; 私聊博主&#xff1a;加入大数据技术讨论群聊&#xff0c;获取更多大数据资料。 &#x1f514; 博主个人B栈地址&#xff1a;豹哥教你大数据的个人空间-豹…

miniconda快速安装

目录 一、Linux下miniconda安装 1.1、安装 1.2、miniconda初始化 二、Windows下miniconda安装 三、maOS下miniconda安装 3.1、安装 3.2、miniconda初始化 四、参考&#xff1a; 本文给出windows、macos、linux下快速安装miniconda方法。 对比conda&#xff0c;minicond…

XUbuntu22.04之simplenote支持的Markdown语法总结(一百九十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Qwt 使用QwtDial绘制钟表

1.概述 QwtDial是Qwt库中的一个类&#xff0c;用于绘制一个可旋转的仪表盘&#xff0c;QwtAnalogClock继承自QwtDial&#xff0c; 模拟时钟。 以下是类继承关系&#xff1a; 2.运行结果 自定义Clock类&#xff0c;继承自QwtAnalogClock&#xff0c;增加一个QTimer&#xff0…

【计算机网络笔记】传输层——可靠数据传输之流水线机制与滑动窗口协议

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

基于tpshop开发多商户源码支持手机端+商家+门店 +分销+淘宝数据导入+APP+可视化编辑

tpshop多商户源码,tpshop商城源码,tpshop b2b2c源码-支持手机端商家门店 分销淘宝数据导入APP可视化编辑 tpshop商城源码算是 thinkphp框架里做的比较早 比较好的源码了&#xff0c;写法简明 友好面向程序猿。 这是一款前几年的版本 虽然后台看着好了些&#xff0c;丝毫不影响…

【Linux】关于Nginx的详细使用,部署项目

前言&#xff1a; 今天小编给大家带来的是关于Nginx的详细使用&#xff0c;部署项目&#xff0c;希望可以给正在学习&#xff0c;工作的你带来有效的帮助&#xff01; 一&#xff0c;Nginx简介 Nginx是一个高性能的开源Web服务器和反向代理服务器。它最初由Igor Sysoev在2004年…

探讨jdk源码中的二分查找算法返回值巧妙之处

文章目录 1.什么是二分查找算法1.1 简介1.2 实现思路 2.二分查找的示例3.jdk 中的 Arrays.binarySearch()4.jdk 中核心二分查找方法解析4.1 为什么 low 是插入点4.2 为什么要进行取反&#xff1a;-&#xff08;low 1&#xff09;4.3 为什么不直接返回 插入点 low 的相反数&…

MySQL学习-获取排名,按行更新

获取排名 需求&#xff1a;获取分类平均值的名次&#xff1f; 比如10个班级的平均分&#xff0c;按照班级名称排序&#xff0c;后面跟着名次。 记录表&#xff1a;student &#xff1b; 字段&#xff1a;banji 班级&#xff1b;AvgS 平均分&#xff1b;pm 排名&#xff1b…

解决问题Conda:CondaValueError: Malformed version string ‘~’ : invalid character(s)

解决问题Conda&#xff1a;CondaValueError: Malformed version string ‘~’ : invalid character(s) 背景 今天使用Conda构建项目运行环境的时候报错&#xff1a;&#xff1a;CondaValueError: Malformed version string ‘~’ : invalid character(s) ##报错问题 在安装te…

Express框架开发接口之书城商店原型图

这是利用Axure画的&#xff0c;简单画一下原型图&#xff0c;根据他们的业务逻辑我们完成书城商店API开发 首页 分类 购物车 个人中心

探索C++中的不变之美:const与构造函数的深度剖析

W...Y的主页&#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a; 关于C的博客中&#xff0c;我们已经了解了六个默认函数中的四个&#xff0c;分别是构造函数、析构函数、拷贝构造函数以及函数的重载。但是这些函数都是有返回值与参数的。提到参数与返回…

Spring Security 6.1.x 系列(4)—— 基于过滤器链的源码分析

一、自动配置 在 Spring Security 6.1.x 系列&#xff08;1&#xff09;—— 初识Spring Security 中我们只引入spring-boot-starter-security 依赖&#xff0c;就可以实现登录认证&#xff0c;这些都得益于Spring Boot 的自动配置。 在spring-boot-autoconfigure模块中集成了…

MyBitis自动拼接了LIMIT

1.前言 最近系统在运营的过程中发现一个很奇怪的问题&#xff0c;莫名其妙的SQL语句会被拼接上一小段SQL&#xff0c;但是发现这被拼接的SQL并不是当前这个API所使用的SQL&#xff0c;因此导致select语句出错。 2.排查思路 2.1.第一步 首先我排查了打印日志里面的错误对应的…

Louis 谈 Restaking:去中心化信任的交流电时刻

人际信任是社会资本的主要形态。信任促成协作&#xff08;主要是经济交易&#xff09;&#xff0c;是人类文明的基石。 当全球已有数十亿人接入互联网&#xff0c;协作的物理限制已经消除&#xff0c;但传统的人际信任仍然局限于家族、长期积累的声誉和长期相处形成的私人关系…

【JAVA学习笔记】55 - 集合-Map接口、HashMap类、HashTable类、Properties类、TreeMap类(难点)

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter14/src/com/yinhai/map_ Map接口 一、Map接口的特点&#xff08;难点&#xff09; 难点在于对Node和Entry和EntrySet的关系 注意:这里讲的是JDK8的Map接口特点 Map java 1) Map与Collect…