BEV学习---LSS-2

news2024/11/24 3:53:45

  • 前言
  • 一、相关参数设置
  • 二、LSS算法前向过程
    • 1.整体步骤
    • 2.创建视锥
    • 3.坐标变换
    • 4.视锥点云特征
    • 5.VoxelPooling
      • 5.1 cumsum_trick(池化累积求和技巧):
      • 5.2 VoxelPooling
      • 总结


前言

        目前在自动驾驶领域,比较火的一类研究方向是基于采集到的环视图像信息去构建BEV视角下的特征完成自动驾驶感知的相关任务。所以如何准确的完成从相机视角向BEV视角下的转变就变得由为重要。

        目前感觉比较主流的方法可以大体分为2种:

        1、显式估计图像的深度信息,完成BEV视角的构建,有文章也称其为自下而上的构建方式,代表作品有LSS、 BEVDet、BEVDepth等;

        2、利用Transformer中的query查询机制(利用BEV Query构建BEV特征),这一过程也被称为自上而下的构建方式,代表作品有BEVFormer、PETR等;

        LSS这篇论文的核心则是通过显式估计图像的深度信息,对采集到的环视图像进行特征提取,并根据估计出来的离散深度信息,实现图像特征向BEV特征的转换,进而完成自动驾驶中的语义分割任务。

一、相关参数设置

        对于感知算法而言,比较重要的是要了解在BEV视角下,x轴和y轴方向的感知距离,以及BEV网格的单位大小。

        在自车周围俯视平面x-y方向划分n个网格,每个网格表示特定物理距离d m。如200×200的BEV网格,每格代表0.5m,这个网格就表示100m的平面范围。如果恰好自车中心在网格中心位置,那么网格就表示自车前、后和左、右各50m的范围。注意这里强调用网格划分平面空间,并不涉及网格内的特征。

        在LSS源码中,其感知范围,BEV单元格大小,BEV下的网格尺寸如下:
        1、感知范围
        x轴方向的感知范围-50m~50m;y轴方向的感知范围-50m~50m;z轴方向的感知范围 -10m ~10m;

        2、BEV单元格大小
        x轴方向的单位长度 0.5m;y轴方向的单位长度 0.5m;z轴方向的单位长度20m;

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

二、LSS算法前向过程

1.整体步骤

在进行详细描述LSS算法前向过程之前,先整体概括下LSS算法包括的5个步骤:

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

        需要注意的是,生成的锥点位置是基于图像坐标系,同时锥点是图像特征上每个单元格映射回原始图像的位置;一般分为两步来做,即视锥生成和锥点由图像坐标系向ego坐标系的坐标转换;

2、对环视图像完成特征提取,并构建图像特征点云

        2.1、先利用Efficientnet-B0对环视图像进行特征提取(输入的环视图像 (bs,N,3,H,W),在进行特征提取之前,会将前两个维度进行合并提取特征,对应维度变换为 (bs,N,3,H,W) -> (bs*N,3,H,W));

        2.2、对其中的后两层特征进行融合,丰富特征的语义信息,融合后的特征尺寸大小为 (bs*N,512,H /16,W/16);

        2.3、估计深度方向的概率分布并输出特征图每个位置的语义特征 (用64维的特征表示),整个过程用1x1卷积层实现;

        2.4、对2.3步骤估计出来的离散深度利用softmax函数计算深度方向的概率密度;

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

3、利用变换后的ego坐标系的点和图像特征点云利用Voxel Pooling构建BEV特征;
4、对生成的BEV特征利用BEV Encoder做进一步的特征融合,然后对特征融合后的BEV特征进行语义分割;

        4.1、对BEV特征先利用ResNet-18进行多尺度特征提取;

        4.2、对输出的多尺度特征进行特征融合 + 对融合后的特征实现BEV网格上的语义分割;
5、最后,将输出的语义分割结果与binimgs的真值做基于像素的交叉熵损失,从而指导模型的学习。

2.创建视锥

        生成视锥,最后得到 D × H × W × 3的张量,这里的张量存储的是视锥点云坐标,也就是常见的(d,u,v)坐标。其中:

        D的取值范围为:[ 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21., 22., 23., 24., 25., 26., 27., 28., 29., 30., 31., 32., 33., 34., 35., 36., 37., 38., 39., 40., 41., 42., 43., 44.],得到41个离散深度值;

        H的取值范围为:[0.0000, 18.1429, 36.2857, 54.4286, 72.5714, 90.7143, 108.8571, 127.0000],在图像高度上8等分(16倍降采样);

        W的取值范围为:[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],在图像宽度上22等分(16倍降采样);

 def create_frustum(self):
        # 原始图片大小, ogfH:128    ogfW:352
        ogfH, ogfW = self.data_aug_conf['final_dim']

        # 下采样16倍后图像大小,fH: 8   fW: 22
        fH, fW = ogfH // self.downsample, ogfW // self.downsample

        # self.grid_conf['dbound'] = [4, 45, 1]
        # 在深度方向上划分网格,ds:D×fH×fW (41*8*22)
        ds = torch.arange(*self.grid_conf['dbound'], dtype=torch.float).view(-1, 1, 1).expand(-1, fH, fW)
        
        D, _, _ = ds.shape   # D:41  表示深度方向上网格的数量
        # 在0到351上划分22个格子  xs:D×fH×fW (41×8×22)
        xs = torch.linspace(0, ogfW - 1, fW, dtype=torch.float).view(1, 1, fW).expand(D, fH, fW)
        
        # 在0到127上划分8个格子  ys:D×fH×fW (41×8×22)
        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[i,j,k,0]就是(i,j)的位置
        # 深度为k的像素的宽度方向上的栅格坐标frustum:D×fH×fW×3
        frustum = torch.stack((xs, ys, ds), -1)
        return nn.Parameter(frustum, requires_grad=False)


3.坐标变换

        Get_Geometry():利用内外参,对N个相机视锥进行坐标变换,输出视锥点云在自车周围物理空间的位置索引;

        对N个相机的视锥进行坐标变换,简单来说就是内外参以及6个视角的变换,输出结果是B×N×D×H×W×3,其中3是ego坐标系下的空间位置[x,y,z],B是batch_id,N是相机个数, D是深度分布数。

        这样就等同于把ego周围空间划分为D×H×W块;

def get_geometry(self, rots, trans, intrins, post_rots, post_trans):
    """    
       rots:相机外参旋转, trans:相机外参平移, intrins:相机内参, post_rots:数据增强旋转, post_trans:数据增强平移
       Determine the (x,y,z) locations (in the ego frame) of the points in the point cloud.
       Returns B x N x D x H/downsample x W/downsample x 3
    """
    B, N, _ = trans.shape  # B: batch size N:环视相机个数

    # undo post-transformation
    # B x N x D x H x W x 3
    # 抵消数据增强及预处理对像素的变化,即还原数据增强中旋转和平移对坐标的影响
    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))

    # 图像坐标系 -> 归一化相机坐标系 -> 相机坐标系 -> 车身坐标系
    # 但是自认为由于转换过程是线性的,所以反归一化是在图像坐标系完成的,然后再利用
    # 求完逆的内参投影回相机坐标系
    points = torch.cat((points[:, :, :, :, :, :2] * points[:, :, :, :, :, 2:3],
                        points[:, :, :, :, :, 2:3]
                        ), 5)  # 反归一化
                        
    combine = 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	#维度不变,坐标值从相机坐标系->世界坐标系

4.视锥点云特征

        观察下面的网格图,首先解释一下网格图的坐标,其中 α 代表某一个深度 softmax 概率(大小为H×W),c代表语义特征的某一个channel的特征图,那么 αc 就表示这两个矩阵的对应元素相乘,于是就为特征图的每一个点赋予了一个depth 概率,然后广播所有的 αc,就得到了不同的channel的语义特征在不同深度(channel)的特征图,经过训练,重要的特征颜色会越来越深(由于 softmax 概率高),反之就会越来越暗淡,趋近于0。

对原始图像进行逐个图像特征点地进行深度和语义的预测,输出视锥点云特征:

        1、首先对原始图像通过EfficientNet学习特征;

        2、然后一层卷积直接出D+C的维度,D属于深度分布,C是语义特征;

        3、对D维深度做 softmax 归一化;

        4、将D与C做外积;
        5、最终输出的是H×W×D×C;

        6、B×N张图像,对应的输出就是  B×N×H×W×D×C。

class CamEncode(nn.Module):			# 提取图像特征,进行图像深度编码
    def __init__(self, D, C, downsample):
        super(CamEncode, self).__init__()
        self.D = D	# 41 深度区间【4-45】
        self.C = C	# 64 点的特征向量维度
	
		# efficientnet 提取特征
        self.trunk = EfficientNet.from_pretrained("efficientnet-b0")

        self.up1 = Up(320+112, 512)	# 上采样模块,输入320+112(多尺度融合),输出通道512
        # 1x1卷积调整通道数,输出通道数为D+C,D为可选深度值个数,C为特征通道数
        self.depthnet = nn.Conv2d(512, self.D + self.C, kernel_size=1, padding=0)
	
	# 深度维计算softmax,得到每个像素不同深度的概率
    def get_depth_dist(self, x, eps=1e-20):
        return x.softmax(dim=1)

    def get_depth_feat(self, x):
        # 使用efficientnet提取主干网络特征  x: BN x 512 x 8 x 22
        x = self.get_eff_depth(x)
        # 1x1卷积变换维度,输出通道数为D+C,x: BN x 105(C+D) x 8 x 22
        x = self.depthnet(x)
        
        # softmax编码,理解为每个可选深度的权重
        # 第二个维度的前D个作为深度维,进行softmax  depth: BN x 41 x 8 x 22
        depth = self.get_depth_dist(x[:, :self.D])
        
        # 将深度概率分布和特征通道利用广播机制相乘
        # 深度值 * 特征 = 2D特征转变为3D空间(俯视图)内的特征
        new_x = depth.unsqueeze(1) * x[:, self.D:(self.D + self.C)].unsqueeze(2)

        return depth, new_x	#  new_x: BN x 64 x 41 x 8 x 22


    def get_eff_depth(self, x):  # 使用efficientnet提取特征
        # adapted from https://github.com/lukemelas/EfficientNet-PyTorch/blob/master/efficientnet_pytorch/model.py#L231
        endpoints = dict()

        # Stem
        x = self.trunk._swish(self.trunk._bn0(self.trunk._conv_stem(x)))  #  x: BN x 32 x 64 x 176
        prev_x = x 

        # Blocks
        for idx, block in enumerate(self.trunk._blocks):
            drop_connect_rate = self.trunk._global_params.drop_connect_rate
            if drop_connect_rate:
                drop_connect_rate *= float(idx) / len(self.trunk._blocks) # scale drop connect_rate
            x = block(x, drop_connect_rate=drop_connect_rate)
            if prev_x.size(2) > x.size(2):
                endpoints['reduction_{}'.format(len(endpoints)+1)] = prev_x
            prev_x = x

        # Head
        endpoints['reduction_{}'.format(len(endpoints)+1)] = x  # x: BN x 320 x 4 x 11
        x = self.up1(endpoints['reduction_5'], endpoints['reduction_4'])  # 对endpoints[4]上采样,然后和endpoints[5] concat 在一起
        return x  # x: 24 x 512 x 8 x 22


    def forward(self, x):

		# depth: B*N x D x fH x fW(24 x 41 x 8 x 22)  x: B*N x C x D x fH x fW(24 x 64 x 41 x 8 x 22)
        depth, x = self.get_depth_feat(x)

        return x

5.VoxelPooling

        VoxelPooling():将上述Get_Geometry()和Cam_Encode()的输出作为输入,根据视锥点云在ego周围空间的位置索引,把点云特征分配到BEV pillar中,然后对同一个pillar中的点云特征进行sum-pooling处理,输出B,C,X,Y的BEV特征。

具体步骤如下:
1、首先将点云特征reshape成M×C,其中M=B×N×D×H×W;
2、然后将Get_Geometry()输出的空间点云转换到体素坐标下,得到对应的体素坐标。并通过范围参数过滤掉无用的点;

3、将体素坐标展平,reshape成一维的向量,然后对体素坐标中B、X、Y、Z的位置索引编码,然后对位置进行argsort,这样就把属于相同BEV pillar的体素放在相邻位置,得到点云在体素中的索引。

4、然后是一个神奇的操作,对每个体素中的点云特征进行sumpooling,代码中使用了cumsum_trick,巧妙地运用前缀和以及argsort的索引。输出是去重之后的Voxel特征,B×C×Z×X×Y。

5、最后使用unbind将Z维度切片,然后cat到C的维度上。代码中Z维度为1,实际效果就是去掉了Z维度,输出为B×C×X×Y的BEV 特征图。B×N张图像,对应的输出就是B×N×H×W×D×C;

5.1 cumsum_trick(池化累积求和技巧):

        模型中使用Pillar累积求和池化,“累积求和”是通过bin id 对所有点进行排序,对所有特征执行累积求和,然后减去 bin 部分边界处的累积求和值来执行求和池化。无需依赖 autograd 通过所有3个步骤进行反向传播,而是可以导出整个模块的分析梯度,从而将训练速度提高 2 倍。

        该层被称为“Frustum Pooling”,因为它将 n 个图像产生的截锥体转换为与摄像机数量 n 无关的固定维度 C×H×W 张量。

        计算原理的过程示意图:

5.2 VoxelPooling

def voxel_pooling(self, geom_feats, x):
        # geom_feats: B x N x D x H x W x 3 (4 x 6 x 41 x 8 x 22 x 3):在ego坐标系下的坐标点;
        # x: B x N x D x fH x fW x C(4 x 6 x 41 x 8 x 22 x 64):图像点云特征

        B, N, D, H, W, C = x.shape  # B: 4  N: 6  D: 41  H: 8  W: 22  C: 64
        Nprime = B*N*D*H*W  # Nprime: 173184

        # flatten x
        x = x.reshape(Nprime, C)   # 将特征点云展平,一共有 B*N*D*H*W 个点

        # flatten indices
        geom_feats = ((geom_feats - (self.bx - self.dx/2.)) / self.dx).long()  # 将ego下的空间坐标[-50,50] [-10 10]的范围平移转换到体素坐标[0,100] [0,20],计算栅格坐标并取整
        geom_feats = geom_feats.view(Nprime, 3)  # 将体素坐标同样展平  geom_feats: B*N*D*H*W x 3 (173184 x 3)
        batch_ix = torch.cat([torch.full([Nprime//B, 1], ix,
                             device=x.device, dtype=torch.long) for ix in range(B)])  # 每个点对应于哪个batch
        geom_feats = torch.cat((geom_feats, batch_ix), 1)  # geom_feats: B*N*D*H*W x 4(173184 x 4), geom_feats[:,3]表示batch_id

        # filter out points that are outside box
        # 过滤掉在边界线之外的点 x:0~199  y: 0~199  z: 0
        kept = (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]  # x: 168648 x 64
        geom_feats = geom_feats[kept]

        # get tensors from the same voxel next to each other
        ranks = 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相近的点就在一起了
        # x: 168648 x 64  geom_feats: 168648 x 4  ranks: 168648

        # cumsum trick
        if not self.use_quickcumsum:
            x, geom_feats = cumsum_trick(x, geom_feats, ranks)
        else:
            x, geom_feats = QuickCumsum.apply(x, geom_feats, ranks)  # 一个batch的一个格子里只留一个点 x: 29072 x 64  geom_feats: 29072 x 4

        # 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: 4 x 64 x 1 x 200 x 200
        final[geom_feats[:, 3], :, geom_feats[:, 2], geom_feats[:, 0], geom_feats[:, 1]] = x  # 将x按照栅格坐标放到final中

        # collapse Z
        final = torch.cat(final.unbind(dim=2), 1)  # 消除掉z维

        return final  # final: 4 x 64 x 200 x 200

总结

LSS的优点

1、LSS的方法提供了一个很好的融合到BEV视角下的方法。基于此方法,无论是动态目标检测,还是静态的道路结构认知,甚至是红绿灯检测,前车转向灯检测等等信息,都可以使用此方法提取到BEV特征下进行输出,极大地提高了自动驾驶感知框架的集成度。

2、虽然LSS提出的初衷是为了融合多视角相机的特征,为“纯视觉”模型而服务。但是在实际应用中,此套方法完全兼容其他传感器的特征融合。如果你想融合超声波雷达特征也不是不可以试试。

LSS的缺点:

1、极度依赖Depth信息的准确性,且必须显示地提供Depth 特征。当然,这是大部分纯视觉方法的硬伤。如果直接使用此方法通过梯度反传促进Depth网络的优化,如果Depth 网络设计的比较复杂,往往由于反传链过长使得Depth的优化方向比较模糊,难以取得较好效果。当然,一个好的解决方法是先预训练好一个较好的Depth权重,使得LSS过程中具有较为理想的Depth输出。

2、外积操作过于耗时。虽然对于机器学习来说,这样的计算量不足为道,但是对于要部署到车上的模型,当图片的feature size 较大, 且想要预测的Depth距离和精细度高时,外积这一操作带来的计算量则会大大增加。这十分不利于模型的轻量化部署,而这一点上,Transformer的方法反而还稍好一些。

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

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

相关文章

Mysql基础练习题 1527.患某种疾病的患者 (力扣)

查询患有 I 类糖尿病的患者 ID &#xff08;patient_id&#xff09;、患者姓名&#xff08;patient_name&#xff09;以及其患有的所有疾病代码&#xff08;conditions&#xff09;。I 类糖尿病的代码总是包含前缀 DIAB1 。 题目链接&#xff1a; https://leetcode.cn/proble…

【JS逆向学习】快乐学堂登陆接口(自定义DES加密、ddddocr验证码识别)

逆向目标 网址&#xff1a;https://www.91118.com/Passport/Account/Login接口&#xff1a;https://www.91118.com/passport/Account/LoginPost参数&#xff1a; passr 逆向过程 输入手机号、密码、验证码 点击登陆&#xff0c;多试几次&#xff0c;然后观察并比较不通请求…

具有RC反馈电路的正弦波振荡器(文氏桥振荡器+相移振荡器+双T振荡器)

2024-9-10&#xff0c;星期二&#xff0c;22:13&#xff0c;天气&#xff1a;雨&#xff0c;心情&#xff1a;晴。今天从下午开始淅淅沥沥一直在下雨&#xff0c;还好我有先见之明没骑自行车&#xff0c;但是我忘带伞了&#xff0c;属于说是有点脑子但是不多了&#xff0c;2333…

通信八股总结for普联

一.信息论 1.香农公式 1.1 香农公式的内容 &#xff1a;信道容量(bps) &#xff1a;信道带宽(Hz) &#xff1a;信噪比&#xff08;dB&#xff09;。 1.2 香农公式的意义 揭示了在有噪声的通信信道中&#xff0c;信息传输速率的理论上限。 可以通过改进编码或者调制技术来…

gradle 学习备忘

所学版本&#xff1a; Gradle User Manualhttps://docs.gradle.org/8.7/userguide/userguide.html?_gl1*1f2c50b*_gcl_au*NzQ2ODAwODgxLjE3MjMzNjI5Mzk.*_ga*NDY3MDM0MDIzLjE3MjMzNjI5NDA.*_ga_7W7NC6YNPT*MTcyNTk3NTU3OC4yLjEuMTcyNTk3NTY0MC42MC4wLjA. Android 官网文档&am…

第十九次CCF计算机软件能力认证题目解析(详细题解+代码+个人解读+持续跟新)

第一题 线性分类器 考虑一个简单的二分类问题——将二维平面上的点分为 A A A 和 B B B 两类。 训练数据包含 n n n 个点&#xff0c;其中第 i i i 个点&#xff08; 1 ≤ i ≤ n 1 ≤i ≤ n 1≤i≤n&#xff09;可以表示为一个三元组 ( x i , y i , t y p e i ) (x_i,y…

strncpy陷阱

最近遇到了一个strncpy的bug&#xff0c;他们居然说这不是bug&#xff0c;而我认为这是很严重的bug&#xff01; 相比于strcpy来说&#xff0c;strncpy具有更高的安全性&#xff0c;但是同时会带来一个问题&#xff0c;就是c字符串不会自动补’\0’。 废话不多说&#xff0c;…

使用Docker安装 Skywalking(单机版)

使用Docker安装 Skywalking&#xff08;单机版&#xff09; 文章目录 使用Docker安装 Skywalking&#xff08;单机版&#xff09;Skywalking 介绍Skywalking 安装 Skywalking 介绍 Skywalking官网 分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容…

GPU相关的一些截图

GPU相关的一些截图

【树和二叉树的相关定义】概念

1.回顾与概览 2.什么是树型结构 3.树的&#xff08;递归&#xff09;定义与基本术语 3.1树的定义 注意&#xff1a;除了根结点以外&#xff0c;任何一个结点都有且仅有一个前驱 3.2树的其他表示方式 3.3树的基本术语 结点&#xff1a;数据元素以及指向子树的分支根结点:非空…

OpenMV——色块追踪

Python知识&#xff1a; 1.给Python的列表赋值&#xff1a; 定义一个元组时就是 元组a (1,2,…) 元组中可以只有一个元素&#xff0c;但是就必须要加一个 “ , ” 如 a (2,) 而列表的定义和元组类似&#xff0c;只是把()换成[]: #那么下面的colour_1 ~ 3属于元组&#xf…

【leetcode C++】动态规划

动态规划解题思路 1. 状态表示 dp表 里面的值所代表的含义&#xff08;1.根据题目要求得出 2.经验 题目要求 3.分析问题过程中&#xff0c;发现重要子问题&#xff09; 2. 状态转移方程 dp[i] 等于什么 3. 初始化 保证填表不越界 4. 填表顺序 为了填写该状态的时候&am…

02 Docker基本管理

2.1 Docker镜像管理 2.1.1 案例&#xff1a;构建各类Docker镜像服务 2.1.2 案例环境 主机 操作系统 主机IP地址 服务器 Centos7.3x86-64 192.168.10.100 2.1.3 案例拓扑原理 通过 Dockerfile 创建常见应用镜像&#xff0c;Dockerfile 的构成如图 2.3 所示 图 2.1 Doc…

为什么说开放式耳机对耳朵更友好?性价比高的四款蓝牙耳机推荐

开放式耳机对耳朵更友好&#xff0c;主要体现在以下方面&#xff1a; 减少耳部闷热和潮湿&#xff1a;开放式耳机的开放结构&#xff0c;不会完全封闭耳朵&#xff0c;使得空气能够自由流通。这样可以有效减少因长时间佩戴导致的耳部闷热和潮湿情况。而耳部处于闷热潮湿的环境…

基于CNN-BiGUR的恶意域名检测方法

本文提出了一种基于 CNN 和 BiGRU 的恶意域名检测方法 CNN-BiGRU-Focal。利用卷积神经网络&#xff08;CNN&#xff09;提取域名字符的局部上下文特征。利用双向门控循环单元网络&#xff08;BiGRU&#xff09;捕捉域名字符序列的时间序列特征。同时&#xff0c;引入改进的 Foc…

十三、MySQL高级—读写分离(6)

&#x1f33b;&#x1f33b; 目录 一、Mycat 介绍1.1 是什么1.2 干什么的1.3 原理 二、安装启动2.1 解压缩文件拷贝到linux下 /usr/local/2.2 三个文件2.3 启动前先修改schema.xml&#xff08;配置&#xff09;2.4 再修改server.xml2.5 验证数据库访问情况2.6 启动程序2.7 启动…

Hive SQL子查询应用

目录 环境准备看如下链接 子查询 查询所有课程成绩均小于60分的学生的学号、姓名 查询没有学全所有课的学生的学号、姓名 解释: 没有学全所有课,也就是该学生选修的课程数 < 总的课程数。 查询出只选修了三门课程的全部学生的学号和姓名 环境准备看如下链接 环境准备h…

应用层 思维导图

绪论&#xff1a; ​“有志者自有千计万计&#xff0c;无志者只感千难万难。” 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 思维导图能很好的帮助到我们去学习和理解记忆知识&#xff0c;当我们对一个知识有了一定的框架后就能有逻辑性的去…

关于我的阿里云服务器被入侵 - 分析报告

目录 引言一、事件概述1. 异地登陆2. 挖矿程序3. 密钥未注册4. 勒索病毒 二、问题分析1. 异地登陆2. 挖矿程序3. 密钥登录失效&#xff08;密钥未注册&#xff09;4. 勒索病毒 三、安全知识讲解1. 密钥登录 四、总结 引言 因为是第一次租云服务器&#xff0c;所以出现了很多的…

Clion配置ESP32开发,一文就够了

目录 1.下载ESP-IDF2.配置ESP32开发环境2.1方法1:Clion官方手册2.2方法2: 3.测试Clion开发ESP32的环境4.关于Clion的monitor无法正常运行的解决方法 我这里使用的CLiion版本是2023.2.1&#xff0c;网上激活方法有很多&#xff0c;这里就不赘述。 电脑是WIn11系统。 1.下载ESP-I…