三维点云深度网络 PointNeXt 的安装配置与测试

news2024/9/27 21:28:15

Title: 三维点云深度网络 PointNeXt 的安装配置与测试


文章目录

  • 前言
  • I. 环境创建
    • 1. 容器创建
    • 2. 容器中安装 CUDA Toolkit 11.3
  • II. 安装过程
    • 1. 安装 PointNeXt
    • 2. 解决安装问题
  • III. 数据准备
    • 1. 数据准备的执行
    • 2. 数据准备的原理
  • IV. 运行测试
    • 1. 显存溢出
    • 2. 训练
    • 3. 测试
    • 4. 标注颜色
  • V. 调试配置
  • 总结


前言

PointNet (2016年) 和 PointNet++ (2017年) 实现了三维点云的部件分割、目标分类、场景语义解析, 是深度神经网络应用于三维点云理解的开山之作.

PointNeXt (2022年, the neXt version PointNet) 是在前辈基础上的优化, 提出了训练策略的优化和网络结构的优化, 旨在进一步提升对三维点云理解的性能.

PoinrNet/PointNet++ 的 Pytorch 版本: https://github.com/yanx27/Pointnet_Pointnet2_pytorch

PointNeXt: https://github.com/guochengqian/PointNeXt

这篇博文仅简单记录一下 PointNeXt 安装配置中的踩坑过程.


I. 环境创建

1. 容器创建

先按照之前博客 “Docker 环境下 3D Guassian Splatting 的编译和配置” 中步骤建立 Docker 容器.

拉取 Ubuntu 20 镜像, 系统版本由后面安装的 Cuda 编译器版本决定.

docker pull ubuntu:focal

创建容器

docker run -it  -d -p 8889:8888 -p 6007:6006 -p 8023:22 \
			--gpus all --ipc=host --name pointnext \
			-v /home/robot/working_space/python/pytorch:/workspace \
			--workdir=/workspace -e DISPLAY=unix$DISPLAY \
			-v /tmp/.X11-unix:/tmp/.X11-unix:rw \
			-e NVIDIA_DRIVER_CAPABILITIES=all ubuntu:focal  /bin/bash

注意宿主机的端口不可以重复绑定, 要与其他容器 (如 gaussian_splatting) 中绑定端口号有区别. 容器名字 pointnext, 容器镜像 ubuntu:focal.


2. 容器中安装 CUDA Toolkit 11.3

PointNeXt 提示 “Cuda-11.3 is required”.

wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda_11.3.0_465.19.01_linux.run
sh cuda_11.3.0_465.19.01_linux.run

记得 “安装驱动 driver” 选项要取消, 因为宿主机中已安装 Nvidia 驱动程序了.

查看是否安装成功.

nvcc --version

CUDA 11.3 编译器在后面安装 torch_scatter 时会用到.


II. 安装过程

1. 安装 PointNeXt

按照 PointNeXt readme 指引操作.

git clone --recurse-submodules git@github.com:guochengqian/PointNeXt.git
cd PointNeXt
source update.sh
source install.sh

安装过程因为环境问题会遇到如下问题.

问题一. undefined symbol: iJIT_NotifyEvent

参考网友处理方法执行.

conda install mkl=2022.0

因为 PointNeXt 是 2022 年发表, 故试了该版本. 如选其他版本(如 2024、2020 等)安装提示都有问题.


2. 解决安装问题

试运行 PointNeXt:

CUDA_VISIBLE_DEVICES=0,1 python examples/segmentation/main.py \
					--cfg cfgs/s3dis/pointnext-s.yaml  mode=train

提示错误如下:

问题二.
Traceback (most recent call last):
File “examples/segmentation/main.py”, line 13, in
from torch_scatter import scatter
ModuleNotFoundError: No module named ‘torch_scatter’

解决方法:

pip install torch_scatter

安装 torch_scatter 前, 需已经安装正确版本的 mkl. 另外, conda 包管理器则不含 torch_scatter 安装包.

再次试运行 PointNeXt, 遇见新的错误.

问题三. ModuleNotFoundError: No module named ‘pointnet2_batch_cuda’

参考网友博文可知, 需要编译安装 pointnet2_batch_cuda.

切换到 openpoint 对应文件夹下, 运行安装命令.

cd openpoints/cpp/pointnet2_batch
python setup.py install

再次试运行 PointNeXt, 遇见新的错误.

问题四. ModuleNotFoundError: No module named ‘chamfer’

老办法安装一下.

pip install chamfer

持续下去, 直到不再遇见缺少模块安装的问题.


III. 数据准备

1. 数据准备的执行

直接下载网友搬运来的 S3DIS 数据库的压缩文件 Stanford3dDataset_v1.2_Aligned_Version.zip.

但是 S3DIS 看似缺少标注数据, 把注释/标注/标签数据蕴含在 Annotations 文件夹下的各个文件的文件名中了.

例如

Stanford3dDataset_v1.2_Aligned_Version/Area_1/conferenceRoom_1 文件夹包含一个完整场景的点云数据及注释.

该文件夹下包含的文件 conferenceRoom_1.txt 包含这个场景全部点的坐标和颜色, 以 XYZRGB 一行行排列.

该文件夹下包含的子文件夹 Annotations 的下面包含的一系列 .txt 文件就是注释/标注文件.

.txt 文件名以 “分类标签_序号.txt” 形式构造, .txt 文件内包含这对应于这个 “分类标签” 的点云数据.

其中 beam_1.txt 中就包含了 conferenceRoom_1 这个完整场景中标注为 “beam” 这一类别的点云.

其他文件以此类推, 相同的标注分类的不同点云用序号区别, 如 chair_1.txtchair_2.txtchair_3.txt 等.

另外, PointNeXt 官方给出了适配的数据集的组织形式如下, 与下载的原始数据集完全不同.

data
 |--- S3DIS
        |--- s3disfull
                |--- raw
                      |--- Area_6_pantry_1.npy
                      |--- ...
                |--- processed
                      |--- s3dis_val_area5_0.040.pkl 

这就要想到 PointNet/PointNet++ 的数据预处理方法了. 先下载

git clone https://github.com/yanx27/Pointnet_Pointnet2_pytorch.git

将 S3DIS 数据集 Stanford3dDataset_v1.2_Aligned_Version 保存到 Pointnet_Pointnet2_pytorchdata/s3dis/ 路径下, 并执行:

cd data_utils
python collect_indoor3d_data.py

预处理后数据保存在 data/stanford_indoor3d/. 这些预处理的数据就是训练 PointNeXt 所需要的.


2. 数据准备的原理

我们简单看一下数据预处理做了什么工作.

/Pointnet_Pointnet2_pytorch/data_utils/collect_indoor3d_data.py 的源代码及注释:

import os
import sys
from indoor3d_util import DATA_PATH, collect_point_label

BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # __file__ 当前 .py 文件所在目录的路径
ROOT_DIR = os.path.dirname(BASE_DIR) # 上一级目录, 即 PointNet++ 文件夹路径
sys.path.append(BASE_DIR) # 加入模块扫描路径

anno_paths = [line.rstrip() for line in open(os.path.join(BASE_DIR, 'meta/anno_paths.txt'))] 
# 按行读入 anno_paths.txt 中的标注/注释文件的目录路径, 删除行尾空格换行符号等, 形成列表
anno_paths = [os.path.join(DATA_PATH, p) for p in anno_paths]  # 数据集标注文件所在目录的绝对路径

output_folder = os.path.join(ROOT_DIR, 'data/stanford_indoor3d')
if not os.path.exists(output_folder):
    os.mkdir(output_folder)

# Note: there is an extra character in the v1.2 data in Area_5/hallway_6. It's fixed manually.
for anno_path in anno_paths: # 开始循环, 一次循环完成一个目录(一个完整点云文件)的处理
    print(anno_path)
    try:
        elements = anno_path.split('/')
        out_filename = elements[-3]+'_'+elements[-2]+'.npy' # Area_1_hallway_1.npy 构造输出文件的名称
        collect_point_label(anno_path, os.path.join(output_folder, out_filename), 'numpy') 
        # 制造适合学习的数据集, 有数据也有标注. 格式是适合于 pytorch 读取和保存的 .npy 二进制格式
    except:
        print(anno_path, 'ERROR!!')

/Pointnet_Pointnet2_pytorch/data_utils/indoor3d_util.py 的部分源代码及注释:

import numpy as np
import glob
import os
import sys

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT_DIR = os.path.dirname(BASE_DIR)
sys.path.append(BASE_DIR)

DATA_PATH = os.path.join(ROOT_DIR, 'data','s3dis', 'Stanford3dDataset_v1.2_Aligned_Version')
g_classes = [x.rstrip() for x in open(os.path.join(BASE_DIR, 'meta/class_names.txt'))]  
# 标注/注释对应的类别
g_class2label = {cls: i for i,cls in enumerate(g_classes)}  # 注释对应的类别
g_class2color = {'ceiling':	[0,255,0],      
                 'floor':	[0,0,255],
                 'wall':	[0,255,255],
                 'beam':        [255,255,0],
                 'column':      [255,0,255],
                 'window':      [100,100,255],
                 'door':        [200,200,100],
                 'table':       [170,120,200],
                 'chair':       [255,0,0],
                 'sofa':        [200,100,100],
                 'bookcase':    [10,200,100],
                 'board':       [200,200,200],
                 'clutter':     [50,50,50]} 
g_easy_view_labels = [7,8,9,10,11,1]
g_label2color = {g_classes.index(cls): g_class2color[cls] for cls in g_classes}


# -----------------------------------------------------------------------------
# CONVERT ORIGINAL DATA TO OUR DATA_LABEL FILES
# -----------------------------------------------------------------------------

def collect_point_label(anno_path, out_filename, file_format='txt'):
    """ Convert original dataset files to data_label file (each line is XYZRGBL).
        We aggregated all the points from each instance in the room.

    Args:
        anno_path: path to annotations. e.g. Area_1/office_2/Annotations/
        out_filename: path to save collected points and labels (each line is XYZRGBL)
        file_format: txt or numpy, determines what file format to save.
    Returns:
        None
    Note:
        the points are shifted before save, the most negative point is now at origin.
    """
    points_list = []
    for f in glob.glob(os.path.join(anno_path, '*.txt')): 
        # 返回 anno_path 目录下所有 .txt 文件列表, 逐个文件开始循环
        cls = os.path.basename(f).split('_')[0] 
        # os.path.basename(f) 为 f 对应文件名, 文件名的第一部分就是注释(标注)信息
        print(f)
        if cls not in g_classes: # note: in some room there is 'staris' class..
            cls = 'clutter'

        points = np.loadtxt(f)
        labels = np.ones((points.shape[0],1)) * g_class2label[cls] 
        # 生成每个点对应的标注 L (Label)
        points_list.append(np.concatenate([points, labels], 1)) # Nx7
        # np.concatenate([points, labels], 1) 实现每个点 XYZRGB 后面加上 L, 维度 Nx7
        # 每一个 .txt 生成一个 points_list 列表中的元素
    
    data_label = np.concatenate(points_list, 0)
    # 把所有 anno_path 目录下的 .txt 文件合并为 data_label 数组
    xyz_min = np.amin(data_label, axis=0)[0:3]
    # 点云分别在 XYZ 三轴上的最小值
    data_label[:, 0:3] -= xyz_min
    # 以最小值为原点, 进行平移
    
    if file_format=='txt':
        fout = open(out_filename, 'w')
        for i in range(data_label.shape[0]):
            fout.write('%f %f %f %d %d %d %d\n' % \
                          (data_label[i,0], data_label[i,1], data_label[i,2],
                           data_label[i,3], data_label[i,4], data_label[i,5],
                           data_label[i,6]))
        fout.close()
    elif file_format=='numpy':
        np.save(out_filename, data_label)
        # 把数组 data_label 保存到 .npy 二进制文件
    else:
        print('ERROR!! Unknown file format: %s, please use txt or numpy.' % \
            (file_format))
        exit()

IV. 运行测试

1. 显存溢出

pointnext-xl.yaml 作为配置文件, 运行

CUDA_VISIBLE_DEVICES=0,1 python examples/segmentation/main.py \
				--cfg cfgs/s3dis/pointnext-xl.yaml  mode=train

遇见内存溢出错误.

RuntimeError: CUDA out of memory. Tried to allocate 434.00 MiB (GPU 0; 23.66 GiB total capacity; 19.15 GiB already allocated; 442.75 MiB free; 19.32 GiB reserved in total by PyTorch) If reserved memory is >> allocated memory try setting max_split_size_mb to avoid fragmentation. See documentation for Memory Management and PYTORCH_CUDA_ALLOC_CONF

故更换其他配置文件测试.


2. 训练

pointnext-s.yaml 作为配置文件, 运行

CUDA_VISIBLE_DEVICES=0,1 python examples/segmentation/main.py \
				--cfg cfgs/s3dis/pointnext-s.yaml  mode=train

中间过程中得到训练后的最佳模型.

best_model
最后训练结束. train
期间可以通过
watch -n 1 nvidia-smi

查看 GPU 工作情况.

两块 GeForce RTX 3090 显卡, 训练了将近 14 小时.


3. 测试

利用训练中得到的最佳模型 (预训练模型) 进行验证测试.

CUDA_VISIBLE_DEVICES=0,1 python examples/segmentation/main.py \
				--cfg cfgs/s3dis/pointnext-s.yaml  mode=test \
				--pretrained_path ./log/s3dis/s3dis-train-pointnext-s-ngpus2-20240724-135644-6vNNukWqpM7JJt5oHsXKzW/checkpoint/s3dis-train-pointnext-s-ngpus2-20240724-135644-6vNNukWqpM7JJt5oHsXKzW_ckpt_best.pth
validation

也可利用训练中得到的最佳模型 (预训练模型) 进行带有可视化输出的验证测试.

CUDA_VISIBLE_DEVICES=0,1 bash script/main_segmentation.sh \
		cfgs/s3dis/pointnext-s.yaml wandb.use_wandb=False mode=test \
		--pretrained_path ./log/s3dis/s3dis-train-pointnext-s-ngpus2-20240724-135644-6vNNukWqpM7JJt5oHsXKzW/checkpoint/s3dis-train-pointnext-s-ngpus2-20240724-135644-6vNNukWqpM7JJt5oHsXKzW_ckpt_best.pth \
		visualize=True

测试验证过程中, 会在 /PointNeXt/log/s3dis/s3dis-train-pointnext-s-ngpus2-20240724-135644-6vNNukWqpM7JJt5oHsXKzW/visualization 下生成一堆 .obj 3D 文件.

任意选取其中一个, 如 gt-s3dis-Area5-2.obj, 利用 Meshlab 软件打开, 可以查看可视化结果.

视角不同视角下的点云图像
1. 门口snapshot00_gt-s3dis-Area5-2.obj
2. 轴侧snapshot02_gt-s3dis-Area5-2.obj
3. 内部snapshot01_gt-s3dis-Area5-2.obj

4. 标注颜色

上面分割测试的可视化结果, 由颜色来表示分类标签. 这里的标注/标签的颜色我们简单绘图对照一下.

import matplotlib.pyplot as plt
import numpy as np

class2color = {'ceiling':     [0, 255, 0],
                'floor':       [0, 0, 255],
                'wall':        [0, 255, 255],
                'beam':        [255, 255, 0],
                'column':      [255, 0, 255],
                'window':      [100, 100, 255],
                'door':        [200, 200, 100],
                'table':       [170, 120, 200],
                'chair':       [255, 0, 0],
                'sofa':        [200, 100, 100],
                'bookcase':    [10, 200, 100],
                'board':       [200, 200, 200],
                'clutter':     [50, 50, 50]}

index = 0
nrows = 4
ncols = 4
fig, axs = plt.subplots(nrows=nrows, ncols=ncols, figsize=(6,8))
for label, color in class2color.items():
    color_img = np.array(color)
    color_img = np.expand_dims(color_img, 0).repeat(5, axis=0)
    color_img = np.expand_dims(color_img, 0).repeat(5, axis=0)
    # 构造颜色块

    axs[index//ncols][index%ncols].imshow(color_img)
    axs[index//ncols][index%ncols].axis("off")
    axs[index//ncols][index%ncols].set_title(label)
    index += 1

while index < 16:
    axs[index//ncols][index%ncols].axis("off")
    index += 1

plt.savefig("color.png")
plt.show()

得到分类标签对应的颜色表.

color

这样对照分类标注/标签颜色表, 打开可视化结果 .obj 文件,可以感受一下 PointNeXt 的对点云的理解与分割能力.


V. 调试配置

宿主机上用 vscode, 打开容器中的 PointNeXt 代码

vscode terminal 中虚拟环境改为 openpoints

conda activate openpoints

ctrl+shift+P, 选择 python 解释器为虚拟环境对应解释器 Python 3.7.12('openpoints') ~/miniforge3/envs/openpoints/bin/python.

我们利用 vpdb 这一给 vscode 自动生成调试配置的工具, 来实现 docker 中 PointNeXt 的跟踪调试设置.

先虚拟环境中安装 vpdb

pip intall vpdb

只需在正常的 Python 命令行前面加上 vpdb, 就可以利用 vpdb 创建调试启动配置用的 .vscode/launch.json 文件.

vpdb CUDA_VISIBLE_DEVICES=0,1 python examples/segmentation/main.py \
					--cfg cfgs/s3dis/pointnext-s.yaml  mode=train

下面就可以愉快地跟踪、调试、学习了.

debug

总结

只是日常踩坑记录, 以备忘.

如有问题请指教.

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

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

相关文章

UDP/TCP协议解析

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

mysql一个小问题引发的思考-mysql类型转换-查询缓存 及 MYSQL查询缓存以及自动选择不使用查询缓存的情况

一、mysql一个小问题引发的思考-mysql类型转换-查询缓存 最近在做的一个项目中有一个SQL语句发现点问题&#xff0c;大概如下&#xff1a; select * from table where cid0 or find_in_set(1, cid); 数据表中的字段cid是字符串类型&#xff0c;原来的后端同学未提过此字段还能是…

Pytorch使用教学3-特殊张量的创建与类型转化

1 特殊张量的创建 与numpy类似&#xff0c;PyTorch中的张量也有很多特殊创建的形式。 zeros:全0张量 # 形状为2行3列 torch.zeros([2, 3]) # tensor([[0., 0., 0.], # [0., 0., 0.]])ones:全1张量 # 形状为2行3列 torch.ones([2, 3]) # tensor([[1., 1., 1.], # …

IEC104转MQTT网关轻松将IEC104设备数据传输到Zabbix、阿里云、华为云、亚马逊AWS、ThingsBoard、Ignition云平台

随着工业4.0的深入发展和物联网技术的广泛应用&#xff0c;IEC 104&#xff08;IEC 60870-5-104&#xff09;作为电力系统中的重要通信协议&#xff0c;正逐步与各种现代监控、管理和云平台实现深度融合。IEC104转MQTT网关BE113作为这一融合过程中的关键设备&#xff0c;其能够…

人工智能:大语言模型提示注入攻击安全风险分析报告下载

大语言模型提示注入攻击安全风险分析报告下载 今天分享的是人工智能AI研究报告&#xff1a;《大语言模型提示注入攻击安全风险分析报告》。&#xff08;报告出品方&#xff1a;大数据协同安全技术国家工程研究中心安全大脑国家新一代人工智能开放创新平台&#xff09; 研究报告…

LeetCode24 两两交换链表中的节点

前言 题目&#xff1a; 24. 两两交换链表中的节点 文档&#xff1a; 代码随想录——两两交换链表中的节点 编程语言&#xff1a; C 解题状态&#xff1a; 没画图&#xff0c;被绕进去了… 思路 思路还是挺清晰的&#xff0c;就是简单的模拟&#xff0c;但是一定要搞清楚交换的…

路由表与IP数据报的转发

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、相关知识 1、路由类型 路由表中有3类路由&#xff1a;直连路由、静态路由、动态路由 直连路由&#xff1a;一般指去往路由器接口直接连接网络的…

Godot游戏制作 05收集物品

创建新场景&#xff0c;添加Area2D节点&#xff0c;AnimatedSprite2D节点 &#xff0c;CollisionShape2D节点 添加硬币 按F键居中&#xff0c;放大视图。设置动画速度设为10FPS&#xff0c;加载后自动播放&#xff0c;动画循环 碰撞形状设为圆形&#xff0c;修改Area2D节点为Co…

政安晨【零基础玩转各类开源AI项目】基于Ubuntu系统部署LivePortrait :通过缝合和重定向控制实现高效的肖像动画制作

目录 项目论文介绍 论文中实际开展的工作 非扩散性的肖像动画 基于扩散的肖像动画 方法论 基于Ubuntu的部署实践开始 1. 克隆代码并准备环境 2. 下载预训练权重 3. 推理 快速上手 驱动视频自动裁剪 运动模板制作 4. Gradio 界面 5. 推理速度评估 社区资源 政安…

Apollo部署与简易架构梳理

文章目录 apollo 安装apollo的基本架构组件机制component编译与加载 节点通讯数据的传输消息读写的实现消息的写端消息读端 常用术语ComponentChannelTaskNodeReader/WriterService/ClientParameter服务发现CRoutineSchedulerMessageDag文件Launch文件Record文件Mainboard Moni…

机会性加密技术:网络安全的新趋势

在当今数字化时代&#xff0c;网络安全已成为各行各业不可忽视的重要议题。随着网络攻击手段的不断演进&#xff0c;传统的加密方式已难以满足复杂多变的安全需求。机会性加密技术&#xff08;Opportunistic Encryption&#xff0c;简称OE&#xff09;&#xff0c;作为一种新兴…

快速入门Jupyter notebook

快速入门 Jupyter notebook 一、前言&#xff08;一&#xff09;优点&#xff08;二&#xff09;特点&#xff08;三&#xff09;调用运行&#xff08;四&#xff09;新建 二、认识界面快捷键&#xff08;一&#xff09;三种模式&#xff08;1&#xff09;蓝色模式&#xff1a;…

qt做的分页控件

介绍 qt做的分页控件 如何使用 创建 Pagination必须基于一个QWidget创建&#xff0c;否则会引发错误。 Pagination* pa new Pagination(QWidget*);设置总页数 Pagination需要设置一个总的页数&#xff0c;来初始化页码。 pa->SetTotalItem(count);设置可选的每页数量…

Spring中使用到的设计模式及其源码分析

前言 众所周知&#xff0c;Spring框架是一个强大而灵活的开发框架。这不&#xff0c;上次的面试刚问到这些&#xff0c;没防住&#xff01;&#xff01;&#xff01;因此下来总结一下。这篇文章主要介绍Spring中使用到的设计模式&#xff0c;自己做个面试复盘&#xff0c;同时…

51单片机嵌入式开发:19、STC89C52R控制LCD1602码表+数码管+后台数显(串口)

STC89C52R控制LCD1602码表数码管后台数显&#xff08;串口&#xff09; 1 概述1.1 项目概述1.2 项目组成部分1.3 功能描述 2 开发环境2.1 支持设备2.2 硬件电路 3 软件代码工程4 演示4.1 Proteus仿真4.2 实物演示 5 总结 1 概述 1.1 项目概述 本项目旨在利用STC89C52R单片机实…

C语言程序设计结构(未完待续...)

文章目录 **C**语言设计的核心&#xff08;灵魂&#xff09;**C**语言程序设计的设计结构顺序结构选择结构循环结构 **C**语言的语句 C语言设计的核心&#xff08;灵魂&#xff09; 程序 数据结构 算法 算法&#xff1a;对于问题解决的方法思路或者步骤 算法的特征&#x…

mac|安装PostgreSQL

1、官网下载&#xff1a;EDB: Open-Source, Enterprise Postgres Database Management 选择需要的版本&#xff1a; 双击得到的.dmg文件 双击&#xff0c;弹窗选择打开&#xff0c;一路next&#xff0c;然后输入你要设置的密码&#xff0c;默认账号名字为&#xff1a;postgres…

vite构建vue3项目hmr生效问题踩坑记录

vite构建vue3项目hmr生效问题踩坑记录 hmr的好处 以下是以表格形式呈现的前端开发中HMR&#xff08;热模块替换&#xff09;带来的好处&#xff1a; 好处描述提升开发效率允许开发者在不刷新整个页面的情况下实时更新修改的代码&#xff0c;减少等待时间保持应用状态在模块替…

一起学Java(1)-新建一个Gradle管理的Java项目

一时兴起&#xff0c;也为了便于跟大家同步学习进展和分享样例代码&#xff0c;遂决定创建一个全新的Java项目&#xff0c;并通过Github与大家分享。本文就是记录该项目的创建过程以及其中的一些知识要点&#xff08;如Gradle等&#xff09;。为了紧跟技术潮流和提高操作效率&a…

怎么给PDF文件加密码?关于PDF文件加密的四种方法推荐

怎么给PDF文件加密码&#xff1f;给PDF文件加上密码是保护文件安全的一种重要方法&#xff0c;特别是当需要在不受授权的访问下保护敏感信息时。这个过程不仅仅是简单地设置密码&#xff0c;而是涉及到对文档内容和访问控制的深思熟虑。加密PDF文件可以有效防止未经授权的用户查…