【记录】本科毕设:基于树莓派的智能小车设计(使用Tensorflow + Keras 搭建CNN卷积神经网络 使用端到端的学习方法训练CNN)

0 申明

这是本人2020年的本科毕业设计,内容多为毕设论文和答辩内容中挑选。最初的灵感来自于早前看过的一些项目(抱歉时间久远,只记录了这一个,见下),才让我萌生了做个机电(小车动力与驱动)和控制(树莓派加载CNN控制)结合的智能小车的想法。

因为本人比较懒,底盘改造于玩具小车,这也给画三维图大大增加了难度((⊙﹏⊙)),特别是轮子上加上轮胎印,加上又是临时学的SolidWorks,难搞。另外,使用树莓派是有python开源库和文件系统的优势,但个人感觉还是32更适合电机控制。

都是三脚猫的设计,恐怕要被大佬笑话,就不贴源码了。比较复杂的多线程的摄像头采集、处理,CNN的训练,参考了网上的项目代码,比如下面这个。

手把手教你——树莓派上利用Tensorflow实现小车的自动驾驶_weixin_41663412的博客-CSDN博客

1 前言

随着计算机技术的发展和近年来深度学习方法的突破,自动驾驶技术在技术支持和社会对驾驶舒适度要求的逐步提高下得到了蓬勃的发展,并逐渐将重心放在以计算机视觉为基础的深度学习算法研究中。

该项目设计了一种原先属于自动驾驶深度学习算法的端到端的卷积神经网络模型作为小车行驶算法,以摄像头采集得到的道路图像作为网络模型输入,小车的驾驶操作作为模型计算输出的智能小车,进行自动驾驶深度学习算法移植到智能小车上的可行性测试。设计最终实现了智能小车在实验道路上遭受外力推动、光照不均等干扰条件下的稳定自动驾驶。本设计系统由树莓派3B作为主控,并与PC机进行无线网络通讯,完成了小车遥控和图像采集、图片处理、CNN建立和训练、小车网络加载和自动驾驶。

另外,该项目对网络模型的各层特征图像和卷积核进行了可视化输出,对网络在道路图像上的感兴趣部分使用热力图进行绘制;对小车的整车结构进行了三维图的绘制,使小车减速齿轮、转向机构的结构和整车的安装布局进行清楚地展示。

前言部分首先介绍了设计的背景、意义、发展现状和创新点;其次,对动力、控制系统的原理进行介绍;再次,介绍了系统硬、软件的设计;接着,是设计的调试和问题解决部分;最后,进行了总结和展望。

关键词:树莓派;自动驾驶;CNN;深度学习;网络可视化

1.1 内容与创新

内容:

1. 智能小车本质上是自动引导小车,基本功能为在铺设的道路内自动行驶。小车自动行驶过程中涉及道路图像采集、计算、驱动控制和电机控制。

2. 道路铺设采用与浅绿色底面对比明显的黑色胶线实线带作为道路的边界,适用黑色绞线虚线带作为道路内的辅助引导。

3. 小车的计算决策使用在汽车自动驾驶中使用的“端到端”的卷积神经网络,对采集得到的道路图像输入模型进行计算,输出小车的方向控制信息。小车的速度通过PWM进行控制,小车的方向操作则来源于输出的方向控制信息。

4. 小车的控制器为使用了ARM Cortex-A53架构1.2GHz 64位四核心处理器的树莓派3B(Raspberry Pi 3 Model B),软件平台为Linux衍生系统Raspbian。

创新:

1. 硬件相关

* 道路建设:不同于单线循迹的小车方案,使用铺设道路边线标注小车行驶的车道范围,以在道路内侧增加虚线作为道路的引导线,增加道路的复杂程度,模拟自动驾驶汽车于一般道路上行驶的“实线+虚线”道路标志线配置。

* 车体制作:使用电机转向结构。通过对购置的小车模型进行全方位改造,使用较少的成本获得能满足需要的小车;连接零件、支承机构的抗震处理和车头的防撞保险结构支持小车更平稳、安全得运行。

* 功能模块:采用模块化硬件设计,为开发和后续扩展升级提供了更大可能,且方便维修保养。

2. 软件相关

* 软件平台:小车软件平台采用基于Debian Linux的Raspbian开源操作系统,在具有庞大生态支持、丰富扩展性的同时,方便同包括Windows、Linux、iOS、Android等市面上流行的系统在内的各类操作系统系统进行通信。

* 使用语言:使用跨平台支持的高级计算机语言Python 3,具有丰富的资源库与庞大的受众人群,十分适合用于在有限时间内进行处理图像、搭建神经网络等科学计算与软件开发。

* 控制算法:使用端到端的卷积神经网络,较之于PID、模糊控制等算法,能更好地适应复杂的环境、应对突发情况。

1.2 原理

1.2.1 动力系统原理

1. 电机

 图中产生磁场的N与S磁极固定不动,其中线圈两端的铜片分别与两片固定的电刷接触,围绕虚线转轴旋转。

2. PWM驱动

对于小功率的智能小车而言,双极性可逆的PWM驱动因为其低速稳定的特点,是较为常用的PWM驱动方案。

 在一个周期T中,电枢电压的平均值U 如下所示:

1.2.2 控制系统原理

1. 无线通信

* SSH登录

 PC机对小车的控制是建立在两个系统(Windows与Debian Linux)的基础上的。常见的方法是PC机使用SSH登录进行子机的日常维护。

 * 远程桌面登录

虚拟网络计算(Virtual Network Computing,VNC)协议在远程虚拟桌面的登录中较为常见,是基于远程帧缓存(Remote Frame Buffer,RFB)协议进行远程通信的,后者属于TCP的应用层协议。

 2. 卷积神经网络

* 反向传播算法

反向传播的思想为计算ANN(人工神经网络)估计值与实际值之间的偏差,并将该偏差从后向前,自输出层起反向传播,直至传播回网络的输入层。各参数的权值将在上述过程的不断迭代中优化、收敛。

 * 卷积运算

卷积神经网络的离散卷积与数学分析中的离散卷积不完全相同,其离散卷积为线性运算。CNN卷积运算中的卷积核也被称为滤波器,其大小将对感受野产生影响。

 * 池化

池化(或称下采样、降采样)可以看成是缩小图像操作,可以在使图像大小符合要求的同时,减少训练运算的数据量,降低网络的训练时间。CNN一般在卷积操作后会跟上池化操作,以便降低网络的训练时间、减少网络过拟合。

1.3 设计

1.3.1 电路原理框图

本设计主要包含上位机(PC机)、电源模块(包括控制和动力电源)、驱动模块、MCU和摄像头,如图所示。其中,通信模块整合在MCU中。

1.3.2 主控模块

由于树莓派3B售价便宜、拥有丰富完备的外设,其系统Raspbian原生支持Python 3,在具有相对强大的性能的同时可以运行神经网络。故使用树莓派3B完成设计是合理的。

 树莓派3B使用的SoC芯片BCM2837,原理图如图所示。

 所使用的CSI摄像头和GPIO口对应的原理图如图所示。

 电源输入与电源管理原理图如图所示。

 1.3.3 LM2596S稳压模块

SolidWorks绘制的LM2596S三维图。

 1.3.4 电机驱动

1. L298N模块

 SolidWorks绘制的L298N模块三维图。

 

2. 后轮减速机构

 3. 前轮转向机构

为了模拟真实车辆的转向方式,使用电机转向方式。

 回正微调装置如图示。

 小车动力及转向模拟如图所示:

1.3.5 小车组装结构与模块分布

本设计的智能小车可分为底层、中层和上层。

1. 小车底层支持结构

小车的底层结构包括小车的底盘、电机、减速齿轮、转向机构、保险防撞结构和移动电源,结构详见图。

2. 小车中层驱动结构

小车的中层主要放置电源稳压模块LM2596S和驱动模块L298N,同时具有上层控制层的连接支承和摄像头桅杆的固定连接,其结构见图 3.12。

3.  小车上层结构

小车的上层主要放置树莓派MCU,同时滑盖式的18650电池盒可以较为方便地更换动力系统的电池,其结构如图示。

 4. 小车整车结构

本设计小车通过完成小车车底、小车中层、小车上层和顶层、桅杆等结构组装,最终完成小车的整车组装,详见图。

 1.3.6 场地道路设计

小车形式的场地道路的好坏决定了小车图像采集、模型训练以及道路测试的质量。参考一般汽车道路与汽车本身的相对宽度大小,结合摄像头的高度与拍摄角度,铺设2倍于小车宽度的道路是合适的。

 场地道路的铺设,考虑时间、成本和可复原原则的因素,在具有浅绿色底漆的地面上使用黑色的电工胶带作为道路的边线。

1.3.7 程序设计

小车控制和PC机处理使用Python高级语言。为方便使用,在PC端使用IDE Pycharm进行所有代码的编写,小车端只需要对代码进行执行。同时,使用SSH或VNC远程桌面在PC机上登录管理树莓派。

序号

模块名

功能

文件

使用硬件PWM的小车驱动控制模块(小车端运行)

包括电机转向和PWM调速在内的小车驱动的基本控制。

调用该文件能完成小车决策输出开始到驾驶执行的过程,直接运行时能检验小车的基本行驶功能。

car_ctrl_hp.py

小车遥控与图像采集模块

(小车端运行)

1、使用了Pygame库的按键扫描功能模块,完成了使用键盘遥控驾驶的按键逻辑,同时调用Ⅰ进行小车的控制;

2、使用多线程在遥控小车时同时采集图片帧,将某时刻产生的图片帧的命名中加入该时刻的遥控操作的方向指令,以供后期的图像处理与网络训练。

clct_data.py

小车遥控测试模块

(小车端运行)

Ⅱ的缩减版本,保留了小车的遥控控制,删除了多线程中的图片采集功能。文件旨在测试遥控的效果和按键逻辑的合理性。

art_drive.py

图像处理模块

(PC端运行)

1、采集到的道路图片的大小固定和图像的矩阵化,通过增加矩阵维度加入图像对应的方向遥控操作值,方便CNN的读取。

2、将矩阵化的图像按照CHUNK的大小建立图片集合“.npz”,每个“.npz”文件中包含了CHUNK个图像矩阵。

process_cnn_img.py

CNN训练模块

(PC端运行)

基于Tensorflow的CNN模型的建立、读取、训练、保存和加载。可分段训练CNN模型。

train_cnn.py

自动驾驶模块

(小车端运行)

加载上传的CNN模型。通过将采集的道路图像输入至CNN模型中,输出计算得到的转向指令,并调用Ⅰ进行小车的自动驾驶。

self_drive.py

效果可视化

(PC端运行)

读取CNN模型。将任意的道路图片输入至该模型中,使用以Tensorflow为后端的keras库进行CNN模型每一层的结果输出并可视化。同时,根据最后一个卷积层的特征映射输出,绘制与原图叠加了网络“兴趣”权重分布的热力图,以说明网络对该图片的“关注点”的分布。

cnn_visualization.py

1. 图像处理

设计中通过process_cnn_img.py文件实现,程序流程如图所示。

 2. 网络搭建与训练

 在train_cnn.py实现网络的搭建和训练,使用开源库Keras和Tensorflow进行。程序流程见图。

对比NVIDIA的端到端学习的CNN,本设计的网络结构有所不同,详见下表。

层(类型)

原端到端学习的CNN

设计的端到端学习的CNN

卷积核大小

输出大小

卷积核大小

输出大小

lambda_1(标准化层)

-

66×200×3

-

120×160×3

conv2d_1(卷积层)

5×5×24

31×98×24

5×5×24

58×78×24

conv2d_2(卷积层)

5×5×36

14×47×36

5×5×36

27×37×36

conv2d_3(卷积层)

5×5×48

5×22×48

5×5×48

12×17×48

conv2d_4(卷积层)

3×3×64

3×20×64

3×3×64

10×15×64

conv2d_5(卷积层)

3×3×64

1×18×64

3×3×64

8×13×64

全连接层

-

1×1164

-

1×6656

1×100

1×250

1×50

1×5

1×10

3. 效果可视化

使用可视化的网络训练效果展示可以直观地了解每一层网络的运作情况,同时也能知晓该网络对图片不同区域的“重视”程度。

 4. 小车遥控与图像采集

在clct_data.py中实现小车的遥控和道路图像采集。

 5. 小车自动驾驶

在self_drive.py中实现小车的自动驾驶。

 2 调试与分析

2.1 网络校验与可视化

神经网络的训练效果,一般可以用样本loss、训练集loss、样本正确率、训练集正确率来衡量。

1. 网络检验

CNN网络参数调整见下表。

模型名

npz

学习率

每步迭代数

批大小

训练集loss

训练集准确率

验证集loss

验证集准确率

M03

200

0.0001

1000

50

0.111

59.1%

0.108

61.2%

M05

90

0.0001

3000

30

0.082

72.7%

0.078

74.2%

M05P

90

0.0001

3000

30

0.110

61.0%

0.096

66.0%

M05PP

87

0.0001

3000

30

0.092

68.6%

0.081

72.5%

M08

208

0.0001

26624

32

0.052

86.0%

0.070

77.7%

M08P

133

0.0001

17024

32

0.052

86.0%

0.071

76.7%

M08PP

133

0.0001

17024

32

0.050

86.6%

0.067

78.0%

M08PE

130

0.00005

16640

16

0.029

92.5%

0.068

79.2%

2. 网络特征可视化

lambda_1层可视化。

 conv2d_1层可视化。

 conv2d_2层可视化。

 可以看出,浅层的特征更趋向于道路表面的形状和纹理,但更深层的特征则更加抽象与多义。下图是conv2d_3至conv2d_5层的输出特征图,可以看出,随着卷积的深入,其表征特征进一步减少,图片特征的提取也愈发抽象。

 3. 模型对区域分布的“关注”程度热力图

对于样片不同区域的“关注”程度,可见下图。

 从该热力图中,模型对图像区域的关注度从高到低按照从红到蓝的颜色进行填充。可以发现,道路底部的反光干扰,在热力图中为偏蓝色,即网络“无视”了强光反射的干扰。

2.2 道路测试与问题解决

模型的有效与否,最终要看实际的运行效果。经过实践后,这里给出了对于各类实际问题的对策和解决方法。在解决了这些问题后,最终得到了能够实现设计目标和指标的模型。

2.2.1 摄像头的有效角度不足与解决

 问题:树莓派摄像头的有效拍摄角不足,在2倍车宽道路的前提下(道路过窄不利于人工驾驶与自动驾驶,且2倍车宽符合真实道路实际),在车辆过直角弯时会出现一边道路拍摄不足,甚至两边道路都无法拍摄到的情况。

解决:

1. 抬升摄像头高度到上限;

2. 在道路边线位置固定不变的情况下,在道路内边线附近分别添加平行的虚线进行辅助。能更好地检验卷积神经网络在智能小车自动驾驶中的实际效果,同时特征的多样性能减轻训练时的过拟合现象。

2.2.2 摄像头太高导致画面抖动

问题:垂直支承的亚克力版对于前后抖动具有一定抗性,而左右抖动下,由于支承面长度仅为2mm,抖动将随着摄像头的抬升而急剧放大。行驶过程存在的抖动对画面的偏移与拍摄清晰度都有巨大影响。

解决:

1. 对垂直支承的亚克力版进行横向的加固

2. 对小车水平亚克力板之间的支承铜柱进行底面与侧面的软材料包裹处理,在螺栓连接处增加弹簧垫片

2.2.3 无线网络的延迟和干扰

问题:在小车进行人工驾驶并多线程采集图像的过程中,存在偶然的延迟问题,经工具查看发现周围无线信道互相干扰严重。

解决:使用手机热点局域网,同时避开拥堵信道,解决控制延迟。

2.2.4 小车弯道冲出的情况与解决

问题:小车在经过某一弯道时,有一定可能会冲出。最可能发生在直角弯道,还发生在靠墙边的道路上。

分析:小车自动驾驶控制上的异常问题一般来自训练的网络,由于场地限制,道路的铺设呈总体环形,局部变相的多类S结构,在道路组成中,道路直行情况最多,其次是各类S弯,最后是直角弯,这就导致了采集的样本中,左、右转向因顺、逆时针的方向采集而比例基本相近,但直线与转弯的指令对应图片数量大约达到7:3的水平,从而导致了转弯训练偏少,样本有一定不均衡现象,而直角转弯则更少得多,其样本不均衡现象的影响更严重。

解决:在原本训练好的模型的基础上,继续增加人工从道路边缘的异常状态开回道路中央正常状态的过程,或仅仅通过该段的过程数据,从而可以有针对性的增补训练数据,进行有针对性的训练。

2.2.5 计算延迟与对策

问题:小车因主控(树莓派3B)的性能限制,在使用网络进行数据帧读取到方向计算的过程,会花费50-150ms不等的时间,对应小车行驶距离为2-10cm不等,对小车的自动驾驶造成了一定的影响,尤其在弯道过程中,将会错过人为认为的“最佳”转弯时机。

解决:重新训练CNN网络,对于输入的图片数据相对应的控制转向指令参数key,进行50ms的提前处理,如原先一张时间戳为1分10秒100毫秒图片的对应控制信号为直行,在经过50ms超前处理后,其对应操作值变为时间戳为1分10秒150毫秒图片的操作值,使图片具有了“预知未来”的效果。

3 成果展示

主要分为:图像采集与训练阶段、人工动力自动方向控制阶段和全自动驾驶阶段。

3.1 遥控采集图像及其处理过程

遥控采集图像过程。

 采集得到的图像部分展示。

 图像处理过程展示。

 3.2 人工动力的自动方向小车

使用人工推动小车,让小车自动控制方向,测试模型的效果并不断改进。

 3.3 小车自动驾驶

端到端训练CNN方法得到的自动驾驶小车展示。

特点:

1. 抗光照干扰;

2. 抗外力推动;

3. 大幅度自矫正。

  • 30
    点赞
  • 194
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答1: 好的,下面是使用 TensorFlow (Keras) 搭建 VGG 卷积神经网络进行手写数字识别的代码: ``` import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers # 加载 MNIST 数据集 (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data() # 数据预处理 x_train = x_train.reshape(-1, 28, 28, 1).astype("float32") / 255.0 x_test = x_test.reshape(-1, 28, 28, 1).astype("float32") / 255.0 # 构建 VGG 网络 model = keras.Sequential( [ layers.Conv2D(64, kernel_size=(3, 3), activation="relu", input_shape=(28, 28, 1)), layers.Conv2D(64, kernel_size=(3, 3), activation="relu"), layers.MaxPooling2D(pool_size=(2, 2)), layers.Conv2D(128, kernel_size=(3, 3), activation="relu"), layers.Conv2D(128, kernel_size=(3, 3), activation="relu"), layers.MaxPooling2D(pool_size=(2, 2)), layers.Conv2D(256, kernel_size=(3, 3), activation="relu"), layers.Conv2D(256, kernel_size=(3, 3), activation="relu"), layers.Conv2D(256, kernel_size=(3, 3), activation="relu"), layers.MaxPooling2D(pool_size=(2, 2)), layers.Conv2D(512, kernel_size=(3, 3), activation="relu"), layers.Conv2D(512, kernel_size=(3, 3), activation="relu"), layers.Conv2D(512, kernel_size=(3, 3), activation="relu"), layers.MaxPooling2D(pool_size=(2, 2)), layers.Flatten(), layers.Dense(256, activation="relu"), layers.Dense(10, activation="softmax"), ] ) # 编译模型 model.compile(loss="sparse_categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) # 训练模型 model.fit(x_train, y_train, batch_size=128, epochs=10, validation_split=0.1) # 评估模型 model.evaluate(x_test, y_test, batch_size=128) ``` 在上面的代码中,我们首先加载了 MNIST 数据集,并对数据进行了预处理。接着,我们构建了一个 VGG 网络,包含了多个卷积层、池化层和全连接层。最后,我们编译了模型并进行了训练和评估。 运行上述代码,即可训练出一个能够识别手写数字的卷积神经网络。 ### 回答2: 使用TensorFlow(Keras)搭建卷积神经网络(VGG)可以实现手写数字识别。手写数字识别是一个常见的机器学习问题,可以通过卷积神经网络(VGG)来解决。 首先,我们需要导入相关的库和数据集。如下所示: ``` import tensorflow as tf from tensorflow.keras.datasets import mnist from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense # 加载MNIST数据集 (x_train, y_train), (x_test, y_test) = mnist.load_data() ``` 数据集包含了60000个训练样本和10000个测试样本,每个样本都是一个28x28像素的手写数字图像。 接下来,我们需要对数据进行预处理。将图像转换为多维数组,并将每个像素值归一化到0-1范围内。还需要对标签进行独热编码处理。 ``` # 对图像数据进行预处理 x_train = x_train.reshape(-1, 28, 28, 1) x_train = x_train / 255.0 x_test = x_test.reshape(-1, 28, 28, 1) x_test = x_test / 255.0 # 对标签进行独热编码 y_train = tf.keras.utils.to_categorical(y_train, num_classes=10) y_test = tf.keras.utils.to_categorical(y_test, num_classes=10) ``` 接下来,我们开始构建VGG模型。 ``` model = Sequential() model.add(Conv2D(64, (3, 3), activation='relu', padding='same', input_shape=(28, 28, 1))) model.add(Conv2D(64, (3, 3), activation='relu', padding='same')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(128, (3, 3), activation='relu', padding='same')) model.add(Conv2D(128, (3, 3), activation='relu', padding='same')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(256, (3, 3), activation='relu', padding='same')) model.add(Conv2D(256, (3, 3), activation='relu', padding='same')) model.add(Conv2D(256, (3, 3), activation='relu', padding='same')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(512, (3, 3), activation='relu', padding='same')) model.add(Conv2D(512, (3, 3), activation='relu', padding='same')) model.add(Conv2D(512, (3, 3), activation='relu', padding='same')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Conv2D(512, (3, 3), activation='relu', padding='same')) model.add(Conv2D(512, (3, 3), activation='relu', padding='same')) model.add(Conv2D(512, (3, 3), activation='relu', padding='same')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Flatten()) model.add(Dense(4096, activation='relu')) model.add(Dense(4096, activation='relu')) model.add(Dense(10, activation='softmax')) ``` 将模型编译后,我们可以对模型进行训练和评估。 ``` model.compile(optimizer=tf.keras.optimizers.SGD(), loss='categorical_crossentropy', metrics=['accuracy']) model.fit(x_train, y_train, batch_size=64, epochs=10, validation_data=(x_test, y_test)) score = model.evaluate(x_test, y_test) print('Test loss:', score[0]) print('Test accuracy:', score[1]) ``` 通过以上步骤,我们就可以使用TensorFlow(Keras)搭建VGG卷积神经网络,并实现手写数字识别。 ### 回答3: 使用TensorFlow(Keras)搭建卷积神经网络VGG模型,并实现手写数字识别的步骤如下: 1. 数据准备:收集手写数字数据集,例如MNIST数据集,包含60000张训练图像和10000张测试图像。 2. 导入相关库:使用TensorFlowKeras库进行模型构建和训练。 3. 数据预处理:对训练和测试数据进行预处理,包括将像素值标准化至0到1之间,将标签进行独热编码等。 4. 构建模型:使用Keras中的Sequential模型,按照VGG网络的结构顺序添加卷积层、池化层和全连接层。VGG网络通常由多个卷积层和全连接层组成,其中每个卷积层均由两个连续的卷积层和一个池化层组成。 5. 编译模型:设置模型的损失函数、优化器和评估指标。常用的损失函数是交叉熵损失函数,优化器可以选择Adam或SGD,评估指标可以选择准确率。 6. 模型训练使用模型的fit()方法进行模型训练,设定训练的批次大小、训练轮数等参数。 7. 模型评估:使用测试集评估模型的准确率和损失值。 8. 手写数字识别:使用训练好的模型对新的手写数字图像进行识别。首先对输入图像进行预处理,然后使用模型的predict()方法预测图像的类别,并输出相应的数字。 以上是使用TensorFlow(Keras)搭建卷积神经网络VGG模型,实现手写数字识别的基本步骤。详细代码实现可以参考TensorFlowKeras的官方文档以及相关教程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值