【自动驾驶】经典LSS算法解析——深度估计

LSS-Lift.Splat,Shoot

  • 论文题目:Lift, Splat, Shoot: Encoding Images From Arbitrary Camera Rigs by Implicitly Unprojecting to 3D

  • 代码:https://github.com/nv-tlabs/lift-splat-shoot

  • 概括:先做深度估计和特征融合,然后投影到 BEV 视图中,在 BEV 视图中做特征融合,在融合后的特征图上做检测、规划等任务。

在这里插入图片描述

目录

  • LSS-Lift.Splat,Shoot
    • 一、概括说明
      • 1、Lift:首先提取图像特征,估计每个像素的深度分布估计(每个像素对应每个深度的特征向量)
      • 2、Splat:将lift的二点特征压缩为BEV特征
      • 3、Shoot:基于BEV特征,进行路径规划。利用深度分布信息,生成点云,并利用点云信息进行3D重建
    • 二、算法步骤
      • 1、生成视锥,并根据相机内外参将视锥中的点投影到ego坐标系;
      • 2、get_cam_feats:对环视图像进行特征提取camera_features,并构建图像特征点云
      • 3、利用ego坐标系下的坐标点与图像特征点云,利用Voxel Pooling构建BEV特征
    • 三、总结
    • 四、参考链接

一、概括说明

1、Lift:首先提取图像特征,估计每个像素的深度分布估计(每个像素对应每个深度的特征向量)

将2D图像(W×H×3W \times H \times 3W×H×3)增加深度信息,升维得到3D(W×H×DW \times H \times DW×H×D),为学习不同深度的维特征,得到W×H×D×CW \times H \times D \times CW×H×D×C维的视锥点云。

步骤:

  • 通过向量c∈RCc\in R^{C}cRC和深度概率α\alphaα的外积,得到每个像素对应每个深度的特征向量αic\alpha_{i}cαic

  • 通过像素估计到的每个深度特征为:(u,v,di,αic)(u,v,d_{i},\alpha_{i}c)(u,v,di,αic)

  • 结合相机成像原理,已知内外参的情况下,可求出像素对应的3D坐标
    di(u;v;1)=K[Rt](x;y;z;1)=T(x;y;z;1)d_{i}(u;v;1)= K[R t](x;y;z;1)=T(x;y;z;1)di(u;v;1)=K[Rt](x;y;z;1)=T(x;y;z;1)
    (x;y;z;1)=diT−1(u;v;1)(x;y;z;1) = d_{i}T^{-1}(u;v;1)(x;y;z;1)=diT1(u;v;1)

  • 每个像素对应的3D特征向量即完成Lift过程,ϕ(x,y,z)=αic\phi(x,y,z) = \alpha_{i}cϕ(x,y,z)=αic

总结:lift过程是通过估计深度来实现2D特征转化为3D特征;此处的深度估计有几种不同的选择,优缺点如下:

特征向量形式按预测概率分布均匀分布One-hot
优势可识别深度,特征转换更精准计算快,无学习参数深度估计的识别度更高,实际为估计每个像素对应的点云
劣势需要MLP做概率估计深度估计无差异化鲁棒性不好

2、Splat:将lift的二点特征压缩为BEV特征

图像特征投影到BEV空间中,采用cumsum Trick 的方法将特征求和,得到C×X×维度C \times X \times 维度C×X×维度的BEV特征。

实际为均匀采样点云;每个点的特征为ϕ(x,y,z)=αic\phi(x, y,z) = \alpha_{i}cϕ(x,y,z)=αic
难点分析:1、多相机存在场合趋于,特征压缩时不应有差异;2、点数量多,百万级别的点(±50m范围,采样间隔为0.5m)
解决:

  • 参考PointPillars将BEV空间划分为H×WH \times WH×W的网格;
  • 对每个估计的3D点(x,y,z),将其特征归属至最近的pillar
  • 利用归属到某个特定pillar的特征进行求和sum pooling

求和是因为考虑到多个视锥点云落在同一个BEV格栅Grid的情况,会出现以下两种情况:

  • 不同高度的视锥点云会落在同一个栅格中,比如电线杆上的不同像素点
  • 不同相机间存在重叠overlap,不同相机观测到的同一个物体,会落在同一个BEV Grid中

3、Shoot:基于BEV特征,进行路径规划。利用深度分布信息,生成点云,并利用点云信息进行3D重建

将轨迹通过模板映射到BEV空间中,并计算轨迹的损失。

图像 backbone 采用 EfficientNet,通过预训练得到深度估计,需要标记检测出的物体在BEV视角下的投影

监督真值是实例分割结果、可行驶区域,Loss 定义为预测结果与 groud truth 的交叉熵

在LSS源码中,其感知范围,BEV单元格大小,BEV下的网格尺寸如下:

输入图像大小为 128×352128 \times 352128×352

  • 感知范围
    xxx轴方向的感知范围 -50m ~ 50m;yyy轴方向的感知范围 -50m ~ 50m;zzz轴方向的感知范围 -10m ~ 10m;

  • BEV单元格大小
    xxx轴方向的单位长度 0.5m;yyy轴方向的单位长度 0.5m;zzz轴方向的单位长度 20m;

  • BEV的网格尺寸
    200×200×1200 \times 200 \times 1200×200×1

  • 深度估计范围
    由于LSS需要显式估计像素的离散深度,论文给出的范围是4m ~ 45m,间隔为1m,也就是算法会估计41个离散深度;

  • 模型使用参数
    imgs:输入的环视相机图片,imgs = (bs,N,3,H,W)(bs, N, 3, H, W)(bs,N,3,H,W)NNN代表环视相机个数,nuSence为N=6N=6N=6
    rots:由相机坐标系->车身坐标系的旋转矩阵,rots=(bs,N,3,3)rots = (bs, N, 3, 3)rots=(bs,N,3,3)
    trans:由相机坐标系->车身坐标系的平移矩阵,trans=(bs,N,3)trans=(bs, N, 3)trans=(bs,N,3)
    intrinsic:相机内参,intrinsic=(bs,N,3,3)intrinsic = (bs, N, 3, 3)intrinsic=(bs,N,3,3)
    post_rots:由图像增强引起的旋转矩阵,postrots=(bs,N,3,3)post_rots = (bs, N, 3, 3)postrots=(bs,N,3,3)
    post_trans:由图像增强引起的平移矩阵,posttrans=(bs,N,3)post_trans = (bs, N, 3)posttrans=(bs,N,3)
    binimgs:由于LSS做的是语义分割任务,所以会将真值目标投影到BEV坐标系,将预测结果与真值计算损失;具体而言,在binimgs中对应物体的bbox内的位置为1,其他位置为0;

二、算法步骤

  LSS算法的五个步骤如下:

  • 1、生成视锥,并根据相机内外参将视锥中的点投影到ego坐标系;
  • 2、对环视图像进行特征提取,并构建图像特征点云;
  • 3、利用变换后的ego坐标系的点与图像特征点云利用voxel pooling构建BEV特征;
  • 4、对生成的BEV特征利用BEV Encoder做进一步的特征融合;
  • 5、利用特征融合后的BEV特征完成语义分割

  模型整体初始化函数,主要分为以下三个模块:

  • CamEncode:图像特征提取

  • BevEncode:BEV特征检测

  • frustum:视锥,用于图像点云坐标和BEV栅格间的坐标转换

class LiftSplatShoot(nn.Module):def __init__(self, grid_conf, data_aug_conf, outC):super(LiftSplatShoot, self).__init__()self.grid_conf = grid_confself.data_aug_conf = data_aug_confdx, bx, nx = gen_dx_bx(self.grid_conf['xbound'],  self.grid_conf['ybound'],self.grid_conf['zbound']) # ['xbound']:最小值、最大值以及步长# dx:X轴、Y轴和Z轴方向上的每个体素的大小 bx:整个网格中最开始的那个体素的中心位置# nx:每个轴上体素的数量self.dx = nn.Parameter(dx, requires_grad=False)self.bx = nn.Parameter(bx, requires_grad=False)self.nx = nn.Parameter(nx, requires_grad=False)self.downsample = 16self.camC = 64self.frustum = self.create_frustum()   # 1self.D, _, _, _ = self.frustum.shapeself.camencode = CamEncode(self.D, self.camC, self.downsample)  # 2self.bevencode = BevEncode(inC=self.camC, outC=outC)   # 3# toggle using QuickCumsum vs. autogradself.use_quickcumsum = Truedef get_voxels(self, x, rots, trans, intrins, post_rots, post_trans):# x: (B, N); rots, trans:相机外参,以旋转矩阵和平移矩阵形式表示# intrins:相机内参; post_rots,post_trans:图像增强时使用的旋转矩阵和平移矩阵,用于在模型训练时撤销图像增强引入的位姿变化geom = self.get_geometry(rots, trans, intrins, post_rots, post_trans)x = self.get_cam_feats(x)x = self.voxel_pooling(geom, x)return xdef forward(self, x, rots, trans, intrins, post_rots, post_trans):x = self.get_voxels(x, rots, trans, intrins, post_rots, post_trans)x = self.bevencode(x)return x

1、生成视锥,并根据相机内外参将视锥中的点投影到ego坐标系;

  生成视锥的代码如下:


def create_frustum():# 原始图片大小  ogfH:128  ogfW:352ogfH, ogfW = self.data_aug_conf['final_dim']# 下采样16倍(self.downsample)后图像大小  fH: 8  fW: 22fH, fW = ogfH // self.downsample, ogfW // self.downsample # self.grid_conf['dbound'] = [4, 45, 1]# 在深度方向上划分网格,即每个点的深度 ds: DxfHxfW (41x8x22)ds = torch.arange(*self.grid_conf['dbound'], dtype=torch.float).view(-1, 1, 1).expand(-1, fH, fW)"""1. torch.linspace(0, ogfW - 1, fW, dtype=torch.float)tensor([0.0000, 16.7143, 33.4286, 50.1429, 66.8571, 83.5714, 100.2857,117.0000, 133.7143, 150.4286, 167.1429, 183.8571, 200.5714, 217.2857,234.0000, 250.7143, 267.4286, 284.1429, 300.8571, 317.5714, 334.2857,351.0000])2. torch.linspace(0, ogfH - 1, fH, dtype=torch.float)tensor([0.0000, 18.1429, 36.2857, 54.4286, 72.5714, 90.7143, 108.8571,127.0000])"""# 在0到351上划分22个格子 xs: DxfHxfW(41x8x22)xs = torch.linspace(0, ogfW - 1, fW, dtype=torch.float).view(1, 1, fW).expand(D, fH, fW)  # 在0到127上划分8个格子 ys: DxfHxfW(41x8x22)ys = torch.linspace(0, ogfH - 1, fH, dtype=torch.float).view(1, fH, 1).expand(D, fH, fW)  # D x H x W x 3# 堆积起来形成网格坐标, frustum[k,i,j,0]就是(i,j)位置,深度为k的像素的宽度方向上的栅格坐标 frustum: DxfHxfWx3frustum = torch.stack((xs, ys, ds), -1)  return nn.Parameter(frustum, requires_grad=False)            

锥点由图像坐标系向自车坐标系进行坐标转化这一过程主要涉及到相机的内外参数,对应代码中的函数为get_geometry()。

def get_geometry(self, rots, trans, intrins, post_rots, post_trans):B, N, _ = trans.shape  # B: batch size N:环视相机个数# undo post-transformation# B x N x D x H x W x 3# 1.抵消数据增强及预处理对像素的变化  RX+T=Y X=R^{-1}(Y-T)points = self.frustum - post_trans.view(B, N, 1, 1, 1, 3)points = torch.inverse(post_rots).view(B, N, 1, 1, 1, 3, 3).matmul(points.unsqueeze(-1))    # 图像坐标系 -> 归一化相机坐标系 -> 相机坐标系 -> 车身坐标系 cam_to_ego# 但是自认为由于转换过程是线性的,所以反归一化是在图像坐标系完成的,然后再利用求完逆的内参投影回相机坐标系# 转换到真实坐标系再乘以内存去畸变,需要注意的是,上一步得到的 xy 是单位深度下的相机坐标,不同深度对应的 xy 是一样的,因此需要乘以深度d才能得到真实世界的坐标points = torch.cat((points[:, :, :, :, :, :2] * points[:, :, :, :, :, 2:3],points[:, :, :, :, :, 2:3]), 5)  # 反归一化# 通过外参转换到 BEV 坐标系下 (R(intrins)^-1)x + t = ycombine = rots.matmul(torch.inverse(intrins))points = combine.view(B, N, 1, 1, 1, 3, 3).matmul(points).squeeze(-1)points += trans.view(B, N, 1, 1, 1, 3)# (bs, N, depth, H, W, 3):其物理含义# 每个batch中的每个环视相机图像特征点,其在不同深度下位置对应在ego坐标系下的坐标return points

2、get_cam_feats:对环视图像进行特征提取camera_features,并构建图像特征点云

  • a)利用Efficientnet-B0主干网络对环视图像进行特征提取

  输入的环视图像 (bs,N,3,H,W)(bs, N, 3, H, W)(bs,N,3,H,W),在进行特征提取之前,会将前两个维度进行合并,一起提取特征,对应维度变换为 (bs,N,3,H,W)→(bs∗N,3,H,W)(bs, N, 3, H, W) \rightarrow (bs * N, 3, H, W)(bs,N,3,H,W)(bsN,3,H,W);其输出的多尺度特征尺寸大小如下:

level0 = (bs * N, 16, H / 2, W / 2)
level1 = (bs * N, 24, H / 4, W / 4)
level2 = (bs * N, 40, H / 8, W / 8)
level3 = (bs * N, 112, H / 16, W / 16)
level4 =  (bs * N, 320, H / 32, W / 32)
  • b)对其中的后两层特征进行融合,丰富特征的语义信息,融合后的特征尺寸大小为(bs∗N,512,H/16,W/16)(bs * N, 512, H / 16, W / 16)(bsN,512,H/16,W/16)
Step1: 对最后一层特征升采样到倒数第二层大小;
level4 -> Up -> level4' = (bs * N, 320, H / 16, W / 16)Step2:对主干网络输出的后两层特征进行concat;
cat(level4', level3) -> output = (bs * N, 432, H / 16, W / 16)Step3:对concat后的特征,利用ConvLayer卷积层做进一步特征拟合;ConvLayer(output) -> output' = (bs * N, 512, H / 16, W / 16)其中ConvLayer层构造如下:
"""Sequential((0): Conv2d(432, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(5): ReLU(inplace=True)
)"""
  • c)估计深度方向的概率分布(用41维数据表示),并输出特征图每个位置的语义特征 (用64维的特征表示),整个过程用1×11 \times 11×1卷积层实现
整体pipeline
output' -> Conv1x1 -> x = (bs * N, 41+64=105, H / 16, W / 16)a)步骤输出的特征:
output = Tensor[(bs * N, 512, H / 16, W / 16)]b)步骤使用的1x1卷积层:
Conv1x1 = Conv2d(512, 105, kernel_size=(1, 1), stride=(1, 1))c)步骤输出的特征以及对应的物理含义:
x = Tensor[(bs * N, 105, H / 16, W / 16)] 
第二维的105个通道分成两部分;第一部分:前41个维度代表不同深度上41个离散深度;第二部分:后64个维度代表特征图上的不同位置对应的语义特征;
  • d)对c)步骤估计出来的离散深度利用softmax()函数计算深度方向的概率密度

  • e)利用得到的深度方向的概率密度和语义特征通过外积运算构建图像特征点云

# d)步骤得到的深度方向的概率密度
depth = x[:, :self.D] = (bs * N, 41, H / 16, W / 16) -> unsqueeze -> (bs * N, 1, 41, H / 16, W / 16)# c)步骤得到的特征,选择后64维是预测出来的语义特征
x[:, self.D:(self.D + self.C)] = (bs * N, 64, H / 16, W / 16) -> unsqueeze(2) -> (bs * N, 64, 1, H / 16, W / 16)# 概率密度和语义特征做外积,构建图像特征点云
# (bs * N, 1, 41, H / 16, W / 16) * (bs * N, 64, 1, H / 16, W / 16) -> (bs * N, 64, 41, H / 16, W / 16)
new_x = depth.unsqueeze(1) * x[:, self.D:(self.D + self.C)].unsqueeze(2)  
#  new_x = x[:, self.D:(self.D + self.C)].unsqueeze(2) * depth.unsqueeze(1)

3、利用ego坐标系下的坐标点与图像特征点云,利用Voxel Pooling构建BEV特征

def voxel_pooling(self, geom_feats, x):# geom_feats:(B x N x D x H x W x 3):在ego坐标系下的坐标点;# x:(B x N x D x fH x fW x C):图像点云特征B, N, D, H, W, C = x.shapedNprime = B * N * D * H * W # 将特征点云展平,一共有 B*N*D*H*W 个点 -> (B*N*D*H*W, C)x = x.reshape(Nprime, C) # flatten indicesgeom_feats = ((geom_feats - (self.bx - self.dx/2.)) / self.dx).long() # ego下的空间坐标转换到体素坐标(计算栅格坐标并取整)geom_feats = geom_feats.view(Nprime, 3)  # 将体素坐标同样展平,geom_feats: (B*N*D*H*W, 3)batch_ix = torch.cat([torch.full([Nprime//B, 1], ix,device=x.device, dtype=torch.long) for ix in range(B)]) # 每个点对应于哪个batchgeom_feats = torch.cat((geom_feats, batch_ix), 1)  # geom_feats: (B*N*D*H*W, 4)# filter out points that are outside box# 过滤掉在边界线之外的特征点 x:0~199  y: 0~199  z: 0kept = (geom_feats[:, 0] >= 0) & (geom_feats[:, 0] < self.nx[0])\& (geom_feats[:, 1] >= 0) & (geom_feats[:, 1] < self.nx[1])\& (geom_feats[:, 2] >= 0) & (geom_feats[:, 2] < self.nx[2])x = x[kept]geom_feats = geom_feats[kept]# get tensors from the same voxel next to each otherranks = geom_feats[:, 0] * (self.nx[1] * self.nx[2] * B)\+ geom_feats[:, 1] * (self.nx[2] * B)\+ geom_feats[:, 2] * B\+ geom_feats[:, 3]  # 给每一个点一个rank值,rank相等的点在同一个batch,并且在在同一个格子里面sorts = ranks.argsort()x, geom_feats, ranks = x[sorts], geom_feats[sorts], ranks[sorts]  # 按照rank排序,这样rank相近的点就在一起了# cumsum trick x: 64 x 1 x 200 x 200if not self.use_quickcumsum:x, geom_feats = cumsum_trick(x, geom_feats, ranks)else:x, geom_feats = QuickCumsum.apply(x, geom_feats, ranks)# griddify (B x C x Z x X x Y)final = torch.zeros((B, C, self.nx[2], self.nx[0], self.nx[1]), device=x.device)  # final: bs x 64 x 1 x 200 x 200final[geom_feats[:, 3], :, geom_feats[:, 2], geom_feats[:, 0], geom_feats[:, 1]] = x  # 将x按照栅格坐标放到final中# collapse Zfinal = torch.cat(final.unbind(dim=2), 1)  # 消除掉z维return final  # final: bs x 64 x 200 x 200
  • 采用cumsum_trick完成Voxel Pooling运算,代码如下:
class QuickCumsum(torch.autograd.Function):@staticmethoddef forward(ctx, x, geom_feats, ranks):x = x.cumsum(0) # 求前缀和kept = torch.ones(x.shape[0], device=x.device, dtype=torch.bool)  kept[:-1] = (ranks[1:] != ranks[:-1])  # 筛选出ranks中前后rank值不相等的位置x, geom_feats = x[kept], geom_feats[kept] # rank值相等的点只留下最后一个,即一个batch中的一个格子里只留最后一个点x = torch.cat((x[:1], x[1:] - x[:-1]))  # x后一个减前一个,还原到cumsum之前的x,此时的一个点是之前与其rank相等的点的feature的和,相当于把同一个格子的点特征进行了sum, 1000 + (679-167) = 1512# save kept for backwardctx.save_for_backward(kept)# no gradient for geom_featsctx.mark_non_differentiable(geom_feats)return x, geom_feats
  • 对生成的BEV特征利用BEV Encoder做进一步的特征融合 + 语义分割结果预测

  a)对BEV特征先利用ResNet-18进行多尺度特征提取,输出的多尺度特征尺寸如下:

level0:(bs, 64, 100, 100)
level1: (bs, 128, 50, 50)
level2: (bs, 256, 25, 25)

  b)对输出的多尺度特征进行特征融合 + 对融合后的特征实现BEV网格上的语义分割

Step1: level2 -> Up (4x) -> level2' = (bs, 256, 100, 100)Step2: concat(level2', level0) -> output = (bs, 320, 100, 100)Step3: ConvLayer(output) -> output' = (bs, 256, 100, 100)'''ConvLayer的配置如下
Sequential((0): Conv2d(320, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(2): ReLU(inplace=True)(3): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(4): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(5): ReLU(inplace=True)
)'''Step4: Up2(output') -> final = (bs, 1, 200, 200) # 第二个维度的1就代表BEV每个网格下的二分类结果
'''Up2的配置如下
Sequential((0): Upsample(scale_factor=2.0, mode=bilinear)(1): Conv2d(256, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)(2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)(3): ReLU(inplace=True)(4): Conv2d(128, 1, kernel_size=(1, 1), stride=(1, 1))
)'''

  最后就是将输出的语义分割结果与binimgs的真值标注做基于像素的交叉熵损失,从而指导模型的学习过程。

三、总结

 1、精度低,对内外参敏感,鲁棒性差

  • 地平面假设在实际工况中,只有较少的场景下满足,且距离越远效果越差;
  • 对有高度的目标,投影后被拉长,畸变严重

 2、成本低

  • 特征转换过程非常直观,计算量相对比较小。

四、参考链接

[1] https://zhuanlan.zhihu.com/p/589146284
[2]https://mp.weixin.qq.com/s?__biz=MjM5NDQwNzMxOA==&mid=2650930587&idx=1&sn=4209bfa3f3a6a9816965ddc839f3cbb5&scene=21&poc_token=HBoBamij0VvmKL9LA_G_VA6K1QDijMvSKDbsCrj1

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

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

相关文章

《【第八篇-图片总结篇】Python图片处理自动化:终极工厂!从裁剪压缩到智能加水印,打造你的视觉内容生产流水线!》

在数字时代&#xff0c;图片无处不在。然而&#xff0c;高质量的图片背后&#xff0c;往往隐藏着繁琐的后期处理&#xff1a;图片文件太大导致加载慢&#xff1b;尺寸不符需要裁剪&#xff1b;版权保护要加水印&#xff1b; 为了兼容性还得批量转换格式……这些重复、机械的工…

frame 与新窗口切换操作【selenium 】

&#x1f9ed; 一、切换到 iframe 内部进行操作在浏览器自动化测试中&#xff0c;iframe 是一个特别的存在。它相当于在当前页面中嵌入了另一个独立的 HTML 页面。当我们试图直接访问 iframe 中的元素时&#xff0c;往往会发现定位不到&#xff0c;比如&#xff1a;elements w…

MYSQL C_API使用全解

文章目录C_API&#xff08;简单的&#xff09;安装这个库使用流程初始化连接mysql_init建立连接mysql_real_connect执行SQL语句mysql_query处理结果mysql_store_resultmsyql_use_resultmysql_num_rowsmsyql_free_resultmysql_num_fieldsmysql_fetch_row多线程安全关闭连接mysql…

闲庭信步使用图像验证平台加速FPGA的开发:第二课——RGB转YCbCr的FPGA硬件编程详解

&#xff08;本系列只需要modelsim即可完成数字图像的处理&#xff0c;每个工程都搭建了全自动化的仿真环境&#xff0c;只需要双击文件就可以完成整个的仿真&#xff0c;大大降低了初学者的门槛&#xff01;&#xff01;&#xff01;&#xff01;如需要该系列的工程文件请关注…

RK3566/RK3568 Android11 修改selinux模式

概述RK3566/RK3568 Android11 SDK默认的selinux是Enforcing模式(强制模式)。Enforcing&#xff1a;强制模式&#xff1a;SELinux在运行中&#xff0c;且已经开始限制domain/type之间的验证关系 Permisssive&#xff1a;宽容模式&#xff1a;SELinux在运行中&#xff0c;如果验证…

iOS Widget 开发-3:Widget 的种类与尺寸(主屏、锁屏、灵动岛)

iOS 支持多种类型的 Widget&#xff0c;分布在主屏幕、锁屏、灵动岛、待机模式、控制中心等多个系统位置。每种 Widget 都有各自的尺寸、交互能力与限制。 本篇将系统梳理 iOS 当前支持的 Widget 类型与尺寸规格。主屏 Widget&#xff08;Home Screen Widgets&#xff09; 主屏…

ffmpeg 中 write_option()函数详细注释

author: hjjdebug date: 2025年 07月 11日 星期五 10:51:23 CST descrip: ffmpeg 中 write_option()函数详细注释 文章目录1. 函数原型1.1 参数说明1.2 SpecifierOpt 说明符选项结构2. write_option 代码注释2.1 谁调用了write_option 函数?3. 小结:write_option()不仅在ffmpe…

PandaCoder重大产品更新-引入Jenkinsfile文件支持

写在前面 安装这个插件可以直接平替 Jenkinsfile Pro &#xff0c;节省200元关于插件介绍的处女篇&#xff1a;https://mp.weixin.qq.com/s/fwMEhmx8vxVlvfnipx09Ag为什么叫「熊猫编码助手」&#xff1f; 熊猫是中国的国宝&#xff0c;备受世界喜爱&#xff0c;代表着中国特色和…

链表算法之【判断链表中是否有环】

目录 LeetCode-141题 LeetCode-141题 给定一个链表的头节点&#xff0c;判断链表中是否存在环 class Solution {public boolean hasCycle(ListNode head) {// checkif (head null || head.next null)return false;// 定义两个指针&#xff0c;一个快指针[fast]&#xff0c…

Ubuntu 22.04安装SQL Server指南

看起来在安装过程中出现了问题&#xff0c;导致 mssql-server 没有正确安装。以下是排查和修复步骤&#xff1a;1. 检查是否成功安装了 mssql-server 运行以下命令&#xff0c;确认是否已安装&#xff1a; dpkg -l | grep mssql-server如果没有任何输出&#xff0c;说明 mssql-…

Vue+ElementUI聊天室开发指南

Hi&#xff0c;我是布兰妮甜 &#xff01;在现代Web应用中&#xff0c;实时聊天功能已成为许多社交平台、协作工具和客户支持系统的核心需求。本文将详细介绍如何使用Vue.js框架配合ElementUI组件库实现一个功能完整的聊天室应用。我们将从项目搭建开始&#xff0c;逐步实现用户…

提升你的AI交互技能:使用Anthropic互动提示教程

探索Anthropic的互动式提示工程教程&#xff1a;让Claude与你更默契 在当今人工智能世界中&#xff0c;熟练掌握有效的提示工程成为了与AI进行高效沟通的关键。Anthropic推出了一款全面且互动性强的教程&#xff0c;名为“Prompt Engineering Interactive Tutorial”&#xff0…

从 JavaFX WebView 迁移至 JxBrowser

长久以来&#xff0c;JavaFX 一直包含一个内置的 WebView 组件&#xff0c;这是在 Java 应用中渲染 Web 内容的一个稳定方案。然而&#xff0c;在更复杂或要求更高的使用场景中&#xff0c;它可能就不够用了。因此&#xff0c;许多开发者转向了像 JxBrowser 这样的替代方案。 …

将 Go 应用从 x86 平台迁移至 Amazon Graviton:场景剖析与最佳实践

简介 近年来&#xff0c;Amazon Graviton 处理器以其优越的性价比和强劲的性能&#xff0c;成为了构建高效、可扩展云原生应用的重要选择。Graviton 采用基于 Arm64 架构的芯片&#xff0c;与传统的 x86 架构相比存在不少架构差异。虽然 Go 天生对 Arm64 具有良好支持&#xf…

arcgis api for js 设置地图服务请求带有请求头信息

通过地图的config模块的请求拦截器来设置请求头信息&#xff0c;如下示例&#xff1a; 1、引入&#xff1a;‘esri/config’ 1、设置请求头信息 import { loadArcgisModules } from /utils/map/mapLoadUtil export default { mounted() {this.loadMap()}, methods: {/** ****…

工业通信升级新选择:耐达讯CCLINKIE转Modbus TCP网关

在工业自动化系统中&#xff0c;协议转换网关的选择直接影响系统稳定性与通信效率。对于CCLINKIE转Modbus TCP场景&#xff0c;耐达讯通信技术网关凭借以下特性&#xff0c;成为多个项目中的优选方案。技术选型要点协议兼容性支持CCLINKIE的令牌环机制与Modbus TCP的TCP/IP协议…

使用python的 FastApi框架开发图书管理系统-前后端分离项目分享

今天给大家分享一个 我最近使用python 框架 fastapi 写的一个web项目 &#xff0c;叫图书管理系统。项目主要是来巩固 python的编程技术。使用的是前端后 分离开发。 主要实现的功能&#xff1a; 1、用户管理&#xff1a;可以新增、编辑、删除用户信息。 2、图书管理&#xff1…

上位机知识篇---Docker

Docker 详细介绍 一、Docker 是什么 Docker 是一个开源的容器化平台&#xff0c;它允许开发者将应用程序及其依赖项打包到一个标准化的单元&#xff08;称为容器&#xff09;中&#xff0c;确保应用在任何环境中都能以相同的方式运行。 简单来说&#xff0c;Docker 解决了 &…

蓝桥杯第十六届(2025)真题深度解析:思路复盘与代码实战

> 省一选手的血泪经验:**避免这些坑,你也能冲进国赛!** 2025年蓝桥杯省赛已落下帷幕,作为近年来**难度最高的一届竞赛**,不少选手在考场上遭遇了“滑铁卢”。本文将以C++ B组真题为例,逐题解析解题思路,并提供**优化后的AC代码与详细注释**。笔者最终排名省一前40%,…

使用gdal读取shp及filegdb文件

一、使用qgis开源工具构建两个文件&#xff0c;分别是filegdb和shp&#xff0c;每个文件包含一个图层&#xff0c;图层内容只包含一个字段&#xff1a;id&#xff0c;有两个数据行&#xff0c;图层几何为多边形&#xff0c;图层都是如下的效果。二、使用rust读取上述文件 rust依…