ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于YOLOv8的自行车识别检测系统:源码部署与模型训练实战

基于YOLOv8的自行车识别检测系统:源码部署与模型训练实战 简介目标检测是计算机视觉中的核心任务之一旨在从图像或视频中定位并识别特定物体。YOLOv8作为当前主流的目标检测框架凭借其高效的Anchor-Free机制和工程化封装成为快速落地视觉应用的理想选择。在智慧交通、园区安防、共享单车管理等场景中自行车识别检测具有广泛需求但实际部署往往涉及数据标注、模型训练、指标评估与推理优化等复杂环节。本文从目标检测的基本原理出发解析YOLOv8在单类目标识别上的技术优势结合一个完整的自行车检测项目介绍数据集准备、训练调参、评估曲线解读以及模型导出与推理部署的完整流程。无论是入门深度学习的初学者还是需要定制视觉方案的工程师都能从中掌握一套可复用的工程实践路径并理解如何规避环境配置、标注质量、性能优化等常见陷阱最终将训练好的模型高效应用于真实业务场景。 拆开这个名为“基于YOLOv8的自行车识别检测系统源码(部署教程训练好的模型各项评估指标曲线).zip”的压缩包我大致扫了一眼就明白这属于标准的端到端目标检测项目。你拿到手的不只是几段训练代码而是一整套从数据标注、模型训练、指标评估到推理部署都齐活的东西对想快速落地视觉识别应用、又不想从零折腾环境的人来说确实能省掉大量弯路。这个项目针对的是“自行车”这一类目标典型场景可以放在智慧交通流量统计、园区或校园慢行安全预警、共享单车乱停放监测、甚至无人配送车对骑行者的避让感知上。适合谁看呢我建议三类人重点参考刚接触YOLO系列想跑通完整流程的初学者需要做定制目标检测但被数据、标注、评估折腾过的算法工程师以及想把手头模型快速对接成接口服务的后端开发者。下面我把这个项目从结构到实操一层层拆开讲重点说清楚每一步为什么这么做以及哪些地方最容易踩坑。1. 项目整体拆解这个压缩包里到底装了什么1.1 自行车识别检测到底在解决什么问题自行车目标检测本质上属于计算机视觉里的单类目标检测任务输入一张图片或一帧视频模型输出画面中所有自行车的位置坐标和置信度。比起通用的COCO 80类检测这种单类专用模型的最大优势是可以在相对更小的模型上获得更高的精度因为网络不需要消耗太多容量去区分几十种物体之间的细微差异。举个例子COCO自带的person和bicycle类别经常互相遮挡、尺度差异大通用模型在密集场景下容易漏检而专用自行车模型可以针对这张类别的形状、轮辐纹理和常见姿态做更充分的拟合。从工程角度看这个项目并非只是训练一个模型而是把整个闭环串起来了目录里应该有训练脚本、推理脚本、数据集配置文件、YOLOv8官方权重、训练好的best.pt以及runs目录下保存的PR曲线、混淆矩阵、F1曲线、loss曲线等评估产物。对使用者来说拿到包之后即使不重新训练也可以直接用现成权重跑推理验证效果后再决定是否用自有数据微调。这种“开箱可用、按需再训”的结构正是我推荐初学者以此作为入门项目的原因。1.2 源码目录结构与交付物解读我带大家把这类项目惯用的目录结构梳理一下因为你拿到手后第一件事就是弄清楚每个文件夹是干嘛的否则会浪费大量时间在定位功能上Bicycle-Detection-System/ ├── datasets/ │ ├── images/ │ │ ├── train/ # 训练集图片 │ │ └── val/ # 验证集图片 │ └── labels/ │ ├── train/ # YOLO格式标注txt │ └── val/ ├── runs/ │ ├── detect/ │ │ ├── train/ # 训练日志与权重 │ │ │ ├── weights/ │ │ │ │ ├── best.pt │ │ │ │ └── last.pt │ │ │ ├── PR_curve.png │ │ │ ├── confusion_matrix.png │ │ │ └── results.csv / results.png │ │ └── val/ # 验证结果图 ├── weights/ │ └── yolov8n.pt / yolov8s.pt # 官方预训练权重 ├── train.py # 训练入口 ├── val.py # 评估入口 ├── detect.py # 推理入口 ├── data.yaml # 数据配置 └── requirements.txt这里有个容易忽略的点best.pt和last.pt的区别。best.pt是训练过程中验证集上指标最优的那一版权重last.pt是最后一次epoch的权重。真正部署和评估都应该用best.pt不要图省事直接拿last.pt用。另外runs/detect/train/里除了权重还会生成args.yaml记录了当前训练的全部超参数这点在你回头复盘“为什么这个精度上不去”时非常有用相当于给自己留了一份训练档案。1.3 为什么选YOLOv8而不选YOLOv5或更老的版本不是说你不能用YOLOv5而是YOLOv8在工程友好度上有几个实打实的优势。首先Ultralytics官方把训练、验证、导出、推理统一封装成了yolo命令行工具和Python API五六行代码就能跑通一次完整流程这对快速验证想法特别关键。其次YOLOv8把Anchor-Free机制做到了默认配置里模型对目标尺度的适应能力更强边界框回归精度更稳定尤其是自行车的轮子、车把这类边缘较细的部位比老版本Anchor-Based方案好调一些。还有一个很多人没注意到的点YOLOv8官方预训练权重就是在COCO上训好的里面已经包含bicycle这个类别。这意味你用它的权重做迁移学习时模型对自行车形状的认识不是从零开始而是已经“见过世面”的。你只需要用自行车数据集对它做微调就能在几天内达到可用的精度。这属于典型的小样本迁移策略也是这个项目能在一个普通显卡上跑出不错效果的根本原因。2. 环境配置从零把项目跑起来2.1 硬件与软件版本要求先说硬件。很多人一听到深度学习就以为必须有顶配显卡其实像yolov8n这种轻量模型在GTX 1660 Ti这种6GB显存的卡上就能把训练和推理都跑起来这在网上已经有很多人验证过我自己也在类似配置的机器上跑过微调任务训练时间会偏长但不会卡死。预算紧张的话用CPU也能做推理只是速度明显慢单张640x640图片大概需要1到3秒训练就不建议了。如果你想训练舒服一点建议显存不低于8GB比如RTX 3060起步。软件版本方面这个项目通常基于Ultralytics官方库所以Python版本建议3.8到3.10PyTorch用1.8以上的版本都行实测在2.x版本下完全兼容。有个网络热词是“pytorch2.13支持yolov8吗”我只能说目前主流版本是2.0到2.5之间选择稳定发行版即可不用追更新。CUDA建议用11.8或12.x对应版本装PyTorch时注意选择匹配的index比如pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118。2.2 安装步骤与关键依赖坑拿到压缩包后建议先新建一个干净的Python虚拟环境不要直接装在系统Python里否则以后装别的库时容易互相打架。我的习惯是conda create -n bicycle python3.10 -y conda activate bicycle cd Bicycle-Detection-System pip install -r requirements.txtrequirements.txt里一般会写ultralytics、torch、torchvision、opencv-python等。如果你是从零装可以只装一个核心库它会自动拉起大部分依赖pip install ultralytics这里有一个我反复强调的坑装完ultralytics后第一次运行yolo命令时它如果发现CLI工具不在PATH里会在当前目录生成一个yolo的快捷入口但你的实际Python环境可能没对应。最稳妥的方式是用python -m ultralytics来调用或者确认python -c from ultralytics import YOLO; print(ok)输出正常后再继续。还有一个小细节解压路径里不要有中文和空格这是老生常谈但真的很多人栽在这。有些Windows下的OpenCV组件对非ASCII路径支持不完整会导致图片读取失败报错还很隐晦。把项目放到D:\projects\bicycle这种纯英文路径下能省掉很多莫名其妙的麻烦。2.3 用一张测试图快速验证环境环境配置是否成功不要急着一上来就训练先用现成权重跑一张图验证。项目里如果带了best.pt直接跑python detect.py --weights runs/detect/train/weights/best.pt --source test.jpg或者用官方API方式写一个最简单的推理脚本from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(sourcetest.jpg, conf0.4, saveTrue) print(results[0].boxes.xyxy)如果输出结果里有坐标张量并且在runs/detect/predict/下生成了带框的图片就说明环境完全正常。这一步相当于组装好电脑后先开机进BIOS确认基本组件没问题再上系统能极大减少后面排查问题的范围。3. 数据集准备与标注实操3.1 数据从哪来公开数据集与自采数据方案自行车这类目标比较特殊公共数据集虽多但质量参差不齐。常见的做法有三条路。第一直接使用COCO数据集中提取bicycle类别的所有样本写个脚本把标注过滤出来即可这种数据覆盖面广、场景丰富适合训练通用模型。第二下载专用的骑行数据集比如Roboflow Universe上搜“bicycle”能找到不少别人整理好的数据集直接以YOLO格式导出省去标注时间。第三如果你想部署在特定场景比如自己小区、园区、某个路口强烈建议自采一批现场图片再做标注因为模型的精度上限很大程度取决于部署环境与训练数据分布的匹配度。这里有个平衡问题数据集不是越大越好而是越“贴近场景”越好。如果你要在一个固定高位摄像头下检测那么训练数据里就应该有大量从相似高度和角度拍摄的图片而不是全用网上的平面街景图。我在实际项目里吃过这个亏拿COCO子集训练的模型放到现场结果因为视角差异导致漏检率飙升后来补充了两三百张现场样本做微调指标才拉回正常水平。3.2 标注工具选择与标注规范标注工具推荐两个一个是本地轻量的LabelImg另一个是可以在线协作的Roboflow。LabelImg适合个人快速标注导出PascalVOC或YOLO格式都方便Roboflow则适合多人协同并且自带数据增强、导出多种格式对团队项目友好。具体操作上用LabelImg时先选择YOLO格式输出然后用矩形框把每辆自行车完整框起来标签统一填bicycle。标注质量直接决定训练上限这块我提三个规范。第一边界框要贴合目标轮廓不要刻意追求刚好切到像素边缘但也不能框得过大把背景大量包进去适度留2%到5%的边距即可。第二对严重遮挡、模糊、暗光的自行车如果人眼都难以辨认建议直接跳过不标因为这些样本很容易变成训练中的噪声。第三类别名称必须统一不要出现bicycle和bike混用的情况否则模型会认为它们是两个类别导致预测混乱。3.3 数据集目录组织与yaml配置YOLOv8的数据集一般按如下目录结构组织datasets/ ├── images/ │ ├── train/ │ └── val/ └── labels/ ├── train/ └── val/每个标注txt文件名必须和对应图片文件名完全相同扩展名不同txt的每一行格式为类别ID 归一化中心x 归一化中心y 归一化宽 归一化高。比如一张640x480的图片中自行车框左上角在(100, 80)、右下角在(300, 400)那么归一化计算是x_center (100 300) / 2 / 640 0.3125 y_center (80 400) / 2 / 480 0.5 width (300 - 100) / 640 0.3125 height (400 - 80) / 480 0.6667所以对应txt行就是0 0.3125 0.5 0.3125 0.6667。这个转换逻辑务必理解因为后面排查标注问题时经常要手算验证。data.yaml配置大概是这样的path: ./datasets train: images/train val: images/val nc: 1 names: 0: bicycle注意path是相对yaml文件所在目录写的训练时建议用绝对路径避免不同机器路径对不上。3.4 训练前数据检查清单在没有真正开始训练之前强烈建议先做一轮数据体检我一般会检查四个点图片是否都能正常打开有没有损坏文件每个txt标注是否存在且非空有没有图片有图无标、有标无图的情况归一化坐标是否都在0到1之间有没有负数或大于1的值训练集和验证集的类别分布是否大致一致。检查方式可以写个简单脚本扫一遍或者直接在白纸上可视化几张标注图。Ultralytics提供了一条很实用的命令能直接把标注画在图片上供人工检查python -c from ultralytics.data import YOLODataset; ds YOLODataset(./data.yaml); print(len(ds))更直接的是你训练一小段后看第一轮输出的验证图如果框的位置漂移或者压根没框十有八九是标注格式有问题。我见过最典型的翻车案例就是txt里写的是像素坐标而不是归一化坐标YOLO训练时会直接把loss拉满模型永远收敛不了。4. 模型的训练与调优4.1 训练命令与完整流程数据集准备妥当后训练就很简单了。如果你用的是Ultralytics官方代码库一条命令就能起训练yolo detect train modelyolov8s.pt datadata.yaml epochs120 imgsz640 batch16 device0或者写一个train.py调用Python API方便后续加自定义逻辑from ultralytics import YOLO model YOLO(yolov8s.pt) # 加载预训练权重 model.train( datadata.yaml, epochs120, imgsz640, batch16, device0, patience20, namebicycle_run )训练过程中你会在终端看到每个epoch的box_loss、cls_loss、dfl_loss以及各类指标。这里要提醒一个新手常犯的错误不要一看到loss下降就急着停止也不要只看训练集loss真正的模型选择依据是验证集指标也就是mAP。代码默认会在每个epoch结束后跑一次验证然后保存验证集上的最佳权重你只需要关注最后那个best.pt就好。4.2 超参数怎么调显存、batch与图片尺寸的取舍超参数调整中最影响“能不能跑起来”的是batch size和imgsz而这又直接受限于显存。以6GB显存为例我自己实测下来yolov8s配合imgsz640时batch建议设在8左右如果内存报错CUDA out of memory就把batch降到4或者把imgsz降到480。这里有一个简单估算显存占用的思路输入图像占用的空间大约是batch × imgsz × imgsz × 3 × 4字节以batch8、imgsz640计算仅输入就是约39MB但实际训练显存大头在中间层特征图和梯度通常是输入的几十倍所以总占用要到4到6GB级别。这里给一个直观的参考表显卡显存推荐模型推荐batch推荐imgsz6GByolov8n / yolov8s86408-12GByolov8s / yolov8m1664016-24GByolov8m / yolov8l16-32640图片尺寸对精度影响也很大。自行车这类目标内部结构不算特别精细640已经足够但如果你部署的场景是远距离监控目标在画面里只有四十几个像素大那就需要提高imgsz到960甚至1280代价是显存和时间成倍增加。一个比较经济的方法是先用640训练出一版再在自有小数据集上用更高分辨率微调这比一开始就跑大分辨率效率高得多。4.3 训练过程中的曲线怎么看训练过程中YOLOv8会在运行目录下实时生成results.png这个图是多子图拼版包含训练loss和验证指标曲线。很多人第一次看这个图觉得信息密度高不知道该关注什么。我的经验是重点看四个子图训练box_loss和cls_loss这两个曲线应该持续下降并趋于平稳说明模型在学习验证集mAP50和mAP50-95这是衡量模型好坏的最终标准应该是上升后趋于平稳验证集precision和recall如果两者差距越拉越大说明置信度阈值设置不合理或者类别不平衡学习率曲线LR一般呈余弦退火下降这是正常的不要因为学习率变小而慌张。如果训练loss已经收敛但mAP还是低问题多半不在训练本身而在数据质量或模型容量。此时可以尝试换更大的yolov8m而不是盲目加大epoch。4.4 精度还能不能再往上提模型精度到瓶颈后有几种实用的提点手段。第一种是数据增强YOLOv8默认会开Mosaic、随机翻转、色彩变换等你可以在hyperparameters里调大hsv_h、hsv_s等参数增强模型对光照变化的鲁棒性。第二种是难例挖掘把验证集里检测失败的图片挑出来看看是漏检还是误检然后针对性地补充这类图片到训练集。第三种非常有效但常被忽视去掉重复度过高的图片。如果你的数据集中有大量连续帧或相似角度的图片模型会在这些重复样本上过拟合反而削弱泛化能力此时用相似度哈希做一次去重会有奇效。另外对于自行车这类单类任务如果你发现类别只有一个但数量极少比如只有三五百张不要直接上大型网络模型容易过拟合。可以先冻结主干网络训练若干epoch再解冻全部参数微调这样既能利用预训练知识又能防止小数据下的过拟合。5. 评估指标曲线不只是看个热闹5.1 指标速查Precision、Recall、mAP项目里最核心的评估产物就是各项指标曲线但前提是你得看得懂它们。先快速过一遍概念。Precision精确率表示预测为自行车的框里真正是自行车的比例Recall召回率表示所有真实的自行车里被成功找出来的比例。两者存在此消彼长的权衡而mAP则是在不同置信度阈值下综合衡量这种权衡的指标。mAP50指的是IoU阈值为0.5时各类别AP的平均值mAP50-95则是在0.5到0.95之间每隔0.05取一个IoU阈值计算AP后再取平均。mAP50-95更严格也更贴近真实部署中对定位精度的要求因为“框得准不准”会直接影响后续业务比如流量统计时框偏移可能导致车辆轨迹断续。5.2 曲线图逐个拆解PR曲线、loss曲线、混淆矩阵打开runs目录下的PR_curve.png你会看到一条从左上角向右下延伸的曲线。曲线越靠近右上角越好说明模型在保持高精确率的同时还能有高召回率。如果曲线整体向右下凹陷意味着模型要么漏检严重要么误检严重。混淆矩阵则展示预测类别和真实类别的匹配情况单类项目里主要看三类数字真阳性的比例正常、把背景误判成自行车的比例误检、把自行车漏掉当成背景的比例漏检。如果背景误检比例高通常需要提高置信度阈值或增加负样本。loss曲线前面提过这里补充一点如果验证loss在某个epoch后开始回升而训练loss还在下降这就是过拟合信号此时应当停止训练或者增加数据增强、加大Dropout。5.3 指标不理想时怎么定位问题我把常见的指标问题总结成一个小表方便快速定位现象最可能原因建议动作mAP50低但训练loss正常数据标注不准或框太松抽查可视化标注修正bad caseprecision很高但recall很低置信度阈值太高或漏检多降低conf检查是否目标过小recall很高但precision很低误检多背景被频繁判断为车补充负样本提高背景判别能力mAP50-95明显低于mAP50边界框定位不精细提高imgsz或换更大模型loss下降慢学习率过低或数据噪声大调整lr清洗标注数据这个表是我在实践中自己总结的不一定每个场景都完全吻合但作为排错起点非常管用。6. 部署实操从权重文件到可用服务6.1 部署方式怎么选训练好的best.pt要真正用起来有几种部署路线。最简单的就是纯Python方式调用Ultralytics库做推理适合原型验证、脚本批处理或快速接入Flask/FastAPI服务。第二种是把模型导出成ONNX格式用ONNXRuntime或OpenCV DNN模块来跑这样就不依赖PyTorch环境部署体积大幅缩小也方便嵌入到C程序里。第三种是深度优化路线导出成TensorRT engine格式在NVIDIA显卡上达到最高推理速度适合实时视频流检测。如果你是新手我建议先走第一种把功能跑通后再优化性能。网上搜“yolov8训练好的模型怎么部署到嵌入式设备”很多人直接上TensorRT但没有考虑到嵌入式设备上的环境适配成本很容易劝退。正确路径是先用PythonONNXRuntime在PC上验证再移植到Jetson等设备。6.2 快速推理脚本实战这里给一个自定义推理脚本的模板方便你接入自己的图片或视频流from ultralytics import YOLO import cv2 model YOLO(runs/detect/train/weights/best.pt) def detect_bicycles(image_path, conf_thres0.4): results model.predict( sourceimage_path, confconf_thres, imgsz640, verboseFalse ) detections [] for r in results: if r.boxes is None: continue for box in r.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() score float(box.conf[0]) detections.append({ bbox: [int(x1), int(y1), int(x2), int(y2)], confidence: round(score, 4) }) return detections if __name__ __main__: print(detect_bicycles(test.jpg))这个脚本里的两个细节值得注意。第一判断r.boxes is None因为有些图片里可能一个目标都没检测到Ultralytics会返回None而不是空列表不判断的话属性访问会直接报错。第二坐标要转成int否则JSON序列化时会出现float精度问题给前后端对接埋坑。6.3 导出ONNX与嵌入式/边缘设备部署导出ONNX的命令特别简单yolo export modelruns/detect/train/weights/best.pt formatonnx opset11导出成功后目录下会多一个best.onnx。然后可以用ONNXRuntime做推理import onnxruntime as ort import numpy as np import cv2 sess ort.InferenceSession(best.onnx) input_name sess.get_inputs()[0].name img cv2.imread(test.jpg) img_resized cv2.resize(img, (640, 640)) blob img_resized[:, :, ::-1].transpose(2, 0, 1) / 255.0 blob np.expand_dims(blob, axis0).astype(np.float32) outputs sess.run(None, {input_name: blob})这里输出解析比Ultralytics的Results对象麻烦一些因为YOLOv8的ONNX输出是一个1x84x8400的张量含义是每个预测候选框的4个坐标加80个类别得分再加若干个额外维度单类项目里通常是1x7x8400或类似结构需要自己做NMS后处理。对于不熟悉后处理的用户更建议直接用onnxruntime配合ultralytics自带的YOLO(best.onnx)加载虽然效率不是极致但省心。如果目标是Jetson Nano或树莓派这类嵌入式设备建议先把模型导出成FP16精度的TensorRT engine能明显缓解显存占用的压力。但这一步需要设备上先装好TensorRT环境且不同JetPack版本的适配细节不一样我建议把它当成第二步优化任务而不是第一步。6.4 推理性能优化心得推理性能优化有几个立竿见影的手段。第一是半精度推理用model.predict(..., halfTrue)在支持FP16的GPU上能直接省一半显存并提升速度。第二是选择合适的推理尺寸如果你的摄像头画面在1920x1080但自行车目标通常占比不小完全可以把imgsz设置为640或800而不是跟输入分辨率硬扛检测速度能提升数倍。第三是批量推理如果是一批离线图片用sourceimage_dir走批处理不要一张张重复加载模型。还有一个容易被忽略的细节CPU推理时YOLOv8默认使用OpenMP多线程但如果你的机器是虚拟机或云函数环境可能被限制单核此时需要手动设置torch.set_num_threads否则会出现CPU占用低但速度极慢的“假死”感。7. 常见问题与排查速查表7.1 环境与启动类问题新手遇到最多的就是环境报错我整理三个最典型的案例。ModuleNotFoundError: No module named ultralytics说明当前Python环境没有安装库激活对应conda环境后重新pip install。CUDA out of memory按前面说的降batch、降imgsz、换小模型三选一或组合使用。还有一种情况是其他程序占用了显存用nvidia-smi查一下杀掉无关进程即可。AttributeError: NoneType object has no attribute shape通常出现在加载权重或图片路径不对时检查路径是否存在、文件是否损坏。7.2 训练异常类问题训练最常见的异常有两个方向。一个是loss突然变成NaN多半是学习率过大、训练数据里有异常标注比如坐标越界或者预训练权重与当前类别数不匹配。先检查data.yaml的nc和names是否跟实际标注一致再考虑降低初始学习率比如lr00.001。另一个是训练若干epoch后只有背景类没有目标框这大概率是数据集组织错了标注txt和图片没配对导致模型从空标注里学不到任何目标信息。训练过程如果很慢先看是不是CPU在跑而不是GPU。YOLO脚本里device参数默认可能是cpu如果用GPU训练需要显式指定device0。验证方法很简单训练时另开一个终端执行nvidia-smi看GPU利用率是不是接近满的。7.3 推理与部署类问题推理部署阶段的问题更偏工程。最常见的是ONNX导出时报opset版本错误或算子不支持这通常靠升级ultralytics库、调整导出参数解决。另一个常见问题是推理结果和训练指标差距巨大比如训练mAP50有0.9但实际跑视频时频繁漏检这个时候别急着怀疑模型先检查推理时的imgsz、conf阈值是否跟训练评估时一致。默认推理conf是0.25如果你设置了0.5漏检增加是正常的需要根据场景做阈值权衡。还有一个容易踩的坑是视频流检测掉帧严重。这是因为你循环读取视频帧时如果模型推理速度跟不上视频帧率帧会被累积下来造成延迟。正确做法是以推理速度为准来“拉流”也就是处理完一帧再去读下一帧而不是无脑读帧喂给模型这个概念很多新手一开始没转过弯。这份项目里最核心的价值不在于它的代码有多炫而在于它把“数据—训练—评估—部署”这条链路完整跑通了。我实际用下来最舒服的一点是Ultralytics封装得够稳你只要不碰那些冷门算子整个流程几乎不会中断。如果你后续想把它变成真正能用的产品我建议优先做两件事一是把手头场景的照片持续收集起来用bad case回灌做增量训练模型会越用越准二是把推理接口用FastAPI包成HTTP服务让业务方能够通过简单的POST请求拿到检测结果这样整个项目就从一个实验脚本变成了可交付的完整系统。本文还有配套的精品资源点击获取
返回列表