SLAM【十一】建图

这篇具有很好参考价值的文章主要介绍了SLAM【十一】建图。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

建图的功能:

  1. 定位:第一次跑,就把地图保存下来,让机器人在下次开机后依然能够在地图中定位。
  2. 导航:需要创建稠密地图,因为要进行路径规划,需要知道地图中那些地方可以通过,那些地方不可以通过
  3. 避障:与导航类似,但更注重局部的、动态的障碍物的处理。也需要稠密地图。
  4. 重建: 创建周围环境的重建模型,用于展示,因此需要美观,能够像电子游戏中的三维场景那样。稠密地图
  5. 交互:人与地图之间的互动。例如用于增强现实。

具体的各种地图类型与用途之间的关系:

SLAM【十一】建图
稀疏地图只建模感兴趣的部分,也就是前面说了很久的特征点(路标点)。
稠密地图是指建模所有看到过的部分。

单目稠密重建

立体视觉

在稠密重建中,我们需要知道每一个像素点的距离,对此的解决方案为:

  1. 使用单目相机,估计相机运动,并且三角化计算像素的距离。
  2. 使用双目相机,利用左右目的视差计算像素的距离(多目原理相同)
  3. 使用RGB-D相机直接获得像素距离。

单目双目的缺点:计算量巨大,最后得到不怎么可靠的深度估计。

因此,稠密地图的重建往往选择RGB-D。

RGB-D缺点:无法被很好的应用在室外、大场景场合中。而单目、双目仍能通过立体视觉估计深度信息。

针对单目的稠密估计,需要完成的是:在给定相机轨迹的基础上,如何根据一段时间的视频序列,来估计某张图像的深度。换言之,我们不考虑 SLAM,先来考虑稍为简单的建图问题。
假定有某一段视频序列,我们通过某种算法得到了每一帧对应的轨迹(当然也很可能是由视觉里程计前端估计所得)。现在我们以第一张图像为参考帧,计算参考帧中每一个像素的深度(或者说距离)。
在使用特征点时,通过以下两步完成这个过程:

  1. 首先,我们对图像提取特征,并根据描述子计算了特征之间的匹配。换言之,通过特征,我们对某一个空间点进行了跟踪,知道了它在各个图像之间的位置。
  2. 然后,由于我们无法仅用一张图像确定特征点的位置,所以必须通过不同视角下的观测,估计它的深度,原理即前面讲过的三角测量。

由于在在稠密深度估计中,不同之处是,在这里我们不能把每个像素当作特征点计算描述子。因此在这里匹配很重要。也就是要解决:如何确定第一幅图的某像素出现在其他图里的位置
当我们知道了某个像素在各个图中的位置,就能像特征点那样,利用三角测量确定它的深度。不过不同的是,在这里我们要使用很多次三角测量让深度估计收敛,而不仅是一次。我们希望深度估计,能够随着测量的增加,从一个非常不确定的量,逐渐收敛到一个稳定值。这就是深度滤波器技术

极线搜索与块匹配

根据题目,这一部分分为极线搜索块匹配
极线搜索就是:在相机在第一个视角拍摄到的图像中确定一个像素 p 1 p_1 p1,根据目前相机的角度可以确定 p 1 p_1 p1像素的深度在某个区域之内,某最小值到无穷远之间。故该像素点对应的空间点就分布在某条线段上。当相机变换了另一个视角,这条线段的投影也形成图像平面上的一条线,这条线就是极线当知道相机在这两个视角的运动时,这条极线就能够确定,最终的问题就是:极线上的哪一点是 p 1 p_1 p1。现在是根据运动来匹配像素,与之前的前端不同的是:前端是先匹配特征点而不是所有的像素(因为不能每个像素都计算描述子),然后根据匹配的特征点来估计相机的运动。这里是根据相机的运动匹配像素

SLAM【十一】建图
这里在寻找 p 2 p_2 p2的过程中,不像特征点法那样计算描述子,这里没有计算,所以只能在极线上搜索和 p 1 p_1 p1长得比较相似的点。所以叫极线搜索。如果一个一个比较像素,有点像直接法。易受光照影响,同时如果有与 p 1 p_1 p1相似的点,但不是真的点呢。这不就有点像回环检测那一部分吗,相似性。
解决方法:在 p 1 p_1 p1 周围取一个大小为 w × w w × w w×w 的小块,然后在极线上也取很多同样大小的小块进行比
较,就可以一定程度上提高区分性。这就是所谓的块匹配。从而假设变成了从像素的灰度不变性变成了图像块的灰度不变性。

p 1 p_1 p1周围的小块 A ∈ R w × w \boldsymbol{A} \in \mathbb{R}^{w \times w} ARw×w,极线上的 n n n个小块记成 B i , . . . , n \boldsymbol{B}_i,...,n Bi,...,n。计算相似度:

  1. SAD(Sum of Absolute Difference)。顾名思义,即取两个小块的差的绝对值之和:
    S ( A , B ) S A D = ∑ i , j ∣ A ( i , j ) − B ( i , j ) ∣ S(\boldsymbol{A}, \boldsymbol{B})_{S A D}=\sum_{i, j}|\boldsymbol{A}(i, j)-\boldsymbol{B}(i, j)| S(A,B)SAD=i,jA(i,j)B(i,j)

  2. SSD。 SSD 并不是说大家喜欢的固态硬盘,而是 Sum of Squared Distance(SSD)(平方和)的意思:
    S ( A , B ) S S D = ∑ i , j ( A ( i , j ) − B ( i , j ) ) 2 S(\boldsymbol{A}, \boldsymbol{B})_{S S D}=\sum_{i, j}(\boldsymbol{A}(i, j)-\boldsymbol{B}(i, j))^{2} S(A,B)SSD=i,j(A(i,j)B(i,j))2

  3. NCC(Normalized Cross Correlation)(归一化互相关)。这种方式比前两者要复杂一些,它计算的是两个小块的相关性:

S ( A , B ) N C C = ∑ i , j A ( i , j ) B ( i , j ) ∑ i , j A ( i , j ) 2 ∑ i , j B ( i , j ) 2 S(\boldsymbol{A}, \boldsymbol{B})_{N C C}=\frac{\sum_{i, j} \boldsymbol{A}(i, j) \boldsymbol{B}(i, j)}{\sqrt{\sum_{i, j} \boldsymbol{A}(i, j)^{2} \sum_{i, j} \boldsymbol{B}(i, j)^{2}}} S(A,B)NCC=i,jA(i,j)2i,jB(i,j)2 i,jA(i,j)B(i,j)
注意:由于这里用的是相关性,所以相关性接近 0 表示两个图像不相似,而接近1才表示相似。前面两种距离则是反过来的,接近 0 表示相似,而大的数值表示不相似。

和我们遇到过的许多情形一样,这些计算方式往往存在一个精度——效率之间的矛盾。精度好的方法往往需要复杂的计算,而简单的快速算法又往往效果不佳。这需要我们在实际工程中进行取舍。另外,除了这些简单版本之外,我们可以先把每个小块的均值去掉,称为去均值的 SSD、去均值的 NCC 等等。去掉均值之后,我们允许像“小块 B 比 A 整体上亮一些,但仍然很相似”这样的情况,因此比之前的更加可靠一些。(这样好像可以避免掉光照的影响)

高斯分布的深度滤波器

就是估计深度,使之达到最优,同时计算量也不大。
第一种方法:假设深度值服从高斯分布,得到一种类卡尔曼式的方法。
第二种方法:均匀-高斯混合分布的假设,另一种形式更为复杂的深度滤波器。
这里主要介绍第一种方法:
设某个像素点的深度 d d d 服从:
P ( d ) = N ( μ , σ 2 ) P(d)=N\left(\mu, \sigma^{2}\right) P(d)=N(μ,σ2)

每当新的数据到来,我们都会观测到它的深度。同样的,假设这次观测亦是一个高斯分布:
P ( d o b s ) = N ( μ o b s , σ o b s 2 ) P\left(d_{o b s}\right)=N\left(\mu_{o b s}, \sigma_{o b s}^{2}\right) P(dobs)=N(μobs,σobs2)
于是,我们的问题是,如何使用观测的信息,更新原先 d d d 的分布。这正是一个信息融合问题。我们明白两个高斯分布的乘积依然是一个高斯分布。设融合后的 d d d的分布为 N ( μ fuse  , σ fuse  2 ) N\left(\mu_{\text {fuse }}, \sigma_{\text {fuse }}^{2}\right) N(μfuse ,σfuse 2),那么根据高斯分布的乘积,有:

μ f u s e = σ o b s 2 μ + σ 2 μ o b s σ 2 + σ obs  2 , σ fuse  2 = σ 2 σ o b s 2 σ 2 + σ obs  2 \mu_{f u s e}=\frac{\sigma_{o b s}^{2} \mu+\sigma^{2} \mu_{o b s}}{\sigma^{2}+\sigma_{\text {obs }}^{2}}, \quad \sigma_{\text {fuse }}^{2}=\frac{\sigma^{2} \sigma_{o b s}^{2}}{\sigma^{2}+\sigma_{\text {obs }}^{2}} μfuse=σ2+σobs 2σobs2μ+σ2μobs,σfuse 2=σ2+σobs 2σ2σobs2

由于我们仅有观测方程而没有运动方程,所以这里深度仅用到了信息融合部分,而无须像完整的卡尔曼那样进行预测和更新。(这里也可以看成“运动方程为深度值固定不动”的卡尔曼滤波器)。那么现在的问题是:如何求 μ o b s \mu_{o b s} μobs σ o b s 2 \sigma_{o b s}^{2} σobs2
这里的不确定性包含两种,一种是几何不确定性和,另一种是光度不确定性。现在暂时只考虑几何不确定性。现在需要确定的是通过极线搜索和块匹配确定的某参考帧中某个像素的这个位置对深度的不确定性有多大。
SLAM【十一】建图
以上图为例。考虑某次极线搜索,我们找到了 p 1 p_1 p1 对应的 p 2 p_2 p2 点,从而观测到了 p 1 p_1 p1的深度值,认为 p 1 p_1 p1 对应的三维点为 P P P。从而,可记 O 1 P O_1P O1P p p p O 1 O 2 O_1O_2 O1O2 为相机的平移 t t t O 2 P O_2P O2P 记为 a a a。并且,把这个三角形的下面两个角记作 α , β α, β α,β。现在,考虑极线 l 2 l_2 l2 上存在着一个像素大小的误差,使得 β β β 角变成了 β ′ β^′ β,而 p p p 也变成了 p ′ p^′ p,并记上面那个角为 γ γ γ。我们要问的是,这一个像素的误差,会导致 p ′ p^′ p p p p 产生多大的差距呢?

根据上面的图,显然有:
a = p − t α = arccos ⁡ ⟨ p , t ⟩ β = arccos ⁡ ⟨ a , − t ⟩ \begin{aligned} a &=\boldsymbol{p}-\boldsymbol{t} \\ \alpha &=\arccos \langle\boldsymbol{p}, \boldsymbol{t}\rangle \\ \beta &=\arccos \langle\boldsymbol{a},-\boldsymbol{t}\rangle \end{aligned} aαβ=pt=arccosp,t=arccosa,t
p 2 p_2 p2 扰动一个像素,将使得 β β β 产生一个变化量,成为 β ′ β^′ β。根据几何关系:

β ′ = arccos ⁡ ⟨ O 2 p 2 ′ , − t ⟩ γ = π − α − β ′ \begin{aligned} \beta^{\prime} &=\arccos \left\langle\boldsymbol{O}_{2} \boldsymbol{p}_{2}^{\prime},-\boldsymbol{t}\right\rangle \\ \gamma &=\pi-\alpha-\beta^{\prime} \end{aligned} βγ=arccosO2p2,t=παβ

于是,由正弦定理, p ′ p^′ p的大小可以求得

∥ p ′ ∥ = ∥ t ∥ sin ⁡ β ′ sin ⁡ γ \left\|\boldsymbol{p}^{\prime}\right\|=\|\boldsymbol{t}\| \frac{\sin \beta^{\prime}}{\sin \gamma} p=tsinγsinβ

因此,确定了由单个像素的不确定性引起的深度不确定性。如果认为极线搜索的块匹配仅一个像素的误差,那么就可以设:

σ o b s = ∥ p ∥ − ∥ p ′ ∥ \sigma_{\mathrm{obs}}=\|\boldsymbol{p}\|-\left\|\boldsymbol{p}^{\prime}\right\| σobs=pp

如果极线搜索的不确定性大于一个像素是,可以按照次推导放大这个不确定性。
在实际工程中,当不确定性小于一定阈值时,就可以认为深度数据已经收敛了。

因此,估计稠密深度的一个完整过程为:

  1. 假设所有像素的深度满足某个初始的高斯分布;
  2. 当新数据产生时,通过极线搜索和块匹配确定投影点位置;
  3. 根据几何关系计算三角化后的深度以及不确定性;
  4. 将当前观测融合进上一次的估计中。若收敛则停止计算,否则返回 2。

这些步骤组成了一套可行的深度估计方式。书上说:这里的深度值是 O 1 P O_1P O1P的长度,与针孔相机模型李提到的“深度”有少许不同,针孔模型中的深度是指像素的z值。这里有些不懂,为什么不一样?懂的朋友希望评论区教我一下,感谢!

像素梯度的问题

在块匹配的过程中,过度依赖物体的纹理,即有明显梯度的小块匹配的准确性高,梯度不明显的反之。进而影响深度计算的精度。

SLAM【十一】建图
像素梯度垂直于极线方向,以及平行于极线方向。先来看平行的情况。在平行的例子里,即使小块有明显梯度,但是当我们沿着极线去做块匹配时,会发现匹配程度都是一样的,因此得不到有效的匹配。反之,在垂直的例子里,我们能够精确地确定匹配度最高点出现在何处。而实际当中,梯度与极线的情况很可能位于二者之间:既不是完全垂直亦不是完全平行。这时,我们说,当像素梯度与极线夹角较小时,极线匹配的不确定性大;而当夹角较大时,匹配的不确定性变小。而在演示程序中,我们统一地把这些情况都当成一个像素的误差,实质是不够精细的。考虑到极线与像素梯度的关系后,应该使用更精确的不确定性模型。

逆深度

假设深度值满足高斯分布 d ∼ N ( μ , σ 2 ) d \sim N\left(\mu, \sigma^{2}\right) dN(μ,σ2),会出现一些不合理的点:

  1. 我们实际想表达的是:这个场景深度大概是 5-10 米,可能有一些更远的点,但近处肯定不会小于相机焦距(或认为深度不会小于 0)。这个分布并不是像高斯分布那样,形成一个对称的形状。它的尾部可能稍长,而负数区域则为零。
  2. 在一些室外应用中,可能存在距离非常远,乃至无穷远处的点。我们的初始值中难以涵盖这些点,并且用高斯分布描述它们会有一些数值计算上的困难。

而逆深度为高斯分布是比较有效的,逆深度就是深度的代数 d ( − 1 ) d_(-1) d(1)

图像间的变换

问题:我们假设了图像小块在相机运动时保持不变,而这个假设在相机平移能够保持成立,但当相机发生明显的旋转时,就难以继续保持了。特别地,当相机绕光心旋转时,一个下黑上白的图像可能会变成一个上黑下白的图像块,导致相关性直接变成了负数(尽管仍然是同样一个块)。

解决方案:将当前帧的图像小块变换到关键帧上对应的小块,使用关键帧上对应的图像小块进行匹配。也就是在块匹配之前,把参考帧与当前帧之间的运动考虑进来。

根据相机模型,参考帧上的一个像素 P R P_R PR 与真实的三维点世界坐标 P W P_W PW 有以下关系:

d R P R = K ( R R W P W + t R W ) d_{R} \boldsymbol{P}_{R}=\boldsymbol{K}\left(\boldsymbol{R}_{R W} \boldsymbol{P}_{W}+\boldsymbol{t}_{R W}\right) dRPR=K(RRWPW+tRW)

类似的,对于当前帧,它亦有 P W P_W PW 在它上边的投影,记作 P C P_C PC

d C P C = K ( R C W P W + t C W ) d_{C} \boldsymbol{P}_{C}=\boldsymbol{K}\left(\boldsymbol{R}_{C W} \boldsymbol{P}_{W}+\boldsymbol{t}_{C W}\right) dCPC=K(RCWPW+tCW)
消去 P W P_W PW,进而得到两幅图像的像素关系:

d C P C = d R K R C W R R W T K − 1 P R + K t C W − K R C W R R W T K t R W d_{C} \boldsymbol{P}_{C}=d_{R} \boldsymbol{K} \boldsymbol{R}_{C W} \boldsymbol{R}_{R W}^{T} \boldsymbol{K}^{-1} \boldsymbol{P}_{R}+\boldsymbol{K} \boldsymbol{t}_{C W}-\boldsymbol{K} \boldsymbol{R}_{C W} \boldsymbol{R}_{R W}^{T} \boldsymbol{K} \boldsymbol{t}_{R W} dCPC=dRKRCWRRWTK1PR+KtCWKRCWRRWTKtRW

当知道 d R , P R d_R,P_R dR,PR 时,可以计算出 P C P_C PC 的投影位置。再给 P R P_R PR 两个分量各一个增量 d u , d v du, dv du,dv,就可以求得 P C P_C PC 的增量 d u c , d v c du_c, dv_c duc,dvc。通过这种方式,算出在局部范围内,参考帧和当前帧图像坐标变换的一个线性关系,构成仿射变换:

[ d u c d v c ] = [ d u c d u d u c d v d v c d u d v c d v ] [ d u d v ] \left[\begin{array}{l} d u_{c} \\ d v_{c} \end{array}\right]=\left[\begin{array}{ll} \frac{d u_{c}}{d u} & \frac{d u_{c}}{d v} \\ \frac{d v_{c}}{d u} & \frac{d v_{c}}{d v} \end{array}\right]\left[\begin{array}{l} d u \\ d v \end{array}\right] [ducdvc]=[duducdudvcdvducdvdvc][dudv]

根据仿射变换矩阵,我们可以把当前帧(或参考帧)的像素进行变换后,再进行块匹配,以期获得对旋转更好的效果。

点云地图

SLAM【十一】建图
点云地图为我们提供了比较基本的可视化地图,让我们能够大致了解环境的样子。它以三维方式存储,使得我们能够快速地浏览场景的各个角落,乃至在场景中进行漫游。点云的一大优势是可以直接由 RGB-D 图像高效地生成,不需要额外的处理。它的滤波操作也非常直观,且处理效率尚能接受。不过,使用点云表达地图仍然是十分初级的。
点云地图是“基础”的或“初级的”,是指它更接近于传感器读取的原始数据。它具有一些基本的功能,但通常用于调试和基本的显示,不便直接用于应用程序。如果我们希望地图有更高级的功能,点云地图是一个不错的出发点。

缺陷:

  1. 点云地图通常规模很大,所以一个 pcd 文件也会很大。
  2. 点云地图无法处理运动物体。

八叉树地图

SLAM【十一】建图

八叉树地图有点像我的世界那样,使用一个一个方块进行堆叠。是一种灵活的、压缩的、又能随时更新的地图形式。

参考

1.视觉SLAM十四讲文章来源地址https://www.toymoban.com/news/detail-411845.html

到了这里,关于SLAM【十一】建图的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于Gazebo搭建移动机器人,并结合SLAM系统完成定位和建图仿真

    博客地址:https://www.cnblogs.com/zylyehuo/ gazebo小车模型创建及仿真详见之前博客 gazebo小车模型(附带仿真环境) - zylyehuo - 博客园 gazebo+rviz 仿真 - zylyehuo - 博客园 参考链接 Autolabor-ROS机器人入门课程《ROS理论与实践》 安装 gmapping 包(用于构建地图): sudo apt install ros-melodic-gmapping 安

    2024年02月04日
    浏览(47)
  • Docker搭建SLAM进行建图

    目录 [前言] Docker介绍 Docker使用步骤 1.将.tar文件导入docker中 2.查看镜像是否成功导入 3. 将镜像生成容器 4.docker使用方法 [前言] 本文主要介绍了使用docker-slam进行建图实验,博主的Ubuntu系统的内存不够用了,跑不了Docker,是在别人的电脑上跑的,故这里就无法提供相关的材料及

    2024年01月18日
    浏览(36)
  • 基于机器人自主移动实现SLAM建图

    博客地址:https://www.cnblogs.com/zylyehuo/ 基于[移动机器人运动规划及运动仿真],详见之前的博客 移动机器人运动规划及运动仿真 - zylyehuo - 博客园 参考链接 Autolabor-ROS机器人入门课程《ROS理论与实践》 ubuntu 18.04

    2024年02月05日
    浏览(45)
  • 视觉SLAM ch12 建图(RGB-D)

    一、RGB-D稠密建图 RGB-D相机 通结构光和飞行时间获取深度。 稠密重建方法 :根据估计的相机位姿,将RGB-D数据转化为点云,然后进行拼接,最终得到由离散的点组成的 点云地图 。 在此基础上,如果希望估计物体的表面,可以用三角网格(Mesh)和面片(Surfel)进行建图;如

    2023年04月18日
    浏览(48)
  • ROS小车——雷达的使用与SLAM建图(4)

    启动激光雷达并查看数据,建图并避障导航 roslaunch robot_navigation lidar.launch在终端输入启动雷达,雷达开始旋转, 并打印scan话题,rostopic echo/scan,在虚拟机中 roslaunch robot_navigation lidar_rviz.launch 查看图形化的雷达数据。 roslaunch robot_navigation robot_slam_lidar.launch在终端输入启动sl

    2023年04月21日
    浏览(52)
  • ROS导航实现:SLAM建图(slam_gmapping)与保存(map_server)

    先安装相关的ROS功能包: 安装 gmapping 包(用于构建地图):sudo apt install ros-ROS版本-gmapping 安装地图服务包(用于保存与读取地图):sudo apt install ros-ROS版本-map-server 安装 navigation 包(用于定位以及路径规划):sudo apt install ros-ROS版本-navigation   新建功能包,并导入依赖: gmapping map_serve

    2024年02月07日
    浏览(36)
  • 基于turtlebot3实现SLAM建图及自主导航仿真

    一、turtlebot3依赖安装 1、安装turtlebot3 sudo apt-get install ros-noetic-turtlebot3-* //安装gmapping建图算法依赖 apt-get install ros-noetic-gmapping //安装dwa局部路径规划算法 apt-get install ros-noetic-dwa-local-planner 2、安装键盘依赖 sudo apt-get install ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control turtlebot3键盘

    2024年02月04日
    浏览(46)
  • ROS-基于PX4的无人机SLAM建图(Cartographer)仿真

    首先在电脑上安装好Ubuntu系统和ROS系统,我安装的是Ubuntu18.04和ROS Melodic,不同的Ubuntu版本对应不同的ROS版本 ROS发布日期 ROS版本 停止支持日期 对应Ubuntu版本 2018年5月23日 ROS Melodic Morenia 2023年5月 Ubuntu 18.04 2016年5月23日 ROS Kinetic Kame 2021年4月 Ubuntu 16.04 (Xenial) Ubuntu 15.10 (Wily) 201

    2024年02月15日
    浏览(56)
  • 经典文献阅读之--Orbeez-SLAM(单目稠密点云建图)

    对于现在的VSLAM而言,现在越来越多的工作开始聚焦于如何将深度学习结合到VSLAM当中,而最近的这个工作就给出了一个比较合适的方法。《Orbeez-SLAM: A Real-time Monocular Visual SLAM with ORB Features and NeRF-realized Mapping》这篇文章,可以轻松适应新的场景,而不需要预先训练,并实时为

    2024年02月13日
    浏览(40)
  • Jetson Nano之ROS入门 - - SLAM之Gmapping建图与路径规划

    SLAM(Simultaneous Localization And Mapping)是指在机器人或移动设备等自主移动系统的运动过程中,同时实时地构建出环境地图并确定自己的位置的技术。SLAM技术已经广泛应用于无人驾驶、机器人导航、虚拟现实等领域。 在SLAM技术中,机器人需要通过自身的传感器,如激光雷达、

    2024年02月08日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包