【自動駕駛】經典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αi?c

  • 通過像素估計到的每個深度特征為:(u,v,di,αic)(u,v,d_{i},\alpha_{i}c)(u,v,di?,αi?c)

  • 結合相機成像原理,已知內外參的情況下,可求出像素對應的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)=di?T?1(u;v;1)

  • 每個像素對應的3D特征向量即完成Lift過程,?(x,y,z)=αic\phi(x,y,z) = \alpha_{i}c?(x,y,z)=αi?c

總結: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)=αi?c
難點分析: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)postr?ots=(bs,N,3,3)
    post_trans:由圖像增強引起的平移矩陣,posttrans=(bs,N,3)post_trans = (bs, N, 3)postt?rans=(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)(bs?N,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)(bs?N,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
英文地址,請注明出處:http://en.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依…