ARTICLE DETAIL

资讯详情

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

瓶装白酒疵品检测全流程实战:YOLOv8训练与部署避坑指南

瓶装白酒疵品检测全流程实战:YOLOv8训练与部署避坑指南 简介瓶装白酒疵品检测数据集面向机器学习和计算机视觉研究者用于训练算法自动识别瓶身划痕、标签破损、密封不良等疵品特征可服务于生产线上的自动化质量检测降低人工抽检成本。压缩包内文件总数为2000个主要类型为JPG格式白酒瓶图像并包含1个JSON标注文件整体大小约213MB。已有293人学习下载适合作为训练集开展CNN等模型的训练与验证。图像涵盖疵品与非疵品样本采集视角包括瓶身正面、侧面等背景与光照条件较为统一便于减少无关变量干扰JSON标注文件可为分类或目标检测任务提供标签依据。解压后可直接划分训练集、验证集与测试集用于模型调优与性能评估快速搭建瓶装白酒外观质检原型系统另外也可作为工业缺陷检测竞赛、毕业设计或课程项目的实验数据基础。1. 瓶装白酒疵品检测数据集.zip先别急着解压先想清楚你要检什么拿到“瓶装白酒疵品检测数据集.zip”多数人第一反应是双击解压然后赶紧丢进训练脚本里跑一轮。这事我干过结果跑出来的模型在验证集上像模像样一到产线就翻车。白酒瓶质检看起来是个标准目标检测任务但它的难点不在“能不能框出瓶子”而在“缺陷类别到底怎么定义、样本分布有多偏、现场图和数据集图差多远”。这次我会从解压 zip 开始走到 YOLOv8 训练、部署验证和几个典型的坑把数据集真正变成能上线的检测模型。适合刚接触工业质检的视觉工程师也适合已经在跑模型但被误检漏检折磨的熟手。2. 拆开 zip 看数据目录结构、类别分布与图像质量的三项体检很多数据集下载下来压缩包里的目录是乱的有的按train / val分好了有的把所有图片堆在一个文件夹里标注文件要么是 XML要么是 JSON要么是一堆同名的 txt。直接写训练脚本之前必须先回答三个问题图在哪、标在哪、类别表有没有。漏掉这一步后面转换格式时一定会撞上类别名错位、空标注、坐标越界之类的怪问题。2.1 目录结构三连问图在哪、标在哪、类别表有没有先用unzip -l看一眼压缩包内部结构不用急着把文件释放到硬盘上# 不解压列出 zip 内的完整路径 unzip -l bottle_wine_defect.zip | head -60这条命令会输出压缩包内所有文件路径和大小。重点看是否有images/、labels/、Annotations/、classes.txt这类目录或文件。如果文件名是乱码或者带中文先记下来后面大概率要处理编码问题。确认结构没问题后再解压# 安静模式解压到指定目录保留目录层级 unzip -q bottle_wine_defect.zip -d bottle_wine_defect-q是 quiet避免刷屏-d指定目标目录。解压完先数一下图片数量这一步能快速发现 zip 是否被截断find bottle_wine_defect -type f \( -name *.jpg -o -name *.png -o -name *.bmp \) | wc -l正常的数据集图片数量应该和标注文件数量在同一数量级。如果图片明显少了一截多半是压缩包传输出问题。此时别急着重下先用unzip -t bottle_wine_defect.zip做完整性测试它会逐个文件校验 CRC有损坏会直接报出来。看过目录之后你需要判断标注属于哪种格式。常见三种标注格式典型文件后续是否要转YOLO txtlabels/*.txt每行class x_center y_center w h不需要直接写 yamlVOC XMLAnnotations/*.xml包含objectname和bndbox需要转成 YOLO txtCOCO JSONannotations/*.json包含images和annotations需要转成 YOLO txt我看到很多白酒疵品数据集用 VOC 格式居多因为打标工具默认导出就是 XML。如果你拿到的是instances_train.json这类 COCO 结构也可以先用json.load()看一眼categories字段拿到类别顺序再决定转换方式。2.2 用 Python 统计类别分布先找长尾再定评估指标拿到目录结构后第一件事不是看图片长什么样而是统计标注类别分布。我用一段非常简单的脚本完成这件事import os import glob from collections import Counter def inspect_yolo_labels(label_dir): 统计 YOLO 格式 txt 的类别分布、标签文件数和空文件数。 label_dir: labels 目录路径 cls_counter Counter() empty_count 0 file_count 0 for txt_path in glob.glob(os.path.join(label_dir, **, *.txt), recursiveTrue): file_count 1 with open(txt_path, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] # 空文件单独统计不参与类别计数 if not lines: empty_count 1 continue for line in lines: cls_id int(line.split()[0]) cls_counter[cls_id] 1 print(标签文件总数:, file_count) print(空标签文件数:, empty_count) print(类别分布:, cls_counter.most_common()) return cls_counter if __name__ __main__: inspect_yolo_labels(bottle_wine_defect/labels)逻辑说明每一行标注的第一个整数是类别 ID后面四个浮点数是归一化后的中心坐标和宽高。脚本只取第一个字段做计数空标签文件单独统计因为空文件代表该图没有缺陷这类图在训练中属于背景样本但数量过多会导致模型偏向输出低置信度。参数说明如果数据集是 VOC XML把读取方式换成xml.etree.ElementTree统计objectname文本即可。另一个注意点是这里统计的是“标注框数”不是“图片数”。一个瓶子可能同时出现标签破损和液位不足一张图里有多个框统计实例数才能反映真实的类别比例。如果某个类别只有几十个框而另一个类别有几千个框那训练时小类别基本学不到东西。这时不要急着追求整体 mAP先决定两件事小类别是合并成一个大类还是做样本扩充。比如“瓶盖歪斜”和“瓶盖破损”经常同时出现合并成“瓶盖缺陷”往往比强行区分更稳定。2.3 图像质量体检模糊、过曝、小目标占比怎么量化类别分布只是数据体检的一环。白酒疵品数据集最常见的质量问题是模糊和过曝产线相机抓拍时瓶子在传送带上移动快门不够快就会出现运动模糊灯箱照明不均匀会让瓶身局部过曝反光区域直接吞掉裂纹细节。我一般用 OpenCV 快速算两个指标import cv2 import numpy as np import glob img_dir bottle_wine_defect/images blur_scores [] brightness [] for img_path in glob.glob(img_dir /*.jpg) glob.glob(img_dir /*.png): img cv2.imread(img_path) if img is None: print(无法读取:, img_path) continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Laplacian 方差越低图像越模糊 blur_scores.append(cv2.Laplacian(gray, cv2.CV_64F).var()) # 灰度均值代表整体亮度 brightness.append(gray.mean()) print(模糊度中位数:, np.median(blur_scores)) print(亮度中位数:, np.median(brightness))逻辑说明Laplacian 算子本质是求二阶梯度边缘清晰的图像梯度响应大方差就高方差低说明图中没有明显边缘。白酒瓶身通常有大面积高光算出来的方差会比一般场景高但如果你发现一批图的模糊度中位数远低于其他批次说明这批数据可能是对焦失败或运动模糊导致的。参数说明np.median()取中位数而不是均值是因为个别极端亮的图像会把均值拉高中位数更能代表整体质量。如果亮度中位数超过 200图像整体过曝裂纹、标签墨迹这类低对比度缺陷会被高光淹没如果低于 80欠曝会让暗部噪点变大。小目标占比也可以在这一步顺手统计读取每个 txt 里的宽高乘上图片实际宽高得到框的像素面积。如果一个框的面积小于32 * 32在 640 输入尺寸下只有约 5 个像素宽模型很难学。遇到这种情况训练时把imgsz从 640 提到 960比加任何注意力模块都管用。3. 把标注转成 YOLO 格式转换脚本与四个边界坑无论原始标注是 VOC 还是 COCO最后都要转成 YOLO 的 txt 格式才能喂给 YOLOv8 训练。这一步的坑非常密集。我见过有人把所有 XML 转完后训练 loss 一直不降最后发现类别 ID 跟data.yaml里的顺序对不上模型把“裂纹”当成“瓶盖破损”在学。3.1 从 VOC/COCO 到 YOLO txt归一化坐标的关键公式VOC XML 里存的是绝对坐标xmin, ymin, xmax, ymax单位是像素。YOLO 需要的是归一化坐标中心点(x_center, y_center)宽高(width, height)取值范围 0 到 1。换算公式很简单x_center (xmin xmax) / 2 / image_width y_center (ymin ymax) / 2 / image_height width (xmax - xmin) / image_width height (ymax - ymin) / image_heightCOCO JSON 则更直接它存的就是x, y, width, height单位是像素只需把x width / 2得到中心点再分别除以图宽高即可。注意一点image_width和image_height必须和图片实际尺寸一致。有的 XML 里写的尺寸和真实图片不一致常见原因是打标软件读取了缩略图或者中间有人裁剪过图片。转换时最好用实际图片尺寸覆盖 XML 的size字段避免所有框都偏移。3.2 转换脚本XML 到 txt 的 Python 实现与参数说明下面这段脚本是我常用的 VOC 转 YOLO 实现兼容了坐标反了、越界、空框等边界情况import os import glob import xml.etree.ElementTree as ET def voc_xml_to_yolo(xml_path, out_dir, class_map): 将单个 VOC XML 转为 YOLO txt。 xml_path: XML 文件路径 out_dir: 输出 txt 目录 class_map: 类别名到 ID 的映射字典 tree ET.parse(xml_path) root tree.getroot() # 从 XML 读取图片尺寸稍后做归一化分母 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) img_name os.path.splitext(os.path.basename(xml_path))[0] out_lines [] for obj in root.iter(object): name obj.find(name).text.strip() # 跳过不在类别表里的名字并打印出来方便排查 if name not in class_map: print(f[skip] 未知类别 {name} in {xml_path}) continue cls_id class_map[name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) # 人工标注偶尔会画反先排序再算宽高 xmin, xmax min(xmin, xmax), max(xmin, xmax) ymin, ymax min(ymin, ymax), max(ymin, ymax) w xmax - xmin h ymax - ymin if w 0 or h 0: print(f[warn] 空框 {img_name}: {name}) continue # 归一化中心坐标和宽高 x_c (xmin w / 2.0) / img_w y_c (ymin h / 2.0) / img_h w_n w / img_w h_n h / img_h # 越界坐标夹回 0-1防止训练脚本断言报错 x_c min(max(x_c, 0.0), 1.0) y_c min(max(y_c, 0.0), 1.0) w_n min(max(w_n, 0.0), 1.0) h_n min(max(h_n, 0.0), 1.0) out_lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {w_n:.6f} {h_n:.6f}) if out_lines: os.makedirs(out_dir, exist_okTrue) out_path os.path.join(out_dir, img_name .txt) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(out_lines)) if __name__ __main__: # 类别表顺序决定 ID后面写 data.yaml 时必须一致 class_map { broken_cap: 0, label_wrinkle: 1, level_low: 2, bottle_crack: 3, missing_print: 4, } # 批量转换所有 XML for xml_path in glob.glob(bottle_wine_defect/Annotations/*.xml): voc_xml_to_yolo(xml_path, bottle_wine_defect/labels, class_map) # 转换后校验图片数和标签数是否一致 n_img len(glob.glob(bottle_wine_defect/images/*.jpg)) n_txt len(glob.glob(bottle_wine_defect/labels/*.txt)) print(f图片数 {n_img}标签数 {n_txt}缺失 {n_img - n_txt})逻辑说明脚本先把xmin / ymin和xmax / ymax做排序防止标注时手滑画反然后计算宽高宽高非正数的框直接丢弃最后做一次 0-1 夹取确保越界坐标不会导致 YOLO 训练阶段的AssertionError。参数说明class_map是最容易出错的地方。data.yaml里的names顺序必须跟这里的 ID 完全一致。如果你发现转换完的 txt 第一列数字和你预期的类别对不上多半是这里写错了。另一种常见做法是从classes.txt文件读取类别顺序写成class_map {name.strip(): idx for idx, name in enumerate(open(classes.txt))}这样可以减少手动维护映射表的成本。3.3 四个边界坑空标注、越界、类别名错位、中文路径第一个坑空标注。数据集里有些正常瓶子的图片没有缺陷打标软件也会生成一个没有object的 XML。转换后得到一个 0 字节的 txt。YOLO 允许空标签文件它代表背景图但如果背景图占比超过 30%模型会倾向于压低所有框的置信度。解决方法是保留少量空标注当作背景样本把明显多余的空文件从训练集和验证集删掉。第二个坑坐标越界。人工标注时把框拖出图像边缘很常见转换后中心点或宽高变成 1.05 这种越界值。训练时数据增强一裁剪边界框就会出问题。解决方式就是在转换脚本里做min / max夹取。注意夹取后宽高可能变成 0需要在加回代码里判断一次。第三个坑类别名错位。XML 里可能是label wrinkle带空格你的class_map写的是label_wrinkle。这种错位不会报错只会让转换脚本打印一堆 skip最后标签文件大量缺失。解决方法是转换前先跑一遍grep -h name bottle_wine_defect/Annotations/*.xml | sort | uniq -c把所有唯一类别名都列出来再建映射表。第四个坑中文路径。Windows 下解压 zip如果原始压缩包内目录名是中文某些图像库无法正确处理路径编码训练时cv2.imread()会返回None报错信息却指向“图片损坏”。解决方式是一开始就把解压目录改成英文比如“瓶装白酒疵品检测数据集”改成bottle_wine_defect。zip 里包含中文文件名时建议用 7-Zip 重新解压它能处理部分编码问题。4. 用 YOLOv8 训练瓶装白酒疵品检测模型选型、YAML 与命令标签格式统一后进入训练阶段。我见过不少团队一上来就选最复杂的模型结果数据量只有两三千张训练不稳定反而被最简单的 YOLOv8 成稿。训练跑通比追求 SOTA 重要得多。4.1 为什么产线质检首选 YOLOv8而不是更“先进”的模型拿瓶装白酒疵品检测来说这是一个目标相对固定、背景单一、实时性要求中等的任务。YOLOv8 的优势在于训练生态完整、导出 ONNX/TensorRT 顺手、默认超参数在中小数据集上表现稳定而且社区资料多出了问题查得到答案。Faster R-CNN 这类两阶段模型在某些小目标场景确实更准但推理速度慢部署到边缘盒子往往要牺牲输入分辨率。RT-DETR 去掉了 NMS理论上部署更优雅但训练收敛对数据量和超参数更敏感在小数据集上不如 YOLO 稳。YOLOv9、YOLOv10 后续版本也不错但它们相对较新工具链和踩坑案例不如 v8 丰富。我的习惯是先跑 YOLOv8n 和 YOLOv8s 两个基线看数据和模型容量是否匹配。瓶子缺陷数量少n 模型如果已经能把 mAP 推到 0.7 以上就不需要盲目上大模型。对比表如下模型速度小目标能力部署难度中小数据集稳定性YOLOv8快中上低高Faster R-CNN慢较好中中RT-DETR中中中高低YOLOv5快中低高但生态略老对白酒瓶这种“单个物体占画面比例大、缺陷占比例小”的场景YOLOv8 加合适的imgsz已经够用。4.2 写 dataset.yaml 和最小训练命令先定 imgsz 和 batchYOLOv8 需要一份dataset.yaml告诉它数据在哪、类别有哪些。文件内容如下# dataset.yaml path: ./bottle_wine_defect train: images/train val: images/val names: 0: broken_cap 1: label_wrinkle 2: level_low 3: bottle_crack 4: missing_print注意path是相对于你执行训练命令的目录建议写绝对路径避免换设备后路径失效。train和val填的是图片目录YOLOv8 会自动把路径中的images替换成labels去找同名 txt。所以图片目录必须叫images标签目录必须叫labels否则会报 “label not found”。训练命令如下pip install -U ultralytics yolo detect train \ modelyolov8n.pt \ databottle_wine_defect/dataset.yaml \ imgsz640 \ epochs120 \ batch16 \ lr00.005 \ patience20 \ device0 \ projectrun/bottle_wine \ namev8n_640参数说明yolov8n.pt是 COCO 预训练权重虽然 COCO 里没有白酒瓶但底层的边缘、纹理特征可以迁移比从零训练收敛快得多。imgsz640是训练输入尺寸如果你在第二章体检时发现小目标占比高改成imgsz960但显存占用会明显增加。batch16是根据 12GB 显存估算的遇到 CUDA out of memory 就先减半再不行切到 CPU 数据加载。lr00.005是我在这个数据量下的常用值。默认的 0.01 在大数据集上没问题但白酒疵品数据集通常只有几千张学习率太大容易过拟合。patience20意味着 20 个 epoch 内验证集 mAP 没有改善就早停避免挂在那边空跑。4.3 训练日志怎么读loss、P、R、mAP50 和 mAP50-95 的读法训练时终端会实时刷新P / R / mAP50 / mAP50-95。很多人只看 mAP50但产线质检更在意召回率因为漏检的疵品会流到市场上误检顶多多一次人工复检。所以我通常把召回率当作第一指标精度第二。训练结束后结果都在run/bottle_wine/v8n_640/results.csv里。用几行 pandas 就能看趋势import pandas as pd df pd.read_csv(run/bottle_wine/v8n_640/results.csv) cols [epoch, metrics/precision(B), metrics/recall(B), metrics/mAP50(B), metrics/mAP50-95(B)] print(df[cols].tail(10))逻辑说明results.csv记录了每个 epoch 的 loss、精度、召回率、mAP。tail(10)看最后十个 epoch也就是模型收敛后的表现。如果最后的mAP50很高但mAP50-95很低说明框的位置不够精确对 IoU 变化敏感如果recall明显低于precision说明漏检严重需要降低置信度阈值或者补充小目标样本。训练过程里还有一个容易误判的点前几个 epoch P 和 R 可能是 0这是正常的因为模型还在预热。如果跑到 20 个 epoch 还是 0九成是标签路径没配对赶紧去检查labels目录。5. 避坑瓶装白酒质检数据集的 5 个常见翻车现场这一章全部来自实际踩坑记录每条都按现象、原因、解决去写。训练脚本能跑只是第一步跑出来的结果能不能上线拼的全是这些细节。5.1 现象loss 降不动问题出在类别不平衡而不是学习率现象训练到第 30 轮box_loss在降cls_loss卡在 2.5 附近不动mAP50 一直在 0.1 徘徊。我一开始以为是学习率太低把lr0从 0.005 调到 0.01结果 loss 直接震荡问题更严重。原因统计类别分布后发现“标签褶皱”有 8000 个框“瓶身裂纹”只有 150 个框。YOLOv8 默认每个 batch 均匀采样图片小类别每几个 batch 才出现一次梯度贡献被大类别淹没。学习率调高只是让大类别学得更快小类别依然学不到。解决先把小类别的图片在训练集里复制两到三份注意验证集和测试集不能复制只能用原始图。这不是最终方案只是让模型先“看到”足够多的小类别样本。训练起来后再把mosaic0.5调低一点因为 mosaic 增强会把小目标的框切掉一部分。最后单独看一下小类别的recall如果还不行就去采集更多现场缺陷图。5.2 现象验证集 mAP 高但产线误检多原因在“背景类”和图像分辨率现象离线验证集 mAP50 跑到 0.93部署到产线后正常瓶子的照片频繁被框成“裂纹”误检率高到线长直接要求关停。原因数据集里几乎没有“正常瓶子”的负样本。所有训练图都是有缺陷的瓶子模型没见过没有缺陷但瓶底反光、瓶身有灰尘、传送带边缘有刮痕的画面。验证集也是从同一批有缺陷图里随机抽的指标当然好看。解决从产线采集一批正常瓶子的图片存成空标签 txt混入训练集。注意空标签文件不要放在labels里导致找不到匹配图片放在images/train下对应标签目录里放同名的空 txt。同时部署时把置信度阈值从默认的 0.25 提高到 0.45如果正常瓶子的误检还是压不住就要考虑把conf提到 0.6再用低阈值召回高风险类别。5.3 现象瓶身有两个缺陷NMS 后只出一个框原因在标签强共现现象一张瓶身图上同时存在“标签褶皱”和“标签破损”两个框高度重叠推理结果只输出其中一个。看起来像模型漏检但单独放这两类图片又能检测出来。原因这两个类别经常在同一位置出现标注框重叠度很高。YOLOv8 在后处理阶段执行 NMS重叠框 IoU 超过阈值会被抑制置信度低的那一个类别就被吞掉了。这不是模型没学到是后处理策略和标签定义打架。解决先统计类别共现矩阵如果两个类别在同一张图中出现且中心点距离很近的比例很高就把它们合并成一个复合类别比如“标签缺陷”。或者改成两阶段方案先检测瓶子区域再对瓶身区域做多标签分类。部署时确保agnostic_nms保持默认关闭让不同类别之间不互相抑制。5.4 现象训练时莫名报 UnidentifiedImageError原因在 zip 解压损坏或中文路径现象训练跑到第 7 个 epoch突然报UnidentifiedImageError: cannot identify image file程序直接退出。重启训练还是同样步数附近崩。原因zip 包在下载或拷贝过程中损坏某张图片文件只有头几个字节是有效 JPG 数据解压时没有报错但解码库读到一半就失败。另外如果图片路径包含中文字符某些图像读取库在特定环境下也会返回类似错误。解决先用完整性测试确认 zip 没问题unzip -t bottle_wine_defect.zip | grep -c No errors found输出为 0 就重新下载。解压后的目录名和文件名统一改成英文避免路径编码导致读取失败。训练脚本里加一个简单的过滤from PIL import Image import glob for img_path in glob.glob(bottle_wine_defect/images/*.jpg): try: img Image.open(img_path) img.verify() except Exception as e: print(损坏图片:, img_path, e)把损坏图片单独移出目录再重新启动训练。5.5 现象换产线后指标崩盘原因在打光、角度、瓶型变了现象A 产线验证集 mAP50 是 0.93把模型部署到 B 产线后mAP 直接降到 0.6。B 产线的瓶子换成了深色瓶光源角度也从侧光变成了顶光。原因这属于典型的数据集分布偏移。训练数据的打光、瓶子颜色、相机位置和现场不完全一致模型把“高光下的反光边缘”当成了裂纹特征换个打光条件特征就失效了。解决训练阶段增加随机光照、对比度、高斯模糊增强让模型不过度依赖某个固定亮度模式。部署新产线前至少收集 50 到 100 张新产线图片标注后做二次微调。微调时学习率降到 0.0005冻结前 10 层只训练后面部分防止旧特征被冲掉。如果换瓶型保留旧权重继续训练通常比从 COCO 重新训练效果更好。6. 落地到产线按时间戳划分验证集、推理提速与一个复现性习惯模型训练完真正的验证才开始。我习惯先在现场图片上做一轮“时间切片”验证再做推理优化最后固化实验记录。6.1 别随机抽验证集按日期切片评估随机划分会把同一天采集、同一批光线下高度相似的图片拆进训练集和验证集造成模型“作弊”。按时间切片更接近真实场景from pathlib import Path val_dir Path(val_live) val_dir.mkdir(exist_okTrue) for img in Path(live_images).glob(*2025-01-0[567].jpg): img.rename(val_dir / img.name)用连续三天的新图片做验证能反映出模型面对新批次的真实表现。6.2 推理提速先导出 ONNX再考虑 TensorRT产线上如果每秒要跑 5 到 10 帧直接用 PyTorch 推理也能应付但边缘盒子 CPU 负载会很高。先导出 ONNX 最省事yolo export modelrun/bottle_wine/v8n_640/weights/best.pt formatonnx imgsz640 halfTruehalfTrue转 FP16速度提升明显精度损失通常在 0.5% 以内。注意导出后要用onnxruntime跑一遍输出的张量维度有些版本导出后第一个维度是动态的需要用固定 batch 重新导出。6.3 固化“数据快照 超参数 随机种子”这是我被坑过之后才养成的习惯。有一次微调后精度反而掉了回头查才发现训练集被某次数据清洗多删了一百多张图。现在每次训练我都会执行sha256sum bottle_wine_defect.zip run_notes/sha256sum.txt cp bottle_wine_defect/dataset.yaml run_notes/20250107_v8n_640.yaml再写一个run_notes.md记下当天用的数据集路径、图片数量、超参数、预训练权重、最后的精度指标。下次复现或者排查问题不用靠记忆猜。数据是黑匣子模型是黑匣子唯一能抓住的就是这些落地的记录。希望帮到你。本文还有配套的精品资源点击获取
返回列表