精度超越ConvNeXt的新CNN——HorNet:通过递归门控卷积实现高效高阶的空间信息交互

作者|科技猛兽 编辑|3D视觉开发者社区

HorNet:通过递归门控卷积实现高效高阶的空间信息交互

论文名称:HorNet: Efficient High-Order Spatial Interactions with Recursive
Gated Convolutions

论文地址:http://arxiv.org/pdf/2207.14284.pdf

1.1 HorNet 原理分析
1.1.1 背景和动机

本文提出了一种基于递归的门控卷积的通用视觉模型,是来自清华大学周杰老师,鲁继文老师团队,以及 Meta AI 的学者们在通用视觉模型方面有价值的探索

卷积神经网络 (CNN) 推动了深度学习和计算视觉领域的显著进步。CNN 因其自身固有的一系列优良的特性,使得它们很自然地适合很多种计算机视觉任务。比如一种特性是平移不变性 (Translation equivariance),这种特性为 CNN 模型引入了归纳偏置 (inductive bias),使之能够适应不同大小的输入图片的尺寸。与此同时,CNN 因为发展已久,社区已经贡献了很多高度优化的实现方案,这些方案使得它在高性能的 GPU 和边缘设备上都非常高效。

视觉 Transformer 模型的发展也撼动了 CNN 的统治地位,通过借鉴 CNN 中的一些优秀的架构设计思路 (金字塔结构,归纳偏置,残差连接等) 和训练策略 (知识蒸馏等),视觉 Transformer 模型已经在图像分类,目标检测,语义分割等等下游任务中取得了不输 CNN 的卓越性能。

人们不禁想问:究竟是什么使得视觉 Transformer 模型比 CNN 性能更强?

其实这个问题很难回答得好,但是我们其实也并不需要完全解答这个问题,可以先从反过来借鉴视觉 Transformer 模型的特点来设计更强大的 CNN 模型开始。

比如,视觉 Transformer 的架构设计一般来将遵循一个 meta architecture,就是虽然 token mixer 的类型会有不同 (Self-attention,Spatial MLP,Window-based Self-attention 等等),但是基本的宏观架构一般都是由4个 stage 组成的金字塔架构。比如:

  • ConvNeXt[1] 借鉴了这样一套宏观架构,并结合 7×7 的 Depth-wise Convolution 构建了一系列高性能的CNN 架构。
  • GFNet[2] 借鉴了这样一套宏观架构,并结合 2D-FFT 的傅里叶变换和 2D-IFFT 的反变换构建了一系列高性能的通用Backbone 架构。
  • RepLKNet[3] 借鉴了这样一套宏观架构,并结合 31×31 的超大核卷积操作以及结构重参数化的方案构建了一系列高性能的通用Backbone 架构。

在一个模型的特征中间,其任意的两个空间位置之间可能存在着复杂和高阶的相互作用,但是这种相互作用也希望模型中显式地建模出来。而 Self-attention 的成功其实就证明了:显式地建模高阶的相互作用有利于提升模型的表达能力。

但是在卷积神经网络中,似乎还没有最近的工作注意到了要显式地建模高阶的相互作用,这也是本文的动机。

1.1.2 HorNet 简介

如下图1所示是本文核心思想图解:通过这张图分析不同操作中特征 (红色块) 和它周围的区域 (灰色块) 的交互。(a) 普通卷积操作不考虑空间的信息交互。(b) 动态卷积操作借助动态权重,考虑周边的区域的信息交互,使得模型性能更强。© Self-attention 操作通过 query,key 和 value 之间的两个连续的矩阵乘法实现了二阶的空间信息交互。(d) 本文所提出的方法可以借助门控卷积和递归操作高效地实现任意阶数的信息交互。可视化建模的基本操作趋势表明,模型的表达能力可以通过增加空间相互作用的阶数来提高。
在这里插入图片描述

图1:本文核心思想图解:通过这张图分析不同操作中特征 (红色块) 和它周围的区域 (灰色块) 的交互。(a) 普通卷积操作 (b) 动态卷积操作 (c) Self-attention 操作 (d) 本文所提的方法

在本文中,作者把视觉 Transformer 成功的关键因素归结为动态权重 (指的是 attention 矩阵的值与具体的输入有关,input-adaptive),长距离建模 (long-range) 和高阶的空间交互 (high-order) 。之前的工作 ConvNeXt[1] ,GFNet[2] ,RepLKNet[3]都满足了动态权重和长距离建模这两条性质,但是却没有考虑到如何使得一个卷积神经网络实现高阶的空间交互。

针对这一点作者提出了一种基于递归和门控卷积的 Recursive Gated Convolution ( g n  Conv  g^{n} \text { Conv } gn Conv ) 使得卷积神经网络也可以完成高阶的空间交互。 g n  Conv  g^{n} \text { Conv } gn Conv 有一些很好的特征, 比如:

  • 高效: 基于卷积的实现避免了 Self-attention 的二次复杂度。而且, 在执行空间交互过程中逐步增加通道宽度的金字塔式的设计也可在有限复杂度的限制下实现高阶的空间交互。
  • 可扩展: 作者将 Self-attention 中的二阶相互作用扩展到任意阶数, 以进一步提高建模能力。由 于没有对卷积的类型做假设, g n  Conv  g^{n} \text { Conv } gn Conv 可以兼容多种卷积核, 比如 31×31 超大卷积核或者 2D FFT \text {FFT} FFT
  • 平移不变性: g n  Conv  g^{n} \text { Conv } gn Conv  继承了标准卷积的平移不变性, 为视觉任务引入了归纳偏置, 避免了注意力机制无法实现归纳偏置的缺陷。

基于 g n  Conv  g^{n} \text { Conv } gn Conv , 和通用视觉架构设计的一般原则, 作者构建了一系列的视觉骨干模型, 称之为 HorNet,并在一系列密集预测的任务上面验证其性能。

1.1.3 g n  Conv  g^{n} \text { Conv } gn Conv :门控卷积实现一阶的空间交互

g n  Conv  g^{n} \text { Conv } gn Conv 的目标是实现长距离建模,和高阶的空间交互。它是用标准卷积,线性投影 (Linear Projection 操作) 和 Element-wise 的乘法构建的,但具有与 Self-attention 类似的权重随输入的变化而变化 (input-adaptive) 的功能。

视觉 Transformer 用来混合空间 token 信息的 Self-attention 矩阵的权重是与输入相关的。但是 Self-attention 的复杂度是和图片的输入分辨率成二次性相关的,在需要高分辨率的下游任务上面带来的计算代价是很大的。在这个工作里面,作者采用高效的卷积和全连接层来执行空间的信息交互。

递归的门控卷积的基本操作是门控卷积 ( g  Conv  g^{} \text { Conv } g Conv ), 设输入特征为 x ∈ R H W × C \mathbf{x} \in \mathbb{R}^{H W \times C} xRHW×C , 门控卷积 g  Conv  g^{} \text { Conv } g Conv 的输出可以写成:
在这里插入图片描述
式中, ϕ in  , ϕ o u t \phi_{\text {in }}, \phi_{\mathrm{out}} ϕin ,ϕout是线性投影操作, 完成 channel 维度的信息交流, f {f} f是 Depth-wise 的卷积。注意到 p 1 ( i , c ) = ∑ j ∈ Ω i w i → j c q 0 ( j , c ) p 0 ( i , c ) p_{1}^{(i, c)}=\sum_{j \in \Omega_{i}} w_{i \rightarrow j}^{c} q_{0}^{(j, c)} p_{0}^{(i, c)} p1(i,c)=jΩiwijcq0(j,c)p0(i,c),其中, Ω i \Omega_{i} Ωi 是 Depth-wise Conv 的 local window,中心坐标为 i , w {i,w} i,w是 Depth-wise Conv 的权重。上式1可以认为是 p 0 ( i ) \mathbf{p}_{0}^{(i)} p0(i)及其周边特征 p 0 ( j ) \mathbf{p}_{0}^{(j)} p0(j)的1阶相互作用。

1.1.4 g n  Conv  g^{n} \text { Conv } gn Conv :高阶的门控卷积实现高阶的空间交互

门控卷积 ( g  Conv  g^{} \text { Conv } g Conv ) 可以实现某一特征和其周边特征的1阶相互作用, 接下来作者设计 g n  Conv  g^{n} \text { Conv } gn Conv 来 实现长距离建模, 和高阶的空间交互。首先通过 ϕ in  \phi_{\text {in }} ϕin 得到一系列的投影特征 p 0 \mathbf{p}_{0} p0 { q k } k = 0 n − 1 \left\{\mathbf{q}_{k}\right\}_{k=0}^{n-1} {qk}k=0n1

在这里插入图片描述
再以递归的方式进行门控卷积:

在这里插入图片描述
式中, 每次递归的过程除以 α \alpha α是为了稳定训练, { f k } \left\{f_{k}\right\} {fk}是一系列的 Depth-wise 卷积操作, { g k } \left\{g_{k}\right\} {gk}用来在每次递归的过程匹配特征的通道数。
在这里插入图片描述
最后将最后一次递归的输出 q n \mathbf{q}_{n} qn输入到投影层 ϕ o u t \phi_{\mathrm{out}} ϕout, 得到 g n  Conv  g^{n} \text { Conv } gn Conv 的结果。 g n  Conv  g^{n} \text { Conv } gn Conv 可以实现某一特征和其周边特征的 n {n} n 阶相互作用。

但是要计算式3, 就需要计算 f k ( q k ) , k = 0 , 1 , … , n − 1 f_{k}\left(\mathbf{q}_{k}\right), k=0,1, \ldots, n-1 fk(qk),k=0,1,,n1, 这一步其实不需要算 n {n} n 次, 而是可以通过直接将组合的特征 { q k } k = 0 n − 1 \left\{\mathbf{q}_{k}\right\}_{k=0}^{n-1} {qk}k=0n1 通过一个 Depth-wise Convolution 来完成, 这可以在 GPU 上进一步简化实现, 提高效率。

为了确保高阶交互不会引入太多的计算开销,作者将每一阶的通道维度设置为指数递减的形式:在这里插入图片描述

1.1.5 g n  Conv  g^{n} \text { Conv } gn Conv 的计算复杂度

g n  Conv  g^{n} \text { Conv } gn Conv 的计算可以分为3部分。

第一步和最后一步的投影层 ϕ in  \phi_{\text {in }} ϕin  ϕ o u t \phi_{\mathrm{out}} ϕout
在这里插入图片描述
Depth-wise 卷积 f {f} f :
Kernel size 为 K × K K \times K K×K的 Depth-wise 卷积作用在特征 { q k } k = 1 n − 1 \left\{\mathbf{q}_{k}\right\}_{k=1}^{n-1} {qk}k=1n1上面, 其中 q k ∈ R H W × C k , C k = C 2 n − k − 1 \mathbf{q}_{k} \in \mathbb{R}^{H W \times C_{k}}, C_{k}=\frac{C}{2^{n-k-1}} qkRHW×Ck,Ck=2nk1C。因此,这部分的计算量是:
在这里插入图片描述
递归的门控操作中维度匹配的 g {g} g 的计算量:
在这里插入图片描述
因此, 总的计算量是:
在这里插入图片描述

1.1.6 通过大卷积核进行长距离的交互

视觉 Transformer 和传统 CNN 的另一个区别是感受野的大小。传统的 CNN 通常通过整个网络使用 3×3 卷积,而视觉 Transformer 则在整个特征图相对较大的局部窗口内 (如 7×7) 计算 Self-attention。为了使 g n  Conv  g^{n} \text { Conv } gn Conv 能够建模长距离的交互,作者采用了两种 Depth-wise 卷积的形式:

  • 7×7 Depth-wise 卷积 (ConvNeXt[1]):是 Swin 和 ConvNeXt 的默认感受野大小。已经被验证在多种视觉任务上表现出了优良的性质。PyTorch 代码如下:
def get_dwconv(dim, kernel, bias):
    return nn.Conv2d(dim, dim, kernel_size=kernel, padding=(kernel-1)//2 ,bias=bias, groups=dim)
  • Global Filter (GFNet[2]):通过 2D-FFT 将特征从空域转化到频域中,而频域中 Element-wise的相乘操作等价于空域中一个具有全局核大小和圆形填充的空间域卷积。在实际实现的时候把 channel 分成两部分,一部分通过 Global Filters,另一部分通过 3×3 Depth-wise Convolution。PyTorch代码如下:
class GlobalLocalFilter(nn.Module):
    def __init__(self, dim, h=14, w=8):
        super().__init__()
        self.dw = nn.Conv2d(dim // 2, dim // 2, kernel_size=3, padding=1, bias=False, groups=dim // 2)
        self.complex_weight = nn.Parameter(torch.randn(dim // 2, h, w, 2, dtype=torch.float32) * 0.02)
        trunc_normal_(self.complex_weight, std=.02)
        self.pre_norm = LayerNorm(dim, eps=1e-6, data_format='channels_first')
        self.post_norm = LayerNorm(dim, eps=1e-6, data_format='channels_first')

    def forward(self, x):
        x = self.pre_norm(x)
        x1, x2 = torch.chunk(x, 2, dim=1)
        x1 = self.dw(x1)

        x2 = x2.to(torch.float32)
        B, C, a, b = x2.shape
        x2 = torch.fft.rfft2(x2, dim=(2, 3), norm='ortho')

        weight = self.complex_weight
        if not weight.shape[1:3] == x2.shape[2:4]:
            weight = F.interpolate(weight.permute(3,0,1,2), size=x2.shape[2:4], mode='bilinear', align_corners=True).permute(1,2,3,0)

        weight = torch.view_as_complex(weight.contiguous())

        x2 = x2 * weight
        x2 = torch.fft.irfft2(x2, s=(a, b), dim=(2, 3), norm='ortho')

        x = torch.cat([x1.unsqueeze(2), x2.unsqueeze(2)], dim=2).reshape(B, 2 * C, a, b)
        x = self.post_norm(x)
        return x

g n  Conv  g^{n} \text { Conv } gn Conv 的 PyTorch 代码如下:

class gnconv(nn.Module):
    def __init__(self, dim, order=5, gflayer=None, h=14, w=8, s=1.0):
        super().__init__()
        self.order = order
        self.dims = [dim // 2 ** i for i in range(order)]
        self.dims.reverse()
        self.proj_in = nn.Conv2d(dim, 2*dim, 1)

        if gflayer is None:
            self.dwconv = get_dwconv(sum(self.dims), 7, True)
        else:
            self.dwconv = gflayer(sum(self.dims), h=h, w=w)
        
        self.proj_out = nn.Conv2d(dim, dim, 1)

        self.pws = nn.ModuleList(
            [nn.Conv2d(self.dims[i], self.dims[i+1], 1) for i in range(order-1)]
        )

        self.scale = s
        print('[gnconv]', order, 'order with dims=', self.dims, 'scale=%.4f'%self.scale)

    def forward(self, x, mask=None, dummy=False):
        B, C, H, W = x.shape

        fused_x = self.proj_in(x)
        pwa, abc = torch.split(fused_x, (self.dims[0], sum(self.dims)), dim=1)

        dw_abc = self.dwconv(abc) * self.scale

        dw_list = torch.split(dw_abc, self.dims, dim=1)
        x = pwa * dw_list[0]

        for i in range(self.order -1):
            x = self.pws[i](x) * dw_list[i+1]

        x = self.proj_out(x)

        return x
1.1.7 g n  Conv  g^{n} \text { Conv } gn Conv 与 Self-attention 之间的联系

本小节目的是说明 g n  Conv  g^{n} \text { Conv } gn Conv 也具有 input adaptive 的能力。
Multi-head Self-attention 的输出可以写成:
在这里插入图片描述
式中, w V w_{V} wV是投影层的权重, 是通过 dot-product 的操作实现的。

从式11可以看出 Self-attention 具有 input adaptive 的能力, 因为 m i j m_{i j} mij与输入索引 i {i} i相关。 g n  Conv  g^{n} \text { Conv } gn Conv 的输出可以写成:
在这里插入图片描述
式中, w n − 1 w_{n-1} wn1是 Depth-wise 卷积 f n − 1 f_{n-1} fn1的权重, w ϕ in  w_{\phi_{\text {in }}} wϕin 是线性投影层\phi_{\text {in }}的权重, g n − 1 = g n − 1 ( p n − 1 ) \mathbf{g}_{n-1}=g_{n-1}\left(\mathbf{p}_{n-1}\right) gn1=gn1(pn1)是到 n {n} n 次递归操作的投影过程。

从式12也可以看出 g n  Conv  g^{n} \text { Conv } gn Conv 也具有 input adaptive 的能力, 因为 h i j c h_{i j}^{c} hijc与输入索引 i {i} i 相关。其实现如下图2所示。 g n  Conv  g^{n} \text { Conv } gn Conv  与 Self-attention 的区别其实在于 h i j c h_{i j}^{c} hijc是从 p n − 1 \mathbf{p}_{n-1} pn1计算出来的, 而它包含 n − 1 n-1 n1阶的空间交互作用。
在这里插入图片描述

图2:HorNet 模型架构

1.1.8 HorNet 模型架构
  • Hornet
    g n  Conv  g^{n} \text { Conv } gn Conv 作为 Token Mixer, 基于 Swin 的架构, 作者设计了一系列的通用视觉架构 HorNet。其基本的 Block 包含一个 Token Mixer 和一个 FFN 层。根据 g n  Conv  g^{n} \text { Conv } gn Conv 里面使用的是 Depth-wise Convolution 还是 GF Layer 分为 HorNet-T/S/B/L 7 × 7 7 \times 7 7×7和 HorNet-T/S/B/L G F {GF} GF。根据 Swin 的整体架构也设置为 4 个 stages, 各个 stage 的通道数分别是 [ C , 2 C , 4 C , 8 C ] [C, 2 C, 4 C, 8 C] [C,2C,4C,8C],对于 HorNet-T/S/B/L, 通道数分别是 C = 64 , 96 , 128 , 192 C=64,96,128,192 C=64,96,128,192。每个 stage 的高阶作用次数分别是 2 , 3 , 4 , 5 2,3,4,5 2,3,4,5

  • HorFPN
    作者进一步把 g n  Conv  g^{n} \text { Conv } gn Conv 作为标准卷积的增强替代方案。因此, 作者用 g n  Conv  g^{n} \text { Conv } gn Conv  替换了 FPN 中用于特征融合的卷积操作, 以改善下游任务的空间交互能力。具体来说, 作者在融合了来自不同金字塔层次的特征后添加了 g n  Conv  g^{n} \text { Conv } gn Conv  。对于目标检测任务, 作者用 g n  Conv  g^{n} \text { Conv } gn Conv  替换了 FPN 中的 3 × 3 3 \times 3 3×3卷积。对于语义分割任务, 把 concatenate 之后的特征之后的 3 × 3 3 \times 3 3×3卷积替换成 g n  Conv  g^{n} \text { Conv } gn Conv 

1.1.9 实验结果

ImageNet-1K 图像分类

不使用 ImageNet-22K 预训练的实验设置:直接在 ImageNet-1K 上训练 300 Epochs。

使用 ImageNet-22K 预训练的实验设置:先使用 ImageNet-22K 预训练 90 Epochs,再在 ImageNet-1K 上训练 30 Epochs。

结果如下图3所示。可以看到,HorNet 模型与最先进的 Transformer 和 CNN 相比,实现了极具竞争力的性能,超过了 Swin ConvNeXt。此外,HorNet 模型也很好地推广到更大的图像分辨率、更大的模型尺寸和更多的训练数据,证明了 HorNet 模型的有效性和通用性。
在这里插入图片描述

图3:ImageNet 图像分类实验结果

语义分割实验结果

数据集: ADE20K, 分割头 UperNet, 160 k 160 \mathrm{k} 160k iterations, AdamW 作为优化器, batch size 设为 16, 结果如下图4所示。具有相似的模型参数量和 FLOPs 的 HorNet 7 × 7 7\times7 7×7 和 HorNet G F \mathrm{GF} GF模型都优于Swin 和 ConvNeXt 模型。具体而言, HorNet 7 × 7 7\times7 7×7 模型在单尺度 mloU 中取得了比HorNet 7 × 7 7\times7 7×7 和 ConvNeXt 系列更好的结果, 表明全局过滤器捕获的全局交互有助于语义分割。而且, HorNet-L 7 × 7 7\times7 7×7 和HorNet-L G F \mathrm{GF} GF模型甚至比 ConvNeXt-XL 少了约 25% 的FLOPs。这些结果清楚地证明了 HorNet 在语义分割方面的有效性和可扩展性。
在这里插入图片描述

图4:语义分割实验结果

目标检测和实例分割实验结果

数据集:COCO,分割头 Mask R-CNN,结果如上图4所示。HorNet 模型在 box AP 和 mask AP 都比Swin/ConvNeXt 的同尺寸模型展现出了更好的性能。与 ConvNeXt 相比,HorNetGF 系列获得 +1.2 ~ 2.0 的 box AP 和 +1.0 ~ 1.9 的 mask AP。

密集预测实验结果

在这部分实验里面作者的目的是展示所提出的 g n  Conv  g^{n} \text { Conv } gn Conv 作为一种更好的融合模块,可以在密集预测任务中更好地捕捉不同层次特征之间的高阶相互作用。具体而言,把 FPN 替换成 HorFPN 之后,结果如下图5所示。对于目标检测和语义分割任务,可以发现 HorFPN 可以显著减少 FLOPs (约 50%),同时获得更好的 mIoU。
在这里插入图片描述

图5:密集预测实验结果

消融实验结果

  • g n  Conv  g^{n} \text { Conv } gn Conv 的消融实验
    消融实验结果如下图6 (a) 所示。Swin-T 精度为81.3%,SE 模块和 n = 1 n=1 n=1 g n  Conv  g^{n} \text { Conv } gn Conv 都可以提升 baseline 模型。作者发现, 4 个 stage 逐渐增大 n {n} n(即 g { 2 , 3 , 4 , 5 } g^{\{2,3,4,5\}} g{2,3,4,5})可以进一步提升性能。这些结果可以表明, g n  Conv  g^{n} \text { Conv } gn Conv 是一种高效和可扩展的操作, 可以比 Self-attention 和 Depth-wise Convolution 更好地捕获高阶空间相互作用。

  • 各向同性架构实验
    作者还在各向同性架构上评估了 g n  Conv  g^{n} \text { Conv } gn Conv 。作者将 DeiT-S 中的 Self-attention 操作替换为 g n  Conv  g^{n} \text { Conv } gn Conv , 并将块数调整为13, 以获得各向同性的 HorNet 模型。作者下图6 (b) 中比较了 DeiT-S、 各向同性 ConvNeXt-S 和各向同性 HorNet-S。各向同性 HorNet-S 大幅超过了 DeiT-S。这些结果 表明, 与普通卷积相比, g n  Conv  g^{n} \text { Conv } gn Conv 能够更好地实现 Self-attention 的功能, 并具有更好的模拟复杂空间相互作用的能力。

  • 其他操作实验
    为了进一步证明 g n  Conv  g^{n} \text { Conv } gn Conv 的通用性, 作者使用 3 × 3 3\times 3 3×3 Depth-wise Convolution 和 3 × 3 3\times 3 3×3 Pool 操作作为 g n  Conv  g^{n} \text { Conv } gn Conv 的基本操作。下图6 © 中的结果表明, g n  Conv  g^{n} \text { Conv } gn Conv 也可以大幅提高这两种运算的性能, 这表明当配备一些更好的基本运算时, g n  Conv  g^{n} \text { Conv } gn Conv 可能会更强大。

在这里插入图片描述

图6:消融实验结果

总结

本文提出了一种基于递归的门控卷积 g n  Conv  g^{n} \text { Conv } gn Conv 的通用视觉模型 HorNet。在本文中,作者把视觉 Transformer 成功的关键因素归结为动态权重 (指的是 attention 矩阵的值与具体的输入有关,input-adaptive),长距离建模 (long-range) 和高阶的空间交互 (high-order)。之前的工作都满足了动态权重和长距离建模这两条性质,但是却没有考虑到如何使得一个卷积神经网络实现高阶的空间交互。 g n  Conv  g^{n} \text { Conv } gn Conv 可以作为一种即插即用的 Token Mixer 来代替视觉 Transformer 中的注意力层,它高效,可扩展,且具有平移不变性。作者也在大量的实验证明了 HorNet 在常用的视觉识别任务上的有效性。

参考

  1. ^abcA ConvNet for the 2020s
  2. ^abcGlobal Filter Networks for Image Classification
  3. ^abScaling Up Your Kernels to 31x31: Revisiting Large Kernel Design in CNNs

版权声明:本文为奥比中光3D视觉开发者社区特约作者授权原创发布,未经授权不得转载,本文仅做学术分享,版权归原作者所有,若涉及侵权内容请联系删文。

3D视觉开发者社区是由奥比中光给所有开发者打造的分享与交流平台,旨在将3D视觉技术开放给开发者。平台为开发者提供3D视觉领域免费课程、奥比中光独家资源与专业技术支持。

快来[3D视觉开发者社区]和开发者们一起讨论分享吧~

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值