ORCA(RVO2)算法优化整理版
在一个2D平面R2R{^2}R2上定义一下内容:(速度:有方向有长度即向量。速率:无方向有长度即常量。)AAA : 机器人A。外部属性:PAP{}{_A}PA : 机器人A的位置(机器人形状的中心点)。rAr{}{_A}rA : 机器人A形状的半径。vA{}{_A}A : 机器人A当前速度。内部属性:vAmaxv{^{max}_A}vAmax : 机器人A的最大速率(常量)。vApref{
很久以前半翻译加描述的写过一篇关于群体避障算法 导航动态避让算法RVO的优化ORCA(Optimal Reciprocal Collision Avoidance)。
英语的语境直译成中文的文章十分不好理解,所以打算重新整理一遍,这篇算是精简优化版的吧,只讲要点不详细描述推导过程。
需要说明的是,ORCA算法是RVO算法的优化,所以有些地方也称ORCA算法为RVO2。
官方文档 :ORCA.pdf
序言
这个ORCA.pdf 文档上使用了大量的数学表达式来帮助描述问题,一眼看去显得十分深奥,晦涩难懂。但!实际上这些数学表达式,它们几乎都是为了把文档上描述的情况如何用在数学表达式上定义下来,描述的内容主要还是普通的逻辑。这样一来这些公式显得“温和”多了,所以如果熟悉了一些数学符号,那么会发现并没有想象中的那么难,相反,这些数学等式还十分有助于帮助理解)。
正文
文中用了大量的英文或数学符号来表示特定的属性,阅读时不好分清,因此这里把它们都一一罗列出来,可以先往下阅读,等遇到不明白的英文字母或者数学符号属性时再来查找。
这是定义在一个2D平面上的动态避让,称平面上的运动单位为机器人,每个机器人的形状大小一致。
机器人的属性分为内部属性和外部属性。任何一个机器人只能观察到其它机器人的外部属性。
对于其它机器人的内部属性,则是根据设想其它机器人跟自己用同样的避障策略而推算出来。
文中提到速度和速率的区别:
速度:有方向有长度,即向量,用
v
\bf v
v表示。
速率:无方向有长度,即常量,用
v
v
v表示。
-----------------------------------------------------------------------------------------------
在一个2D平面
R
2
R{^2}
R2上定义以下内容:
A
A
A : 机器人A(设想机器人是2D平面上的一个圆)。
V
A
V_{_A}
VA :机器人A的任意速度集合。
------------
A
A
A 的外部属性----------------
P
A
P_{_A}
PA : 机器人A的位置(机器人形状的中心点)。
r
A
r_{_A}
rA : 机器人A形状的半径。
v
A
\bf v_{_A}
vA : 机器人A当前速度。
------------
A
A
A的内部属性-----------------
v
A
m
a
x
v{^{max}_A}
vAmax : 机器人A的最大速率(常量)。
v
A
p
r
e
f
{\bf v}{^{pref}_A}
vApref : 机器人A的最倾向速度。
v
A
n
e
w
{\bf v}{^{new}_A}
vAnew : 机器人A新选择的速度。
------------评估运算相关属性-----------
D
(
p
,
r
D({\bf p}, r
D(p,r) : 以点
p
\bf p
p为圆心,半径为
r
r
r的圆。
τ
τ
τ : 某一小段时间,常量。
V
A
V_{_A}
VA :
A
A
A的任意速度集合,未具体指定。
V
O
A
∣
B
τ
VO{^τ_{A|B}}
VOA∣Bτ :
A
A
A对
B
B
B的
τ
τ
τ时间内有碰撞速度集合(不考虑
B
B
B的速度影响,设
B
B
B为静态)。
C
A
A
∣
B
τ
CA{^τ_{A|B}}
CAA∣Bτ :
A
A
A对
B
B
B的
τ
τ
τ时间内无碰撞速度集合(有考虑
B
B
B的速度影响,设
B
B
B为动态)。
O
R
C
A
A
∣
B
τ
ORCA{^τ_{A|B}}
ORCAA∣Bτ :
A
A
A对
B
B
B的
τ
τ
τ时间内最理想互惠无碰撞的速度集合。
O
R
C
A
A
τ
ORCA{^τ_{A}}
ORCAAτ :
A
A
A对其它任何机器人的
τ
τ
τ时间内最佳互让无碰撞的速度集合。
v
A
o
p
t
{\bf v}{^{opt}_A}
vAopt :
A
A
A对
B
B
B最优化避免碰撞自适应速度,这是个动态的速度,跟周围机器人数量自动适,在周围其它机器人数量密度低时,
v
A
o
p
t
{{\bf v}^{opt}_A}
vAopt偏向
v
A
p
r
e
f
{\bf v}{^{pref}_A}
vApref,周围机器人数量密度高时
v
A
o
p
t
{{\bf v}}{^{opt}_A}
vAopt偏向
0
0
0。
-----------------------------------------------------------------------------------
算法大体思路图
图解:
以红色圆为
A
A
A,其它颜色的圆各叫
B
C
D
E
F
BCDEF
BCDEF等。
图中各种颜色的直线就是上述的平面的分割线,
A
A
A对于
B
B
B的半平面有效的一侧下文称为
O
R
C
A
A
∣
B
τ
ORCA_{A|B}^{τ}
ORCAA∣Bτ。白色区域是
A
A
A避开多个其它颜色圆的多个半平面的交集区域,也就是自己的可选速度范围,下文称为
O
R
C
A
A
τ
ORCA_{A}^{τ}
ORCAAτ(白色区域图中)。左边黑色点位置是目的地点,
A
A
A的期望速度
v
A
p
r
e
f
{\bf v}_{A}^{pref}
vApref(图中灰色线段)总是指向它,毕竟那是自己要去的地方嘛。而
A
A
A的当前速度
v
A
{\bf v}_{A}^{}
vA(图中黑色线段)的下一步选择新速度
v
A
n
e
w
{\bf v}_{A}^{new}
vAnew(图中的黑色线段的下一步状态)时也想指向它,却无奈被限制在白色区域里,但它不死心,所以它每在下一步选择新速度
v
A
n
e
w
{\bf v}_{A}^{new}
vAnew都会尽可能的向灰色速度靠近,直到到达目的地。由此,风骚走位图形成
4、 互让避免碰撞
4.1、准备工作
图1
图1a:在方位空间上,有2个机器人A和B。
图1b:在速度空间上,障碍速度
V
O
A
∣
B
τ
VO{^τ_{A|B}}
VOA∣Bτ(灰色部分)几何形状为一个截头圆锥体,它的顶点位于原点,它的两边与以
P
A
−
P
B
P{_A}-P{_B}
PA−PB为中心
r
A
+
r
B
r_{_A}+r_{_B}
rA+rB为半径的圆相切。圆锥体的截头范围由
(
r
B
+
r
A
)
{(r_{_B}+r_{_A})}
(rB+rA)
τ
τ
τ决定,上图为
τ
=
2
τ = 2
τ=2时的范围。
说明:这个坐标系是速度空间坐标系,假设
B
B
B为静止状态,如果
A
A
A移动时不撞上
B
B
B,那么
A
A
A的速度不能为
V
O
A
∣
B
τ
VO{^τ_{A|B}}
VOA∣Bτ,几何意义可以解释为
A
A
A在
τ
τ
τ时间段内不能往
B
B
B方向走超过
(
P
B
−
P
A
)
−
(
r
B
+
r
A
)
(P_{_B}-P_{_A})-{(r_{_B}+r_{_A})}
(PB−PA)−(rB+rA)的距离,否则发生碰撞。
图1c:在速度空间上,
A
A
A不与
B
B
B发生碰撞的速度为
C
A
A
∣
B
τ
CA{^τ_{A|B}}
CAA∣Bτ。
说明:
V
O
A
∣
B
τ
VO{^τ_{A|B}}
VOA∣Bτ与
V
B
V_{_B}
VB的明科夫斯基和(图1(c)亮灰色部分)), 其几何意义可以解释为,这是把
V
B
V_{_B}
VB(图1(c)暗灰色部分)考虑在内后得出的
A
A
A相对于
B
B
B在
τ
τ
τ时间内会碰撞的速度集合,那么它们的补集为
C
A
A
∣
B
τ
CA{^τ_{A|B}}
CAA∣Bτ。
所以有:
设
D
D
D是以
p
\bf p
p为圆心,
r
r
r为半径的圆。
D
(
p
,
r
)
=
{
q
∣
∣
∣
q
−
p
∣
∣
<
r
}
D(\bf p, r) = {\lbrace{\bf q} |\; ||q-p||<r \rbrace}
D(p,r)={q∣∣∣q−p∣∣<r}
那么有表达式1:
V
O
A
∣
B
τ
=
{
v
∃
t
∈
[
0
,
τ
]
:
:
t
v
∈
D
(
p
B
−
p
A
,
r
A
+
r
B
)
}
VO_{_{A|B}}^{τ} = {\lbrace{\bf v}∃t \in [0,τ] :: t{\bf v} \in D({\bf p}_{_B} −{\bf p}_{_A}, r_{_A} + r_{_B})\rbrace}
VOA∣Bτ={v∃t∈[0,τ]::tv∈D(pB−pA,rA+rB)}
说明:
∃
∃
∃:存在的意思。
:
:
::
:: :等比的意思。
V
O
A
∣
B
τ
VO_{_{A|B}}^{τ}
VOA∣Bτ表达式解释为:时间段
τ
τ
τ范围内存在一个时间
t
t
t,使得
t
∗
v
t*\bf v
t∗v的结果速度集合为
D
D
D,即上图1(b)的截头圆锥体的截头范围由
τ
τ
τ决定。
那么有表达式2:
C
A
A
∣
B
t
(
V
B
)
=
v
∣
v
∉
V
O
A
∣
B
t
⊕
V
B
CA_{_{A|B}}^{t}(V_{_B}) = {{\bf v}|{\bf v}\notin VO_{_{A|B}}^{t}⊕V_{_B}}
CAA∣Bt(VB)=v∣v∈/VOA∣Bt⊕VB
表达式说明:
⊕
⊕
⊕ : 明科夫斯基和的意思,在这里的几何意义是,
A
A
A在找出障碍速度时,把
B
B
B的速度考虑进去,而不再设想
B
B
B是静态的。
C
A
A
∣
B
t
CA_{_{A|B}}^{t}
CAA∣Bt(
V
B
V_{_B}
VB)表达式解释为:
A
A
A对应
B
B
B避免碰撞的速度集合为
V
O
A
∣
B
t
VO_{_{A|B}}^{t}
VOA∣Bt⊕
V
B
V_{_B}
VB的补集。
图2
图解:
O
R
C
A
A
∣
B
τ
ORCA{^τ_{A|B}}
ORCAA∣Bτ是被一条直线分割开的一个半平面集合,这个半面集合为
n
\bf n
n指向的一侧(这个条件确定半平面的方向)。
其中这条线垂直于
u
\bf u
u,且穿过点
v
A
o
p
t
+
1
2
{\bf v}^{opt}_{_A}+\frac{1}{2}
vAopt+21(这两个条件就可以确定一条直线)。
u
\bf u
u为以
v
A
o
p
t
−
v
B
o
p
t
{\bf v}^{opt}_{_A}-{\bf v}^{opt}_{_B}
vAopt−vBopt为起点,指向最接近
O
R
C
A
A
∣
B
τ
ORCA{^τ_{A|B}}
ORCAA∣Bτ边界上的点的向量。
4.2、求 O R C A A ∣ B τ ORCA{^τ_{A|B}} ORCAA∣Bτ:
步骤如下:
1、求
u
\bf u
u:
则有表达式:
u
=
(
arg min
v
∈
∂
V
O
A
∣
B
τ
∣
∣
v
−
(
v
A
o
p
t
−
v
B
o
p
t
)
∣
∣
)
−
(
v
A
o
p
t
−
v
B
o
p
t
)
{\bf u}=( \argmin_{{\bf v}\in ∂VO{^τ_{A|B}}}|| {\bf v}−({\bf v}_{_A}^{opt}−{\bf v}_{B}^{opt})||)−({\bf v}_{A}^{opt}−{\bf v}_{B}^{opt})
u=(v∈∂VOA∣Bτargmin∣∣v−(vAopt−vBopt)∣∣)−(vAopt−vBopt)
说明:
a
r
g
m
i
n
\rm arg min
argmin : 表达式函数,当范数为最小值时得到的
v
\bf v
v的值。
∣
∣
n
∣
∣
||n||
∣∣n∣∣ : n的范数。
∂
∂
∂ : 偏导数符号。
表达式解释为:
假设
A
A
Ah和
B
B
B的自适应速度分别为
v
A
o
p
t
{\bf v}{^{opt}_A}
vAopt和
v
B
o
p
t
{\bf v}{^{opt}_B}
vBopt,如果
A
A
A会撞上
B
B
B碰撞,则
v
A
o
p
t
−
v
B
o
p
t
∈
V
O
A
∣
B
τ
{\bf v}^{opt}_{_A}-{\bf v}^{opt}_{_B}∈VO{^τ_{A|B}}
vAopt−vBopt∈VOA∣Bτ。
u
\bf u
u是以
v
A
o
p
t
−
v
B
o
p
t
{\bf v}^{opt}_{_A}-{\bf v}^{opt}_{_B}
vAopt−vBopt为起点,指向最接近
O
R
C
A
A
∣
B
τ
ORCA{^τ_{A|B}}
ORCAA∣Bτ边界上的点的向量。
2、求
n
\bf n
n:
n
=
(
v
A
o
p
t
−
v
B
o
p
t
)
+
u
\bf n = ({\bf v}_{A}^{opt}−{\bf v}_{B}^{opt})+\bf u
n=(vAopt−vBopt)+u
表达式解释为:
n
\bf n
n是以
V
O
A
∣
B
τ
的
边
界
上
的
点
VO{^τ_{A|B}}的边界上的点
VOA∣Bτ的边界上的点
(
(
v
A
o
p
t
−
v
B
o
p
t
)
+
u
)
(({\bf v}_{A}^{opt}−{\bf v}_{B}^{opt})+\bf u)
((vAopt−vBopt)+u)为起点向外延伸作的法线。
3、求
O
R
C
A
A
∣
B
τ
ORCA{^τ_{A|B}}
ORCAA∣Bτ
得到
O
R
C
A
A
∣
B
τ
=
{
v
∣
(
v
−
(
v
A
o
p
t
+
1
2
u
)
)
⋅
n
≥
0
}
ORCA{^τ_{A|B}} = \lbrace{\bf v} \,|\, ({\bf v-({\bf v}_{A}^{opt}+\frac12{\bf u}))·{\bf n}≥0}\rbrace
ORCAA∣Bτ={v∣(v−(vAopt+21u))⋅n≥0}
表达式解释为:
O
R
C
A
A
∣
B
τ
ORCA{^τ_{A|B}}
ORCAA∣Bτ是被一条直线分割开的一个半平面集合,这个半面集合为
n
\bf n
n指向的一侧(这个条件确定半平面的方向)。
其中这条线垂直于
u
\bf u
u,且穿过点
v
A
o
p
t
+
1
2
{\bf v}^{opt}_{_A}+\frac{1}{2}
vAopt+21(这两个条件就可以确定一条直线)。
图4a:8个机器人的方位,箭头所指的方向为它们的速度。
图4b:图中阴影半平面为
A
A
Az在其他机器人的影响下可选的速度,当前所有机器人
τ
=
2
τ=2
τ=2,
v
∗
o
p
t
=
v
∗
{\bf v}^{opt}_{_*}={\bf v}_*
v∗opt=v∗(表达式解释为
v
∗
o
p
t
{\bf v}^{opt}_{_*}
v∗opt选择任意一个可选的速度,在[0-
v
∗
p
r
e
f
{\bf v}^{pref}_{_*}
v∗pref]内)。图中切碎的阴影块区域就是
O
R
C
A
A
τ
ORCA_{A}^{τ}
ORCAAτ。箭头则是
A
A
A当前的速度。
5、对于多个机器人碰撞的情况
有流程图:
5.1、基本解决方案
A
A
A对于其它所有
B
B
B共同的
O
R
C
A
ORCA
ORCA:
O
R
C
A
A
τ
=
D
(
0
,
v
A
m
a
x
)
⋂
(
⋂
A
≠
B
O
R
C
A
A
∣
B
τ
)
ORCA_{A}^{τ} = D(0,v_{A}^{max})\bigcap (\bigcap_{A\neq B} ORCA_{A|B}^{τ})
ORCAAτ=D(0,vAmax)⋂(A=B⋂ORCAA∣Bτ)
表达式解释为:
O
R
C
A
A
τ
ORCA_{A}^{τ}
ORCAAτ的集合
v
{\bf v}
v为
A
A
A的最大速率为半径的圆集合与相对于其它
B
B
B的
O
R
C
A
ORCA
ORCA的交集。
A
A
A得到的新速度:
v
A
n
e
w
=
arg min
v
∈
O
R
C
A
A
τ
∣
∣
v
−
(
v
A
p
r
e
f
)
∣
∣
{\bf v}^{new}_{_A}=\argmin_{{\bf v}\in ORCA{^τ_{A}}}|| {\bf v}−({\bf v}^{pref}_{_A}) ||
vAnew=v∈ORCAAτargmin∣∣v−(vApref)∣∣
表达式解释为:
求得
A
A
A的速度
v
A
n
e
w
{\bf v}^{new}_{_A}
vAnew为
O
R
C
A
A
τ
ORCA{^τ_{A}}
ORCAAτ中最接近
v
A
p
r
e
f
{\bf v}^{pref}_{_A}
vApref的速度。
(说明:求
v
n
e
w
{\bf v}^{new}
vnew需要用到线性规划法提高效率。)
A
A
A得到的新位置:
p
A
n
e
w
=
p
A
+
v
A
n
e
w
∆
t
{\bf p}^{new}_{_A}={\bf p}^{}_{_A}+{\bf v}^{new}_A∆t
pAnew=pA+vAnew∆t
表达式解释为:
A
A
A的新位置为
A
A
A的旧位置加上速度乘以时间。
性能优化:
其实对于
A
A
A只关心与自己接近的其它机器人,即超出距离
(
v
A
m
a
x
+
v
B
m
a
x
)
τ
(v{^{max}_A}+v{^{max}_B})τ
(vAmax+vBmax)τ的 则忽略,可以用
k
D
−
t
r
e
e
kD-tree
kD−tree找出范围内的机器人,然后用线性规划法求出最合适的速度。
5.2、剩下的问题,关于 v A p r e f {\bf v}^{pref}_{_A} vApref的选择:
图5a:当三个机器人如图向
A
A
A移动时,
A
A
A能选择的速度为0。
图5b:
A
A
A可选择的半平面速度集合,在所有机器人的
τ
=
2
τ=2
τ=2,
v
∗
o
p
t
=
v
∗
{\bf v}^{opt}_{_*}={\bf v}_*
v∗opt=v∗条件下,
O
R
C
A
A
τ
ORCA{^τ_{A}}
ORCAAτ有可能为空,这就无法保证在
τ
τ
τ时间内不发生碰撞。
图5c:
A
A
A可选择的半平面速度集合,在所有机器人的
τ
=
2
τ=2
τ=2,
v
∗
o
p
t
=
0
{\bf v}^{opt}_{_*}=0
v∗opt=0条件下,如图区域
v
∗
o
p
t
=
v
∗
{\bf v}^{opt}_{_*}={\bf v}_*
v∗opt=v∗为0,结果可能会导致看起来机器人行动僵硬,不自然。
如何选择
v
A
p
r
e
f
{\bf v}^{pref}_{_A}
vApref:
1、
v
A
o
p
t
=
v
A
{\bf v}^{opt}_{_A}={\bf v}_{_A}
vAopt=vA(对于所有机器人):根据机器人当前情况,在
[
0
,
v
p
r
e
f
]
[0,{\bf v}^{pref}]
[0,vpref]范围内自适应选择合适的速度,周围低密度时选择偏向
v
p
r
e
f
{\bf v}^{pref}
vpref,高密度时选择偏向
0
0
0。但关于
O
R
C
A
A
τ
ORCA{^τ_{A}}
ORCAAτ高密度时依然可能为空,这是使用线性规划法求解最佳速度会失败,所以需要用到3D线性规划法求解最佳速度,以此保证有解,接下来下文会讲到。
5.3、密集条件下
当所有机器人选择
v
A
o
p
t
=
v
A
{\bf v}^{opt}_{_A}={\bf v}_{_A}
vAopt=vA时(即根据环境选择自适应速度),在机器人高密度的情况下时
O
R
C
A
A
τ
ORCA{^τ_{A}}
ORCAAτ可能为空,线性规划法求最最新速度会失败,为了足够的安全,使用3D线性规划法。
3D线性规划法:
设
d
A
∣
B
(
v
)
d_{}{_{A|B}}({\bf v})
dA∣B(v)为
v
{\bf v}
v到半平面
O
R
C
A
A
∣
B
τ
ORCA{^τ_{A|B}}
ORCAA∣Bτ边上的欧氏距离(非垂直距离),如果
v
∈
O
R
C
A
A
∣
B
τ
{\bf v}∈ORCA{^τ_{A|B}}
v∈ORCAA∣Bτ,那么
d
A
∣
B
(
v
)
d_{}{_{A|B}}({\bf v})
dA∣B(v)为负数。那么选择的新速度
v
A
n
e
w
{\bf v}^{new}_{_A}
vAnew则是在同时与多个半平面的欧式距离最大化时的速度中,选择一个长度最短的速度。这个理解起来有些绕,画个图:
图解:
图中橙色和绿色的箭头就是点
0
0
0分别同时到各个半平面的最大化欧式距离,那么选择其中最短的欧式距离所指向的点作为新的速度,即图中橙色的箭头指向的点。
通过3D线性规划法求出的
v
A
n
e
w
{\bf v}^{new}_{_A}
vAnew表达式:
v
A
n
e
w
=
arg min
v
∈
D
(
0
,
v
A
m
a
x
)
max
B
≠
A
d
A
∣
B
(
v
)
{\bf v}^{new}_{_A}= \argmin_{{\bf v}\in D({0,v^{max}_{_A})}} \max_{B\neq A} d_{}{_{A|B}}({\bf v})
vAnew=v∈D(0,vAmax)argminB=AmaxdA∣B(v)
表达式解释为:
几何上, 各个半平面以相同的速度向外垂直移动半平面的边,直到得出一个有效的速度。
以上可以用3D线性规划法求解,对于其它每个机器人B,距离函数 d A ∣ B ( v ) d_{}{_{A|B}}({\bf v}) dA∣B(v)是第三维 ( v , d ) ({\bf v},d) (v,d)平面空间。要查找的新的点 ( v ∗ , d ∗ ) ({\bf v^*},d^*) (v∗,d∗)则是各个平面距离函数算出的结果中在所有平面上的交集部分(即上图中橙色和绿色所指向的目标点的速度),选择它们中 d d d值最小的 v {\bf v} v为 v A n e w {\bf v}^{new}_{_A} vAnew赋值。不管机器人密度情况如何,3D线性规划总能有解。
5.4、静态障碍
图6a:
A
A
A和障碍线段
O
O
O。
图6b:
τ
=
2
τ=2
τ=2时的障碍速度
V
O
A
∣
O
τ
VO{^τ_{A|O}}
VOA∣Oτ几何图。
图6c:
O
R
C
A
A
∣
O
τ
ORCA{^τ_{A|O}}
ORCAA∣Oτ的半平面切割线相切于
V
O
A
∣
O
τ
VO{^τ_{A|O}}
VOA∣Oτ的边界且与
v
A
o
p
t
{\bf v}{^{opt}_A}
vAopt距离最短。其中
v
A
o
p
t
{\bf v}{^{opt}_A}
vAopt总是等于
0
0
0。
可以假设障碍物是由多条线段组成的模型。设 O O O为这些线段中的其中一条, A A A是以 p A {\bf p}_{}{_A} pA为原点 r A r_{}{_A} rA为半径的机器人,那么对 O O O的障碍速度为 V O A ∣ O τ VO{^τ_{A|O}} VOA∣Oτ:
V O A ∣ O τ = { v ∃ t ∈ [ 0 , τ ] : : t v ∈ O ⊕ − D ( p A , r A ) } VO_{_{A|O}}^{τ} = {\lbrace{\bf v}∃t \in [0,τ] :: t{\bf v} \in O⊕- D({\bf p}_{_A}, r_{_A} )\rbrace} VOA∣Oτ={v∃t∈[0,τ]::tv∈O⊕−D(pA,rA)}
V
O
A
∣
O
τ
VO_{_{A|O}}^{τ}
VOA∣Oτ是一个非凸平面,是一个平头的锥体,因此不能用上述的线性规划算法高效算出新的速度。
对于障碍
O
O
O,所有机器人选择
v
A
o
p
t
=
0
{\bf v}{^{opt}_A}=0
vAopt=0,这能保证总存在有效速度在
τ
τ
τ时间内避免碰撞。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)