做目标检测这几年我最大的体会是模型迭代快踩坑也快但真正让项目卡壳的常常不是什么玄学调参而是数据集本身没搞顺。今天想聊的是检测数据集制作的全流程重点放在收集、标注以及最容易被忽略的VOC、COCO、YOLO三种格式互转上。这三个格式听着就是文件夹和文件后缀的区别实际里面藏着的坐标体系、类别映射和尺寸来源问题能让一个经验丰富的人也在半夜加班。这篇文章会把我在实际项目里用过的流程、踩过的坑、以及最终沉淀下来的一套“一次搞对”的方法完整整理出来给正在做检测数据或者准备从零搭建检测项目的朋友一个可复用的参考。我见过太多人花了一周做标注最后因为格式问题重新导出一遍又花掉两三天。所以这篇的重点不是教你用某个标注软件点框而是把背后的原理讲透为什么从VOC转YOLO要做归一化为什么COCO的类别ID不能直接拿来做YOLO的类别ID为什么读图尺寸这件事不能偷懒。搞清楚这些你会发现所谓互转不过是同一份物理坐标在不同表示方式之间做翻译。1. 先把三条格式的地基打牢VOC、COCO、YOLO到底在存什么1.1 三种格式的定位差异Pascal VOC的XML格式是最老牌也最普及的检测标注格式每个标注文件对应一张图片文件里用object标签描述一个目标包括类名和边界框坐标。它的优点是人眼可读、结构简单、各种老工具都支持缺点是不方便存储实例分割、关键点这类复杂标注类别信息全靠字符串一旦类名拼写不一致后期处理就要出问题。MS COCO的JSON格式是目前学术界和工业界都绕不开的标准一个大的JSON文件包含images、annotations、categories三个核心字段整份标注集中在一个文件里。它的好处是信息密度高支持bbox、segmentation、keypoints等多种标注类型mmdetection、detectron2这类框架几乎默认吃COCO格式评测脚本也都是围绕COCO设计的。但代价是JSON结构嵌套深、文件动辄几百MB肉眼排查极其痛苦写解析代码时稍不留神字段名就写错。YOLO格式是Ultralytics系列训练最常用的纯文本格式一张图片对应一个同名txt文件每行五个数字类别索引、归一化中心点x、归一化中心点y、归一化宽度、归一化高度。它极其轻量几乎没有任何冗余信息训练读取速度也快但牺牲了可读性和扩展性。直观地说VOC像是一个填好的纸质表格COCO像是一本结构严谨的账本而YOLO像是一串只有你能看懂的数字编码。1.2 坐标系和存储形式的本质区别三种格式最大的差别不是文件后缀而是坐标系的表示方式。VOC用的是绝对像素坐标下的xmin/ymin/xmax/ymax也就是矩形框左上角和右下角的真实像素位置COCO用的是绝对像素坐标下的x/y/width/height左上角坐标加宽高YOLO则要求归一化的x_center/y_center/width/height一切都除以图片原始宽度和高度。把VOC的xmin直接塞给YOLO结果必然是全盘偏移加训练失败这不是格式互转工具能解决的而是需要明白转换语义。我画一个简单的对比表来说明格式存储粒度坐标表示类别表示适合场景VOC XML每图一个XML绝对像素 xmin/ymin/xmax/ymax字符串类名小项目、通用标注工具COCO JSON整个数据集一个JSON绝对像素 x/y/width/heightcategory_id名字映射复杂标注、学术界评测YOLO txt每图一个txt归一化 x_center/y_center/width/height从0开始的整数索引YOLO系列训练、轻量部署这里要特别强调类别ID这个问题。COCO的category_id通常从1开始而且并不连续因为历史版本合并过很多类YOLO则严格要求类别索引从0开始连续编号。直接拿COCO的category_id去写YOLO的txt大概率得到一堆超出类别范围的数字训练时要么报错要么把所有目标都当成一个类。正确做法是先维护一份独立的类别映射表例如{person:0, car:1, ...}转换过程里先查表再写文件。提示做格式互转之前先把类别名和索引的映射表定死这个不默认、不动态生成会省掉九成的后期麻烦。2. 数据收集从哪里找、怎么筛、怎么整理才不会后期返工2.1 公开数据集与自采数据的取舍数据收集是整个流程里最容易被低估的一环。很多人觉得收集数据就是往文件夹里丢图片等到标注完、训练完发现检测效果很差才意识到数据分布、数据质量、许可合规这些东西全都欠考虑。公开数据集方面Pascal VOC 0712、MS COCO、BDD100K这类经典集合覆盖了通用目标、驾驶场景拿来跑通流程没问题领域数据集的价值在于垂直场景比如电力红外巡检场景下的firc-dataset、工业传送带上的异物检测数据集、开关闭合状态数据集这些往往更贴近实际业务但规模和标注质量参差不齐。如果是自采数据需要考虑比公开数据集更多的事。我在做车辆检测的时候踩过一个大坑收集了一堆高清街景图标注得也认真模型训练出来后在雨天夜间的检测率直接崩盘。原因很简单数据集里晴天白天的占比超过90%场景分布严重偏斜。所以在开始标注之前先对采集回来的原始图片做一轮粗筛和分组按照场景、光照、目标尺度、目标密度分一下类尽量保证每一类都有一定数量而不是一股脑全丢进去。对于数据许可问题我的建议是提前看数据集license。有的数据集明确允许学术使用但禁止商用有的数据集虽然可以自由下载但衍生标注后的版权边界并没拿到授权。这个环节宁可谨慎也不要等到模型上线前才去处理合规问题。另外从Roboflow这类平台收集领域数据集时注意检查图片是否有水印、是否被resize过、是否已经做过数据增强有些公开数据集的“原始图片”其实本身就带有预处理痕迹会直接影响后续训练。2.2 清洗、去重和分布统计图片收集到本地以后不要急着标先做一轮清洗。清洗不是看美丑而是筛掉坏样本完全损坏的图片、分辨率过低的目标区域、被严重遮挡到连人都无法确认的目标、被过度压缩导致出现马赛克块的图片这些留着只会增加标注成本和噪声。实际操作里我通常会写一个脚本扫描所有图片检查图片能否正常解码、宽高是否超过阈值、彩色通道是否正常把异常文件直接挪到一个_rejected文件夹不删除方便二次确认。去重这一步很多人会偷懒但重复图片对训练的影响比想象中大。如果同一场景的连续帧都进了训练集模型相当于反复见到几乎一样的目标容易过拟合到这些样本上验证时看着指标很好换到真实环境就露馅。可以用图片的感知哈希或者简单的均值哈希做粗排加上文件大小和md5的比较把重复度高的样本挑出来。需要注意的是连续视频帧里同一个小目标出现在不同位置这种不叫重复真正要处理的是完全一样的图片或只有轻微压缩差异的图片。类别分布统计应该在做标注之前就先通过抽样估算一次。全量标注完再统计当然也可以但到那时候类别样本差距悬殊补数据就要重新标注代价很高。我现在习惯先随机抽取10%到20%的图片做预标注统计各类别样本数如果发现极大类别不平衡比如缺陷检测场景里正常样本占了95%那就要在采集环节想办法补充缺陷样本或者考虑过采样、合成等方式。这里不需要精确统计粗糙的分布感知就能避免后期大返工。2.3 训练集/验证集/测试集划分数据划分的最佳时机是在标注完成之后、格式转换之前因为要保证同一个场景、同一段视频里的画面不能同时出现在训练集和验证集里。我当时做无人机视角的目标检测把航拍视频连续帧随机切分结果验证集里全是和训练集几乎同帧的图片精度虚高得离谱部署到新航线才发现模型根本没有泛化能力。正确的做法是给数据加一个“场景ID”或者“视频序列ID”划分数据时按场景而不是按单张图片分。划分比例上我比较常用的是7:2:1或者8:1:1具体取决于数据规模。数据量小的时候验证集至少也要留下几十张否则指标波动太大根本没法判断模型好坏。对于小目标检测或长尾分布的场景测试集最好能覆盖所有类别哪怕少数类只有一点点样本也要放到测试集里来评估不然训练时模型有没有学会识别稀有物体你完全不知道。注意划分后不要再对验证集做任何数据增强也不要在调参时反复看验证集结果来决定改哪个超参数。测试集就是用来最终验收的一旦污染后续所有论文级别的评测都不可信。划分完成之后我习惯把数据集的组织结构固定下来比如data/ images/ scene01_001.jpg scene01_002.jpg ... labels/ scene01_001.txt ... annotations/ train.json val.json test.json把图片和标注分离放可以避免后续某些工具扫描目录时把XML、JSON、TXT一起读进去导致混乱。路径统一用相对路径根目录用一个环境变量或配置项引入这样代码换机器跑也不会因为绝对路径断掉。3. 标注规范与工具选型标注这件事用对工具能少加一半班3.1 工具选型对比标注工具选得好不好直接影响时间成本和质量底限。我这些年用过的不算多但每一款都测了一段时间最后留下来的是这么几款LabelImg适合纯手动小规模标注界面轻量导出VOC或YOLO都方便缺点是多人协同很差图片一多就卡Label Studio功能全支持目标框、多边形、关键点以及多种格式导出团队协作和任务管理都做得不错适合中型项目X-AnyLabeling自带很多推理模型的预标注能力我个人在重复性极高的工业缺陷场景里效率提升非常明显CVAT是开源里妥妥的第一梯队在线部署后多人同时标注配脚本管理任务适合拿到大规模外包标注时自建平台。工具开源支持格式预标注能力多人协作上手成本LabelImg是VOC/YOLO弱无极低Label Studio是VOC/COCO/YOLO/自定义中等强中等X-AnyLabeling是VOC/COCO/YOLO强无中等CVAT是VOC/COCO/YOLO/TFRecord强极强较高Roboflow标注端否多格式强强低但受平台限制选工具的底层逻辑是看你要标什么、多少人标、要不要预标注。纯单机小批次LabelImg足够如果标注量几千张起步并且是多类别的物体框直接上X-AnyLabeling或者Label Studio省下来的时间足够你多训几版模型。尤其现在工业界常见的做法是“用模型标数据、人工纠错”一个能加载预训练模型做自动标注的工具几乎是刚需能让标注效率翻倍。3.2 标注规范定义标注规范这件事我一直建议在开工前用一页文档写死而不是在群里口头说。类名统一用英文小写加下划线不用中文、不用空格、不用大写例如defect_scratch、air_switch_open这能规避掉一堆后续编码和路径问题。框的定义要界定清晰是框住整个物体还是框住可见部分重度遮挡目标怎么处理目标只有几个像素要不要标这些如果不定清楚不同标注员标出来的结果风格完全不同模型学到的目标边界就不是稳定的。我的经验是常规目标框住完整的最小外接矩形只要目标可见超过30%就标严重遮挡目标如果还能确认类别也要标但只标可见部分不要凭想象脑补被遮挡的区域。对于目标重叠严重的情况比如一堆货物叠在一起我的规则是只要两个目标的重叠度让标注员自己都分不清边界就只标最前面的那个目标后面完全不可见的一律不标。这样做出来的标注干净训练时模型也不会被矛盾样本折磨。标注时还有一件很容易忽略的事背景和负样本。不是所有图片里都要有目标保留一部分完全没有目标的背景图对减少误检非常有帮助。YOLO格式下背景图对应一个空白的txt文件不需要额外配置COCO格式下一张没有annotation的图片也完全可以只出现在images字段里。工业场景里因为背景中的类目标志被误检的案例太多了留一些干净背景图是性价比极高的操作。多个标注员一起干活的时候我建议随机抽5%的图片由两个人重复标计算一下框之间的IoU低于0.7的返工。这是最直接的质量抽检手段比事后看模型指标发现问题要省时得多。内部团队或者外包标注都可以用这个阈值做一次验收。3.3 半自动标注提高效率半自动标注是现在做检测数据最值得投入的方向。具体做法很简单先拿一个已经可用的预训练模型可以是通用模型也可以是你之前训的模型对未标注图片做推理把输出的检测框自动写入标注文件然后人工在标注工具里打开这些预标注结果只做确认、修改框边界、删除误检和补充漏检。我用X-AnyLabeling做电力设备红外图像标注时流程基本变成“模型把红外图像里的发热部件框个七七八八我来修正细节”速度比完全手动标注快两三倍。这个方法唯一的风险是模型错误会“灌输”给标注员。比如模型对某一类目标总是漏检标注员习惯性信任预标注结果就很容易把这些漏检带进最终数据集。我的对策是预标注结果必须做抽样审查重点看模型漏检率高的类别同时预标注用的模型不能和最终训练模型完全一样否则错误模式会被重复放大。半自动的意义是减少重复劳动而不是替代人工质检。拖过标注阶段之后我强烈建议保留一份“原始标注中间文件”不要把标注工具导出的结果当作最终唯一资产。因为后续你可能要改类名、合并类别、过滤小目标一旦原文件被覆盖或转换间的信息丢失想恢复就要重新标注一遍。我的方法是每个环节都生成新文件夹、不动原始导出文件最多在最终数据集里保留一份干净副本。4. 核心难关三种格式互转的完整实现与逆转换4.1 从VOC XML转YOLO TXTVOC转YOLO是出现频率最高的转换需求因为很多标注工具默认导出的就是VOC XML而YOLO训练必须消耗txt格式。核心逻辑就是读XML里的size获取图片宽高然后把绝对的xmin/ymin/xmax/ymax换算成归一化的x_center/y_center/width/height。有一个细节要特别注意宽高应该是图片的真实像素尺寸所以要么依赖XML里记录的size节点要么用OpenCV重新读一遍图获取真实宽高。我曾经遇到过XML里的size和实际图片尺寸不一致的情况那是有人对图片做了resize但标注工具没更新元数据结果转换出的YOLO文件全部错位。下面给一个小型但可用的转换函数作为参考import xml.etree.ElementTree as ET def voc_xml_to_yolo_txt(xml_path, txt_path, class_map): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) rows [] for obj in root.findall(object): name obj.find(name).text.strip().lower() if name not in class_map: print(f跳过未映射类别: {name}) continue cls_id class_map[name] bndbox obj.find(bndbox) xmin float(bndbox.find(xmin).text) ymin float(bndbox.find(ymin).text) xmax float(bndbox.find(xmax).text) ymax float(bndbox.find(ymax).text) # 边界裁剪防止坐标越界 xmin max(0, min(xmin, img_w - 1)) xmax max(0, min(xmax, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) ymax max(0, min(ymax, img_h - 1)) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h rows.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(rows))我用的坐标保留六位小数足够YOLO使用也保持文本文件不会过分膨胀。边界裁剪那两行很重要因为部分标注员手抖会把框拉出图片边界如果不裁归一化后会出现大于1或小于0的值训练时虽然不一定报错但会让模型学到超出图像范围的框推理时容易出现奇怪的预测位置。另一个隐性坑是极窄的框宽或高只有1像素归一化后几乎等于0如果数据里有大量这种目标建议在转换时过滤掉或者单独标记因为极小目标对检测模型的回归压力非常大。4.2 从YOLO TXT转COCO JSONYOLO转COCO听起来是逆过程其实更麻烦因为TXT里没有图片尺寸、没有图片ID、没有类别名映射所有上下文信息都需要从外部文件和文件名规则里恢复。我踩过的最深一个坑是有些数据集文件夹里图片路径五花八门文件名还能重复不加处理直接生成COCO JSON结果一张图片对应了完全错位的Annotation。所以这一步我强烈建议先扫描文件建立以image_id到图片路径的映射顺序排序后统一分配ID不要在循环里随便递增。生成COCO时图片ID可以从1开始类别ID从1开始也没问题因为COCO本来就不是从0开始但为了跨格式统一我通常把内部ID仍然和类别名绑定。实现思路是先读取所有图片宽高再读取每个txt每行的归一化坐标乘回对应图片的真实宽高得到像素级的x/y/width/height组装成COCO的annotation对象。这里有一个容易被忽略的点YOLO坐标是中心点加宽高转成COCO需要把它改写成左上角加宽高即x_top_left (x_center - width/2) * img_w否则评测低分甚至跑到图外。COCO JSON里每个annotation必须有id、image_id、category_id、bbox、area、iscrowd等字段。area的计算没有争议就是宽高乘积iscrowd置0即可。如果以后需要做实例分割YOLO格式就帮不上忙了因为它压根没有segmentation信息所以从YOLO转COCO时要注意转换结果只适合检测任务不能指望它神奇地补出轮廓。数据集格式互转能保留的语义信息是有限的这一点在做方案设计时就要想清楚。4.3 从COCO JSON转VOC/YOLO及反向注意事项COCO转YOLO最常见的需求是把一个从网上下载或者从mmdetection评测中拿到的COCO格式标注转到YOLO训练里。解析JSON时先把categories按id排序并建立name到连续数字索引的映射然后遍历annotations用image_id把对应图片信息查出来得到宽高再将bbox里的像素坐标还原成归一化中心点。这里最关键的中间步骤是把COCO的x/y/width/height转换成中心点因为COCO存的左上角坐标但YOLO需要中心点坐标。公式是abs_x_center x width / 2 abs_y_center y height / 2 norm_x abs_x_center / img_w norm_y abs_y_center / img_h norm_w width / img_w norm_h height / img_hCOCO转VOC时很少需要生成XML文件因为VOC格式更多是作为存储和标注工具的中间交换格式但如果真有这个需求记得bndbox节点里必须是整数或至少保留两位小数因为很多老工具解析XML时类型处理得很脆弱。另外COCO里的segmentation、area、iscrowd在VOC中根本没有对应位置直接丢弃即可不要强行写成一个自定义字段否则VOC工具可能因为未知子节点报错。反向从COCO JSON转到其他格式时还有几个隐性规则。COCO的annotation可能会有同一个图片出现多实例共享完全相同的边框这在拥挤场景里并不罕见但如果直接转成VOC两个XML对象完全相同没问题转成YOLO也没问题只是可能在数据增强时叠加出非常奇怪的标签如果训练是检测器这种完全重叠框对loss贡献极小一般可以直接合并成一个框或者保留一个实例。另外COCO允许iscrowd1的群体标注这种标注不能被普通检测训练直接使用转格式时要过滤掉否则模型会尝试为“一大群人”预测一个框逻辑上完全错误。4.4 格式互转避坑清单格式互转里很多问题不是代码逻辑错了而是数据假设错了。我统计过自己项目里出过的bug排名靠前的几个基本可以列成一张避坑清单类别名里有空格或大小写不一致转格式后映射不到。直接拿COCO的category_id当YOLO class_id索引溢出。图片宽高用XML里的旧值但实际图片已经resize。路径分隔符在Windows下是\Linux下是/读取时没统一处理。空标注文件在YOLO里合法但有些脚本读到空文件会直接报错。JSON字典遍历顺序不稳定导致图片ID每次生成不一样。重复的文件名在不同子目录下全局字符串路径查重失败。图片有旋转EXIF信息读取到的尺寸和显示方向不一致。前三个是重灾区几乎每个转换脚本都可能遇到。解决办法也很直接写转换脚本前先写一个小型预检工具扫描所有输入文件统计类别名集合、图片尺寸、文件数量打印出来人工过一眼再开始转换。不要等到转换完拿训练Error慢慢猜。5. 我踩过的那几个坑问题排查与急救5.1 常见问题速查表下面这个表格基本浓缩了我最近两年在数据制作和格式转换上遇到的高频问题方便你直接对号入座现象可能原因解决思路训练时loss不降所有预测框都指向同一块区域类别ID错乱大量目标被当成背景或错误类检查txt第一列数字和类别映射表对照检测框整体偏移但大小正常YOLO的归一化坐标被当作像素坐标使用确认是否把中心点坐标乘以了图片宽高某些图片在训练时直接报标签错误txt行数超过预期或空文件被当作非法过滤空文件检查是否有损坏标注验证集指标虚高同一视频序列的帧被分进训练和验证按场景或序列划分数据换设备训练后图片找不到数据集里用了绝对路径全部改成相对路径用配置项固定根目录中文类名在Linux下乱码编码不统一所有人统一用UTF-8类名不用中文图片尺寸不对训练预处理报错EXIF旋转或实际分辨率变化重新读取图片尺寸并更新标注合成数据里框滞后一帧视频抽帧和标注时间戳不同步全流程使用同一抽帧脚本记录时间戳这里面“损失函数不降”是最伤人的我调了半天模型最后发现是类别索引串位。那次我拿到一个COCO格式的标注直接提取category_id当成YOLO class id来写txtCOCO里面car的id是3YOLO里car的id是5每个框都指向其他类别模型当然学得一团糟。所以无论什么时候我都保留一份类别映射JSON不让代码隐式猜测。5.2 自检脚本10分钟摸清数据集健康状况与其等到训练时崩不如在数据进入训练前跑一轮自检。我习惯写的脚本不复杂但覆盖几个关键点一是统计每张图片的标注框数量发现框数超过某一阈值或者为0的图片单独列出来人工看二是统计每个类别的实例数量输出最少的类和最多的类给后续样本平衡做参考三是计算标注框面积和图片面积的比值如果小目标占比过高需要评估模型结构是否适合。这个自检脚本还会检查每个框是否越界、是否出现宽高为负、是否和某个类别的出现频率有明显矛盾如果发现异常直接输出到warning_report.txt。核心的检查逻辑大概是这样def check_annotation(img_path, boxes, img_width, img_height): for i, box in enumerate(boxes): x_min, y_min, x_max, y_max box if x_min 0 or y_min 0 or x_max img_width or y_max img_height: print(f越界框: {img_path} #{i}) if (x_max - x_min) 1 or (y_max - y_min) 1: print(f退化框: {img_path} #{i})跑完自检之后我还会随机抽取20到30张图片用OpenCV把标注框画在原图上人工眼睛扫一遍。这个肉眼抽查环节绝对不能省因为它能看到很多统计量发现不了的问题比如框的中心点没对准目标、遮挡部分被硬框出来、类别标错但形状相似等。自检脚本加肉眼抽查前后不超过20分钟但对后面训练投入的数小时甚至数天来说是一笔绝对划算的时间投资。可视化抽样也有讲究不要只抽前几帧要均匀地从不同场景、不同类别分布里抽样最好让脚本输出一个横向拼图一眼扫过去就能发现分布是否单调。我在某些项目里发现样本里90%的框都集中在图片的中央区域后来才知道是标注员习惯性地关注画面中线附近边缘区域大量目标漏标。这种问题单看统计量可能不显眼但画出来就能看出来模型后期对边缘目标的检测能力也基本废掉。5.3 一个来自真实项目的完整转换流程列一个我之前做传送带异物检测数据时的真实操作顺序。原始数据来自工厂现场摄像头抽帧后得到几千张图片标注工具导出的是VOC XML。我先写脚本把所有XML扫一遍统计类别名发现类名有broken、BROKEN、damaged三种写法实际是同一种东西于是先统一类名映射。接着按视频序列划分好训练验证集避免同一输送带画面跨集合。然后用上述VOC转YOLO函数生成txt每一条都打印异常信息检查是否有框越界、图片尺寸缺失等问题。最后转出YOLO格式后用YOLOv8预训练模型训练了一个小规模的快速验证模型看头几个epoch的loss和验证集mAP是否符合预期确认数据没大问题后才放心进入正式的模型训练和迭代。这个流程看着很琐碎但它把“数据质量不可控”这件最大的风险前移了。模型训练是概率性的数据错误不是数据错误如果进入训练会以一种非常隐蔽的方式污染最终权重你可能完全不知道模型为什么在特定场景失效。所以我现在的做法永远是先数据自检再数据可视化最后才轮到模型训练和调参。关于格式互转什么时候做最佳我的建议是放在划分之后、训练之前。因为划分是基于原始图像和标注的语义信息而格式只是表达层。如果在划分前就转成YOLO后续想再按场景切分写回逻辑就会更麻烦。反过来把VOC或COCO当作“工作格式”把YOLO当作“训练格式”需要调试或者可视化时再转回带坐标解說的格式这会让整个数据管线的灵活性高很多。我个人在实际操作中的体会是检测数据集制作没有捷径但有体系。把收集、清洗、标注、划分、格式互转每一步拆开建立对应的自检和备份机制后面的一百个模型训练任务都会受益。这里面的核心不是哪个工具好用、哪段代码写得漂亮而是你始终知道自己的数据以什么形式存在、在什么时候转换、为什么转换、转换后有没有信息丢失。永远不要把格式互转当成一句话带过的小事它承载的是整个数据标注过程的最终交付质量。