复现PointNet(分割网络):Windows + PyTorch+代码

一、平台

Windows 10

GPU RTX 3090 + CUDA 11.1 + cudnn 8.9.6

Python 3.9

Torch 1.9.1+cu111

所用的原始代码:https://github.com/fxia22/pointnet.pytorch​​​​​​​

二、数据

shapenetcore_partanno_segmentation_benchmark_v0

三、代码

分享给有需要的人,代码质量勿喷。

对源代码进行了简化和注释。

不搞原作者的可视化工具,分割结果保存成txt,或者利用 laspy 生成点云。

别问为啥在C盘,问就是2T的三星980Pro

3.1 文件组织结构

3.2 dataset.py

修改了部分txt文件的路径

from __future__ import print_function
import os
import os.path
import numpy as np
import sys
from tqdm import tqdm 
import json
from plyfile import PlyData, PlyElement
import torch
import torch.utils.data as data


def get_segmentation_classes(root):
    catfile = os.path.join(root, 'synsetoffset2category.txt')
    cat = {}
    meta = {}

    with open(catfile, 'r') as f:
        for line in f:
            ls = line.strip().split()
            cat[ls[0]] = ls[1]

    for item in cat:
        dir_seg = os.path.join(root, cat[item], 'points_label')
        dir_point = os.path.join(root, cat[item], 'points')
        fns = sorted(os.listdir(dir_point))
        meta[item] = []
        for fn in fns:
            token = (os.path.splitext(os.path.basename(fn))[0])
            meta[item].append((os.path.join(dir_point, token + '.pts'), os.path.join(dir_seg, token + '.seg')))
    
    with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'num_seg_classes.txt'), 'w') as f:
        for item in cat:
            datapath = []
            num_seg_classes = 0
            for fn in meta[item]:
                datapath.append((item, fn[0], fn[1]))

            for i in tqdm(range(len(datapath))):
                l = len(np.unique(np.loadtxt(datapath[i][-1]).astype(np.uint8)))
                if l > num_seg_classes:
                    num_seg_classes = l

            print("category {} num segmentation classes {}".format(item, num_seg_classes))
            f.write("{}\t{}\n".format(item, num_seg_classes))

def gen_modelnet_id(root):
    classes = []
    with open(os.path.join(root, 'train.txt'), 'r') as f:
        for line in f:
            classes.append(line.strip().split('/')[0])
    classes = np.unique(classes)
    with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'modelnet_id.txt'), 'w') as f:
        for i in range(len(classes)):
            f.write('{}\t{}\n'.format(classes[i], i))


class ShapeNetDataset(data.Dataset):
    def __init__(self,
                 root,
                 npoints=2500,
                 classification=False,
                 class_choice=None,
                 split='train',
                 data_augmentation=True):
        self.npoints = npoints
        self.root = root
        self.catfile = os.path.join(self.root, 'synsetoffset2category.txt')
        self.cat = {}
        self.data_augmentation = data_augmentation # 数据扩充
        self.classification = classification
        self.seg_classes = {}

        # 读synsetoffset2category.txt中的数据,并以字典的形式存储到self.cat中
        with open(self.catfile, 'r') as f:
            for line in f:
                # strip():移除字符串头尾指定的字符(默认为空格或换行符)
                # split():指定分隔符对字符串进行切片,返回分割后的字符串列表(默认为所有的空字符,包括空格、换行\n、制表符\t等)
                ls = line.strip().split()
                # cat为字典,通过[键]索引。键:类别;值:文件夹名称
                self.cat[ls[0]] = ls[1]
        # print(self.cat) #所有类和代号

        if not class_choice is None:
            self.cat = {k: v for k, v in self.cat.items() if k in class_choice}

        self.id2cat = {v: k for k, v in self.cat.items()} # key和value互换

        self.meta = {}
        # json文件类似xml文件,可存储键值对和数组等
        # split=train
        # format():字符串格式化函数,使用{}代替之前的%
        splitfile = os.path.join(self.root, 'train_test_split', 'shuffled_{}_file_list.json'.format(split))
        #from IPython import embed; embed()
        filelist = json.load(open(splitfile, 'r'))

        # for item in self.cat:item为键
        # for item in self.cat.values():item为值
        # for item in self.cat.items():item为键值对(元组的形式)
        # for k, v in self.cat.items():更为规范的键值对读取方式
        # meta为字典,键为类别,键值为空
        for item in self.cat:
            self.meta[item] = []

        for file in filelist:
            _, category, uuid = file.split('/')
            if category in self.cat.values():
                self.meta[self.id2cat[category]].append((os.path.join(self.root, category, 'points', uuid+'.pts'),
                                        os.path.join(self.root, category, 'points_label', uuid+'.seg')))

        self.datapath = []
        # cat存储类别及其所在文件夹,item访问键,即类别
        for item in self.cat:
            # meta为字典,fn访问值,即路径
            for fn in self.meta[item]:
                # item为类别,fn[0]为点云路径,fn[1]为用于分割的标签路径
                self.datapath.append((item, fn[0], fn[1]))
                # sorted():对所有可迭代兑现进行排序,默认为升序;sorted(self.cat)对字典cat中的键(种类)进行排序,排序结果的类型为list
                # zip():  函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组
                # dict(): 创建字典。dict(zip(['one', 'two'], [1, 2])) -> {'two': 2, 'one': 1}

        self.classes = dict(zip(sorted(self.cat), range(len(self.cat))))
        # print(self.classes) #训练所用的类别

        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'num_seg_classes.txt'), 'r') as f:
            for line in f:
                ls = line.strip().split()
                self.seg_classes[ls[0]] = int(ls[1])
        self.num_seg_classes = self.seg_classes[list(self.cat.keys())[0]]
        # print(self.seg_classes, self.num_seg_classes) #所有类和被分割的数量

    # 该方法的实例对象可通过索引取值,自动调用该方法
    def __getitem__(self, index):
        # 获取类别、点云路径、分割标签路径元组
        fn = self.datapath[index]
        # 获取数字编码的类别标签
        cls = self.classes[self.datapath[index][0]]
        # 读取pts点云
        point_set = np.loadtxt(fn[1]).astype(np.float32)
        # 读取分割标签
        seg = np.loadtxt(fn[2]).astype(np.int64)
        #print(point_set.shape, seg.shape)

        # 重新采样到self.npoints个点
        choice = np.random.choice(len(seg), self.npoints, replace=True)
        #resample
        point_set = point_set[choice, :]

        # 去中心化
        point_set = point_set - np.expand_dims(np.mean(point_set, axis = 0), 0) # center
        # 计算到原点的最远距离
        dist = np.max(np.sqrt(np.sum(point_set ** 2, axis = 1)),0)
        # 归一化
        point_set = point_set / dist #scale

        # 默认False  开启旋转任意角度并加上一个bias,增强数据的抗干扰能力
        if self.data_augmentation:
            theta = np.random.uniform(0,np.pi*2)
            rotation_matrix = np.array([[np.cos(theta), -np.sin(theta)],[np.sin(theta), np.cos(theta)]])
            point_set[:,[0,2]] = point_set[:,[0,2]].dot(rotation_matrix) # random rotation
            point_set += np.random.normal(0, 0.02, size=point_set.shape) # random jitter

        seg = seg[choice]
        point_set = torch.from_numpy(point_set) #转换数据格式
        seg = torch.from_numpy(seg)
        cls = torch.from_numpy(np.array([cls]).astype(np.int64)) #cls为对应的代号,比如Airplane对应0

        if self.classification:
            return point_set, cls
        else:
            return point_set, seg

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


class ModelNetDataset(data.Dataset):
    def __init__(self,
                 root,
                 npoints=2500,
                 split='train',
                 data_augmentation=True):
        self.npoints = npoints
        self.root = root
        self.split = split
        self.data_augmentation = data_augmentation
        self.fns = []
        with open(os.path.join(root, '{}.txt'.format(self.split)), 'r') as f:
            for line in f:
                self.fns.append(line.strip())

        self.cat = {}
        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../misc/modelnet_id.txt'), 'r') as f:
            for line in f:
                ls = line.strip().split()
                self.cat[ls[0]] = int(ls[1])

        print(self.cat)
        self.classes = list(self.cat.keys())

    def __getitem__(self, index):
        fn = self.fns[index]
        cls = self.cat[fn.split('/')[0]]
        with open(os.path.join(self.root, fn), 'rb') as f:
            plydata = PlyData.read(f)
        pts = np.vstack([plydata['vertex']['x'], plydata['vertex']['y'], plydata['vertex']['z']]).T
        choice = np.random.choice(len(pts), self.npoints, replace=True)
        point_set = pts[choice, :]

        point_set = point_set - np.expand_dims(np.mean(point_set, axis=0), 0)  # center
        dist = np.max(np.sqrt(np.sum(point_set ** 2, axis=1)), 0)
        point_set = point_set / dist  # scale

        if self.data_augmentation:
            theta = np.random.uniform(0, np.pi * 2)
            rotation_matrix = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]])
            point_set[:, [0, 2]] = point_set[:, [0, 2]].dot(rotation_matrix)  # random rotation
            point_set += np.random.normal(0, 0.02, size=point_set.shape)  # random jitter

        point_set = torch.from_numpy(point_set.astype(np.float32))
        cls = torch.from_numpy(np.array([cls]).astype(np.int64))
        return point_set, cls


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

if __name__ == '__main__':
    dataset = sys.argv[1]
    datapath = sys.argv[2]

    if dataset == 'shapenet':
        d = ShapeNetDataset(root = datapath, class_choice = ['Chair'])
        print(len(d))
        ps, seg = d[0]
        print(ps.size(), ps.type(), seg.size(),seg.type())

        d = ShapeNetDataset(root = datapath, classification = True)
        print(len(d))
        ps, cls = d[0]
        print(ps.size(), ps.type(), cls.size(),cls.type())
        # get_segmentation_classes(datapath)

    if dataset == 'modelnet':
        gen_modelnet_id(datapath)
        d = ModelNetDataset(root=datapath)
        print(len(d))
        print(d[0])

3.3 model.py

没变化

3.4 train_segmentation.py

修改了部分文件的路径,我看起来更舒服

# 参考
# 牙牙要健康 https://blog.csdn.net/yangyu0515/article/details/129362565
# LingbinBu https://blog.csdn.net/yuanmiyu6522/article/details/121435650

#使用最新版本的 print 函数
from __future__ import print_function
import numpy as np
import os
import random
import torch
import torch.nn.parallel
import torch.nn.functional as F
import torch.optim as optim
import torch.utils.data
from tqdm import tqdm
#不显示warnning
import warnings
warnings.filterwarnings('ignore')

from pointnet.dataset import ShapeNetDataset
from pointnet.model import PointNetDenseCls, feature_transform_regularizer



# region 超参数
batchSize = 8
learn_rate = 0.001
epochs = 100
workers = 0
outFolder = 'C:/xinjiang/py/xjPointNet/trainModelSeg'
try:
    os.makedirs(outFolder)
except OSError:
    pass
pathModel = ''
pathDataset = 'C:/xinjiang/py/xjPointNet/shapenetcore_partanno_segmentation_benchmark_v0/'
class_choice = 'Chair'
featureTransform = False
# endregion


if __name__ == "__main__":

    # region 随机数
    # 返回1~10000间的一个整数,作为随机种子 opt的类型为:<class 'argparse.Namespace'>
    manualSeed = random.randint(1, 10000)  # fix seed
    print("Random Seed: ", manualSeed)
    # 保证在有种子的情况下生成的随机数都是一样的
    random.seed(manualSeed)
    # 设置一个用于生成随机数的种子,返回的是一个torch.Generator对象
    torch.manual_seed(manualSeed)
    # endregion


    # region 数据集 分割
    train_dataset = ShapeNetDataset(
        root=pathDataset,
        classification=False,
        class_choice=[class_choice])
    train_dataloader = torch.utils.data.DataLoader(
        train_dataset,
        batch_size=batchSize,
        shuffle=True,                       #shuffle=True 打乱数据顺序
        num_workers=int(workers))
    test_dataset = ShapeNetDataset(
        root=pathDataset,
        classification=False,
        class_choice=[class_choice],
        split='test',
        data_augmentation=False)
    testdataloader = torch.utils.data.DataLoader(
        test_dataset,
        batch_size=batchSize,
        shuffle=True,
        num_workers=int(workers))

    #训练集-验证集-测试集 位于数据集文件夹中的 train_test_split
    print('Amount of train =',len(train_dataset), '. Amount of test =',len(test_dataset))
    num_classes = train_dataset.num_seg_classes
    print(class_choice,'is segmented into {', num_classes,'} sections.')
    # endregion


    # region 点云分割模型 实例化
    classifier = PointNetDenseCls(k=num_classes, feature_transform=featureTransform)
    # 如果有预训练模型,将预训练模型加载
    if pathModel != '':
        classifier.load_state_dict(torch.load(pathModel))

    #Adam优化器,用于优化神经网络的权重
    #betas是Adam优化器的两个衰减因子,分别用于一阶矩估计(mean)和二阶矩估计(uncentered variance)
    #0.9 是用于计算梯度的指数移动平均的衰减因子
    #0.999 是用于计算梯度平方的指数移动平均的衰减因子
    optimizer = optim.Adam(classifier.parameters(), lr=learn_rate, betas=(0.9, 0.999))

    #学习率调度器,用于调整优化器的学习率。StepLR 是一种简单的调度器,它在每个指定的步数(step_size)降低学习率。
    #每经过 20 个epoch(训练数据集的完整循环),学习率将被调整
    #将当前学习率乘以gamma来降低学习率。每经过step_size步,学习率将变为当前学习率的一半。
    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)

    #将模型移动到 GPU 上进行计算
    classifier.cuda()
    # endregion


    # region 训练

    #将输入字符串 x 用深蓝色着色,通常用于在命令行界面中打印带有颜色的文本。
    test_blue = lambda x: '\033[94m' + x + '\033[0m'

    for epoch in range(epochs):
        # 表示完成了一个训练周期,更新学习率。
        scheduler.step()

        # 遍历
        for i, data in enumerate(train_dataloader, 0):
            # 点-标签
            points, target = data # points: torch.Size([batchSize,2500,3])。target:torch.Size([batchSize,1,2500])
            points = points.transpose(2, 1) # points:torch.Size([batchSize, 3, 2500]) = batchSize个3行2500列

            # cuda() 方法用于将张量的数据存储在GPU。
            points, target = points.cuda(), target.cuda()

            # 模型参数的梯度归零。避免backward时梯度累加。通常在每个训练迭代的开始处调用
            optimizer.zero_grad()

            # 训练
            classifier = classifier.train()
            # 预测:pred=torch.Size([batchSize, 2500, 4]);trans=torch.Size([2, 3, 3]);trans_feat不保存,为None。
            pred, trans, trans_feat = classifier(points)

            # 将张量 pred 重新调整为一个二维张量,其中每行有num_classes列。-1表示由PyTorch自动计算该维度的大小,以保持原有张量元素的总数不变。
            pred = pred.view(-1, num_classes)       #torch.Size([batchSize*2500, 4])

            # target.view(-1, 1):通过view方法将target调整为一个二维张量,其中每行有一个元素。-1表示由PyTorch自动计算该维度的大小,以保持原有张量元素的总数不变。
            # [:, 0]:使用切片操作,保留每行的第一个元素。
            # -1:将每个元素减去1。这样的操作可能用于调整标签的范围,使其符合模型输出的范围。
            target = target.view(-1, 1)[:, 0] - 1   #torch.Size([batchSize*2500, 4])

            # 负对数似然损失(Negative Log Likelihood Loss)
            loss = F.nll_loss(pred, target)

            # 将计算得到的正则项乘以一个系数(0.001)后添加到原始的损失上。防止过拟合或者提高模型的泛化能力。
            # 对feature_transform中64X64的变换矩阵做正则化,满足AA^T=I
            if featureTransform:
                loss += feature_transform_regularizer(trans_feat) * 0.001

            # loss反向传播。计算损失相对于模型参数的梯度。在前向传播之后,通过调用该方法,PyTorch会自动计算各个模型参数对损失的梯度。这些梯度将被存储在相应参数的.grad 属性中。
            loss.backward()
            # 梯度下降,参数优化。根据优化算法的规则,使用梯度信息来更新模型的参数,使损失函数值减小,从而让模型更好地适应训练数据。
            optimizer.step()

            # pred_choice包含了每个样本的模型预测的类别索引。
            # max(1):对底层数据进行操作,沿着第 1 个维度(通常是类别的维度)找到每行的最大值。返回一个元组,包含最大值和对应的索引。
            # [1]:取元组中的第二个元素,即最大值对应的索引。
            pred_choice = pred.data.max(1)[1] # torch.Size([batchSize*2500])

            # 当前批次中模型的正确预测数量。eq:逐元素比较
            correct = pred_choice.eq(target.data).cpu().sum()

            # 输出信息
            print('[%d: %d/%d] train loss=%f; accuracy=%f' % (
            epoch, i, len(train_dataset)/batchSize, loss.item(), correct.item()/float(batchSize*2500)))


            # ---------- 每隔10个批次,验证一次
            if i % 10 == 0:
                j, data = next(enumerate(testdataloader, 0))
                points, target = data
                points = points.transpose(2, 1)
                points, target = points.cuda(), target.cuda()
                classifier = classifier.eval()
                pred, _, _ = classifier(points)
                pred = pred.view(-1, num_classes)
                target = target.view(-1, 1)[:, 0] - 1
                loss = F.nll_loss(pred, target)
                pred_choice = pred.data.max(1)[1]
                correct = pred_choice.eq(target.data).cpu().sum()
                print('[%d: %d/%d] %s loss: %f  accuracy: %f' % (
                epoch, i, len(train_dataset)/batchSize, test_blue('test==='), loss.item(), correct.item() / float(batchSize * 2500)))

        # 保存模型
        torch.save(classifier.state_dict(), '%s/seg_model_%s_%d.pth' % (outFolder, class_choice, epoch))
    # endregion


    # region benchmark mIOU
    shape_ious = []
    # tqdm 进度条,以显示循环迭代的进度。tqdm 的名称来自阿拉伯语 "taqaddum",意为 "进展" 或 "前进"。
    for i, data in tqdm(enumerate(testdataloader, 0)):
        points, target = data
        points = points.transpose(2, 1)
        points, target = points.cuda(), target.cuda()
        classifier = classifier.eval()
        pred, _, _ = classifier(points)
        pred_choice = pred.data.max(2)[1] #第 2 个维度的最大值

        pred_np = pred_choice.cpu().data.numpy()
        target_np = target.cpu().data.numpy() - 1 #标签的取值范围调整为从0开始

        for shape_idx in range(target_np.shape[0]):
            parts = range(num_classes)
            part_ious = []
            for part in parts:
                I = np.sum(np.logical_and(pred_np[shape_idx] == part, target_np[shape_idx] == part))#计算当前类别 part 的交集数量
                U = np.sum(np.logical_or(pred_np[shape_idx] == part, target_np[shape_idx] == part)) #计算当前类别 part 的并集数量
                if U == 0:
                    # 如果交集和并集为空,将 IoU 设置为 1。这是为了处理分母为零的情况,确保 IoU 在这种情况下被定义为 1。
                    iou = 1  # If the union of groundtruth and prediction points is empty, then count part IoU as 1。
                else:
                    iou = I / float(U)
                part_ious.append(iou) #将每个类别 part 的 IoU 添加到 part_ious 列表中
            shape_ious.append(np.mean(part_ious)) # 计算所有类别的 IoU 的平均值,并将其添加到 shape_ious 列表中,表示当前形状的平均 IoU。

    print("mIOU for class {}: {}".format(class_choice, np.mean(shape_ious)))
    # endregion

3.5 show_seg.py

生成txt,或者利用 laspy 生成点云。不费那老鼻子劲搞原作者的可视化工具。

from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn.parallel
import torch.utils.data
from torch.autograd import Variable
import warnings
warnings.filterwarnings('ignore')

from pointnet.dataset import ShapeNetDataset
from pointnet.model import PointNetDenseCls


pathModel = 'C:/xinjiang/py/xjPointNet/trainModelSeg/seg_model_Chair_9.pth'
pathDataset = 'C:/xinjiang/py/xjPointNet/shapenetcore_partanno_segmentation_benchmark_v0/'
choicedClass = 'Earphone'


if __name__ == "__main__":
    test_ds = ShapeNetDataset(
        root=pathDataset,
        class_choice=[choicedClass],
        split='test',
        data_augmentation=False)

    idx = 5
    print("model %d/%d" % (idx, len(test_ds)))

    # torch.Size([2500, 3]),torch.Size([2500])
    point, seg = test_ds[idx]

    # xyz <class 'numpy.ndarray'>    (2500, 3)
    point_np = point.numpy()

    cmap = plt.cm.get_cmap("hsv", 10)
    cmap = np.array([cmap(i) for i in range(10)])[:, :3]
    gt = cmap[seg.numpy() - 1, :]

    state_dict = torch.load(pathModel)
    classifier = PointNetDenseCls(k= state_dict['conv4.weight'].size()[0])
    classifier.load_state_dict(state_dict)
    classifier.eval()

    point = point.transpose(1, 0).contiguous()

    point = Variable(point.view(1, point.size()[0], point.size()[1]))
    pred, _, _ = classifier(point)
    # label <class 'torch.Tensor'> torch.Size([1, 2500])
    pred_choice = pred.data.max(2)[1]
    # label <class 'numpy.ndarray'> (2500, 1)
    pred_choice_np = np.reshape(pred_choice.numpy(), (pred_choice.numpy().size, 1))

    # rgb <class 'numpy.ndarray'>    (2500, 3)
    pred_color = cmap[pred_choice.numpy()[0], :]

    # xyzrgbl <class 'numpy.ndarray'>    (2500, 7)
    pcrgbl = np.hstack((point_np, pred_color*255, pred_choice_np))

    # # ---------- 保存成 txt ----------
    # pathResTxt = 'result_' + choicedClass+'_'+str(idx) + '.txt'
    # np.savetxt(pathResTxt, pcrgbl, fmt='%f', delimiter='\t')

    # ---------- 保存成 las ----------
    import laspy
    # data
    newx = point_np[:, 0]
    newy = point_np[:, 1]
    newz = point_np[:, 2]
    newred = 255*pred_color[:, 0]
    newgreen = 255*pred_color[:, 1]
    newblue = 255*pred_color[:, 2]
    newclassification = pred_choice_np[:, 0]
    minx = min(newx)
    miny = min(newy)
    minz = min(newz)
    # create a new header
    newheader = laspy.LasHeader(point_format=3, version="1.2")
    newheader.scales = np.array([0.0001, 0.0001, 0.0001])
    newheader.offsets = np.array([minx, miny, minz])
    newheader.add_extra_dim(laspy.ExtraBytesParams(name="Classification", type=np.uint8))
    # create a Las
    newlas = laspy.LasData(newheader)
    newlas.x = newx
    newlas.y = newy
    newlas.z = newz
    newlas.red = newred
    newlas.green = newgreen
    newlas.blue = newblue
    newlas.Classification = newclassification
    # write
    newLasPath = 'result_' + choicedClass + '_' + str(idx) + '.las'
    newlas.write(newLasPath)

四、结果

训练了10Chairepoch,用最后一个模型分割某个Table

效果还不错呢

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【c语言】指针小结

一、指针是什么&#xff1f; 可以通过运算符&来取得变量实际保存的 起始地址 。 &#xff08;这个地址是虚拟地址&#xff0c;并不是真正物理内存上的地址。&#xff09; 数据类型 *标识符 &变量; int *pa &a; int *pa NULL; (NULL表示地址为0的内存空间&a…

金和OA JC6 ntko-upload 任意文件上传漏洞

产品介绍 金和网络是专业信息化服务商,为城市监管部门提供了互联网监管解决方案,为企事业单位提供组织协同OA系统开发平台,电子政务一体化平台,智慧电商平台等服务。 漏洞概述 金和 OA JC6 ntko-upload接口处存在任意文件上传漏洞&#xff0c;攻击者可以通过构造特殊请求包上…

使用Go语言的HTTP客户端库进行API调用

随着微服务架构和RESTful API的普及&#xff0c;API调用成为了日常开发中的常见任务。Go语言提供了多种工具和库来帮助开发者轻松地与API进行交互。本文将介绍如何使用Go语言的HTTP客户端库进行API调用。 在Go语言中&#xff0c;标准库中的net/http包提供了基本的HTTP客户端功…

【物联网】手把手完整实现STM32+ESP8266+MQTT+阿里云+APP应用——第3节-云产品流转配置

&#x1f31f;博主领域&#xff1a;嵌入式领域&人工智能&软件开发 本节目标&#xff1a;本节目标是进行云产品流转配置为后面实际的手机APP的接入做铺垫。云产品流转配置的目的是为了后面能够让后面实际做出来的手机APP可以控制STM32/MCU&#xff0c;STM32/MCU可以将数…

光明源@智慧厕所技术:优化生活,提升卫生舒适度

在当今数字科技飞速发展的时代&#xff0c;我们的日常生活正在经历一场革命&#xff0c;而这场革命的其中一个前沿领域就是智慧厕所技术。这项技术不仅仅是对传统卫生间的一次升级&#xff0c;更是对我们生活品质的全方位提升。从智能感应到数据分析&#xff0c;从环保设计到舒…

vmware中ubuntu虚拟机不能够用共享文件夹

有时候发现装好虚拟机后&#xff0c;然后 虚拟机-设置-选项-共享文件夹 然后使用快捷键ctrlaltt 打开命令行&#xff0c;cd /mnt下没有看到hgfs文件夹 解决办法是安装vmware tools工具 此时想通过点击 虚拟机-安装vmwaretools工具 按钮 居然发现该按钮是灰色的&#xff0…

Spring中的数据校验

文章目录 引言摘要正文基于 ValidationUtils的简单校验基于自定义 Validator的校验Spring内置校验 LocalValidatorFactoryBeanHibernateValidator校验使用HibernateValidator自定义校验规则 总结 引言 我们在日常的软件开发过程中&#xff0c;尤其是WEB开发过程中&#xff0c;…

vr眼镜和AR眼镜的区别有哪些?哪些产品可以支持VR应用?

vr眼镜怎么连接手机 要将VR眼镜连接到手机上&#xff0c;您可以按照以下步骤进行&#xff1a; 1. 确保您的手机支持VR应用程序&#xff1a;首先&#xff0c;确保您的手机具备运行VR应用程序的硬件和软件条件。一些VR应用程序可能对设备有特定的要求&#xff0c;如处理器性能、操…

Dart调用JS对10000条定位数据滤波

使用Dart调用JS&#xff0c;还是为了练习跨语言调用&#xff1b; 一、编写对应的JS代码 平时在开发时不推荐将算法放在JS里&#xff0c;我这里是简单的做一下数据过滤&#xff1b; 首先生成一些随机定位数据&#xff0c;在实际开发中可以使用真实数据&#xff1b; // 随机定…

【DevOps-06】Jenkins实现CI/CD操作

一、简要说明 基于Jenkins拉取GitLab的SpringBoot代码进行构建发布到测试环境实现持续集成 基于Jenkins拉取GitLab指定发行版本的SpringBoot代码进行构建发布到生产环境实现CD实现持续部署 二、准备Springboot工程 1、IDEA新建工程 2、填写项目工程信息 3、选择Springboot版本…

从查询到高质量回答:发挥 RAG 和 Rerankers 的潜力

每日推荐一篇专注于解决实际问题的外文&#xff0c;精准翻译并深入解读其要点&#xff0c;助力读者培养实际问题解决和代码动手的能力。 欢迎关注公众号 原文标题&#xff1a;From Queries to Quality Answers: Harnessing the Potentials of RAG and Rerankers 原文地址&…

微信小程序如何搜索iBeacon设备

1.首先在utils文件夹下创建bluetooth.js和ibeacon.js 2.在 bluetooth.js文件中写入 module.exports {initBluetooth: function () {// 初始化蓝牙模块wx.openBluetoothAdapter({success: function (res) {console.log(蓝牙模块初始化成功);},fail: function (res) {console.l…

citeSpace保姆级安装使用教程

citeSpace保姆级安装使用教程 文章目录 citeSpace保姆级安装使用教程CiteSpace功能与参数区安装使用知网数据导出citespace数据导入结果 设置操作隐藏节点 CiteSpace功能与参数区 安装 citeSpace安装教程 citespace下载 网址&#xff1a;https://citespace.podia.com/ 安装之…

Nginx 的 gzip 压缩

目录 1. 为什么要开启gzip 压缩 2.对网站配置文件进行修改 1. 为什么要开启gzip 压缩 nginx使用gzip压缩主要是为了降低网站的带宽消耗和提升访问速度。通过对页面进行压缩&#xff0c;可以减少传输的数据量&#xff0c;从而减少网络传输的时间和带宽消耗。 当浏览器接收到压…

视频剪辑指南:如何将多个视频快速批量合并的方法

在日常生活和工作中&#xff0c;经常要将多个视频片段合并为一个完整的视频。但是手动剪辑每个视频不仅费时&#xff0c;而且效率低下。那么如何解决这个问题呢&#xff0c;可以采用一些快速批量合并视频的方法。现在一起来看看云炫AI智剪如何批量合并视频的具体步骤吧。 合并…

天津大数据培训机构 大数据时代已到来!

大数据时代已经来临&#xff0c;越来越多的人开始关注大数据&#xff0c;并且准备转行大数据。但是&#xff0c;对于一个外行人或者小白来说&#xff0c;大数据是什么&#xff1f;大数据需要学什么&#xff1f;什么样的大数据培训机构是靠谱的&#xff1f;这几个简单的问题就足…

数据库 补充 树,红黑树,b树,b+树

01.树 02.二叉树和二叉平衡树 03.平衡二叉树的恢复 将导致不平衡的结点称作被破坏者&#xff0c;破坏了结点的平衡的结点成为破坏者&#xff0c;经过调整可以让该树平衡的结点称为调整结点。 LL型&#xff1a; 以被破坏者的左孩子结点作为调整结点&#xff0c;对其进行右旋…

【书生·浦语大模型实战营01】《书生·浦语大模型全链路开源体系》

《书生浦语大模型全链路开源体系》 1. 大模型成为热门关键词 LLM发展时间轴 2. 书生浦语全链条开源开放体系 微调&#xff1a;XTuner 支持全参数微调支持LoRA等低成本微调 评测&#xff1a;OpenCompass 全方位评测&#xff0c;性能可复现80套评测集&#xff0c; 40万道题目…

网工内推 | 保险业网工,有绩效奖金,CISP认证优先,最高16K

01 华贵人寿保险股份有限公司 招聘岗位&#xff1a;系统管理岗&#xff08;主机管理方向&#xff09; 职责描述&#xff1a; 1.负责数据中心私有云平台的规划建设以及后期的运行维护&#xff1b; 2.负责公司操作系统的规划、部署与日常维护&#xff1b; 3.负责操作系统运维相关…

idea设置注释在鼠标当前位置,使其不从顶格位置添加注释

idea设置注释在鼠标当前位置&#xff0c;使其不从顶格位置添加注释 默认情况下&#xff0c;注释都是从改行的顶格开始&#xff0c;看起来不太美观而且不易清除分级 设置让其从代码处开始&#xff0c;步骤&#xff1a;File–>Sttings–>Editor–>Code Style &#xff…