SIRA-PCR: Sim-to-Real Adaptation for 3D Point Cloud Registration 论文解读

news2024/12/23 3:42:22

目录

一、导言

二、 相关工作

 1、三维点云配准工作

2、无监督域适应

三、SIRA-PCR

1、FlyingShape数据集

2、Sim-to-real自适应方法

3、配准

4、损失函数


一、导言

        该论文来自于ICCV2023,论文提出了一种新的方法SIRA-PCR,通过利用合成数据FlyingShapes解决现有数据稀缺问题。

        数据稀缺原因:现有的基于数据驱动的深度学习方法一般依赖于两类数据,一是单一物体级别如ModelNet40,ShapeNet,二是对于室内场景水平如3DMatch。虽然单一物体级别数据集有较强的几何形状,但很难推广到真实的室内场景,室内场景的数据集来训练性能很好,但捕获真实的室内场景十分耗时且估计的相机姿态存在错误标签。所以针对场景级的合成数据集仍然数据稀缺,所以本文创建了一个合成场景数据集FlyingShapes。

(1)构建了第一个大规模的室内合成数据集FlyingShapes,使用基于物理和随机的策略将ShapeNet对象插入3D-FRONT场景中。

(2)设计了一个称为SIRA的管道,包括一个自适应重采样模块,目的是缓解合成数据和真实数据之间的域差距(分布差异),从而提高点云配准的性能。

二、 相关工作

 1、三维点云配准工作

        一种是直接配准,一种是基于对应的方法(与以前的博文类似)

2、无监督域适应

        无监督自适应是一种解决源域(合成数据)与目标域(真实数据)之间分布差异的方法,旨在利用源域的标注数据和目标域的无标注数据,学习一个可以将源域数据映射到目标域的模型,从而提高目标域任务的性能,通过这样的方式,利用合成数据集来解决真实数据集标注不足的问题。

        对于以往的点云配准任务来说,一般通过利用GAN来执行域对抗训练,使得两个域的点云特征难以被鉴别器区分。还有方法通过自监督学习里解决域对齐。

三、SIRA-PCR

        SIRA-PCR框架,首先通过SIRA结构基于FlyingShapes数据集来训练合成数据到真实数据的域自适应,之后基于3DMatch或3DLoMatch(实验工作)使用GeoTransformer来训练配准工作。

        SIRA-PCR框架的目的:就是通过先训练一遍我们改进的合成数据集FlyingShape,来学习到一些真实数据集中应该有的特征,这样优化了真实数据集中由于标注错误,数据样本少而训练不足的问题,其实从本质上就是增加更多的样本量,完全可以把做合成数据集到合成2真实的域适应的工作看成一种增加样本量的方式。

1、FlyingShapes数据集

        3D-FRONT数据集是一个由专业设计师设计的室内场景和家具布局的场景数据集,但由于结构过于简单,FlyingShape数据集从3D-FRONT场景数据集中添加一定的家具模型来模拟真实的场景。

        FlyingShapes数据集考虑了三个因素进行优化:

(1)几何增强

        由于现实场景中家具摆放不一定合理,可能存在一定的随机性。所以使用两种方式添加对象数据集(单一对象),分别是基于物理意义的(重力要求),随机放置(无视重力)。

        由于3D-FRONT数据集中存在大量的地板、墙壁、天花板等简单的平面,对结构的泛化效果有限,从而也使得网络过分关注这些简单的平面结构,所以以50%的概率去除这些平坦平面,进而提高对于对象数据集中物体的几何结构平衡。

(2)高质量的视角选择

        视角选择着重考虑,点云数据集中,每一团点云结构应该包含较为足够数量的对象(大于5个对象结构),并且视角保持人类视角,且移动保持人类转动行进的速度(设置高度为1.6m,水平视角360°,垂直方向仰角0到45°,以30°或15°来均匀采样视图)

(3)数据准备

        为模拟RGB-D摄像机的深度信息,我们通过虚拟摄像机绘制深度图并转换为点云,保留重叠范围在30%以上的点云,提高模型的泛化能力。

2、Sim-to-real自适应方法

        这一部分就是做无监督域适应工作,其实本质来说也是一个GAN网络。

生成器(ResampleGAN):

        生成器采用Encoder-Decoder结构,Encoder部分是ResampleKPConvEncoder结构(KPConv+ARM自适应重采样模块+FPN,用于提取特征),Decoder部分是MLPDecoder结构(本质是三层1维卷积通过LeakyReLU激活函数相连,用于将特征转换为三维坐标)

        Encoder部分又可以看做一个KPConv与FPN的结合,并且每一层都会添加一个ARM重采样结构。KPConv负责从顶到底提取特征,FPN部分负责多层次的特征再次提取。

        ARM重采样:Adaptive Re-sample Module,实现了基于注意力机制的点的局部重采样,利用点的特征和邻点的信息来计算一个点的加权平均坐标,保证了每一步的卷积操作输出后都有针对邻点特征的优化。

        ARM算法:输入所有点的坐标(坐标矩阵),构建每个点周围的一个局部patch,并得到该点的特征和该点邻点特征,并通过加权邻点特征和该点特征,得到新的坐标点,作为调整点位置。

        对于论文图3的解释如下:

        我们举点云中任何一个点P_i为例子,实际是直接用点云的坐标矩阵来进行下面计算。

        对于一个点P_i的特征f_i(d维列向量)与P_i周围K个点的邻点特征F_i^P(K*d维矩阵)的转置相乘,并除以\sqrt{d}进行归一化,之后通过Softmax函数得到权重系数w_i(K维列向量),w_i乘以K个邻近点坐标矩阵P_i (K*3维矩阵)得到重采样点y_i

        公式解释:(其中,W^A就是将点运算转换为矩阵运算,可忽略看)

                                                   w_i=softmax(\frac{(F_i^PW^A)(f_iW^A)^T}{\sqrt{d}})

                                            y_i=\sum_{k=1}^K w_{i,k} P_{i,k}^\mathrm{P}, \qquad \sum_{k=1}^Kw_{i,k}=1

         

生成器代码如下:

#重采样代码
class PatchResampleBlock(nn.Module):

    def __init__(self, feat_channels) -> None:
        r"""Initialize a patch resample block.

        Args:
            feat_channels: dimension of input features
        """
        super(PatchResampleBlock, self).__init__()
        self.feat_channels = feat_channels
        self.feat_proj = nn.Linear(self.feat_channels, self.feat_channels)

    def forward(self, points, feats, neighbor_indices):
        point_num, neighbor_limit = neighbor_indices.shape

        # adjust neighbor_indices (stand still when the patch has few neighbors)
        point_indices = torch.arange(point_num,
                                     device=neighbor_indices.device).reshape(
                                         (point_num, 1)).repeat(
                                             (1, neighbor_limit))  # (N ,K)
        neighbor_indices = torch.where(neighbor_indices < point_num,
                                       neighbor_indices, point_indices)

        # feature projection
        feats = self.feat_proj(feats)
        neighbor_feats = feats[neighbor_indices]  # (N, K, d)
        neighbor_points = points[neighbor_indices]  # (N, K, 3)

        neighbor_weights = torch.einsum(
            "nd,nkd->nk", feats,
            neighbor_feats)  # (N, d) x (N, K, d) -> (N, K)
        neighbor_weights = nn.functional.softmax(neighbor_weights /
                                                 self.feat_channels**0.5,
                                                 dim=-1)  # (N, K) -> (N, K)

        output_points = torch.einsum(
            "nk,nkp->np", neighbor_weights,
            neighbor_points)  # (N, K) x (N, K, 3) -> (N, 3)

        return output_points

#KPconv+FPN+ARM作为Encoder
class ResampleKPConvEncoder(nn.Module):

    def __init__(self, input_dim, output_dim, init_dim, kernel_size,
                 init_radius, init_sigma, group_norm):
        super(ResampleKPConvEncoder, self).__init__()

        self.encoder1_1 = ConvBlock(input_dim, init_dim, kernel_size,
                                    init_radius, init_sigma, group_norm)
        self.encoder1_2 = ResidualBlock(init_dim, init_dim * 2, kernel_size,
                                        init_radius, init_sigma, group_norm)

        self.encoder2_1 = ResidualBlock(init_dim * 2,
                                        init_dim * 2,
                                        kernel_size,
                                        init_radius,
                                        init_sigma,
                                        group_norm,
                                        strided=True)
        self.encoder2_2 = ResidualBlock(init_dim * 2, init_dim * 4,
                                        kernel_size, init_radius * 2,
                                        init_sigma * 2, group_norm)
        self.encoder2_3 = ResidualBlock(init_dim * 4, init_dim * 4,
                                        kernel_size, init_radius * 2,
                                        init_sigma * 2, group_norm)

        self.encoder3_1 = ResidualBlock(init_dim * 4,
                                        init_dim * 4,
                                        kernel_size,
                                        init_radius * 2,
                                        init_sigma * 2,
                                        group_norm,
                                        strided=True)
        self.encoder3_2 = ResidualBlock(init_dim * 4, init_dim * 8,
                                        kernel_size, init_radius * 4,
                                        init_sigma * 4, group_norm)
        self.encoder3_3 = ResidualBlock(init_dim * 8, init_dim * 8,
                                        kernel_size, init_radius * 4,
                                        init_sigma * 4, group_norm)

        self.encoder4_1 = ResidualBlock(init_dim * 8,
                                        init_dim * 8,
                                        kernel_size,
                                        init_radius * 4,
                                        init_sigma * 4,
                                        group_norm,
                                        strided=True)
        self.encoder4_2 = ResidualBlock(init_dim * 8, init_dim * 16,
                                        kernel_size, init_radius * 8,
                                        init_sigma * 8, group_norm)
        self.encoder4_3 = ResidualBlock(init_dim * 16, init_dim * 16,
                                        kernel_size, init_radius * 8,
                                        init_sigma * 8, group_norm)

        self.resample4 = PatchResampleBlock(feat_channels=init_dim * 16)
        self.decoder4 = UnaryBlock(init_dim * 16 + 3, init_dim * 16,
                                   group_norm)

        self.resample3 = PatchResampleBlock(feat_channels=init_dim * 8)
        self.decoder3 = UnaryBlock(init_dim * 24 + 3, init_dim * 8, group_norm)

        self.resample2 = PatchResampleBlock(feat_channels=init_dim * 4)
        self.decoder2 = UnaryBlock(init_dim * 12 + 3, init_dim * 4, group_norm)

        self.resample1 = PatchResampleBlock(feat_channels=init_dim * 2)
        self.decoder1 = UnaryBlock(init_dim * 6 + 3, output_dim, group_norm)

        self.outputlayer = LastUnaryBlock(output_dim, output_dim)

    def forward(self, data_dict):
        points_list = data_dict['points']
        neighbors_list = data_dict['neighbors']
        subsampling_list = data_dict['subsamples']
        upsampling_list = data_dict['upsamples']

        feats_s1 = torch.ones((points_list[0].shape[0], 1),
                              dtype=torch.float32).to(points_list[0])
        feats_s1 = self.encoder1_1(feats_s1, points_list[0], points_list[0],
                                   neighbors_list[0])
        feats_s1 = self.encoder1_2(feats_s1, points_list[0], points_list[0],
                                   neighbors_list[0])

        feats_s2 = feats_s1
        feats_s2 = self.encoder2_1(feats_s2, points_list[1], points_list[0],
                                   subsampling_list[0])
        feats_s2 = self.encoder2_2(feats_s2, points_list[1], points_list[1],
                                   neighbors_list[1])
        feats_s2 = self.encoder2_3(feats_s2, points_list[1], points_list[1],
                                   neighbors_list[1])

        feats_s3 = feats_s2
        feats_s3 = self.encoder3_1(feats_s3, points_list[2], points_list[1],
                                   subsampling_list[1])
        feats_s3 = self.encoder3_2(feats_s3, points_list[2], points_list[2],
                                   neighbors_list[2])
        feats_s3 = self.encoder3_3(feats_s3, points_list[2], points_list[2],
                                   neighbors_list[2])

        feats_s4 = feats_s3
        feats_s4 = self.encoder4_1(feats_s4, points_list[3], points_list[2],
                                   subsampling_list[2])
        feats_s4 = self.encoder4_2(feats_s4, points_list[3], points_list[3],
                                   neighbors_list[3])
        feats_s4 = self.encoder4_3(feats_s4, points_list[3], points_list[3],
                                   neighbors_list[3])

        resampled_points4 = self.resample4(points_list[3], feats_s4,
                                           neighbors_list[3])
        latent_s4 = torch.cat([feats_s4, resampled_points4],
                              dim=1)  # (N4, 64*16+3)
        latent_s4 = self.decoder4(latent_s4)

        resampled_points3 = self.resample3(points_list[2], feats_s3,
                                           neighbors_list[2])
        latent_s3 = nearest_upsample(latent_s4, upsampling_list[2])
        latent_s3 = torch.cat([latent_s3, feats_s3, resampled_points3],
                              dim=1)  # (N3, 64*16+64*8+3)
        latent_s3 = self.decoder3(latent_s3)

        resampled_points2 = self.resample2(points_list[1], feats_s2,
                                           neighbors_list[1])
        latent_s2 = nearest_upsample(latent_s3, upsampling_list[1])
        latent_s2 = torch.cat([latent_s2, feats_s2, resampled_points2],
                              dim=1)  # (N2, 64*8+64*4+3)
        latent_s2 = self.decoder2(latent_s2)  # (N1, 256)

        resampled_points1 = self.resample1(points_list[0], feats_s1,
                                           neighbors_list[0])
        latent_s1 = nearest_upsample(latent_s2, upsampling_list[0])
        latent_s1 = torch.cat([latent_s1, feats_s1, resampled_points1],
                              dim=1)  # (N1, 64*4+64*2+3)
        latent_s1 = self.decoder1(latent_s1)  # (N1, 256)

        output = self.outputlayer(latent_s1)  # (N1, 256)

        return output

#三层卷积作为Decoder
class MLPDecoder(nn.Module):

    def __init__(self, dimoffeat=256):
        super(MLPDecoder, self).__init__()
        self.sharedmlp = nn.Sequential(
            nn.Conv1d(dimoffeat, int(dimoffeat / 2), 1),
            nn.LeakyReLU(negative_slope=0.2),
            nn.Conv1d(int(dimoffeat / 2), int(dimoffeat / 8), 1),
            nn.LeakyReLU(negative_slope=0.2),
            nn.Conv1d(int(dimoffeat / 8), 3, 1))

    def forward(self, feat):
        feat = feat.transpose(-1, -2)  # (N, d) -> (d, N)
        feat = feat.unsqueeze(0)  # (d, N) -> (batch, d, N)
        output = self.sharedmlp(feat)  # (batch, d, N) -> (batch, 3, N)
        output = output.squeeze(0)  # (batch, 3, N) -> (3, N)
        output = output.transpose(-1, -2)  # (3, N) -> (N, 3)

        return output

#生成器
class ResampleGAN(nn.Module):

    def __init__(self, input_dim, dimofbottelneck, init_dim, kernel_size,
                 init_radius, init_sigma, group_norm):
        super(ResampleGAN, self).__init__()
        self.encoder = ResampleKPConvEncoder(input_dim, dimofbottelneck,
                                             init_dim, kernel_size,
                                             init_radius, init_sigma,
                                             group_norm)
        self.decoder = MLPDecoder(dimoffeat=dimofbottelneck)

    def forward(self, data_dict):
        feat = self.encoder(data_dict)
        points_recovered = self.decoder(feat)

        return points_recovered

 判别器(ResampleGAN):

        判别器输入点云中的坐标矩阵和邻点坐标信息,考虑到生成器采用不同的ARM重采样自适应,会导致改变局部的密度,所以在多尺度下(小、中、大尺度,长度分别是5,10,20)进行特征提取(特征提取使用PointNet网络),并进行特征融合,判别器的目的是判断不同层次的点是真实还是合成的。

class MultiScalePointNet(nn.Module):

    def __init__(self, dimoffeat=256, multiscale=[5, 10, 20]) -> None:
        super(MultiScalePointNet, self).__init__()
        self.multiscale = multiscale
        self.patchpointnets = nn.ModuleList()
        for scale in multiscale:
            self.patchpointnets.append(
                nn.Sequential(
                    nn.Conv1d(3, dimoffeat // 4, kernel_size=1, stride=1),
                    nn.LeakyReLU(negative_slope=0.2),
                    nn.Conv1d(dimoffeat // 4,
                              dimoffeat // 2,
                              kernel_size=1,
                              stride=1), nn.LeakyReLU(negative_slope=0.2),
                    nn.Conv1d(dimoffeat // 2,
                              dimoffeat,
                              kernel_size=1,
                              stride=1)))
        self.maxpool = nn.AdaptiveMaxPool1d(output_size=1)
        self.sharedmlp = nn.Sequential(
            nn.Conv1d(len(multiscale) * dimoffeat,
                      dimoffeat,
                      kernel_size=1,
                      stride=1), nn.LeakyReLU(negative_slope=0.2),
            nn.Conv1d(dimoffeat, dimoffeat // 2, kernel_size=1, stride=1),
            nn.LeakyReLU(negative_slope=0.2),
            nn.Conv1d(dimoffeat // 2, dimoffeat // 4, kernel_size=1, stride=1),
            nn.LeakyReLU(negative_slope=0.2),
            nn.Conv1d(dimoffeat // 4, dimoffeat // 8, kernel_size=1, stride=1),
            nn.LeakyReLU(negative_slope=0.2),
            nn.Conv1d(dimoffeat // 8, 1, kernel_size=1, stride=1))

    def forward(self, points, neighbor_indices):
        patchfeats_list = []
        for idx in range(len(self.multiscale)):
            neighbors = points[
                neighbor_indices[:, :self.multiscale[idx]]]  # shape (N, K, 3)
            neighbors = neighbors - points[:, None, :]
            neighbors = neighbors.transpose(1, 2)  # (N, K, 3) -> (N, 3, K)

            feats = self.patchpointnets[idx](
                neighbors)  # (N, 3, K) -> (N, d, K)
            patchfeats = self.maxpool(feats)  # (N, d, K) -> (N, d, 1)
            patchfeats = patchfeats.squeeze(-1)  # (N, d, 1) -> (N, d)
            patchfeats = patchfeats.transpose(0, 1)  # (N, d) -> (d, N)
            patchfeats_list.append(patchfeats)

        multiscalefeats = torch.cat(patchfeats_list,
                                    dim=0)  # m x (d, N) -> (md, N)

        multiscalefeats = multiscalefeats.unsqueeze(
            0)  # (md, N) -> (batch, md, N)
        out = self.sharedmlp(
            multiscalefeats)  # (batch, md, N) -> (batch, 1, N)
        out = out.squeeze(0)  # (batch, 1, N) -> (1, N)
        out = out.squeeze(0)

        return out

3、配准

        配准工作backbone使用的是GeoTransformer结构作为配准模块,并且使用GeoTransformer中提到的局部到全局的配准策略LGR策略来替换RANSAC。

        LGR可以利用所有的对应关系来估计变换矩阵,而不像RANSAC使用一部分内点而存在内点分布不均匀的影响,LGR可以更好地处理重复几何结构和低重叠比例情况。

4、损失函数

        损失函数包含两个模块,对于SIRA和点云配准两个部分分别进行训练。

        对于SIRA模块,使用倒角损失,生成器损失和判别器损失三部分来训练域自适应。

        对于配准模块,使用重叠圆损失(点对应损失)和全局点对损失两部分训练(使用的就是Geotrans的配准模块,损失也一模一样)。

四、实验

1、对比不同框架

        提前获得了一部分数据集(合成数据集)的情况下,其实效果相比GeoTransformer提升并没有很大,说明这个方法idea很好,但其实用在配准工作其实一般。

2、点云配准训练中,不同数量的数据集对性能指标的影响

        在不同的backbone下,显然Samples越少,对其他backbone影响越来越明显,对SIRA-PCR影响则很微小。性能指标包括:特征匹配召回,离群比、配准召回。不得不说这不就是因为提前吃了一遍数据集,学到了特征的影响吗。

3、其他实验

        对于FlyingShapes数据集的建立,引入structure3D效果提升,这个不太了解情况,证明了删除平面确实有效。

        消融实验中对于SIRA的不同组件以及FlyingShapes的object data和scene data进行消融。

论文参考:ICCV 2023 Open Access Repository        

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

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

相关文章

[RoarCTF2019]黄金6年

下载解压得一个MP4视频文件 播放了一下几秒过&#xff0c;太快了&#xff0c;使用Potplayer做视频切片&#xff0c;空格暂停播放&#xff0c;使用D F两个快捷键逐帧查看&#xff08;F前进一帧&#xff0c;D后退一帧&#xff09; 发现一个二维码&#xff0c;扫码得key1&#xf…

PiX4Dmatic1.63 Pix4Dsurvey1.63 实景三维建模软件功能介绍 下载软件License使用

PIX4D matic摄影测量软件是一款非常不错的摄影测量软件&#xff0c;这款软件用于廊道和大比例尺测绘的下一代摄影测量软件&#xff0c;PIX4D matic也支持常用的垂直坐标系及其相应的大地水准面。 PIX4D matic(摄影测量软件)是一款非常不错的摄影测量软件&#xff0c;这款软件用…

如果提议者提议区块的时间晚了,会对见证有何影响?

原文标题&#xff1a;《On Attestations, Block Propagation, and Timing Games》 撰文&#xff1a;Nero_eth 编译&#xff1a;Tia&#xff0c;Techub News 如今&#xff0c;提议者的时序博弈已经很常见了&#xff0c;很多研究也都在分析这一现象。 本篇文章将带大家了解提议…

windows上传的文本在linux执行不了,格式转换

在windows编辑的文件脚本上传到linux里面执行不了 1.现象描述 比如在windows编辑简单的文本 2.上传到linux后执行无结果 无响应 3.编码问题 比普通文件多了with CRLF line terminators结尾格式。 cat -v 可以让隐藏的转义字符也打印中显示 4.原因windows和linux的换行符不…

逐级删除空目录 如果目录非空则停止删除操作 os.removedirs(path)

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 逐级删除空目录 如果目录非空 则停止删除操作 os.removedirs(path) [太阳]选择题 下列关于代码和os.removedirs(path)函数说法正确的是&#xff1f; import os os.makedirs("D:/test1…

GAMES101——作业5 光线与三角形相交(菲涅尔反射率)

任务 需要修改的函数是&#xff1a; Renderer.cpp 中的 Render() &#xff1a;这里你需要为每个像素生成一条对应的光线&#xff0c;然后调用函数 castRay() 来得到颜色&#xff0c;最后将颜色存储在帧缓冲区的相应像素中。 Triangle.hpp 中的 rayTriangleIntersect() :…

测试八股文(自总版)(更新中...)

测试八股文---从网上各处搜罗滴 软件测试的一些基础知识1. 什么是软件&#xff1f;1. 什么是软件测试?2. 软件测试分类?3. 软件生命周期的各阶段4. 几个模型---瀑布模型 , V模型 , 敏捷开发模型5. 软件测试基本流程&#xff08;1&#xff09;需求分析测试需求分析具体怎么来进…

力扣爆刷第174天之TOP200五连刷136=140(最小k数、字典序、跳跃游戏)

力扣爆刷第174天之TOP200五连刷136140&#xff08;最小k数、字典序、跳跃游戏&#xff09; 文章目录 力扣爆刷第174天之TOP200五连刷136140&#xff08;最小k数、字典序、跳跃游戏&#xff09;一、LCR 159. 库存管理 III二、450. 删除二叉搜索树中的节点三、440. 字典序的第K小…

【C语言】进程和线程详解

目录 C语言进程和线程详解1. 进程和线程的对比2. 进程的基本概念2.1 进程的定义2.2 进程的特点2.3 进程的生命周期 3. 进程管理3.1 进程创建3.2 进程间通信&#xff08;IPC&#xff09;3.2.1 管道&#xff08;Pipe&#xff09; 4. 线程的基本概念4.1 线程的定义4.2 线程的特点 …

Halcon灰度图像的形态学运算

Halcon灰度图像的形态学运算 本文介绍的算子的输入类型是灰度的Image图像。 1. 灰度图像与区域的区别 基于区域的形态学运算与基于灰度图像的形态学运算的根本区别在于&#xff0c;二者输入的对象不同。前者输入的是一些区域&#xff0c;并且这些区域是经过闽值处理的二值图…

微信小程序在线客服源码系统全端通吃 带完整的安装代码包以及搭建部署教程

系统概述 “微信小程序在线客服源码系统全端通吃”是一款集智能客服、人工客服、消息管理、数据分析等功能于一体的综合性解决方案。该系统基于微信小程序平台开发&#xff0c;支持全端接入&#xff08;包括Web、App、小程序等&#xff09;&#xff0c;实现多渠道客户服务的无…

英国海外媒体通稿宣发:顶级媒体宣发

1.伦敦日报londonjournal 作为英国首都的权威日报&#xff0c;伦敦日报一直是英国新闻界的佼佼者。它详尽报道伦敦及英国各地的政治、经济、社会、文化、体育等各方面的新闻&#xff0c;深受读者喜爱。 2.英国先驱报ukherald 英国先驱报是一份全国性日报&#xff0c;以深度分…

源码构建LAMP

目录 一、安装Apache 二、安装Mysql 三、安装PHP 四、安装论坛 一、安装Apache 1.cd 到opt目录下面&#xff0c;将压缩包拉进Xhell 2.解压缩apr和httpd压缩包 tar xf apr-1.6.2.tar.gz tar xf apr-util-1.6.0.tar.gz tar xf httpd-2.4.29.tar.bz2 3.将apr-1.6.2 移动到ht…

playbook(剧本)基本应用、playbook常见语法、playbook和ansible操作的编排

playbook(剧本): 是ansible⽤于配置,部署,和管理被控节点的剧本。⽤ 于ansible操作的编排。 使⽤的格式为yaml格式 一、YMAL格式 以.yaml或.yml结尾 ⽂件的第⼀⾏以 "---"开始&#xff0c;表明YMAL⽂件的开始(可选的) 以#号开头为注释 列表中的所有成员都开始于…

开放式耳机原理是什么?它通过不入耳的方式带来动感音乐

开放式耳机的原理主要分为两种类型&#xff1a;气传导和骨传导。 气传导耳机&#xff1a;这种耳机的工作原理依赖于空气作为声音传播的介质。具体来说&#xff0c;音频设备通过耳机线将电信号传递到耳机&#xff0c;耳机内部的驱动单元&#xff08;通常是动圈式或平衡电枢式&am…

未开启语音助手时,远程控制功能助你快速在家找回手机!

完成一整天的大扫除之后&#xff0c;顺手就想摸出手机刷一下短视频&#xff0c;但摸不到。干了一天活&#xff0c;手机放哪里都忘了&#xff0c;于是不得不在几个房间之间寻找。 但找过手机的都知道&#xff0c;越找越是找不到。糟糕的是前几天我嫌麻烦&#xff0c;把语音助手…

<数据集>铝型材缺陷识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1885张 标注数量(xml文件个数)&#xff1a;1885 标注数量(txt文件个数)&#xff1a;1885 标注类别数&#xff1a;10 标注类别名称&#xff1a;[budaodian, tufen, loudi, qikeng, pengshang, tucengkailie, zangdi…

光伏模拟器的应用

太阳能光伏 (PV) 模拟器是一种可编程电源&#xff0c;用于模拟太阳能电池板。模拟器具有快速瞬态响应&#xff0c;可响应负载条件的变化并保持电压-电流特性的输出。 用户可以根据系统规格定义太阳能电池板配置&#xff0c;并通过选择环境条件来选择适当的环境条件进行模拟。用…

通风采光排烟天窗现行七本图集概览

在建筑设计与施工中&#xff0c;通风采光排烟天窗作为优化室内环境的重要设备&#xff0c;选择合适的型号及合理应用至关重要。当前市场上存在着多本标准化、规范化的通风采光排烟天窗图集&#xff0c;为设计师、工程师及施工单位、通风采光排烟天窗生产厂家提供丰富的参考资源…

如何有效找到目标客户群体?

在激烈的市场竞争中&#xff0c;找到并锁定目标客户群体是企业成功的关键。以下是几种有效的策略&#xff0c;帮助您精准定位并吸引目标客户。 1. 明确市场定位与客户画像 首先&#xff0c;企业需要明确市场定位&#xff0c;并绘制详细的客户画像&#xff0c;包括年龄、性别、…