点云处理入门--PointNetPointNet++论文与代码详解
基础知识
点云数据:
点云是一种通过三维扫描设备或计算机图形学技术获取的三维空间数据,通常由一系列点组成,每个点包含其在三维空间中的坐标(如 x,y,z),有时还可能包含颜色、强度等附加信息。
介绍几种常见的点云存储格式:
1、XYZ
最简单的点云文件格式,通常是一个纯文本文件,每行表示一个点,包含三个数值(x,y,z坐标)
2、PLY
文件可以是纯文本或二进制格式,支持存储点的坐标、颜色、法线等附加信息,支持点云和网格(mesh)数据。
3、OBJ
主要用于存储三维模型(网格),但也可用于点云数据。文件格式为纯文本,支持顶点坐标、纹理坐标、法线等信息,不支持颜色。
Mesh和点云的转换:
Mesh(网格)是一种由顶点、边和面组成的三维模型,而点云是离散的点集合。虽然它们在形式上有所不同,但可以通过一些算法相互转换:
- Mesh 转点云:可以通过泊松采样(Poisson Sampling)、均匀采样(Uniform Sampling)等方法从网格表面生成点云。
- 点云转 Mesh:可以通过泊松重建(Poisson Surface Reconstruction)、Marching Cubes 等算法从点云生成网格。
PointNet论文讲解
论文地址:https://arxiv.org/abs/1612.00593
code:https://github.com/charlesq34/pointnet
pytorch实现:https://github.com/fxia22/pointnet.pytorch
简单来说,点云数据就是一堆坐标点。
如何处理点云数据?

体素化方法:将点云数据转换为规则的三维网格(体素),每个体素包含一定范围内的点云数据,对每个体素内的点云进行特征提取(平均值、最大值等),然后利用3D卷积神经网络(CNN)进行处理。
多视图方法:将点云从多个视角投影到二维平面上,生成多个二维图像,然后使用传统的2D卷积网络进行处理。
这两种方法都没有直接利用点云原始数据,而是经过转换后输入网络。
PointNet直接使用原始点云数据,近年来越来越多的方法这样做。
直接对点云特征进行学习,就需要解决两个问题:点云数据的无序性、旋转的不变性。
1、数据的无序性
点云中的点的排列顺序是随机的,不具有固定的结构。点云中的点可以任意交换位置,而不会影响其对物体或场景的描述。这点和图像数据是不同的,而传统的深度学习方法(如卷积神经网络)通常依赖于输入数据的固定结构(如图像的像素排列)。
这就要求模型能够不受数据排列顺序的影响,提取到准确的特征。
如何能够不受排列顺序影响呢?我们可以使用一些函数来处理,比如max,min,sum,avg等,这些函数的处理结果不受序列顺序影响。但是还有一个问题,如果说一个点云nx3,我们对每一个维度采取max处理,得到1x3的特征(这反映了这个点云在三个维度上的最大值),这样做损失的信息太多了,输出的特征仅仅继承了三个坐标轴上的最大特征。
所以我们需要更多的信息,我们首先将特征映射到高维空间,简单来说将点云数据通过MLP扩充维度,如nx1024,然后 取点云序列在每一个维度的最大值1x1024,组成的向量代表整个点云序列的特征,这样一来,避免了序列顺序的影响,同时也增加了更多的特征信息。

2、旋转不变性
点云在经过旋转变换后,其特征表示应保持不变。
换句话说,如果我们在空间内旋转一个物体,比如一把伞,那么他的点云数据(坐标)都已经被改变,我们需要模型能够包容这种旋转操作,依然识别出这是一把伞。
PointNet保证旋转不变性的做法是–设计了T-Net来学习点云的旋转。
T-Net是一个小型的神经网络。主要目的是通过学习一个仿射变换矩阵,对输入点云或特征空间进行对齐,从而减少姿态变化对模型性能的影响。
具体做法:输入点云为nx3,先扩充维度(三次卷积操作,卷积核为1x1,通道数分别是64,128,1024)得到nx1024向量,然后对其进行最大池化(确保模型对点云无序性具有不变性),得到特征1x1024,然后先通过两层全连接层,分别映射到 512 维和 256 维,最后把256维的特征映射到kxk的矩阵(在网络架构中有两处,一处是3x3,一处是64x64)。这个映射也很简单,就是再过一个全连接,维度变成 k 2 k^2 k2,然后reshape成kxk的矩阵就行。最后这个矩阵就是学习了点云旋转的特征,将其乘以输出的点云即可。
PointNet代码
PointNet分为两个版本,点云分类和点云分割
首先看分类,分类的网络结构图应该如下图所示,红色箭头代表跳过中间的feature_transform

输入为一个 N × 3 N \times 3 N×3的点云,代码里实际的输入格式为Bx3xN
先过一个inout_transform,也就是论文中说的T-Net(这部分怎么处理的,可以看下面的代码注释)
然后得到特征为Bx3xN,然后过一个卷积,变为Bx64xN,也就是图中的mlp(64,64),两个64分别代表之前的通道数和之后的通道数,
然后按照我画的红色箭头,再过一个mlp(64,128,1024),也就是三个卷积,最后输出Bx1024xN,然后做一个max pool变为Bx1024
然后用全卷积神经网络nn.Linear,将1024–>512–>256–>k,也就是图中的mlp(512,256,k)
k就是分类的类别数量,最后再过一个softmax得到分类结果。
比如分两类,最后输出的Bx2也就是每个点云2个概率值,分别代表该点云是这两类的概率。
这就是分类架构。
分割与分类不同,分类预测整个点云的概率,如果一共有五类,那么就输出五个数,代表该点云是这五类的概率。
而分割需要预测每一个点的类别,要输出Nx5个数字,代表n个点是这个五类的概率。
分割的网络结构如下图所示。

与分类相比有几个地方不一样。
首先就是第一次mlp(64,64)之后,需要再过一个feature_transformer,这次仿射变换矩阵是64x64的,过了之后保持大小形状不变,然后把这个Bx64xN保存下来,后面继续正常往下走,直到走到最后max pool之后输出一个全局特征Bx1024,这个时候我们使用复制操作,将其复制为Bx1024xN,也就是复制n个,然后把这个特征与前面保存下来的那个Bx64xN,做一个拼接,得到Bx1088xN(64+1024),拿这个特征去做一维卷积,将通道数由1088一直降到128,也就是得到Bx128xN,最后再来一个一维卷积,直接维度降到k,BxkxN,k代表分类的类别。然后做一个softmax,得到每个点的k个类别概率(softmax之前要转换一下,可以参考代码操作)。
代码以及注释(pytorch版本)
from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.utils.data
from torch.autograd import Variable
import numpy as np
import torch.nn.functional as Fclass STN3d(nn.Module): #空间变换网络(Spatial Transformer Network)def __init__(self):super(STN3d, self).__init__()self.conv1 = torch.nn.Conv1d(3, 64, 1)self.conv2 = torch.nn.Conv1d(64, 128, 1)self.conv3 = torch.nn.Conv1d(128, 1024, 1)self.fc1 = nn.Linear(1024, 512)self.fc2 = nn.Linear(512, 256)self.fc3 = nn.Linear(256, 9)self.relu = nn.ReLU()self.bn1 = nn.BatchNorm1d(64)self.bn2 = nn.BatchNorm1d(128)self.bn3 = nn.BatchNorm1d(1024)self.bn4 = nn.BatchNorm1d(512)self.bn5 = nn.BatchNorm1d(256)def forward(self, x): # x:BxDxN eg:(32, 3, 2500) 32个点云,每个点云2500个点,每个点三个坐标xyzbatchsize = x.size()[0] # 输入点云数据 BxDxN D=3x = F.relu(self.bn1(self.conv1(x))) # 一维卷积 通道数3-->64x = F.relu(self.bn2(self.conv2(x))) # 一维卷积 通道数64-->128x = F.relu(self.bn3(self.conv3(x))) # 一维卷积 通道数128-->1024x = torch.max(x, 2, keepdim=True)[0] # Bx1024xN 在维度2上取最大值 Bx1024x1 即在每个维度上对N个点取最大值x = x.view(-1, 1024) # 去除最后一列 Bx1024x = F.relu(self.bn4(self.fc1(x))) # 1024-->512x = F.relu(self.bn5(self.fc2(x))) # 512-->256x = self.fc3(x) # 256-->9# 为每一个batch生成一个单位矩阵iden = Variable(torch.from_numpy(np.array([1,0,0,0,1,0,0,0,1]).astype(np.float32))).view(1,9).repeat(batchsize,1)if x.is_cuda:iden = iden.cuda()x = x + iden # 变换矩阵与单位矩阵相加 这个操作可以理解为:防止网络乱学 引导其接近单位矩阵x = x.view(-1, 3, 3) # 调整为Bx3x3return xclass STNkd(nn.Module):#这个与上面的STN是一样的 这个输出64x64 上面输出3x3def __init__(self, k=64):super(STNkd, self).__init__()self.conv1 = torch.nn.Conv1d(k, 64, 1)self.conv2 = torch.nn.Conv1d(64, 128, 1)self.conv3 = torch.nn.Conv1d(128, 1024, 1)self.fc1 = nn.Linear(1024, 512)self.fc2 = nn.Linear(512, 256)self.fc3 = nn.Linear(256, k*k)self.relu = nn.ReLU()self.bn1 = nn.BatchNorm1d(64)self.bn2 = nn.BatchNorm1d(128)self.bn3 = nn.BatchNorm1d(1024)self.bn4 = nn.BatchNorm1d(512)self.bn5 = nn.BatchNorm1d(256)self.k = kdef forward(self, x):batchsize = x.size()[0]x = F.relu(self.bn1(self.conv1(x)))x = F.relu(self.bn2(self.conv2(x)))x = F.relu(self.bn3(self.conv3(x)))x = torch.max(x, 2, keepdim=True)[0]x = x.view(-1, 1024)x = F.relu(self.bn4(self.fc1(x)))x = F.relu(self.bn5(self.fc2(x)))x = self.fc3(x)iden = Variable(torch.from_numpy(np.eye(self.k).flatten().astype(np.float32))).view(1,self.k*self.k).repeat(batchsize,1)if x.is_cuda:iden = iden.cuda()x = x + idenx = x.view(-1, self.k, self.k)return xclass PointNetfeat(nn.Module):def __init__(self, global_feat = True, feature_transform = False):super(PointNetfeat, self).__init__()self.stn = STN3d()self.conv1 = torch.nn.Conv1d(3, 64, 1)self.conv2 = torch.nn.Conv1d(64, 128, 1)self.conv3 = torch.nn.Conv1d(128, 1024, 1)self.bn1 = nn.BatchNorm1d(64)self.bn2 = nn.BatchNorm1d(128)self.bn3 = nn.BatchNorm1d(1024)self.global_feat = global_featself.feature_transform = feature_transformif self.feature_transform:self.fstn = STNkd(k=64)def forward(self, x): # x:BxDxN D就是通道数 输入的时候D=3 代表xyz坐标n_pts = x.size()[2]trans = self.stn(x) # 做一个仿射变换 trans:Bx3x3x = x.transpose(2, 1) # BxDxN --> BxNx3x = torch.bmm(x, trans) # 与仿射变换的结果相乘 结果大小为BxNx3x = x.transpose(2, 1) # x:Bx3xNx = F.relu(self.bn1(self.conv1(x))) # 3-->64 Bx64xNif self.feature_transform:trans_feat = self.fstn(x) # 做一个仿射变换 trans_feat: 64x64x = x.transpose(2,1)x = torch.bmm(x, trans_feat) # 与仿射变换的结果相乘 x = x.transpose(2,1) # 结果大小为BxNx64else:trans_feat = Nonepointfeat = x # feature_transform=false : pointfeat Bx64xNx = F.relu(self.bn2(self.conv2(x))) # 64-->128 Bx128xNx = self.bn3(self.conv3(x)) # 128-->1024 Bx1024xNx = torch.max(x, 2, keepdim=True)[0] # 最大池化 Bx1024x1x = x.view(-1, 1024) # Bx1024if self.global_feat:return x, trans, trans_feat # x是编码后的特征 teans是仿射变换矩阵 trans_feat是else:x = x.view(-1, 1024, 1).repeat(1, 1, n_pts) # 使用复制 将x:Bx1024x1-->Bx1024xN# x(Bx1024xN)和pointfeat(Bx64xN)在维度1上拼接 结果大小为Bx1088xNreturn torch.cat([x, pointfeat], 1), trans, trans_featclass PointNetCls(nn.Module): # 分类def __init__(self, k=2, feature_transform=False):# 分类时 feature_transform=Falsesuper(PointNetCls, self).__init__()self.feature_transform = feature_transformself.feat = PointNetfeat(global_feat=True, feature_transform=feature_transform) self.fc1 = nn.Linear(1024, 512)self.fc2 = nn.Linear(512, 256)self.fc3 = nn.Linear(256, k)self.dropout = nn.Dropout(p=0.3) # 每个神经元有 30% 的概率被置零 最后一层使用self.bn1 = nn.BatchNorm1d(512)self.bn2 = nn.BatchNorm1d(256)self.relu = nn.ReLU()def forward(self, x): # BxDxNx, trans, trans_feat = self.feat(x) # 提取特征 x:Bx1024 trans:Bx3x3 trans_feat=Nonex = F.relu(self.bn1(self.fc1(x))) # Bx1024-->Bx512x = F.relu(self.bn2(self.dropout(self.fc2(x)))) # Bx512-->Bx256 x = self.fc3(x) # Bx256 --> k 要分几类 默认是2return F.log_softmax(x, dim=1), trans, trans_feat # 过一个log_softmax输出结果class PointNetDenseCls(nn.Module): # 分割def __init__(self, k = 2, feature_transform=False):super(PointNetDenseCls, self).__init__()self.k = kself.feature_transform=feature_transformself.feat = PointNetfeat(global_feat=False, feature_transform=feature_transform)self.conv1 = torch.nn.Conv1d(1088, 512, 1)self.conv2 = torch.nn.Conv1d(512, 256, 1)self.conv3 = torch.nn.Conv1d(256, 128, 1)self.conv4 = torch.nn.Conv1d(128, self.k, 1)self.bn1 = nn.BatchNorm1d(512)self.bn2 = nn.BatchNorm1d(256)self.bn3 = nn.BatchNorm1d(128)def forward(self, x):batchsize = x.size()[0]n_pts = x.size()[2]x, trans, trans_feat = self.feat(x) # 提取特征 x:Bx1088xN trans:Bx3x3 trans_feat=Bx64x64x = F.relu(self.bn1(self.conv1(x))) # Bx1088xN --> Bx512xNx = F.relu(self.bn2(self.conv2(x))) # Bx512xN --> Bx256xNx = F.relu(self.bn3(self.conv3(x))) # Bx256xN --> Bx128xNx = self.conv4(x) # Bx128xN --> BxkxNx = x.transpose(2,1).contiguous() # BxNxKx = F.log_softmax(x.view(-1,self.k), dim=-1) # BxNxK-->B*NxK 然后过一个log_softmaxx = x.view(batchsize, n_pts, self.k) # BxNxKreturn x, trans, trans_feat # x:BxNxK, trans:Bx3x3, trans_feat:Bx64x64def feature_transform_regularizer(trans):d = trans.size()[1]batchsize = trans.size()[0]I = torch.eye(d)[None, :, :]if trans.is_cuda:I = I.cuda()loss = torch.mean(torch.norm(torch.bmm(trans, trans.transpose(2,1)) - I, dim=(1,2)))return lossif __name__ == '__main__':sim_data = Variable(torch.rand(32,3,2500))trans = STN3d()out = trans(sim_data)print('stn', out.size())print('loss', feature_transform_regularizer(out))sim_data_64d = Variable(torch.rand(32, 64, 2500))trans = STNkd(k=64)out = trans(sim_data_64d)print('stn64d', out.size())print('loss', feature_transform_regularizer(out))pointfeat = PointNetfeat(global_feat=True)out, _, _ = pointfeat(sim_data)print('global feat', out.size())pointfeat = PointNetfeat(global_feat=False)out, _, _ = pointfeat(sim_data)print('point feat', out.size())cls = PointNetCls(k = 5)out, _, _ = cls(sim_data)print('class', out.size())seg = PointNetDenseCls(k = 3)out, _, _ = seg(sim_data)print('seg', out.size())相关文章:
点云处理入门--PointNetPointNet++论文与代码详解
基础知识 点云数据: 点云是一种通过三维扫描设备或计算机图形学技术获取的三维空间数据,通常由一系列点组成,每个点包含其在三维空间中的坐标(如 x,y,z),有时还可能包含颜色、强度等附加信息。 介绍几种常…...
通过Nginx负载均衡+Keepalived实现业务高可用
通过Nginx负载均衡和Keepalived可以实现业务的高可用,以下是详细的实现步骤: 环境准备 假设我们有3台服务器,IP地址分别为: 服务器1(Nginx Keepalived 主节点):192.168.1.100服务器2&#x…...
Spark技术系列(三):Spark算子全解析——从基础使用到高阶优化
Spark技术系列(三):Spark算子全解析——从基础使用到高阶优化 1. 算子核心概念与分类体系 1.1 算子本质解析 延迟执行机制:转换算子构建DAG,行动算子触发Job执行任务并行度:由RDD分区数决定(可通过spark.default.parallelism全局配置)执行位置优化:基于数据本地性的…...
ES6模块化详解:导入与导出方式
在现代 JavaScript 开发中,模块化是代码管理和组织的重要工具。ES6(ECMAScript 2015)引入了模块化的概念,通过 import 和 export 来组织代码,使得模块的管理变得更加清晰和简洁。本文将详细介绍 ES6 中的各种模块导入导…...
每日学习Java之一万个为什么?[MySQL面试篇]
分析SQL语句执行流程中遇到的问题 前言1 MySQL是怎么在一台服务器上启动的2 MySQL主库和从库是同时启动保持Alive的吗?3 如果不是主从怎么在启动的时候保证数据一致性4 ACID原则在MySQL上的体现5 数据在MySQL是通过什么DTO实现的6 客户端怎么与MySQL Server建立连接…...
常用空间数据结构对比
空间数据结构是用来组织和查询多维空间数据的算法结构。它们在地理信息系统 (GIS)、计算机图形学、机器人导航、机器学习等领域非常重要。以下是几种常见空间数据结构的对比: 1. 四叉树(Quadtree) 适用场景:二维空间数据&#x…...
AnythingLLM+LM Studio本地知识库构建
前置操作: 已经安装以下软件,并配置后: DeepSeek-R1-Distill-Llama-8B-Q4_K_M.ggufLM-Studio-0.3.10-6-x64 软件准备: 下载AnythingLLM:AnythingLLM | The all-in-one AI application for everyone 点击"Dow…...
使用 Java 更新 Word 文档中的图表数据-超详细
使用 Java 更新 Word 文档中的图表数据 在日常的工作中,尤其是在数据分析和报告自动化的场景中,可能会遇到需要定期更新 Word 文档中的图表数据的需求。比如,生成数据报告时,我们需要在图表中更新一些动态的数据值。今天…...
Qt常用控件之下拉框QComboBox
下拉框QComboBox QComboBox 是一个下拉框控件。 1. QComboBox属性 属性说明currentText当前选中的文本。currentIndex当前选中的条目下标(从 0 开始,如果没有条目被选中则该值为 -1)。editable是否允许被修改。为 true 时,QCom…...
Qt 中集成mqtt协议
一,引入qmqtt 库 我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台时 方便,直接编译就行了。 原始仓库路径:https://github.com/emqx/qmqtt/tree/master 二,使用 声明一个单例类,将订阅到…...
2024年第十五届蓝桥杯大赛软件赛省赛Python大学A组真题解析
文章目录 试题A: 拼正方形(本题总分:5 分)解析答案试题B: 召唤数学精灵(本题总分:5 分)解析答案试题C: 数字诗意解析答案试题A: 拼正方形(本题总分:5 分) 【问题描述】 小蓝正在玩拼图游戏,他有7385137888721 个2 2 的方块和10470245 个1 1 的方块,他需要从中挑出一些…...
AI大模型-提示工程学习笔记19-自我反思
目录 1. 自我反思的核心思想 (1) LLM 的局限性 (2) Reflexion 的解决方案 2. Reflexion 的工作流程 (1) 任务输入 (2) 初始生成 (3) 反思 (Reflection) (4) 调整与改进 (5) 迭代 (6) 结果输出 3. Reflexion 的关键组件 (1) 大语言模型 (LLM) (2) 反思者 (Reflector…...
GaussDB 学习实战指南:从部署到高并发优化的全流程解析
引言 GaussDB 作为华为推出的高性能分布式数据库,凭借其 分布式架构、高可用性、云原生支持 等特性,成为企业级应用的核心选择。本文将以 实战操作为核心,覆盖 集群部署、数据分片、性能调优、容灾备份、云上迁移 五大场景,通过真实案例与代码示例,助你快速掌握 GaussDB …...
vue3 Props的使用
Props是什么? 官方地址:Props | Vue.js 在 Vue 中,props 是父组件向子组件传递数据的一种机制。 props 是子组件中定义的自定义属性,父组件通过这些属性向子组件传递数据。 它们是单向数据流的一部分,意味着数据只能…...
Ecode前后端传值
说明 在泛微 E9 系统开发过程中,使用 Ecode 调用后端接口并进行传值是极为常见且关键的操作。在上一篇文章中,我们探讨了 Ecode 调用后端代码的相关内容,本文将深入剖析在 Ecode 中如何向后端传值,以及后端又该如何处理接收这些值…...
【Linux】进程状态(二)
目录 前言: 一、进程状态: 1.运行状态(时间片) 2.阻塞状态 3.阻塞挂起状态 二、Linux进程状态: 1.运行状态(R)和阻塞状态(S) 2.深度睡眠状态(D) 3.停止状态(T) 3.1使进程在后台运行 4.追踪暂停状态(t) 5.死亡状态(X)和僵尸状态…...
domain 网络安全 网络安全域
🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 文章目录 1、域的概述 1.1、工作组与域1.2、域的特点1.3、域的组成1.4、域的部署概述1.5、活动目录1.6、组策略GPO 2、域的部署实验 2.1、建立局域网…...
链表和STL —— list 【复习笔记】
1. 链表 1.1 链表的定义和类型 和顺序表一样,链表也是一种线性表,线性表存储结构为链式存储就是链表 链式存储不仅要保存数据元素,还要保存数据元素间的关系,这两个部分信息形成了结点。结点有两个域:数据域&#x…...
Java Map实现类面试题
Java Map实现类面试题 HashMap Q1: HashMap的实现原理是什么? HashMap基于哈希表实现,使用数组链表红黑树(Java 8)的数据结构。 public class HashMapPrincipleExample {// 模拟HashMap的基本结构public class SimpleHashMap&…...
技术架构和工程架构区别
技术架构 技术架构是对某一技术问题解决方案的结构化描述,包括组件结构及其交互关系。它涵盖部署方案、存储方案、缓存方案、日志方案等多个方面,旨在通过组织人员和技术,以最低的成本满足需求和应对变化,保障软件的稳定高效运…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...
