ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

VOC转YOLO格式并用YOLOv8训练小样本鼠检测模型

VOC转YOLO格式并用YOLOv8训练小样本鼠检测模型 简介这是一份面向目标检测学习与研究者的鼠rat标注数据集图片经labelImg工具人工核对标注同时输出VOC和YOLO两种主流格式解压后可直接衔接YOLO系列、Faster R-CNN等常见目标检测框架的训练流程无需额外转换格式适合用于模型训练、验证和算法对比实验也能支撑目标检测课程设计或科研预实验。压缩包共1762个文件其中jpg原始图片587张xml与txt标注文件各587份整体约149.16MB包内按图片、xml标注、txt标注分三个目录存放结构与文件名一一对应便于按需查看和批量处理。标注遵循边界选取准确、目标应标尽标、多轮一致性校验等规范框选质量相对可靠。当前已有146人学习下载对需要快捷获取干净双格式数据集、省去自建标注环节的入门与进阶开发者都比较实用。1. 一份587张左右的鼠数据集标注格式带VOC和YOLO意味着什么你大概率是从某个标注平台、实验室共享或旧项目里拿到了这么一份压缩包图片数量只有587张左右Annotations目录里是VOC风格的XMLlabels目录里又放着YOLO风格的txt两边都对不上。做鼠患监控、实验室动物行为分析或养殖场巡检的工程师看到这种数据的第一反应不是“开训”而是先问这两套标注格式我该信哪一套小样本量下能不能训出一个能用的鼠检测模型我的答案是能但前提是你得先把VOC和YOLO这两种格式的坐标体系吃透再带着“587张图容易过拟合”的警觉去做迁移学习和数据划分。这篇文章把数据检查、格式转换、YOLOv8训练到小样本避坑完整走一遍适合手里刚拿到这类小规模标注数据、想快速落地一个鼠类检测器的从业者。不是所有目标检测项目都需要上万张图587张也能出活关键是每一步别踩错。2. 先看懂标注格式VOC的XML字段与YOLO的txt坐标换算2.1 VOC标注的XMLobject、bndbox和那些不常用的字段VOC格式Pascal VOC的标注文件是一个XML每个目标被包在一个object标签里核心字段是类别名name和边界框bndbox。bndbox里存放的是像素绝对坐标xmin、ymin、xmax、ymax。这是VOC和YOLO最大的区别——VOC记“框的左上角和右下角在哪里”以像素为单位YOLO记“框的中心点在哪、宽高是多少”以归一化比例为单位。一份典型的鼠数据集VOC标注长这样annotation filenamerat_001.jpg/filename size width1920/width height1080/height /size object namerat/name truncated0/truncated difficult0/difficult bndbox xmin120/xmin ymin45/ymin xmax310/xmax ymax290/ymax /bndbox /object /annotationtruncated和difficult这两个字段在目标检测里经常被忽略。truncated表示目标是否被图片边缘截断difficult表示是否难以辨认。我在实际项目里的做法是转换格式时直接把这两个字段丢弃不去纠结。原因很简单YOLO的训练管线根本不消费这两个字段而小数据集里如果因为difficult1就扔掉目标反而会把本来就稀缺的正样本进一步减少。需要注意的坑在size节点。它记录的是图片宽高但有些标注工具导出的size和真实图片尺寸不一致——比如标注员先在某张缩放预览图上标完工具却写进了原始尺寸。所以转换时不要用XML里的size要直接读图片文件本身拿宽高这是格式转换后误检率飙升的一个重要来源。2.2 YOLO标注的txt五个数字的语义与归一化YOLO格式的标注是纯文本每行表示一个目标五个数字用空格分隔0 0.512 0.415 0.198 0.227第一个数字是类别ID从0开始后四个数字分别是归一化后的框中心x坐标、中心y坐标、框宽、框高。归一化的分母是图片的实际宽度和高度所以这四个值理论上都在0到1之间和图片分辨率无关。这种相对坐标的好处不用多说不管输入图片是1280还是640YOLO训练时统一缩放到imgsz标注不需要跟着变。VOC的像素坐标则必须依赖原始分辨率一旦图片被缩放坐标要重新算。但“理论上在0到1之间”这句话恰恰是小数据转换最容易翻车的地方。常见情形是VOC框的xmax超过了图片宽度转换后bw大于1或者xmin为0中心点cx接近0甚至负数。YOLO训练时对这些越界数值容忍度很低轻则loss震荡重则触发后续提到的BN崩溃。所以在转换脚本里做一次clamp是必须的细节在第三章给完整代码。2.3 两种格式的坐标换算像素坐标与相对坐标的换算公式VOC转YOLO的换算公式并不复杂但每一步都要说清楚。设图片宽度为W、高度为HVOC框的四个坐标为xmin, ymin, xmax, ymax像素归一化中心xcx ((xmin xmax) / 2) / W归一化中心ycy ((ymin ymax) / 2) / H归一化框宽bw (xmax - xmin) / W归一化框高bh (ymax - ymin) / H注意公式里没有加1。很多标注工具里xmax是“包含边界”的像素索引但YOLO的归一化模型把它当作连续坐标处理差一个像素在这种587张的小规模数据里影响可以忽略不需要纠结。反向转换YOLO转VOC则在推理后画框时会用到xmin (cx - bw / 2) * Wxmax (cx bw / 2) * W反过来得到像素框。我在验证标注是否转对时会用这段反向换算把预测框画回原图肉眼看一遍比任何自动化指标都直观。3. 把VOC转成YOLO格式587张图的转换脚本与校验清单3.1 转换前的目录整理images、Annotations、labels三层结构拿到原始数据先别急着写脚本。我一般先把目录理顺成统一结构后面所有步骤都基于这个结构跑避免路径混乱rat_dataset/ ├── images/ # 587张jpg文件名与XML一一对应 ├── Annotations/ # 587个xmlVOC格式标注 └── labels/ # 空的等待写入YOLO格式txt这里有个容易忽略的细节YOLO的txt文件要求与图片同名但扩展名为.txt。比如图片是rat_001.jpg对应标注就是rat_001.txt。转换前先用ls对比一下images和Annotations的文件数量如果XML比图片少说明有图片没标注如果XML多说明有孤儿标注。587张左右的数据集人工核对一遍文件名也就二十分钟但这步能避免训练时“图片有但labels目录里找不到对应txt”的报错。类别名的确认也要在这个阶段做。打开所有XML扫一遍name字段用一行命令提取所有类别grep -h name Annotations/*.xml | sort | uniq -c如果里面同时出现rat、mouse甚至RAT说明标注员没统一命名。YOLO只认类别ID不认字符串所以转换脚本里必须有一张类别映射表把这些写法归并成同一个ID。鼠检测通常只做单类把所有别名映射到0即可。3.2 Python转换脚本XML解析、边界裁剪与txt写出我通常用Python写一个一次性转换脚本用xml.etree.ElementTree解析XML用OpenCV读取图片实际尺寸然后逐个目标转成YOLO行并写入txt。完整脚本如下import os import cv2 import xml.etree.ElementTree as ET IMG_DIR images VOC_DIR Annotations LABEL_DIR labels CLASS_MAP {rat: 0, mouse: 0, RAT: 0} # 把同名不同写法的类别归并到ID 0 os.makedirs(LABEL_DIR, exist_okTrue) for xml_name in os.listdir(VOC_DIR): if not xml_name.lower().endswith(.xml): continue xml_path os.path.join(VOC_DIR, xml_name) stem os.path.splitext(xml_name)[0] # 优先找jpg找不到再往后备扩展名里找 img_path os.path.join(IMG_DIR, stem .jpg) if not os.path.exists(img_path): img_path os.path.join(IMG_DIR, stem .jpeg) if not os.path.exists(img_path): img_path os.path.join(IMG_DIR, stem .png) img cv2.imread(img_path) if img is None: print(f[skip] 图片读不出来: {img_path}) continue H, W img.shape[:2] tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.findtext(name, ).strip() if name not in CLASS_MAP: print(f[warn] 未映射类别 {name} 在 {xml_name}) continue cls_id CLASS_MAP[name] bndbox obj.find(bndbox) xmin float(bndbox.findtext(xmin)) ymin float(bndbox.findtext(ymin)) xmax float(bndbox.findtext(xmax)) ymax float(bndbox.findtext(ymax)) # VOC像素坐标 - YOLO归一化坐标 cx ((xmin xmax) / 2.0) / W cy ((ymin ymax) / 2.0) / H bw (xmax - xmin) / W bh (ymax - ymin) / H # 防止标注出界导致训练崩溃clamp到[0,1] cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) bw min(max(bw, 0.0), 1.0) bh min(max(bh, 0.0), 1.0) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) out_path os.path.join(LABEL_DIR, stem .txt) with open(out_path, w) as f: f.write(\n.join(lines))脚本逻辑分三段定位图片并读取真实宽高解析XML里的每个目标换算坐标再写文件。有几个地方必须说明关于CLASS_MAP它把rat、mouse、RAT统一映射到ID 0。设计这个映射表是为了应对标注不规范你要是数据集里真有“鼠”的其他英文写法直接往字典里加就行。关于坐标输出格式.6f保留6位小数。对于587张这种规模的数据集6位精度足够因为归一化坐标的分母不过几千像素再多的位数只会让txt文件变大没实际意义。关于clamp这是这次转换里最关键的防御。VOC标注里偶尔有xmax大于图片宽度的坏数据如果不裁掉训练时会出现框中心在画面外、宽高为负数的情况直接影响anchor匹配和损失计算。把数值压回0到1之间虽然会稍微改变框的真实位置但对训练稳定性帮助远大于损失的那几个像素。另外一个隐蔽问题是XML的命名空间。部分从在线标注平台导出的VOC文件会在根节点带xmlns属性导致root.findall(object)什么都查不到。判断方法打印root.tag看是不是{http://xxx}annotation这种带花括号的写法。如果有在解析前做一个字符串替换把命名空间剥掉或者用tag.split(})[-1]提取标签名。常见做法是直接在脚本开头加一行xml_data re.sub(rxmlns[^], , xml_data, count1)简单粗暴但有效。3.3 转换后的三重校验可视化叠加、数值范围检查、文件数量比对转换完不等于能用。我每次都会做三件事缺一不可第一件事检查文件和数量。labels下的txt数量应该和images下的图片数量完全一致一个不多一个不少ls images | wc -l ls labels | wc -l如果不一致大概率是某些XML没有object节点脚本写出了一个空txt或者没写文件。YOLOv8对空txt容忍但如果一张图在训练集里完全没有目标它作为一个负样本参与训练在验证集里则会被当作“背景正确”计入指标拉高你的mAP假象。后面会专门讨论空标注文件怎么办。第二件事检查txt内容范围。写一个小循环读取所有labels文件统计每一行的数值是否都在0到1之间。有一个越界值都要追回去查原始XMLpython -c import os for f in os.listdir(labels): for line in open(os.path.join(labels, f)): parts line.strip().split() if not parts: continue vals [float(x) for x in parts[1:]] if min(vals) 0 or max(vals) 1: print(f, line.strip()) 正常情况下不会有输出。如果有输出回到3.2的脚本检查是不是某张图的W或H读成了0或者图片读取失败走了fallback路径。第三件事也是最直观的随机抽几张图把txt的归一化坐标还原成像素框画在原图上肉眼确认框是否贴合。这一步能把“坐标对但框画歪了”的所有潜在问题一次性暴露出来。可视化叠加的短脚本在这里很实用import cv2 img cv2.imread(images/rat_001.jpg) H, W img.shape[:2] with open(labels/rat_001.txt) as f: for line in f: parts line.strip().split() if not parts: continue cid, cx, cy, bw, bh map(float, parts) x1 int((cx - bw / 2) * W) y1 int((cy - bh / 2) * H) x2 int((cx bw / 2) * W) y2 int((cy bh / 2) * H) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(int(cid)), (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(check_vis.jpg, img)这段代码里map(float, parts)把字符串行转成浮点数然后按YOLO反向换算公式把中心宽高形式还原成左上角右下角像素坐标。cv2.rectangle的绿框直接重叠在原图上框和老鼠的位置对不对一眼就能看出来。我在自己项目里抽了二十多张检查发现过两处xmin和xmax写反的坏标注。这种问题靠数值检查抓不到可视化检查一定不能省。4. 用YOLOv8训练鼠检测模型模型选型、数据划分与关键参数4.1 为什么选YOLOv8n而不是自己从头训练backbone587张图的目标检测第一原则是绝对不要从头训练一个完整模型。从零训练需要几十万张同类图才能让backbone学到通用的视觉特征587张连一个epoch的多样性都撑不起来训出来基本就是过拟合。选择YOLOv8nnano版本的理由有三点。一是模型参数只有三百多万比v8s或v8m小了一个量级在相同数据量下更不容易过拟合。二是预训练权重yolov8n.pt在COCO上见过大量小动物、户外、室内场景它的底层特征对鼠类检测有很好的迁移基础。三是训练速度足够快一张普通消费级显卡就能在几小时内跑完一百多个epoch方便迭代参数。如果你手里的卡是V100这种显存充足的型号batch size可以直接往上提但模型还是选nano起步。这里有一个容易犯的错误看到别的项目用yolov8m或yolov8x效果更好就照搬到鼠数据集上。模型越大在小数据上越容易记住训练集噪声验证集mAP反而不如nano。小样本的先验规律就是“模型容量越小越安全”。等你的数据集扩展到几千张再考虑切到s或m版本。4.2 train/val划分与data.yaml587张图不能全扔给训练数据划分是587张小数据集里最容易被低估的一步。随机划分最大的问题在于鼠类数据集里同一段监控视频截出的连续帧极其相似如果这些帧同时落在训练集和验证集里验证集指标会虚高得离谱模型真正部署到新场景时立刻原形毕露。我一般建议按来源划分如果图是按视频帧截的先按视频片段分组再把整个片段划到训练或验证绝不让同一段视频的帧跨集。没有视频来源信息的话至少按文件名前缀聚类后划分。划分代码import os import random import shutil BASE rat_dataset IMG_DIR os.path.join(BASE, images) LAB_DIR os.path.join(BASE, labels) random.seed(42) images sorted(os.listdir(IMG_DIR)) random.shuffle(images) val_ratio 0.15 val_count int(len(images) * val_ratio) val_images set(images[:val_count]) for split in [train, val]: os.makedirs(os.path.join(BASE, split, images), exist_okTrue) os.makedirs(os.path.join(BASE, split, labels), exist_okTrue) for img in images: label os.path.splitext(img)[0] .txt split val if img in val_images else train shutil.move(os.path.join(IMG_DIR, img), os.path.join(BASE, split, images, img)) shutil.move(os.path.join(LAB_DIR, label), os.path.join(BASE, split, labels, label))random.seed(42)让每次划分结果一致这是一个可复现性的习惯。val_ratio 0.15意味着约88张做验证、499张做训练对587张的总量来说验证集已经足够评估再大就会挤占训练样本。划分后写data.yaml这是YOLOv8读取数据集的唯一入口path: /path/to/rat_dataset train: train/images val: val/images names: 0: ratpath字段用绝对路径train和val是相对path的目录。names的顺序必须和txt里的类别ID严格对应。这里没有test字段也完全合法YOLOv8会在验证集上评估。单类检测时names就一个键别写成names: [rat]这种list形式yaml解析会报错。划分后检查一个关键数字验证集里含目标标注的图片数量。如果val里全是空图验证mAP等于没意义。快速检查方式find val/labels -name *.txt -size 0 | wc -l这个数字应该远小于88如果接近88必须调低验证比例或者把含目标的图优先划到验证集。4.3 训练命令与参数调整batch、imgsz、mosaic和预训练取舍数据准备好了训练命令采用迁移学习模式从预训练权重续训。这是我跑小样本鼠检测的常用命令yolo detect train \ datarat_dataset/data.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ device0 \ patience30 \ lr00.005 \ lrf0.01 \ mosaic1.0 \ projectruns/detect \ namerat_run逐个说明关键参数。modelyolov8n.pt意味着加载COCO预训练权重而不是从头初始化。epochs120做早停上限小数据集通常在四十到八十个epoch收敛再多就开始过拟合patience30让它连续30个epoch验证集不提升就自动停止不用死等。lr00.005相比默认的0.01调低了一半。理由是小数据集的梯度噪声大学习率太高容易在几个epoch内把预训练权重冲坏。如果你观察到一个epoch内loss从0.5跳回1.0优先把lr0往0.001降。mosaic1.0是马赛克增强默认开启。但对587张小数据集我建议先开着跑一版看结果如果验证集mAP一直上不去再尝试mosaic0.0关闭对比。马赛克增强会把四张图拼成一张相当于给模型看更多上下文但它也会让目标分布剧烈变化有些项目在小数据上反而因此训练不稳定。关于imgsz640如果原始图片分辨率远高于640比如1920x1080的监控帧直接缩到640会让老鼠在画面里很小检测难度剧增。此时有两个选择一是imgsz960或imgsz1280用更高分辨率训练保细节但显存开销和训练时间大涨二是先用640跑通流程看小目标表现再决定。我倾向后者因为流程先通更重要。训练启动后看什么来判断是否正常终端里主要盯三个loss值box_loss框回归损失、cls_loss分类损失、dfl_loss分布焦点损失。前几个epoch里loss下降慢是正常的但如果在第20个epoch之后cls_loss还在0.8以上徘徊大概率是类别别名没归并好或空标注文件干扰回到第三章重新检查数据。5. 鼠数据集训练的五个常见坑BN崩溃、loss不降和混淆矩阵异常5.1 BN崩溃loss突然变成nan的排查现象训练进行到某几个epoch终端打印的loss突然变为nan然后后续所有epoch的loss都归零模型输出全是背景类验证集全部检测不到老鼠。原因BatchNorm层在小batch size下统计量不稳定。587张图按batch8来算一个epoch只有六十多个stepBN的均值和方差估计在少样本条件下容易发散一旦数值溢出变成nan后面梯度传播全部污染。另外AMP混合精度在低精度条件下偶尔也会触发数值溢出。解决按先后顺序排查。第一步把batch提到16或32BN统计量需要足够大的样本。第二步在训练命令里加ampFalse关闭混合精度确认是不是它引发的溢出。第三步把lr0降一个数量级因为过大的学习率会让权重一步跨到数值溢出区。这三板斧下去绝大多数BN崩溃都能救回来。我自己遇到过一次最后是batch从8调到16并且关掉AMP解决的。5.2 前几个epoch的loss不降是warmup还是标注坏了现象训练头五个epoch里loss完全不动甚至轻微上涨然后才缓慢下降。原因YOLOv8默认有warmup阶段前三个epoch学习率从零线性升到目标值这个阶段loss不降太正常。但如果你已经跑到第十个epochloss还在初始值附近横盘那就不是warmup的事了。解决先排除标注问题。跑一次验证看precision是不是为0。如果precision为0大概率是labels目录里有大量空txt或者格式转换时类别ID和data.yaml里的names顺序对不上。打开任意一个txt打印前两行再看对应图片确认框坐标合理。另外检查预训练权重路径是否正确——如果modelyolov8n.pt没被下载成功Ultralytics会自动新建一个随机初始化模型那种情况下loss下降会非常慢你还在用迁移学习的预期去判断自然觉得不对。5.3 混淆矩阵总和不为1归一化方向搞混了现象训练结束后输出的混淆矩阵图行或列加起来不等于1甚至大于2。很多人看到这个就开始怀疑指标计算结果。原因混淆矩阵里的数值是每个类别的样本计数Ultralytics在展示时做了归一化但方向是“按列为单位”也就是每一列和为1表示“预测成这个类的所有样本里实际各是什么类别”。而机器学习教材里通常讲的是按行归一化每一行表示“该类实际样本被预测成什么的比例”。另一个干扰因素是背景类background也被统计进分子分母进一步破坏了总和为1的直觉。解决不要盯着矩阵图直接看两个数字验证集输出的mAP50和mAP50-95。如果mAP50上了0.8模型就是可用的混淆矩阵画得漂不漂亮不影响部署决策。真要手动算混淆矩阵用yolo detect val之后在runs/detect/rat_run/目录下的confusion_matrix.png之外还有一种做法自己加载模型对验证集做推理全程用numpy统计真实类别和预测类别的配对关系按自己的需求做归一化而不是改Ultralytics的绘图逻辑。5.4 空标注文件与类别名不统一现象训练日志里GT数量异常验证时mAP很高但实际预测效果极差或者cls_loss怎么调都降不到0.1以下。原因空标注文件是主要嫌疑。如果训练集里混入了一批没有任何目标的图片txt为0字节模型会在这些背景图上学习“什么都不输出”这本身不算坏事但验证集里如果也有空图模型只要全图都输出背景在空图上的“检测正确”就会被记为成功虚抬mAP。另一个常见问题是XML里类别名有rat、RAT、mouse三种写法转换时没有统一映射导致同一个鼠类被拆成多个类别ID模型被迫区分“大小写不同的鼠”分类能力自然崩。解决训练前统计空txt占比如果超过5%把空图从验证集剔掉或补齐标注。类别名统一在上一步的CLASS_MAP里解决同时压缩names字典只保留一个类别。锱铢必较地保证“每一张有目标的图标注都是它该有的那个ID”小数据集的所有训练结果都建立在这条基础上。5.5 标注框出界坐标转换后的clamp处理现象训练早停或者推理阶段矩形框画出去老远坐标变成负数或大于图片宽高。原因原始VOC标注里xmax可能因为标注员手滑或工具bug而超出图片边界。转换脚本如果没有做clampYOLO的txt里就会出现bw1.2这种超过1的数值训练时锚框匹配和NMS都会受影响。解决转换阶段做O到1的clamp参考第三章代码里的四个min(max())调用。推理阶段对预测框再做一次像素级clamp保证画框坐标不越界。这两个位置的处理是“训练前防御”和“部署前兜底”缺一不可。早期项目里我有一次没有在转换时处理训练一直不收敛最后逐张排查才发现有7张图的标注框宽高变成了1.5倍图宽。6. 从587张到可用模型离线增强、留出测试视频与自校验6.1 离线增强与留出视频验证训练完成后模型在验证集上的mAP只是第一道关。你更该关心的是它换一个环境、换一台摄像机之后还能不能认出老鼠。一个我保留至今的习惯训练前就从原始监控里抽出几段连续视频完全隔离在训练和验证集之外作为“模拟部署”测试素材。如果模型在这几段陌生视频上连续丢帧或者乱框说明模型学到的是训练集场景的纹理噪声而不是老鼠本身的特征。当验证集指标还行、测试视频表现不佳时优先考虑离线增强。Ultralytics自带的在线增强参数hsv_h、hsv_s、hsv_v、degrees、translate能帮一部分忙但587张图的多样性瓶颈单靠在线增强打不破。我会额外做一组离线增强副本亮度加减、左右翻转、随机旋转10度以内、高斯模糊。增强后的图片和原图一起参与训练相当于把有效样本量放大了3到4倍。离线增强要注意的一点是做镜像或旋转时标注坐标要跟着几何变换同步更新直接复制图片不改txt会导致标注错位。6.2 部署前用抖动测试检验过拟合这里给一个我常用的自校验技巧对测试视频里同一帧做亮度扰动观察模型的检测结果是否稳定。代码如下import cv2 # 加载一帧部署场景的图分别做两次亮度调整后送入模型预测 frame cv2.imread(deploy_frame.jpg) dark cv2.convertScaleAbs(frame, alpha0.6, beta0) bright cv2.convertScaleAbs(frame, alpha1.4, beta30) # 调用训练好的模型分别检测三个版本 model runs/detect/rat_run/weights/best.ptconvertScaleAbs的alpha是亮度缩放系数beta是加性增益。如果dark和bright两版预测出的鼠框位置明显漂移或直接消失说明模型对光照过拟合回去把离线增强里的亮度扰动范围加大重新训练。我用这个方法发现过训练好的模型在夜间场景下召回率骤降的问题这属于小样本训练的通病不值得意外值得用数据补强。折腾到最后最值得记住的一条经验是587张鼠数据集最贵的资源不是训练时间而是你那一个下午的格式转换和坏标注排查。每次拿到新数据我都会先把XML打开看十张、把转换后的txt画出来看十张、把验证集过一遍再碰训练参数。这套笨功夫帮我在无数个小项目里提前挡掉了问题希望你也能用上。希望帮到你。本文还有配套的精品资源点击获取
返回列表