简介:面向计算机视觉初学者与农业智能化研究者,这份压缩包以YOLOv8实现大豆叶病目标检测,完整展示从数据集构建、模型设计到训练推理的YOLO框架搭建流程。资源共7个文件,包含6个Python脚本和1个Markdown说明,脚本分别覆盖网络结构、数据加载、损失计算、训练与推理等核心模块,说明文档则可辅助快速上手。压缩包仅9KB,代码精简,适合逐行研读YOLO整体构建思路。目前已有47人学习浏览。对于希望脱离纯理论、通过实际项目掌握PyTorch目标检测流程的读者,既能作为大豆叶病识别的参考实现,也可当作搭建自定义YOLO检测器的起点,借助轻量代码快速迁移到其他检测场景,加深对YOLO框架各环节的理解。
1. 大豆叶病检测为什么绕不开YOLOv8:从预训练权重翻车说起
大豆叶病的病斑通常只占叶片面积的百分之几,叶片本身形态各异,光照和土壤背景又混杂,直接拿现成的YOLOv8预训练权重跑,要么漏检,要么把叶脉当成病斑。真正的问题在于,多数人用YOLO只学会了跑predict,没学会从环境、数据到训练、评估的完整链路。这份基于YOLOv8的大豆叶病目标检测项目,拆的正是这条链路:环境搭建、Labelme标注转换、训练调参、避坑闭环、部署验证。适合两类人——课题或实习需要做植物病虫害检测的从业者,以及想借一个具体任务把YOLO框架整体摸透的学习者。
2. YOLOv8的工程结构与环境搭建:先把Anaconda和PyTorch版本对齐
2.1 读懂ultralytics包结构:训练流水线其实只有三个入口
拿到这份资源先别急着跑demo,先看一下ultralytics包的目录结构。YOLOv8在2023年之后把代码统一收进ultralytics这一个包里,入口是CLI命令yolo和Python API,网上大量旧版YOLOv5的detect.py、train.py那种用法在v8上走不通。我一般会花二十分钟把ultralytics目录过一遍,重点看cfg/models/v8下的yolov8n.yaml、yolov8s.yaml,以及cfg/datasets里自带的data.yaml模板。真正训练和推理的逻辑全在ultralytics/engine/trainer.py和predictor.py里,入口脚本只是一层薄壳。
这种全局观到后面调参时特别有用。训练命令里传的data、epochs、batch这些参数,进了trainer.py之后会经过settings和checker校验,很多报错信息在v8里被规范化成一句英文提示。看不懂英文提示时直接翻trainer.py里对应的raise语句,一眼就能定位到问题是出在数据路径还是参数类型。靠这个办法排查,比在论坛里搜同样的报错快得多,也更不容易被各种过时答案带偏。
另外一个值得注意的点是YOLOv8网络结构本身。v8在C2f模块里把v5的CSP结构做了升级,颈部PAN-FPN保留,Head换成了Decoupled Head,分类和回归分支分开输出。理解这一点不是浪费时间——后面遇到loss曲线不收敛、框回归不准时,你会知道问题大概率出在回归分支的损失权重上,而不是整体结构。
2.2 Ubuntu 20.04下CPU/GPU环境:PyTorch版本和驱动对齐是第一步
环境配置这一步新手最容易翻车的地方,是PyTorch版本和CUDA版本没有对齐。先创建一个独立的conda环境,别把YOLOv8装进base环境里,后面换项目时依赖冲突会非常难受:
conda create -n yolo python=3.9 conda activate yolopython=3.9是稳妥选择。YOLOv8官方要求Python大于等于3.8,3.10和3.11也能跑,但有些标注转换脚本依赖的库还没跟上,回到3.9能少踩很多坑。这一步本身没有技术难度,难的是养成"每个项目一个独立环境"的习惯。
接下来装PyTorch。这里CPU版本和GPU版本的安装命令完全不同,最容易犯的错是拿GPU安装命令丢给一台没有NVIDIA显卡的机器,然后收到"No CUDA GPUs are available"。先确认机器上有没有卡:
nvidia-smi有输出说明驱动在,记下右上角CUDA Version那一行。比如显示CUDA Version 12.0,不代表你要装cu120的torch,而是告诉你驱动支持的上限。接着看显卡算力,GTX 1660 Ti这类图灵架构和RTX 30系安培架构都能用cu118的预编译包。确认完硬件之后再选安装命令:
# CPU版本 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # GPU版本(CUDA 11.8,覆盖多数GTX 10系之后显卡) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118细节在这里:装CPU版和GPU版都指定了--index-url,而不是直接pip install torch。直接装默认拉最新版torch,可能跟你机器上的CUDA驱动不匹配,而--index-url能精确控制要装的CUDA版本后缀。装完别急着下一步,先验证:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"GPU机器上看到类似2.1.0+cu118 True才算通过。CPU机器上返回False不影响后续流程,只是训练慢。这里顺便说一句,GPU显存6G以下的机器,比如GTX 1660 Ti,训练时batch设8、imgsz设640是能跑的,但如果同时开着浏览器,很容易在某个epoch中间突然OOM,训练前把占用显存的应用全关掉。
2.3 装ultralytics与首跑验证:用yolov8n.pt验环境而不是验模型
PyTorch就位后装主包:
pip install ultralyticsultralytics会自动带上opencv-python、numpy、pandas这些依赖。我第一次装的时候卡在opencv的版本冲突上——项目里老代码装的是opencv-contrib-python-headless,ultralytics又要opencv-python,两个包会互相覆盖。解决方法是先卸载再装:
pip uninstall opencv-contrib-python-headless -y pip install opencv-python然后下载预训练权重yolov8n.pt。这个权重是v8全系列里最轻量的一版,参数量约320万,几十MB大小,拿来做环境验证最合适。执行后权重文件会自动下载到当前目录,后续运行会优先读本地文件。
# verify_env.py from ultralytics import YOLO model = YOLO("yolov8n.pt") results = model.predict(source="https://ultralytics.com/images/bus.jpg", save=True) print(results[0].boxes.data.shape)这段代码的逻辑是:加载一个预训练模型,对示例图片做推理,save=True把结果图写到runs/detect/predict目录,最后打印检测框的张量形状。输出类似(3, 6)就说明一张图里框出了3个目标,每条记录是[x1, y1, x2, y2, conf, cls]。
首跑验证的目的是验证环境而不是验证模型。如果卡在权重下载,通常是网络问题,这个文件可以从镜像站手动下载放到当前目录。看到输出shape正常,说明从CUDA到opencv到权重加载整条链路都通,环境就算立住了。这里也兼顾了纯CPU环境——CPU机器跑同样代码,只要不报错就算过,只是推理耗时明显长,属正常现象。
3. 大豆叶病数据集构建:从Labelme标注到YOLO格式的四步流水线
3.1 类目设计与图像采集:先定小目标再定标签
大豆叶病的检测难点在于病斑小、和健康叶片颜色反差小,而且不同病害在普通RGB图像上差异不如想象中明显。做数据集之前,先和植保背景的人确认类目边界。常见做法是把霜霉病、灰斑病、细菌性斑疹病、大豆锈病先分开,背景和健康叶片单独作为背景类处理,或者干脆不标。我建议初期类目宁少勿多,四五个类目先跑通流程,再逐步加类。
图像采集阶段要注意多样性。用手机或无人机在不同光照、不同角度、不同生育期拍摄,不要全是同一块地同一角度。如果训练图全部来自同一个下午的同一块田,模型极易过拟合到那个时段的光照和土壤颜色上,后续换场景实测基本废掉。图片数量上,每个类目至少200张起步,单类图像少于100张时,训练出来的模型基本靠预训练权重的底子硬撑,病斑特征学不到多少。类目不平衡的问题也一样,某个病种只有几十张图,训练时会被其他类目淹没,后面可以用class_weights选项补偿。
3.2 Labelme标注要点:多边形工具和矩形框的取舍
标注工具我固定用Labelme,因为它是开源工具里对不规则轮廓支持最好、跨平台体验最一致的一个:
pip install labelme labelme --labels labels.txtlabels.txt里按行写类目名,换行就是分隔符:
soybean_rust frog_eye_leaf_spot brown_spot bacterial_blight有个跟YOLO默认标注相关的细节:Labelme默认让你画多边形,优势是能把病斑不规则的轮廓贴合得更准,但YOLOv8训练时用的是矩形框,多边形标注的信息在转换时会退化成外接矩形。所以标注时直接使用"Create Rectangle"工具按对角线拉框,省掉后面多边形的坐标整理。Labelme生成的JSON本质上是一样的结构,shapes字段里每个元素有label、points、shape_type,矩形也存成四个点的多边形,转换脚本要兼容这个数据结构。
每张图标完会生成同名JSON文件,建议图片和JSON放在同一个文件夹。标注工作量如果超过几百张,按"先标50张→训练一版→挑漏检再补标"的节奏推进,一次性标完三百张再训练,返工成本极高——你会在训练完才发现标注标准不对,比如有的框把病斑和健康叶脉框在一起,然后回头改两三百个文件,非常消磨耐心。
3.3 标注统一转YOLO格式:JSON与XML转换脚本与坐标坑
Labelme导出的是JSON,老项目常用labelImg导出VOC风格的XML。这份资源里给了转换脚本,把两种格式统一成YOLO训练需要的txt文件。YOLO格式每行是class x_center y_center width height,全部除以图像宽高做归一化。核心代码逻辑如下:
import json import os from PIL import Image def labelme_to_yolo(json_path, img_dir, out_dir, class_names): with open(json_path, encoding="utf-8") as f: data = json.load(f) img_path = os.path.join(img_dir, data["imagePath"]) img = Image.open(img_path) w, h = img.size base = os.path.splitext(os.path.basename(json_path))[0] with open(os.path.join(out_dir, base + ".txt"), "w") as out: for shape in data["shapes"]: label = shape["label"] if label not in class_names: continue cls_id = class_names.index(label) points = shape["points"] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) x_center = ((x_min + x_max) / 2) / w y_center = ((y_min + y_max) / 2) / h box_w = (x_max - x_min) / w box_h = (y_max - y_min) / h out.write(f"{cls_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f}\n")代码逻辑不复杂:读出每个shape的标签和顶点坐标,取所有顶点的最小最大值构成框,再算中心点和宽高,最后全部除以图像原始宽高归一化。这里循环里的cls_id = class_names.index(label)是低效写法,类目多了以后建议改成字典映射,但这个量级的数据影响不大。
这个脚本有两个关键注意点。第一,data["imagePath"]跨平台时经常带着反斜杠或绝对路径,不能直接拼路径用,最好统一用自己传入的img_dir。第二,分母用的是原始图像宽高,如果图像后面做了resize,转换完的txt必须整体重新算,不能拿640×640的尺寸去归一化1920×1080的原始坐标。这类错误训练时不报错,但框全部偏掉,排查起来很费劲。
XML转YOLO的思路相同,只是把json.load换成解析VOC的xml树,取bndbox节点里的xmin、ymin、xmax、ymax。VOC的坐标天然是矩形框,不需要处理多边形退化的问题,反而更省事。我见过不少项目在两种格式之间来回倒腾,最后标准不统一导致训练集里混了两种坐标系,所以转换完最好抽几张图可视化验证。
3.4 数据集划分:8:1:1切分与固定随机种子
划分脚本我固定用8:1:1:
import os import random import shutil imgs = "images" src = "labels_txt" train_imgs, val_imgs, test_imgs = [], [], [] all_files = [f for f in os.listdir(imgs) if f.endswith(".jpg")] random.seed(42) random.shuffle(all_files) n = len(all_files) train_files = all_files[: int(n * 0.8)] val_files = all_files[int(n * 0.8) : int(n * 0.9)] test_files = all_files[int(n * 0.9):] for split, files in [ ("train", train_files), ("val", val_files), ("test", test_files), ]: os.makedirs(f"dataset/{split}/images", exist_ok=True) os.makedirs(f"dataset/{split}/labels", exist_ok=True) for f in files: shutil.copy(os.path.join(imgs, f), f"dataset/{split}/images/{f}") label = os.path.splitext(f)[0] + ".txt" shutil.copy(os.path.join(src, label), f"dataset/{split}/labels/{label}")random.seed(42)这行很关键。不固定种子的话,每次运行划分结果都不同,训练集和验证集的图片集合一直在漂移,两次实验之间没法对比。固定种子后,任何人在任何机器上跑同一个脚本,得到完全一样的划分,这是复现实验结果的地基。
并行地,train、val、test三个目录下必须同时存在images和labels两个子目录。YOLOv8的data.yaml配置的就是这两个路径,labels目录缺失时训练直接报"No labels found in ..."。另外注意脚本里只处理了jpg后缀,如果数据集里有png或其他格式的图片,要把后缀判断改成白名单。
到这里,数据集已经是标准YOLO格式。有些人会纠结要不要生成train.txt、val.txt这种列表文件,YOLOv8的ultralytics包不依赖这类文件,它只认目录结构。按目录摆放就是最不容易出错的方案。
4. 训练一个真正认识大豆叶病的模型:配置与调参实录
4.1 data.yaml的写法:路径和类目不能各写各的
训练前先写data.yaml,这份配置是模型和数据之间的桥梁,写错一个字段训练直接起不来,或者悄悄用错类目映射:
path: /home/user/soybean/dataset train: train/images val: val/images test: test/images names: 0: soybean_rust 1: frog_eye_leaf_spot 2: brown_spot 3: bacterial_blightpath写绝对路径,train和val写相对path的目录。如果path是相对路径,ultralytics会基于当前工作目录去拼,一旦在别的机器或者别的目录下启动训练,路径就会失效。names的列表顺序必须与第3章转换脚本里class_names的顺序完全一致——YOLO的txt标签里存的是类ID,不是类名,ID和类名错位时训练不会报错,但推理结果命名全是错的。
这里有一个看似离谱但很常见的坑:class_names列表写的是soybean_rust, frog_eye_leaf_spot, brown_spot, bacterial_blight,data.yaml里手一抖改成soybean_rust, brown_spot, bacterial_blight, frog_eye_leaf_spot,前50轮loss正常下降,等到看结果时才发现模型把灰斑病全部识别成了细菌性斑疹。这类错误定位很花时间,所以每次调整类目后,第一件事是打开data.yaml核对names顺序,最好同时打开一个txt标签文件抽查类ID和图像内容是否对应。
4.2 训练命令与超参数含义:CPU机器和高显存机器各怎么取舍
数据就绪后,启动训练:
yolo train model=yolov8n.pt data=soybean.yaml epochs=100 batch=8 imgsz=640 device=0各参数含义拆开说:epochs=100是完整遍历数据集100轮,大豆叶病这类小数据集50到100轮足够,再多会过拟合;batch=8是每轮迭代喂给模型的图像张数,对损失计算的稳定性影响很大;imgsz=640是输入分辨率,YOLOv8默认值就是640,数据原始分辨率比这个高时会在训练时自动缩放;device=0指定0号显卡,CPU环境改成device=cpu或直接不写。
预先训练权重档位的选择可以看这个表:
| 权重 | 参数量 | 推理速度 | 适合场景 |
|---|---|---|---|
| yolov8n.pt | 约320万 | 最快 | CPU部署、快速验证流程 |
| yolov8s.pt | 约1110万 | 快 | 显存够用时的默认选择 |
| yolov8m.pt | 约2590万 | 中等 | 精度优先、GPU推理 |
大豆叶病属于小目标检测,更大模型理论精度更高,但训练时间和部署成本都上去了。我一般训练阶段用yolov8n或yolov8s起步,验证能收敛后再考虑加模型容量。
如果手头只有一台没有独立显卡的机器,比如现在很多教程里提到的"Ubuntu 20.04搭建YOLOv8环境CPU版本"场景,参数选择完全不同:
yolo train model=yolov8n.pt data=soybean.yaml epochs=50 batch=2 imgsz=416 device=cpu workers=0batch=2是因为CPU训练时矩阵运算在内存上跑,batch太大容易把内存打满,16G内存的机器batch=4已经是压力线;imgsz=416直接把输入分辨率降下来,特征图变小、计算量减半,代价是病斑这种小目标更难点到;workers=0关闭数据加载多进程,避免CPU环境下频繁出现worker崩溃的旧毛病。这套配置在入门级CPU机器上,一个epoch大约几分钟到十几分钟,整个训练跑完大概需要过夜,不是不能接受,但要有预期管理。
4.3 迁移学习的正确姿势:冻结层数与训练轮次
训练从预训练权重继续,不是从零开始。yolov8n.pt在COCO数据集上见过的类目跟大豆病害毫不相关,但底层的边缘、纹理、颜色特征对植物病害一样有效,这部分先验信息能显著缩短收敛时间。数据量少的时候,直接全参训练容易把预训练权重里那点通用特征也带偏,所以有了freeze参数:
yolo train model=yolov8n.pt data=soybean.yaml epochs=100 freeze=10freeze=10表示冻结模型前10层的权重,反向传播时这些层不更新,只微调后面的检测头。数据量只有两三百张时冻结防过拟合,数据量大于一千张时冻结反而限制了新特征的学习。我自己的经验是:300张以下冻结前10到12层,300到1000张冻结前5层或不冻结,超过1000张直接全参训练。
另外还有个参数和epochs搭配使用——patience早停。ultralytics默认patience=100,意思是验证集指标连续100个epoch不提升就自动停。在小数据集上这个值偏大,我一般设到30,训练到40轮左右指标不再动,脚本自动跳到下一个阶段,省下大量等待时间。
4.4 看损失曲线判断是不是在真学:box_loss、cls_loss与dfl_loss
训练过程中,ultralytics会在runs/detect/train/下生成weights目录和results.png。results.png里画着三条损失曲线——box_loss、cls_loss、dfl_loss,和三条指标曲线——precision、recall、mAP。很多初学者只看mAP,忽略损失曲线,其实损失曲线更能暴露问题。YOLOv8的损失由三部分组成:box_loss用CIoU衡量边界框定位误差,cls_loss用BCEWithLogits衡量分类误差,dfl_loss让边界框的分布更贴合目标形状,三者的权重在ultralytics配置里可调。
正确读曲线的方式:train的loss曲线持续下降且不振荡,说明模型在收敛;val的loss曲线在某个epoch后开始回升,说明过拟合开始,这时把epochs往回调。如果train和val的loss从头到尾几乎不下降,先怀疑数据而不是模型——检查标注框是不是全写成了0.5000 0.5000这种中心点默认值,或者类别ID是不是全部写成了0。训练结束后使用weights/best.pt,它是验证集指标最好的权重,不是最后一轮的。每次都有人拿last.pt去部署,然后发现效果比训练时差一大截,这就是典型的使用错误。
5. 避坑指南:从标注到训练最常见的五个翻车现场
5.1 模型全不检或框全偏:中心点坐标算错了
现象:训练完成后推理,框的位置明显偏向左上或右下,或者一张图只检到零星几个目标。
原因:JSON转YOLO时,把YOLO格式的x_center, y_center理解成了左上角坐标。YOLO格式存的是归一化后的中心点,VOC和COCO格式存的是左上右下顶点,转换时坐标系对不上,框自然会整体偏移。
解决:先在自己转换脚本里打印一条记录,用一张已知目标位置的图,人工验证x_center=0.5的框是不是落在图片正中。我每次转完都会挑三张图,把txt坐标反向解析还原成像素坐标画框,肉眼核对后才会进训练。这一步多花五分钟,能省掉后面几小时的无效训练。
5.2 训练中途loss变成nan
现象:epoch跑到第10轮左右,训练日志里loss突然变成nan,后面所有指标跟着变nan。
原因:学习率过大。尤其batch很小或数据集很小时,梯度更新步长超出数值范围,参数直接发散。另一个常见原因是标签里出现了0宽0高的空框,训练时模型对空框求损失,分母为零。
解决:先降学习率。YOLOv8默认从0.01开始,遇到nan我一般直接改到0.001再试。同时检查labels目录下是否有空txt文件,空文件对应的是标注后被删除的图片,直接用脚本清掉。改了这两处,nan基本不会再出现。
5.3 验证集精度高但实拍图几乎不检
现象:训练和验证的mAP看着有0.8,拿到实地拍的照片却几乎无输出,或者检出结果全是同一个类目。
原因:训练图大多来自同一个拍摄环境,背景和光照单一,模型记住了那个环境的浅层特征而不是病斑本身的特征。另一个常见原因是推理时置信度门槛推得太高,把真正的小病斑过滤掉了。
解决:推理时先把置信度降低试试,yolo predict model=best.pt source=xxx.jpg conf=0.1。如果调到0.1能检出来,说明模型本身没问题,是阈值的取舍问题。如果还是检不出来,就要回去补数据——把实地拍的照片里漏检的目标补标,加进训练集再训一版。
5.4 CPU推理慢到没法用
现象:普通笔记本CPU上跑yolov8n推理640×640的图像,单张耗时两秒以上,完全没法做实时检测。
原因:模型虽然是轻量级,但网络结构是深度神经网络,CPU上矩阵运算天然慢几个量级。而且imgsz=640时特征图大,前向传播的计算量翻倍。
解决:模型保持yolov8n不比,把imgsz降到416,conf阈值从默认的0.25提到0.5。这两个参数一动,单张推理时间能降到原来的三分之一左右。不过在大豆叶病场景里,"漏检比误检更严重",conf提到0.5要谨慎。如果是科研用途需要看全量输出,保持0.25就行;如果是为快速筛选拍照样本,0.5再配合高recall权重,效率更高。
5.5 images目录与labels目录数量对不上
现象:训练开始时报错"Found 120 images but only 118 labels",或者反过来,labels比images多。
原因:有图片没有标注,或转换脚本漏写了txt,或空的标签文件被自动跳过。这类问题在小数据集里更隐蔽,因为数量差异不大,很多人直接忽略,但漏标的那部分图片在训练时会被当成无目标的负样本参与损失计算,干扰分类器。
解决:用一个脚本把两边文件名做差集,找出缺哪几张图,补标或删图,确保数量一一对应。切分完之后还要抽查验证集里有没有空标签,空标签文件会让这一张图始终被当成背景,验证指标虚高。这个过程每次换数据集都要走一遍,没有捷径。
6. 模型验证与轻量部署:混淆矩阵、置信度门限和RKNN导出的实操
6.1 用val和混淆矩阵做最终验收
训练结束后,第一步不是急着导出模型,而是跑一个完整的验证流程:
yolo val model=runs/detect/train/weights/best.pt data=soybean.yaml这条命令会重新在验证集上推理,输出每个类目的precision、recall和mAP50、mAP50-95。注意mAP50和mAP50-95的区别——前者是IoU阈值0.5下的均值,后者是0.5到0.95每隔0.05取一次再平均。大豆叶病这种小目标场景,mAP50看着高但mAP50-95偏低,说明框的定位精度不稳,边界没有贴合病斑。这时候优先检查box_loss和dfl_loss的训练曲线,而不是急着加数据。
val输出里有一张confusion_matrix.png,YOLOv8的混淆矩阵总和常常不等于样本总数,这是正常现象,不需要死磕加和。因为每个预测框既可能命中真实框,也可能被归类为背景,并且一张图里的多个目标会各自独立贡献计数,口径本身不是闭合的。我一般只关注对角线对应类目的数值占比,以及"背景被误检成病斑"那一行的量级,后者高说明背景干扰严重。
6.2 从PyTorch到ONNX到RKNN的导出链路
部署到边缘设备通常不是直接用PyTorch权重,而是先转成中间格式。先做ONNX导出:
yolo export model=runs/detect/train/weights/best.pt format=onnx导出后可以用onnxruntime在CPU上跑一遍,验证导出的模型和PyTorch原版输出是否一致。跑完验证再走下一步——如果目标设备是RK3588这类瑞芯微平台,还需要经过RKNN-Toolkit转成rknn格式。这条路最常卡住的地方是YOLOv8的DFL(Distribution Focal Loss)结构在RKNN上解码输出不对,框的位置全是偏移的,需要手写DFL解码逻辑替换默认的后处理。这一步没有太多官方教程可以参考,属于典型的"看着简单、实际要调一两天"的活。
从此以后,我每次训练完都强制自己走一遍固定流程:先看val指标和混淆矩阵,再抠出验证集里的三四张难例送进推理脚本肉眼检查,最后确认没问题才考虑导出部署。这个习惯帮我砍掉了大半次"导出后才发现模型不行"的返工。完整的数据处理脚本、训练配置模板和转换工具链都收在这份项目资源里,照着走一遍,比抱着文档啃十天有用得多。希望帮到你。
本文还有配套的精品资源,点击获取