Win10—YOLOv5实战+TensorRT部署+VS2019编译(小白教程~易懂易上手)---超详细

Win10—YOLOv5实战+TensorRT部署+VS2019编译(小白教程~易懂易上手)

一位入门CV的小白,第一次使用TensorRT来加速部署YOLOv5项目。

此文详细记录了实操过程中的种种坑与不足,一来方便自己回顾温习,二来帮助想使用YOLOv5与TensorRT来部署项目的小伙伴避免踩一些坑,三来希望得到各位大佬大牛的高见!

1 软件下载及安装

​ 本文操作均在Win10系统上完成,需要用到的软件与依赖包有:cuda 10.2 , cudnn 7.6.5 , VS2019 , OpenCV 3.4.0 , Anaconda3 , CMake 3.19.4 , TensorRT 7

image-20210830095251854

(PS:先安装VS2019,再安装CUDA10.2+CUDNN7.6.5; 否者后面CMake编译会报错“No CUDA toolset found”)

⭐⭐⭐

[PS:以上所有软件及依赖库均可在官方下载获得,按照下面叙述教程操作即可实现;官网访问速度很慢,下载需要等待很长时间,如若想一次性快速下载即可使用,请访问链接(包含全部软件): https://blog.youkuaiyun.com/weixin_39588099/article/details/119994609?spm=1001.2014.3001.5501]


(1) 安装VS2019

​ 用来学习与练手,社区版就已足够(本文使用社区版)。如需更多功能或商用,请安装专业版或企业版。

官网链接:https://visualstudio.microsoft.com/zh-hans/downloads/

image-20210824223440671

下载后,双击vs_***.exe进行安装,需勾选如下信息(此处我已安装):

image-20210824224641545

然后自定义安装路径,执行即可(需耐心等待一个小时左右),安装成功后界面

image-20210824225147310

(2) 安装NVIDIA显卡驱动

​ 第一步需要在设备管理器中查看自己电脑的显卡型号,比如在这里可以看到本文中使用的显卡型号为NVIDIA GeForce GTX 1660 Ti

image-20210824230535262

NVIDIA 显卡驱动官网链接:https://www.nvidia.cn/Download/index.aspx?lang=cn , 搜索就可以下载电脑相对应的英伟达显卡驱动

image-20210824230555838

安装完成之后,在命令提示符cmd中输入执行:

nvidia-smi

如果报错:‘nvidia-smi’ 不是内部或外部命令,也不是可运行的程序或批处理文件。
则需要把’C:\Program Files\NVIDIA Corporation\NVSMI’添加到环境变量的path中,再重新打开cmd窗口输入命令执行。
如果输出下图所示的显卡信息,则说明电脑的显卡驱动安装成功。

image-20210824231605449

(3) 安装CUDA10.2+CUDNN7.6.5

1) CUDA安装

​ CUDA用的是10.2版本,官网链接:https://developer.nvidia.com/cuda-10.2-download-archive?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exelocal

image-20210824232904085

下载后得到文件:cuda_10.2.89_441.22_win10.exe,点击安装即可,此处使用默认路径以方便后面配置路径

image-20210824233924962

image-20210824234256337

安装完成后设置环境变量

image-20210824235300596

右键点击此电脑,打开属性—>高级系统设置—>环境变量,可以看到系统变量中多了CUDA_PATH和CUDA_PATH_V10_2两个环境变量(CUDA默认安装位置路径为:C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.2)

接下来,还需要在系统变量中添加以下五个变量:

CUDA_SDK_PATH = C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.2
CUDA_LIB_PATH = %CUDA_PATH%\lib\x64
CUDA_BIN_PATH = %CUDA_PATH%\bin
CUDA_SDK_BIN_PATH = %CUDA_SDK_PATH%\bin\win64
CUDA_SDK_LIB_PATH = %CUDA_SDK_PATH%\common\lib\x64

image-20210825000153713

在系统变量中双击打开Path变量, 在其末尾添加如下指令路径:

%CUDA_LIB_PATH%;%CUDA_BIN_PATH%;%CUDA_SDK_LIB_PATH%;%CUDA_SDK_BIN_PATH%;
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\lib\x64
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\include
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\extras\CUPTI\lib64
C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.2\bin\win64
C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.2\common\lib\x64

image-20210825000802152

2) CUDNN安装

​ CUDNN官方链接:https://developer.nvidia.com/cudnn

第一次需要用邮箱注册,然后登录

image-20210825002613415

image-20210825002628646

下载后得到文件:cudnn-10.2-windows10-x64-v7.6.5.32.zip

将压缩包文件解压打开,然后将cuda目录下的bin,include,lib中的文件分别复制粘贴到路径C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2文件夹下相对应的目录

image-20210825003649233

3) 安装测试

最后测试CUDA是否配置成功,打开CMD执行:

nvcc -V

即可看到如下图所示CUDA的信息,则配置成功(此后可愉快的使用GPU啦……)

image-20210825004547300

(4) 安装OpenCV3.4.0

​ OpenCV3.4.0,官网链接: https://opencv.org/opencv-3-4.html

image-20210826222113049

选择Win pack点击下载(此处官网下载,或许很慢,需要耐心等待)

下载完成后,双击opencv-3.4.0-vc14_vc15.exe运行进行解压,将压缩包解压到相应目录(自定义路径),如:D:\Program Files (x86)\opencv,然后在系统变量Path的末尾添加:D:\Program Files (x86)\opencv\build\x64\vc15\bin,即完成安装

image-20210825232150255

(5) 安装Anaconda3+Pytorch1.7

​ 对于Anaconda3,直接在官网下载安装包,官网链接:https://www.anaconda.com/products/individual-d

image-20210825232217660

下载后点击进行安装,一直执行下一步,选择好软件的安装路径,例如本文中的安装路径为D:\ProgramData\Anaconda3,然后耐心等待,等到安装完成

安装默认是官网海外服务器,添加国内镜像源就可以实现快速的Download,清华TUNA提供了Anaconda仓库的镜像

打开Anaconda Prompt (Anaconda3)依次运行以下命令:

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/conda config --set show_channel_urls yes

对于Pytorch1.7,打开Anaconda Prompt (Anaconda3)创建虚拟环境,其中需要命名(这个命名随意的,选择能让你开心的命名最重要),本文中使用pytorch1.7作为环境名

conda create -n pytorch1.7 python=3.8

创建了环境以后,激活

conda activate pytorch1.7

激活后暂时不操作且不能关闭,我们需要在所创建的pytorch1.7环境下安装pytorch的1.7版本,本文中选择离线安装(速度更快)

需要下载官方安装包,链接:https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/win-64/

此处选择的版本为:pytorch-1.7.1-py3.8_cuda102_cudnn7_0.tar.bz2

image-20210825232240691

然后在环境里执行

conda install --offline pytorch-1.7.1-py3.8_cuda102_cudnn7_0.tar.bz2

耐心等待,安装完成即可

(6) 安装CMake3.19.4

​ 本文中安装的是cmake-3.19.4版本,Github链接:https://github.com/Kitware/CMake/releases?after=v3.19.5

image-20210825232259400

下载完成后,一直点击下一步执行即可

其中出现下图所示情况,询问是否将CMake添加到系统环境变量选择第二个(所有用户均可访问的环境变量),最后一行如果勾选,将在桌面创建快捷方式图标,根据个人选择,然后继续点击Next,最后点击Install,等待程序安装完成

image-20210825232316269

安装完成之后,重启电脑,然后打开Anaconda Prompt (Anaconda3),输入以下命令

cmake /V

如果出现cmake的版本号,则说明cmake已经安装成功,如果没出现或者报错,则检查是否存在其他较低的版本或者检查是否已经将cmake添加到系统环境变量Path中!

image-20210825232338821

2 YOLOv5项目下载及安装

(1) 项目下载

本文中直接下载V4.0源码(方便快捷),Github链接:https://github.com/ultralytics/yolov5/tags

image-20210826221932692

下载压缩包,完成后解压到自定义文件夹下

(2) 权重下载

下载V4.0版本相对应的预训练权重文件

权重文件Github链接:https://github.com/ultralytics/yolov5/releases,本文中使用的权重是yolov5s.pt,下载完成后,将其放到yolov5-4.0\weights目录下

image-20210826222155649

(3) 安装依赖库

安装所需库,利用清华镜像源进行安装,再次打开Anaconda Prompt (Anaconda3),激活前面创建的虚拟环境

conda activate pytorch1.7

继续执行以下指令,进入到yolov5-4.0目录下,例如(根据自己的解压路径修改)

d:cd D:\YOLOv5_TensorRT-PeiZhi\yolov5-4.0

在此路径下执行

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt

(4) 安装测试

在yolov5-4.0目录下,上述步骤后继续执行

python detect.py --source ./data/images/ --weights weights/yolov5s.pt --conf 0.6

出现以下信息,则测试成功

image-20210825233132923

3 TensorRT7下载及安装

(1) 安装包下载

安装包官方链接:https://developer.nvidia.com/tensorrt

进入后操作步骤为:

  • 点击GET STARTED
  • 进入后点击Download Now
  • 然后点击Login
  • 此后选择TensorRT 7

此处下载对应的版本

image-20210826005712698

得到压缩包:TensorRT-7.0.0.11.Windows10.x86_64.cuda-10.2.cudnn7.6.zip

(2) 环境配置

  • 在D盘(其他盘也可)新建文件夹,命名为tensorrt_tar,然后将下载的压缩包拷贝到此文件夹下

  • 将压缩文件解压得到TensorRT-7.0.0.11的文件夹,将里边lib文件夹的绝对路径添加到环境变量中,即D:\tensorrt_tar\TensorRT-7.0.0.11\lib

  • 将TensorRT-7.0.0.11目录中的lib文件夹下的.dll文件全部复制到C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\bin目录下

  • 使用python接口的tensorrt时,需要安装pycuda包

    进入网址,下载对应版本的.whl文件:https://www.lfd.uci.edu/~gohlke/pythonlibs/?cm_mc_uid=08085305845514542921829&cm_mc_sid_50200000=1456395916&cm_mc_uid=08085305845514542921829&cm_mc_sid_50200000=1456395916#pycuda , 本文中使用如下.whl文件(供参考)

image-20210826222244352

#打开Anaconda Prompt (Anaconda3)#激活pytorch1.7虚拟环境cd XXX       			#进入到.whl文件目录pip install XXX.whl      #直接运行命令安装即可

(3) 示例测试

1) VS2019配置
  • 此处对sampleMNIST示例进行测试,进入到D:\tensorrt_tar\TensorRT-7.0.0.11\samples\sampleMNIST目录下,选择sample_mnist.sln文件—>右键点击打开方式—>选择Microsoft Visual Studio 2019打开文件
  • 然后依次点击 项目—>属性—>VC++目录
  • 将路径D:\tensorrt_tar\TensorRT-7.0.0.11\lib分别加入可执行文件目录库目录
  • 将D:\tensorrt_tar\TensorRT-7.0.0.11\include加入C/C++ —> 常规 —> 附加包含目录
  • 将nvinfer.lib、nvinfer_plugin.lib、nvonnxparser.lib和nvparsers.lib加入链接器–>输入–>附加依赖项

image-20210826222326980

image-20210826222353081

2) pgm文件下载

进入到D:\tensorrt_tar\TensorRT-7.0.0.11\data\mnist目录下,运行download_pgms.py文件

#打开Anaconda Prompt (Anaconda3)#激活pytorch1.7虚拟环境cd XXX       			#进入到download_pgms.py文件目录python download_pgms.py  #直接运行后会在文件夹中多出10个.pgm文件

image-20210826222407967

如若下载失败或太慢,可打开download_pgms.py文件修改下载链接,如下所示

  • 将35行的链接 http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz 修改为 http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
  • 将38行的链接 http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz 修改为 http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
3) 安装测试

在VS2019中,将sample_mnist项目选中右键执行生成,事先需选取Release x64平台

image-20210826222429692

显示成功后,便点击 调试—>开始执行(不调试)(H)

image-20210826222446596

最后生成如下图所示结果,则测试成功(数字是随机的,此处显示的是1,可以不同)

image-20210826222500798

4 TensorRT加速部署⭐

(1) tensorrtx下载

对于tensorrtx,Github链接:https://github.com/wang-xinyu/tensorrtx/tags,选择与YOLOv5相对应的V4.0版本下载

image-20210826222559677

下载完成后,将压缩包放置在D盘并解压,随后修改文件夹名为tensorrtx

(2) dirent.h下载

Dirent 是一个 C/C++ 编程接口,允许程序员在 Linux/UNIX 下检索有关文件和目录的信息。 该项目为 Microsoft Windows 提供了 Linux 兼容的 Dirent 接口,Github链接:https://github.com/tronkko/dirent

进入选择 Code—>Download ZIP 即可下载,下载后将压缩包解压,进入将include文件夹复制到tensorrtx目录下

image-20210826222616693

(3) yolov5s.wts生成

进入D:\tensorrtx\yolov5文件夹中,将gen_wts.py文件复制到开始下载的YOLOv5项目(yolov5-4.0)文件夹中

image-20210826222736430

然后操作

#打开Anaconda Prompt (Anaconda3)#激活pytorch1.7虚拟环境cd XXX       			#进入到yolov5-4.0项目目录python gen_wts.py        #直接运行后会在文件夹中多出1个yolov5s.wts文件

稍等一会儿,yolov5s.wts将会生成,存在于文件夹中

(4) CMakeList.txt修改⭐

官方给出的CMakeList是linux版本的,想在Windows系统下运行项目需要修改CMakeList文件。具体修改完成后的内容如下:

复制粘贴即可用,需根据自己路径修改5处(##1—>##5)

cmake_minimum_required(VERSION 2.6)project(yolov5)  set(OpenCV_DIR "D:\\Program Files (x86)\\opencv\\build")    ##1set(OpenCV_INCLUDE_DIRS "D:\\Program Files (x86)\\opencv\\build\\include")    ##2set(OpenCV_LIBS "D:\\Program Files (x86)\\opencv\\build\\x64\\vc14\\lib\\opencv_world340.lib")    ##3set(TRT_DIR "D:\\tensorrt_tar\\TensorRT-7.0.0.11")    ##4add_definitions(-std=c++11)add_definitions(-DAPI_EXPORTS)option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)set(CMAKE_CXX_STANDARD 11)set(CMAKE_BUILD_TYPE Debug)set(THREADS_PREFER_PTHREAD_FLAG ON)find_package(Threads)# setup CUDAfind_package(CUDA REQUIRED)message(STATUS "    libraries: ${CUDA_LIBRARIES}")message(STATUS "    include path: ${CUDA_INCLUDE_DIRS}")include_directories(${CUDA_INCLUDE_DIRS})####enable_language(CUDA)  # add this line, then no need to setup cuda path in vs####include_directories(${PROJECT_SOURCE_DIR}/include)include_directories(${TRT_DIR}\\include)include_directories(D:\\tensorrtx\\include)    ##5#find_package(OpenCV)include_directories(${OpenCV_INCLUDE_DIRS})include_directories(${OpenCV_INCLUDE_DIRS}\\opencv2)# -D_MWAITXINTRIN_H_INCLUDED for solving error: identifier "__builtin_ia32_mwaitx" is undefinedset(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Ofast -D_MWAITXINTRIN_H_INCLUDED")# setup opencvfind_package(OpenCV QUIET    NO_MODULE    NO_DEFAULT_PATH    NO_CMAKE_PATH    NO_CMAKE_ENVIRONMENT_PATH    NO_SYSTEM_ENVIRONMENT_PATH    NO_CMAKE_PACKAGE_REGISTRY    NO_CMAKE_BUILDS_PATH    NO_CMAKE_SYSTEM_PATH    NO_CMAKE_SYSTEM_PACKAGE_REGISTRY)message(STATUS "OpenCV library status:")message(STATUS "    version: ${OpenCV_VERSION}")message(STATUS "    libraries: ${OpenCV_LIBS}")message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")include_directories(${OpenCV_INCLUDE_DIRS})link_directories(${TRT_DIR}\\lib)link_directories(${OpenCV_DIR}\\x64\\vc14\\lib)add_executable(yolov5 ${PROJECT_SOURCE_DIR}/calibrator.cpp yolov5 ${PROJECT_SOURCE_DIR}/yolov5.cpp ${PROJECT_SOURCE_DIR}/yololayer.cu ${PROJECT_SOURCE_DIR}/yololayer.h)target_link_libraries(yolov5 "nvinfer" "nvinfer_plugin")target_link_libraries(yolov5 ${OpenCV_LIBS})        target_link_libraries(yolov5 ${CUDA_LIBRARIES})target_link_libraries(yolov5 Threads::Threads)

(5) tensorrtx编译运行

在D:\tensorrtx\yolov5目录下新建build文件夹,随后打开cmake-gui软件

确定好源代码路径和生成路径—>点击Configure并设置环境—>点击Finish,等待Configure done—>点击Generate并等待Generate done—>点击Open Project

image-20210826230748608

image-20210826231025526

打开项目后,使用Release x64平台生成解决方案,如下图所示,即生成成功

image-20210826231532992

(6) tensorrtx加速命令

由上述步骤可知,项目编译成功,会在D:\tensorrtx\yolov5\build\Release目录下生成yolov5.exe可执行文件

将第(3)步骤中生成的yolov5s.wts复制粘贴到D:\tensorrtx\yolov5\build\Release目录下,打开cmd执行

yolov5.exe -s yolov5s.wts yolov5s.engine s

则会在目录下生成一个yolov5s.engine文件

image-20210826232250261

在D:\tensorrtx\yolov5\build目录下,新建samples文件夹,里面存放待检测图像

还是在D:\tensorrtx\yolov5\build\Release目录下,打开cmd执行

yolov5.exe -d yolov5s.engine ../samples

可得到加速后的检测结果

image-20210826234651843

(7) 正常与加速检测对比

直接运行原版项目中的detect.py文件进行检测,从结果可看出检测速度很慢

image-20210826234605427

两幅图像的检测速度分别为43ms和38ms,而进过TensorRT加速后的检测速度为7ms与7ms,可见加速明显

5 总结

到此,利用TensorRT来加速部署YOLOv5这一实战项目已成功实现。

Vue3 中封装 WebSocket,并加入心跳检测和断开重连机制是一种常见的需求,可以保证连接的稳定性和数据通信的可靠性。以下是详细的介绍及步骤: --- ### **一、WebSocket 封装概述** 我们通过 Vue3 的 `composition API` 或全局工具的方式对 WebSocket 进行封装,同时添加以下功能: 1. **心跳检测**:定时发送消息给服务器,判断客户端和服务端是否正常在线。 2. **断开重连**:当发生网络异常导致 WebSocket 断开时,自动尝试重新建立连接。 --- ### **二、实现代码** #### 1. 创建 WebSocket 工具类 我们可以创建一个独立的 JavaScript 文件(如 `wsService.js`),专门用于管理 WebSocket 的生命周期及相关操作。 ```javascript // wsService.js class WsService { constructor(url) { this.url = url; // WebSocket URL this.socket = null; // WebSocket 实例 this.reconnectCount = 0; // 重连次数计数器 this.isReconnecting = false; // 是否正在重连标志位 this.pingIntervalId = null; // 心跳检测间隔 ID this.reconnectTimeoutId = null; // 重连超时时间 ID this.init(); // 初始化 WebSocket 链接 } init() { if (this.socket === null || this.socket.readyState !== WebSocket.OPEN) { this.connect(); } } connect() { try { console.log('尝试连接 WebSocket...'); this.socket = new WebSocket(this.url); this.socket.onopen = () => { console.log('WebSocket 连接成功'); this.startPingPong(); // 启动心跳检测 this.resetReconnectCounter(); // 成功连接后重置重连计数器 }; this.socket.onerror = error => { console.error('WebSocket 发生错误:', error); }; this.socket.onclose = event => { console.warn(`WebSocket 关闭, code=${event.code}, reason=${event.reason}`); this.restartConnection(); // 自动重连 }; this.socket.onmessage = messageEvent => { const data = JSON.parse(messageEvent.data); console.log('收到服务端消息:', data); if (data.type === 'pong') { // 如果是 pong 响应,则处理心跳包返回结果 console.log('心跳响应已收到.'); } else { // 其他业务逻辑... } }; } catch (e) { console.error('WebSocket初始化失败', e.message); } } startPingPong(intervalTime = 5000) { clearInterval(this.pingIntervalId); // 清除之前的定时任务避免重复启动 this.pingIntervalId = setInterval(() => { if (this.socket && this.socket.readyState === WebSocket.OPEN) { console.log("向服务器发送心跳..."); this.send({ type: "ping" }); // 客户端主动发 ping 包 } }, intervalTime); } restartConnection(maxRetries = 5, delayMs = 3000) { if (!this.isReconnecting) { this.isReconnecting = true; let retryAttemptsLeft = maxRetries; const reconnectFunc = () => { if (retryAttemptsLeft <= 0) { console.error("超过最大重试次数"); return; } setTimeout(() => { console.log(`正在进行第 ${maxRetries - retryAttemptsLeft + 1} 次重连...`); this.connect(); retryAttemptsLeft--; }, delayMs); }; clearTimeout(this.reconnectTimeoutId); // 确保不会多次触发重连函数 this.reconnectTimeoutId = setTimeout(reconnectFunc, delayMs * ++this.reconnectCount); } } resetReconnectCounter() { this.reconnectCount = 0; clearTimeout(this.reconnectTimeoutId); this.isReconnecting = false; } send(data) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify(data)); } else { console.error('无法发送数据,当前未处于 OPEN 状态!'); } } close(code = 1000, reason = '') { if (this.socket) { this.socket.close(code, reason); } clearInterval(this.pingIntervalId); clearTimeout(this.reconnectTimeoutId); this.resetReconnectCounter(); } } export default function createWsInstance(wsUrl) { return new WsService(wsUrl); } ``` --- #### 2. 在组件中使用 WebSocket 将上述工具引入到需要使用的 Vue 组件内即可直接实例化并调用相关方法: ```vue <template> <div>WebSocket 示例</div> </template> <script setup> import { onMounted, onUnmounted } from 'vue'; import createWsInstance from './utils/wsService'; const wsUrl = 'wss://example.com/socket'; // 替换实际地址 let wsClient = null; onMounted(() => { wsClient = createWsInstance(wsUrl); wsClient.send({ action: 'subscribe', channel: 'chat' }); window.addEventListener('beforeunload', handleUnload); }); function handleUnload() { if (wsClient) { wsClient.close(1000, '用户离开页面'); } } onUnmounted(() => { if (wsClient) { wsClient.close(); } }); </script> ``` --- ### **三、总结** 以上是对 Vue3 下基于 Composition API 和普通 ES6 类完成的一个简单可靠的 WebSocket 封装示例,其中包含关键的心跳维护以及断线后的智能重连设计思路,可以根据实际情况调整参数值等细节内容进一步优化性能!
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电科CVer攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值