PyTorch深度学习实战(21)——从零开始实现Faster R-CNN目标检测

news2024/11/26 11:47:33

PyTorch深度学习实战(21)——从零开始实现Faster R-CNN目标检测

    • 0. 前言
    • 1. Fast R-CNN 目标检测模型组成
      • 1.1 锚框
      • 1.2 区域提议网络
      • 1.3 分类和回归
    • 2. 实现 R-CNN 目标检测
      • 2.1 数据处理
      • 2.2 模型构建
      • 2.3 模型训练与测试
    • 小结
    • 系列链接

0. 前言

Faster R-CNN 是对 R-CNN 系列算法的进一步改进,与 R-CNN 和 Fast R-CNN 不同,Faster R-CNN 提出了一种全新的目标检测框架,将候选框生成和目标分类合并到一个网络中,实现了端到端的训练,可以同时优化候选框生成和目标分类任务,提高了检测的准确性和效率。同时,利用共享的卷积特征可以加速特征提取的计算,进一步提高了检测速度。在本节中,将介绍 Faster R-CNN 的工作原理,然后在自定义数据集上训练 Faster R-CNN 目标检测模型。

1. Fast R-CNN 目标检测模型组成

R-CNNFast R-CNN 模型的主要缺陷是它们使用两个独立的网络,一个用于识别可能包含对象的区域,另一个用于对预测的识别对象边界框进行校正。此外,这两种模型都需要多次前向传播才能完成所有区域提议的检测。而新兴的目标检测算法主要集中于训练单个神经网络,并能够在一次前向传播中检测所有目标。接下来,我们将介绍单阶段对象检测算法的各个组成部分,包括锚框、区域提议网络 (Region Proposal Network, RPN) 和 RoI (Region of Interest) 池化。

1.1 锚框

锚框 (Anchor Box),也称先验框 (Prior Box),在目标检测中,锚框是一些预设的固定大小和宽高比的矩形框,这些矩形框被放置于输入图像中的所有可能位置。目标检测算法通过在输入图像的每个位置上运用锚框,来判断该位置是否包含目标,并对矩形框进行调整,从而尽可能准确地预测出真实的目标位置。
我们已经学习了如何基于选择性搜索方法获取区域建议,相比之下,锚框可以更快速地提供目标检测结果。在使用锚框进行目标检测时,我们首先生成一系列预定义的锚框,对于每个锚框,需要对其进行分类(即确定其是否包含目标)和回归(即调整它们的位置),以得到预测边界框,通过使用这些预测边界框,可以获得目标检测结果。区域提议通常包括几个阶段,如生成候选区域、提取特征并进行分类以及回归。而使用锚框,则可以直接在特征图上进行分类和回归,从而提高了检测效率和准确性。
一般来说,大多数物体的形状是类似的,例如,在大多数情况下,人的图像对应的边界框高度大于宽度,而卡车的图像对应的边界框宽度大于高度。因此,只需检查数据集中各种目标对象对应的标注边界框,我们就可以相当准确地了解图像中物体的高度和宽度。此外,图像中感兴趣的对象可能会被缩放,导致高度和宽度大幅降低,但相应的宽高比保持不变(即高度/宽度)。
当我们根据数据集的标注信息了解了图像中目标对象的宽高比、高度和宽度后,我们就可以定义能够代表大多数对象边界框的锚框。通常,可以对图像中目标对象的实际边界框应用 K-means 聚类获得代表性锚框。
获得锚框后,将其应用于目标检测模型中:

  1. 将每个锚框从左上角滑到右下角覆盖整张图像
  2. 与目标对象交并比 (Intersection over Union, IoU) 较高的锚框被标记为包含目标的锚框,其他的则被标记为 0

我们可以修改 IoU 的阈值,如果 IoU 大于给定阈值,则对象类别为 1;如果小于另一指定阈值,则对象类别为 0,否则将其保留为未知类别。
获取了真实边界框之后,构建模型来预测物体的位置以及与锚框相对应的偏移量,以使其与真实框匹配。下图展示了锚框的表示方式:

锚框示意图

在上图中,包含两个锚框,一个高度大于宽度,另一个宽度大于高度,以对应图像中的目标对象。在图像上滑动这两个锚框,并记录与真实框的 IoU 最高的位置,以指定该特定位置包含目标对象,而其余位置则不包含目标对象。除了以上两个锚框之外,我们还会创建多个具有不同比例的锚框,以便适应图像中不同比例的目标对象:

不同比例锚框

在上图中,可以看到所有锚框的中心位置相同,但比例不同。在下一小节中,我们将介绍区域提议网络 (Region Proposal Network, RPN) ,利用锚框来预测可能包含对象的区域。

1.2 区域提议网络

假设图像尺寸为 224 x 224 x 3,且锚框形状为 8 x 8,如果步幅为 8,那么每一行都需要取出 224/8 = 28 个图像切片,也就是说,一张图片会产生 28*28 = 576 个切片。然后,将每个切片输入到区域提议网络 (Region Proposal Network, RPN) 模型中,以确定该切片是否包含目标对象。简而言之,RPN 可以输出每个切片包含目标对象的概率。接下来,我们比较选择性搜索的输出和 RPN 的输出。
选择性搜索是一种基于图像分割的方法,它通过将图像分割成多个区域,然后合并这些区域来产生候选框,最终输出的是多个候选框,用于表示可能包含待检测目标对象的区域。区域提议网络 (Region Proposal Network, RPN) 是一种深度学习算法,它将整张图像作为输入,并输出若干个“建议区域” (proposed region)。这些建议区域是基于锚框 (anchor box) 概念计算得到的,每个建议区域都是一个带有坐标和大小信息的矩形框,用于表示可能包含待检测目标对象的区域。前者是传统的基于图像分割的算法,而后者则是深度学习算法,更加高效准确。
基于选择性搜索的区域提议生成是在神经网络之外完成的,而 RPN 则是目标检测网络的一部分。使用 RPN 可以不必在网络外部执行区域提议计算,这样,我们仅需要使用一个单一的模型就可以确定候选区域、确定图像中物体的类别以及相应的边界框位置。
接下来,我们将学习 RPN 如何确定候选区域(在滑动锚框后获得的切片)是否包含目标对象。在训练数据中,有与物体对应的真实标注信息,获取每个候选区域并与图像中物体的真实边界框进行比较,以确定该候选区域与真实边界框之间的 IoU 是否大于给定阈值。如果 IoU 大于给定阈值(例如 0.5),则该候选区域包含目标对象;如果 IoU 小于给定阈值(例如 0.1),则该候选区域不包含目标对象,并且在训练时忽略所有 IoU 在这两个阈值之间 (0.1 - 0.5) 的候选区域。
使用训练模型预测区域候选是否包含目标对象后,执行非极大值抑制 (non-maximum suppression, NMS),因为多个重叠的区域可能包含同一个目标对象。

综上,RPN 通过执行以下步骤训练模型,使其能够识别具有较高置信度包含目标对象的候选区域:

  1. 在图像上滑动不同纵横比和大小的锚框以获取图像切片
  2. 计算图像中目标对象的真实边界框与图像切片间的 IoU
  3. 准备训练数据集,IoU 大于指定阈值的图像切片包含目标对象,而 IoU 小于指定阈值的图像切片不包含目标对象
  4. 训练模型识别包含目标对象的区域
  5. 执行非极大值抑制,识别出包含对象概率最高的候选区域,并剔除与其重叠度较高的其他候选区域

1.3 分类和回归

使用以下步骤预测对象类别及其边界框偏移量:

  1. 识别包含目标对象的区域
  2. 使用 RoI (Region of Interest) 池化确保所有候选区域的特征图尺寸相同

上述步骤包含如下两个问题:

  1. 区域区域与目标对象边界框并未完全对应
  2. 可以确定区域是否包含目标对象,但不确定该区域中的目标对象类别

在本节中,我们将解决以上两个问题。将得到的形状一致的特征图输入到神经网络中,并且希望网络能够预测区域内包含的目标对象类别以及与该区域相对应的偏移量,以确保预测边界框尽可能接近目标对象真实边界框:

请添加图片描述

从上图中可以看到,将 RoI 池化的输出(形状为 7 x 7 x 512) 作为输入,并在展平后将其连接到全连接层,得到以下两个预测结果:

  1. 区域内的对象类别
  2. 区域内预测边界框的偏移量

因此,如果数据中包含 20 个类别,则神经网络将包含 25 个输出——21 个类别概率(包括背景类别)以及边界框高度、宽度和两个中心坐标的偏移量。
我们可以使用下图来总结,Faster R-CNN 目标检测模型的架构与原理:

Faster R-CNN

2. 实现 R-CNN 目标检测

在本节中,使用 PyTorch 构建 Faster R-CNN 模型检测图像中目标对象的类别及其边界框,我们将继续使用与 R-CNN 一节中相同的数据集构建 Faster R-CNN 目标检测模型。

2.1 数据处理

(1) 读取包含图像及其边界框和类别信息元数据的 DataFrame

import selectivesearch
from torchvision import transforms, models, datasets
from torchvision.ops import nms
import os
import torch
import numpy as np
from torch.utils.data import DataLoader, Dataset
from glob import glob
from random import randint
import cv2
from pathlib import Path
import torch.nn as nn
from torch import optim
from matplotlib import pyplot as plt
import pandas as pd
import matplotlib.patches as mpatches
from PIL import Image

device = 'cuda' if torch.cuda.is_available() else 'cpu'

IMAGE_ROOT = 'open-images-bus-trucks/images/images'
DF_RAW = df = pd.read_csv('open-images-bus-trucks/df.csv')
print(DF_RAW.head())

(2) 定义标签对应的索引:

label2target = {l:t+1 for t,l in enumerate(DF_RAW['LabelName'].unique())}
label2target['background'] = 0
target2label = {t:l for l,t in label2target.items()}
background_class = label2target['background']
num_classes = len(label2target)

(3) 定义图像预处理函数 preprocess_image() 与图像查找函数 find()

def preprocess_image(img):
    img = torch.tensor(img).permute(2,0,1)
return img.to(device).float()

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:
        return results[0]
    else:
        return results

(4) 定义数据集类 OpenDataset

定义 __init__ 方法,将图像文件夹和包含图像元数据的 DataFrame 作为输入:

class OpenDataset(torch.utils.data.Dataset):
    w, h = 224, 224
    def __init__(self, df, image_dir=IMAGE_ROOT):
        self.image_dir = image_dir
        self.files = glob(self.image_dir+'/*')
        self.df = df
        self.image_infos = df.ImageID.unique()

定义 __getitem__ 方法,返回预处理后的图像和目标值:

    def __getitem__(self, ix):
        # load images and masks
        image_id = self.image_infos[ix]
        img_path = find(image_id, self.files)
        img = Image.open(img_path).convert("RGB")
        img = np.array(img.resize((self.w, self.h), resample=Image.BILINEAR))/255.
        data = df[df['ImageID'] == image_id]
        labels = data['LabelName'].values.tolist()
        data = data[['XMin','YMin','XMax','YMax']].values
        data[:,[0,2]] *= self.w
        data[:,[1,3]] *= self.h
        boxes = data.astype(np.uint32).tolist() # convert to absolute coordinates
        # torch FRCNN expects ground truths as a dictionary of tensors
        target = {}
        target["boxes"] = torch.Tensor(boxes).float()
        target["labels"] = torch.Tensor([label2target[i] for i in labels]).long()
        img = preprocess_image(img)
        return img, target

以上 __getitem__ 方法将输出作为张量字典而非张量列表返回,这是因为我们期望输出包含边界框的绝对坐标和标签信息。

定义 collate_fn 方法(处理字典列表)和 __len__ 方法:

    def collate_fn(self, batch):
        return tuple(zip(*batch)) 

    def __len__(self):
        return len(self.image_infos)

(5) 创建训练和验证数据加载器和数据集:

from sklearn.model_selection import train_test_split
trn_ids, val_ids = train_test_split(df.ImageID.unique(), test_size=0.1, random_state=99)
trn_df, val_df = df[df['ImageID'].isin(trn_ids)], df[df['ImageID'].isin(val_ids)]
len(trn_df), len(val_df)

train_ds = OpenDataset(trn_df)
test_ds = OpenDataset(val_df)

train_loader = DataLoader(train_ds, batch_size=4, collate_fn=train_ds.collate_fn, drop_last=True)
test_loader = DataLoader(test_ds, batch_size=4, collate_fn=test_ds.collate_fn, drop_last=True)

2.2 模型构建

(1) 定义模型:

import torchvision
from torchvision.models.detection.faster_rcnn import FastRCNNPredictor

device = 'cuda' if torch.cuda.is_available() else 'cpu'

def get_model():
    model = torchvision.models.detection.fasterrcnn_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)
    return model

该模型包含以下关键子模块:

  • GeneralizedRCNNTransform:在调整图像大小后执行归一化转换
  • BackboneWithFPN:将输入转换为特征图
  • RegionProposalNetwork:为图像特征图生成锚框,并针对分类和回归任务获取区域特征图
  • RoIHeads 采用区域特征图,使用 RoI 池化对其进行处理,生成固定大小的区域特征图,并返回每个区域提议的分类概率和偏移量

(2) 定义函数在批数据上训练网络,并计算验证数据集的损失值:

def train_batch(inputs, model, optimizer):
    model.train()
    input, targets = inputs
    input = list(image.to(device) for image in input)
    targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
    optimizer.zero_grad()
    losses = model(input, targets)
    loss = sum(loss for loss in losses.values())
    loss.backward()
    optimizer.step()
    return loss, losses

@torch.no_grad()
def validate_batch(inputs, model, optimizer):
    model.train()
    input, targets = inputs
    input = list(image.to(device) for image in input)
    targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
    optimizer.zero_grad()
    losses = model(input, targets)
    loss = sum(loss for loss in losses.values())
    return loss, losses

2.3 模型训练与测试

(1) 训练模型。

初始化模型:

model = get_model().to(device)
optimizer = torch.optim.SGD(model.parameters(), lr=0.005,
                            momentum=0.9, weight_decay=0.0005)
n_epochs = 10

train_loss_epochs = []
train_loc_loss_epochs = []
train_regr_loss_epochs = []
train_objectness_loss_epochs = []
train_rpn_box_reg_loss_epochs = []
val_loss_epochs = []
val_loc_loss_epochs = []
val_regr_loss_epochs = []
val_objectness_loss_epochs = []
val_rpn_box_reg_loss_epochs = []

训练模型并计算训练和测试数据集的损失值:

for epoch in range(n_epochs):
    _n = len(train_loader)
    trn_loss = []
    trn_loc_loss = []
    trn_regr_loss = []
    trn_objectness_loss = []
    trn_rpn_box_reg_loss = []
    val_loss = []
    val_loc_loss = []
    val_regr_loss = []
    val_objectness_loss = []
    val_rpn_box_reg_loss = []
    for ix, inputs in enumerate(train_loader):
        loss, losses = train_batch(inputs, model, optimizer)
        loc_loss, regr_loss, loss_objectness, loss_rpn_box_reg = \
            [losses[k] for k in ['loss_classifier','loss_box_reg','loss_objectness','loss_rpn_box_reg']]
        pos = (epoch + (ix+1)/_n)
        trn_loss.append(loss.item())
        trn_loc_loss.append(loc_loss.item())
        trn_regr_loss.append(regr_loss.item())
        trn_objectness_loss.append(loss_objectness.item())
        trn_rpn_box_reg_loss.append(loss_rpn_box_reg.item())
    train_loss_epochs.append(np.average(trn_loss))
    train_loc_loss_epochs.append(np.average(trn_loc_loss))
    train_regr_loss_epochs.append(np.average(trn_regr_loss))
    train_objectness_loss_epochs.append(np.average(trn_objectness_loss))
    train_rpn_box_reg_loss_epochs.append(np.average(trn_rpn_box_reg_loss))

    _n = len(test_loader)
    for ix,inputs in enumerate(test_loader):
        loss, losses = validate_batch(inputs, model, optimizer)
        loc_loss, regr_loss, loss_objectness, loss_rpn_box_reg = \
          [losses[k] for k in ['loss_classifier','loss_box_reg','loss_objectness','loss_rpn_box_reg']]
        pos = (epoch + (ix+1)/_n)
        val_loss.append(loss.item())
        val_loc_loss.append(loc_loss.item())
        val_regr_loss.append(regr_loss.item())
        val_objectness_loss.append(loss_objectness.item())
        val_rpn_box_reg_loss.append(loss_rpn_box_reg.item())
    val_loss_epochs.append(np.average(val_loss))
    val_loc_loss_epochs.append(np.average(val_loc_loss))
    val_regr_loss_epochs.append(np.average(val_regr_loss))
    val_objectness_loss_epochs.append(np.average(val_objectness_loss))
    val_rpn_box_reg_loss_epochs.append(np.average(val_rpn_box_reg_loss))

(2) 绘制损失值随训练的变化情况:

epochs = np.arange(n_epochs)+1
plt.plot(epochs, train_loss_epochs, 'bo', label='Training loss')
plt.plot(epochs, val_loss_epochs, 'r', label='Test loss')
plt.title('Training and Test loss over increasing epochs')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid('off')
plt.show()

模型监测

(3) 使用训练后的 Faster R-CNN 模型预测测试图像。

Faster R-CNN 目标检测模型的输出包含目标对象对应的边界框、类别标签和置信度分数。定义 decode_output 函数,接受模型的输出并得到由应用非极大值抑制后的边界框、置信度分数和类别组成的列表:

from torchvision.ops import nms
def decode_output(output):
    'convert tensors to numpy arrays'
    bbs = output['boxes'].cpu().detach().numpy().astype(np.uint16)
    labels = np.array([target2label[i] for i in output['labels'].cpu().detach().numpy()])
    confs = output['scores'].cpu().detach().numpy()
    ixs = nms(torch.tensor(bbs.astype(np.float32)), torch.tensor(confs), 0.05)
    bbs, confs, labels = [tensor[ixs] for tensor in [bbs, confs, labels]]

    if len(ixs) == 1:
        bbs, confs, labels = [np.array([tensor]) for tensor in [bbs, confs, labels]]
    return bbs.tolist(), confs.tolist(), labels.tolist()

# print(clss)
def show_bbs(im, bbs, clss):
    fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(6, 6))
    ax[0].imshow(im)
    ax[0].grid(False)
    ax[0].set_title('Original image')
    if len(bbs) == 0:
        ax[1].imshow(im)
        ax[1].set_title('No objects')
        plt.show()
        return
    ax[1].imshow(im)
    for ix, (xmin, ymin, xmax, ymax) in enumerate(bbs):
        rect = mpatches.Rectangle(
                (xmin, ymin), xmax-xmin, ymax-ymin, 
                fill=False, 
                edgecolor='red', 
                linewidth=1)
        ax[1].add_patch(rect)
        centerx = xmin # + new_w/2
        centery = ymin + 20# + new_h - 10
        plt.text(centerx, centery, clss[ix],fontsize = 20,color='red')
    ax[1].grid(False)
    ax[1].set_title('Predicted bounding box and class')
    plt.show()

获取测试图像中目标对象的边界框和类别:

model.eval()
for ix, (images, targets) in enumerate(test_loader):
    if ix==20: 
        break
    images = [im for im in images]
    outputs = model(images)
    for ix, output in enumerate(outputs):
        bbs, confs, labels = decode_output(output)
        info = [f'{l}@{c:.2f}' for l,c in zip(labels, confs)]
        show_bbs(images[ix].cpu().permute(1,2,0), bbs=bbs, clss=labels)

模型测试

小结

Faster R-CNN 的核心组件是区域提议网络 (Region Proposal Network, RPN) 和共享卷积特征。首先,通过卷积神经网络,将输入图像提取为特征图,然后,RPN 在特征图上滑动窗口,并为每个窗口位置生成多个候选框,RPN 利用锚框 (anchor boxes) 与真实目标框的匹配程度,为每个候选框分配得分,并预测边界框的偏移量。在候选框生成后,Faster R-CNN 通过 RoI 池化层从共享的特征图上提取固定尺寸的特征表示,这些特征表示被输入到分类器和边界框回归器中,进行目标分类和边界框位置的精修。Faster R-CNN 的优势在于,可以同时优化候选框生成和目标分类任务,提高了检测的准确性和效率。在本节中,我们使用 PyTorch 中提供的 fasterrcnn_resnet50_fpn 模型类训练了 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目标检测

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

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

相关文章

手机抬手亮屏解锁,用到了哪些硬件?

随着时代发展,智能手机以丰富的功能及便利性,成为了人们必不可少的物品,其中人脸解锁功能是非常有用的功能,广受年轻人的喜爱,那么你知道她是如何实现吗?今天凡小亿带你们探索! 手机抬手亮屏解锁…

谨以此篇,纪念我2023年曲折的计算机保研之路

目录 阶段一:迷茫阶段二:准备个人意愿保研材料准备套磁老师5.1日 浙大线上编程测试5.8日 浙大线上面试 —— 一面5.17日 浙大线上面试——二面5.29日 实验室面试结果5.27日 南开线上面试6.20日 华师电话面试 阶段三:旅途北航CS(6.…

ebpf的快速开发工具--libbpf-bootstrap

基于ubuntu22.04-深入浅出 eBPF 基于ebpf的性能工具-bpftrace 基于ebpf的性能工具-bpftrace脚本语法 基于ebpf的性能工具-bpftrace实战(内存泄漏) 什么是libbpf-bootstrap libbpf-bootstrap是一个开源项目,旨在帮助开发者快速启动和开发使用eBPF(Extended Berk…

微服务拆分的思考

一、前言 前面几篇文章介绍了微服务核心的两个组件:注册中心和网关,今天我们来思考一下微服务如何拆分,微服务拆分难度在于粒度和层次,粒度太大拆分的意义不大,粒度太小开发、调试、运维会有很多坑。 二、微服务划分…

【初识Linux】:常见指令(2)

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux的基础知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏:C语言:从入门到精通 数…

为健康护航的小帮手,dido E55S Pro智能手表体验

现在很多年轻人每天都要长时间工作,没有时间锻炼身体,很容易导致各种健康隐患,工作效率也容易下降,非常有必要通过智能手表等工具,随时监测自己的健康状态。现在支持健康监测的智能手表非常多,用起来也简单…

Java反射使用实例

Java反射:解析类的秘密 Java反射是一项强大的功能,允许开发人员在运行时检查、操作和实例化类、方法、字段以及其他Java程序中的元素。这种能力赋予了Java语言更大的灵活性和动态性,但也需要慎重使用,因为它可能会导致性能问题和…

[Halcon检测] 划痕检测之高斯导数提取

📢博客主页:https://loewen.blog.csdn.net📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢本文由 丶布布原创,首发于 CSDN,转载注明出处🙉📢现…

带你了解小程序的框架之谜

🏅我是默,一个在CSDN分享笔记的博主。📚📚 🌟在这里,我要推荐给大家我的专栏《微信小程序 》。🎯🎯 🚀无论你是编程小白,还是有一定基础的程序员,…

BUUCTF学习(6): 命令执行ip

1、介绍 2、hackbar安装 BUUCTF学习(四): 文件包含tips-CSDN博客 ?ip127.0.0.1;ag;cat$IFS$9fla$a.php 空格过滤 $IFS$9 检查源代码 结束

ssm+vue的药品管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频: ssmvue的药品管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构&…

英语进阶指南:高效学习方法,提升英语水平 | 开源专题 No.35

这些开源项目集合了英语学习与翻译工具,包括英语进阶指南、多功能翻译工具、面向程序员的英语学习指南和单词记忆软件。它们提供实用方法,覆盖多个学习方面,满足不同需求。无论您是英语初学者还是想进一步提升,这些资源都能助您轻…

系列八、Redis的事务

一、是什么 可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不允许加塞。 二、能干嘛 一个队列中,一次性、顺序性、排他性的执行一些列命令。 三、怎么玩…

3.Vue-在Vue框架中搭建路由

题记 以下是在vue框架中搭建路由的全过程,包括全部代码。 创建路由 如果你的文件中没有 router文件夹,可以使用以下命令创建: vue add router 注意:生成的路由文件会因为选择的自定义选项不同,而有所差异 生成的代码…

【ArcGIS Pro二次开发】(71):添加图层名称和路径到字段

如题,这个工具的目的就是将图层的名称和路径添加到字段值中。 有时候图层的名称和路径也是重要的信息,需要参与到字段的计算或是分析中,但是Arcgis Pro中没有一个方便的方法可以将其写入字段值,因此,就做了这么一个小…

力扣第77题 组合 c++ 回溯经典题 注释加优化 代码

题目 77. 组合 中等 相关标签 回溯 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1: 输入:n 4, k 2 输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ] 示例 2&a…

基于Java的健身俱乐部管理系统设计与实现(亮点:健身课程课程、会员下单、在线支付)

文章目录 前言具体实现截图详细视频演示为什么选择我自己的网站自己的小程序(小蔡coding) 代码参考数据库参考源码获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c…

怎么查苹果手机激活时间?详细查看指南来了!

苹果手机之所以受到广大消费者的喜爱,是因为它提供了优质的用户体验、强大的生态系统以及美观的设计等等。很多小伙伴在购买iPhone后都会担心一个问题:手机是否为新机,手机有没有被激活过?那么,怎么查苹果手机激活时间…

电压放大器在铁电材料表征中的应用有哪些

电压放大器在铁电材料表征中具有广泛的应用。铁电材料是一类特殊的功能材料,具有独特的电荷分布和结构变化特性,在电子器件、传感器、存储器等领域具有重要的应用价值。而电压放大器作为一种重要的电子器件,可以提供稳定可靠的信号放大功能&a…

mysql分组排序并取每组的前1条记录

实际开发中会遇到使用mysql分组统计,并且要求取每组数据的前1条数据 如下:需要根据模块分组,取每组中的前1条优质供应商数据,取前几条数据,其实会用到上篇文章中的排名函数row_number() 函数来实现,其含义…