MMRotate旋转目标检测自定义DOTA数据集训练与滑窗推理避坑

发布时间:2026/10/27 20:43:21
MMRotate旋转目标检测自定义DOTA数据集训练与滑窗推理避坑 1. 先想清楚旋转框到底解决什么问题MMRotate 值不值得用聊 MMRotate 和 DOTA 格式自定义数据集训练之前我先把一件事说透很多人上来就找配置文件改类别数结果训练 loss 正常、mAP 却死活上不去最后发现是标注的四个角点顺序反了。旋转目标检测这套东西90% 的坑不在模型而在数据和坐标定义上。这篇内容适合三类人一是手上有一批无人机航拍图、遥感图、工业质检图目标密集且带方向水平框套上去互相压得没法看二是已经跑通了水平框检测想平移到旋转框这套体系但被 angle_version、DOTA 格式、大图滑窗推理这些概念卡住三是跑通了官方 DOTA 示例但换成自己的数据就各种报错。不管你属于哪一类下面这些流程细节和踩坑记录基本能省掉你两三个星期的试错。MMRotate 是 OpenMMLab 体系下专做旋转框Oriented Object Detection的工具箱底层依赖 PyTorch 和 MMCV配置文件风格和 MMDetection 一脉相承。它的价值在于把 Rotated RetinaNet、Oriented R-CNN、RoI Transformer、R3Det、S2A-Net、ReDet 这些论文里的方法统一到了一套接口下数据格式、评估口径、训练流程都对齐了你换算法只需要换一个 config 文件不用重写数据加载。这一点在需要横向对比多个方法的时候特别省事。1.1 水平框在密集场景里到底烂在哪水平框HBB的本质是用一个轴对齐矩形包住目标它丢掉了角度信息。在目标稀疏、方向随机的场景里这个损失无所谓比如街景里的行人、车辆。但在航拍俯视图里船停在港口是一条条平行排列的飞机停在停机坪也是整齐排布这时候用水平框相邻两条船的外接矩形有大面积重叠NMS 一压直接压掉一半。更麻烦的是训练阶段的标签分配。水平框的 IoU 计算简单但两个相互垂直的长条目标水平框 IoU 可能高达 0.6模型根本分不清该学哪个。旋转框OBB用四个角点或者中心点加宽高加角度来表示能贴着目标轮廓走IoU 计算也换成了多边形 IoU 或高斯 IoU。代价当然有角度回归存在周期性−90° 和 90° 其实是同一个方向、边界不连续、标注成本高、推理后处理更复杂。我一般会跟人说如果水平框的 mAP 已经能满足业务就别折腾旋转框投入产出不划算只有当密集排列、长宽比悬殊这两类目标占比超过两成才值得换。1.2 MMRotate 在工具箱版图里的位置市面上做旋转框的方案大体三条路一是自己基于 MMDetection 改把 bbox coder 换成旋转版本二是用 MMRotate 这种专门工具箱三是一些商业或者半开源的遥感检测框架。第一条路我试过改到后面 R3Det 那种 refine 结构根本接不上第三条路文档质量参差算法覆盖不全。MMRotate 的优势是算法覆盖够全从单阶段的 Rotated RetinaNet、R3Det、FCOS 变体到两阶段的 Rotated Faster R-CNN、RoI Transformer、Oriented R-CNN再到基于高斯建模的 KLD、GWD、KFIoU 和基于角度分类的 CSL配置都在仓库里跟着文档走能跑起来。评估口径也统一到了 DOTAMetric省了自己写 mAP 计算。它的问题也明显版本迭代快0.x 和 1.x 的 API 差异不小0.x 用 mmcv-full 1.x1.x 换成了 MMEngine mmcv 2.x网上搜到的教程一半是旧版本的。所以你在动手之前第一件事应该是确定自己用哪个版本然后只照那个版本文档做别混着看。1.3 DOTA 标注格式为什么成了事实标准DOTA 是一个大规模航拍目标检测数据集它的标注格式简单到极致每张图对应一个同名 txt第一行写数据来源第二行写地面采样距离之后每一行是一个目标前八个数字是四个角点的 x、y 坐标第九个是类别名第十个是难度标记0 表示容易1 表示困难。这种纯文本、一行一目标的格式好处是机器好读、人也好改用 Excel 或者脚本批量处理都方便。坏处是没有 schema 约束坐标顺序写错了也不报错只会静默地让 mAP 掉一半。所以它成了事实标准几乎所有旋转框工具箱都优先支持 DOTA 格式你把自己的标注转成这个格式基本就打通了生态。1.4 我把整个流程拆成了五步完整跑通一次自定义数据集的训练我的做法是这么拆的环境版本对齐数据标注转成 DOTA txt 并校验角点顺序大图切分成子图并划分训练验证集改配置文件类别、pipeline、学习率、角度版本训练完做滑窗推理和结果合并再评估。这五步里第一步和第三步最容易出隐性错误第四步最容易出显性报错第五步最容易被忽略。下面我按这个顺序展开每一段都会说清楚为什么这么选以及我当时踩了什么坑。2. 环境搭建与数据集准备从原始标注到可训练的 DOTA 子图2.1 环境版本的三条硬约束装环境这件事MMRotate 的坑比 MMDetection 还多一层因为它对 mmcv 的版本更敏感。我给三条硬约束照着对基本不会出问题。第一条PyTorch 和 CUDA 的匹配。这个不用多说torch.cuda.is_available()返回 False 就回去重装别在这上面浪费时间。第二条mmcv 版本必须和 MMRotate 的主版本号对齐。MMRotate 1.x 用 mmcv 2.x安装名是mmcv不是mmcv-full0.x 用 mmcv-full 1.x。我见过有人装了 mmcv-full 1.7 又想跑 1.x 的配置报No module named mmengine还以为是缺包其实是整个依赖树都不对。判断方法很简单进 Python 敲import mmrotate; print(mmrotate.__version__)1 开头就用 mmcv 2.x0 开头就用 mmcv-full 1.x。第三条编译型依赖要提前装。DOTA 的官方评估脚本用到了多边形 IoU 的 C 扩展需要 swig 和 gcc 配合很多人卡在这一步。我的建议是先跑通训练评估阶段实在编译不过就先用工具箱自带的 DOTAMetric 顶着它算出来的 mAP 和官方脚本在大多数情况下差距很小不影响你调模型。提示不要在没有版本锁定的情况下用pip install -e .装完就跑建议先用pip freeze requirements-lock.txt把能跑通的环境冻下来后面换机器或者重装能省大量时间。2.2 DOTA txt 格式逐字段拆解与坐标顺序这一段是全文最该反复看的地方。一个标准的 DOTA 标注文件长这样imagesource:xxx gsd:0.5 1024 512 1200 512 1200 640 1024 640 large-vehicle 0 860 300 980 280 990 400 870 420 small-vehicle 1前两行是元信息图片源和地面采样距离自定义数据集不知道 gsd 就写 0大多数流程不读它但少数评估脚本会用它做小目标过滤所以最好别乱填。从第三行开始才是目标。关键是那八个数字。它们是四个角点顺序必须是左上 → 右上 → 右下 → 左下沿着目标轮廓顺时针走一圈。这个顺序不是随便定的因为后续算角度、算宽高、做旋转框编码解码全都依赖这个约定。如果你的标注工具导出的是从右上开始逆时针或者干脆是无序的四个点那模型学到的角度就是错的。怎么判断你的标注有没有问题一个土办法随机抽十张图用 OpenCV 把四个点连成线画出来看看是不是按顺时针闭合的以及第一个点是不是左上角。我见过最隐蔽的一种错误是角点顺序看着对但因为图像坐标系 y 轴朝下标注员按数学坐标系y 轴朝上的直觉标了结果角度整体反了 90 度。这种问题不会报错只会让模型在验证集上看着还行、测试集上一塌糊涂。类别名那一列建议用英文、不带空格、用下划线连接比如storage-tank、large-vehicle。带空格会在按空格切分的时候直接错位。难度标记没有难例就统一填 0如果你确实有遮挡严重、只露一角的样本标 1 也行评估的时候它会不参与 mAP 计算相当于给模型减负。2.3 自建数据集的标注工具与标注规范水平框的标注工具选择很多但支持旋转框的就那么几个。常用的有 roLabelImg基于 labelImg 改的旋转版本、X-AnyLabeling 这类支持旋转框和 AI 辅助标注的工具。选哪个不是重点重点是导出格式要能转成 DOTA 的四点格式。我的经验是标注规范的制定比工具选择重要得多。旋转框标注有两个高频不一致一是同一个类别的目标有人沿长边标、有人沿短边标二是边界贴合的松紧程度不统一。这两点在水平框里影响不大但在旋转框里会直接影响角度回归的稳定性。我一般会定这么几条规则所有目标统一沿长边方向定义角度短边方向作为宽度边界贴合以可见轮廓为准被遮挡部分做合理推断推断不出来的直接标难例长宽比小于 1.2 的近似方形目标比如储油罐、圆形操场标注时允许 90 度以内的角度抖动因为这类目标本身角度就无意义。另外提醒一句如果你的数据来源是航拍大图一张图动辄 8000×6000 像素直接标会很难看清小目标。建议先切图再标切图尺寸和后续训练保持一致通常是 1024这样标注的一致性最好也省去了后期切图时标注框跨界的问题。2.4 切图、划分与缓存split.py 参数怎么定大图必须切这一点没什么商量余地。DOTA 官方图普遍在 8000 像素以上一张图里几千个目标直接送进网络显存撑不住而且小目标缩放后只剩几个像素。MMRotate 仓库里带了tools/data/dota/split.py它的作用是按窗口把大图裁成子图同时把跨界的标注框也做相应裁剪。核心参数是--rates每个子集的比例、--gap子图之间的重叠像素、--nproc并行进程数。窗口尺寸我一般定 1024重叠 200。200 这个数字不是拍脑袋它要保证宽度在 200 像素以内的目标至少完整出现在某一张子图里。如果你的目标普遍较大比如港口里上百米的大船重叠可以加到 300目标普遍很小200 足够甚至可以用 150 换更多样本。--rates控制训练集和验证集的划分比例比如1.0 0.5表示训练集全用、验证集用一半。这里有个细节要注意split.py 的划分逻辑是先把原始大图分成两组再各自切图而不是切完图再随机分。这么做的原因是为了避免同一张大图的子图同时出现在训练和验证集里否则验证指标会虚高——因为相邻子图有 200 像素重叠模型等于见过验证集的一部分。切完之后目录结构大概是images/放子图labelTxt/放对应 txt根目录下有一个trainval.txt列出所有图名。这个 txt 就是配置里ann_file要指向的文件。最后一个容易被忽略的点是缓存。DOTA 格式的数据集每个样本对应一个小 txt训练时反复读文件 IO 开销不小尤其是机械硬盘上。数据集类里一般有use_bbox_cache这类开关打开后会把这些 txt 预先生成 pkl 缓存第一届 epoch 慢一点后面快不少。训练轮数多的时候这个收益很明显。3. 配置改造实操把官方 DOTA 配置改成自己的数据集3.1 三处 num_classes 与 metainfo 必须同步改新手最容易漏的就是类别数。官方配置里是 15 类DOTA 的完整类别表换成你自己的数据如果只改一处训练可能不报错但评估阶段会因为类别数不匹配崩掉或者 mAP 算出来永远接近零。要改的地方至少三个一是model.bbox_head.num_classes单阶段或者model.roi_head.bbox_head.num_classes两阶段有时两处都有二是train_dataloader.dataset.metainfo里的classes元组三是val_dataloader和test_dataloader里同样的metainfo。这里有个偷懒但不推荐的做法继承基类配置直接在子配置里覆盖。我最初就是继承_base_/datasets/dota.py然后在子配置里重写metainfo结果发现基类里的 classes 元组被合并而不是替换导致类别数对不上。后来改成在子配置里显式声明完整的metainfo问题就没了。类别顺序一定要和标注文件里的字符串严格一致大小写敏感、连字符和下滑线敏感。large-vehicle和large_vehicle会被当成两个类别一个永远学不到另一个永远没标签。3.2 pipeline 与小目标增强的取舍数据增强这块旋转框比水平框麻烦因为很多增强比如 RandomCrop、Resize 后裁剪需要同步变换多边形标签。MMRotate 里常用的增强有随机翻转、随机旋转RandomRotate要谨慎旋转后的边界框可能出画、多尺度 Resize、光度畸变。我的默认起点是这么配的训练 pipeline 用LoadImageFromFile → LoadAnnotations → Resize多尺度短边 800~1024→ RandomFlip水平、垂直、对角都开→ Pad → PackDetInputs。垂直翻转和斜对角翻转在航拍场景里特别有用因为航拍图本身没有上下之分翻过来依然是合理样本等于免费扩了数据。多尺度 Resize 要注意一个坑如果你的数据集里小目标特别多比如车辆、船只尺度范围不要往小里放太多否则缩到 600 像素的时候目标只剩几个像素标签分配阶段直接判为负样本等于白训。宁可范围窄一点比如 900~1100。验证和测试 pipeline 就别加增强了固定Resize到某个尺度加Pad就行。如果测试图是原始大图没切分那要换成滑窗推理的 pipeline这一点在第四节展开。3.3 学习率、batch 与迭代次数的换算官方 DOTA 配置的默认是 8 卡 × 2 张/卡SGD基础学习率 0.01训练 12 个 epoch。你单卡跑的时候不能直接照抄否则要么训不动要么炸。线性缩放规则是lr base_lr × (实际 batch × 卡数) / (官方 batch × 卡数)。单卡 batch 2就是 0.01 × 2/16 0.00125。但实测下来这个值对单卡稍小收敛偏慢我一般会取 0.002~0.0025配合更长的 warmup从 500 iter 拉到 1000 iter前期更稳。epoch 数取决于数据量。如果切完图只有一两千张12 epoch 可能欠拟合我会拉到 24 甚至 36如果切完图有一两万张12 epoch 加上多尺度增强基本够。判断标准很简单看验证集 mAP 曲线如果在最后一个 epoch 还在明显上升那就加轮数如果早早平了甚至开始掉那就是过拟合得减轮数或者加增强。优化器我基本用 SGD momentum0.9、weight_decay0.0001没特殊理由不用换。AdamW 在旋转框任务上有些配置能稍微快一点收敛但显存占用高、超参更敏感不适合快速验证。3.4 angle_version 与算法选择angle_version是 MMRotate 配置里最容易被忽略、又影响最大的参数。常见取值有三个ocOpenCV 定义角度范围 [−90°, 0)、le90长边定义[−90°, 90)、le135长边定义[−45°, 135)。这三者的区别本质是对同一个框可以用几种方式描述这个问题给出了不同答案。用oc的时候一个宽高互换、角度差 90 度的描述会被认为是不同的框这在回归时会造成边界跳变用le90则通过约束长边方向把描述唯一化。我的建议是新项目一律从le90起步配合数据集标注时统一沿长边定义角度。如果你拿到的是别人的数据角度定义不明那就用不同 angle_version 各训 10 个 epoch 看哪个 loss 收敛更平滑选那个。这个办法虽然土但比猜靠谱。算法选型上我的经验排序是要快、部署简单选 Oriented R-CNN它在精度和速度上平衡得最好而且中间层输出的旋转 RoI 很轻数据量小、目标少选 Rotated RetinaNet 这类单阶段不容易过拟合目标密集且尺寸差异极大考虑 RoI Transformer 或者 R3Det带 refine 阶段的结构如果角度回归一直不稳、loss 抖得厉害换基于高斯建模的 GWD 或 KLD 版本它们把旋转框转成二维高斯分布绕开了角度周期性这个根本问题。3.5 启动训练与日志怎么看命令本身很简单python tools/train.py configs/oriented_rcnn/oriented-rcnn-le90_r50_fpn_1x_dota.py \ --work-dir work_dirs/my_data_oriented_rcnn \ --amp--amp是混合精度能省显存提速但个别算法在 AMP 下会有数值问题出现 loss 变 NaN 就先关掉它排查。启动后第一件事是看日志里打印的类别列表和类别数确认是对的。第二件事看第一批 iter 的 loss 量级分类 loss 一般在 0.5~3 之间回归 loss 看具体算法如果一上来就是几百上千大概率是学习率太大或者标签有问题。训练过程中我主要盯三个东西total loss 有没有持续下降、分类 loss 和回归 loss 的比例是否失衡、验证集 mAP 的上涨趋势。如果分类 loss 一直降但回归 loss 不降通常是角度或者尺度回归出了问题反过来则是正样本太少或者标签分配阈值过严。4. 推理、评估与提交结果文件4.1 DOTAMetric 的评估口径MMRotate 1.x 里验证和测试用DOTAMetric指标是 mAP计算方式是按类别算 AP 再平均IOU 阈值 0.5。它和水平框的 VOC mAP 最大的区别在于 IoU 用的是多边形 IoU而且难例difficulty1不参与计算。这里有个细节值得说如果你的目标是近似正方形旋转框的多边形 IoU 对角度误差非常敏感。一个 45 度角的储油罐角度偏 10 度IoU 可能从 0.85 掉到 0.6。所以近似方形目标的类别mAP 天生会比长条形目标低这不是模型不行是度量方式决定的。遇到这种情况我会额外看一眼该类别的 AR召回而不是死磕 mAP。4.2 大图滑窗推理与结果合并训练用子图推理用大图这是标准流程但很多人卡在合并这一步。大图直接送进网络显存放不下所以要用滑窗按 1024 窗口、200 重叠遍历整张大图每个窗口跑一次推理得到一堆子图坐标下的框再映射回原图坐标。映射公式很简单窗口左上角原点(ox, oy)子图里的框(x, y)加上偏移就是原图坐标。麻烦的是重叠区域的重复框靠 NMS 合并。合并时 IoU 阈值我一般设 0.1~0.2比常规的 0.5 要低因为重叠区域同一个目标在两个窗口里的预测可能差异较大阈值高了会留一堆重复框。MMRotate 的配置里对测试阶段有多尺度翻转增强TTA的开关航拍场景下开水平垂直翻转的 TTA 通常能涨 0.5~1 个点 mAP代价是推理时间翻几倍。如果只做离线批处理这个买卖划算如果要求实时就别开。4.3 可视化与人工复核指标只是参考我每次训完必须做一次人工复核。做法是把预测结果按置信度排序抽前 200 个高置信度和随机 200 个中低置信度画在图上肉眼扫一遍。高置信度的框主要看有没有系统性偏移比如整体偏左上几像素那可能是坐标映射的 offset 差了一格切图时的边界处理导致。中低置信度的框主要看漏检和误检的规律比如是不是某个类别在特定背景下总被漏掉或者背景里的相似纹理总被误判。这一步花不了半小时但它能告诉你指标看不出来的问题。我遇到过一次 mAP 0.78 感觉还行可视化一看发现验证集里有三张图标注是空的转换脚本漏了模型在上面疯狂误检拉低了整体指标把这三张补上之后 mAP 直接涨到 0.82。4.4 提交文件生成与 mAP 复算的坑如果需要生成 DOTA 格式的提交文件一般是让测试流程输出 pkl再转成每张图一个 txt 的格式每行x1 y1 x2 y2 x3 y3 x4 y4 confidence注意这里是置信度不是类别名类别由文件名或者目录区分。用官方评估脚本复算 mAP 时最大的坑是编译依赖。多边形 IoU 的扩展需要 swig 和对应版本的 gcc很多人在这一步报一堆链接错误。我的处理顺序是先检查 swig 版本再检查 gcc 版本是否过高有些新版 gcc 对老代码的兼容性不好实在不行用一个干净的容器环境编译一次把编译好的 so 文件拷出来复用。还有一个隐性坑是评估脚本对 txt 格式的容错很低。多余的空格、科学计数法表示、行尾的换行符处理不当都可能导致解析失败但不报错只是那个目标被静默丢弃。建议提交前用脚本扫一遍检查每行的字段数是不是 9 个。5. 踩坑实录与调参经验5.1 loss 不降或者疯狂震荡的几种典型原因排在第一位的原因永远是标注坐标顺序。表现是 loss 能降到一个值就卡住验证 mAP 在 0.1~0.3 徘徊。排查方法是把某张训练图连同它的标注框画出来肉眼判断框是不是贴合目标、角点顺序是不是顺时针。我吃过一次亏转换脚本里用了np.argsort按 x 坐标排序遇到窄长斜框的时候排序结果就乱了找了三天。第二个原因是学习率过大。表现是前几十个 iter loss 就飙到几百甚至 NaN。先关 AMP再把学习率除以 10 试一次如果能平稳下降就是学习率问题。第三个原因是类别名里有隐藏字符。从 Excel 导出的时候经常带空格或者制表符看着是ship实际是ship类别列表对不上所有标签被当成背景loss 会呈现一种分类 loss 恒定在某个值的诡异状态。用cat -A看一遍就能发现。5.2 小目标与密集目标漏检小目标漏检在航拍场景太常见了。除了前面说的切图尺寸和尺度增强范围还有几个针对性手段把 RPN 或者 anchor 的尺度调小一档把正样本 IoU 阈值从 0.5 降到 0.4适当提高每张图的采样正样本数。这几个改动加起来在小车辆、小船这类目标上通常能涨 3~5 个点召回。密集目标的问题主要是 NMS 把相邻目标压掉了。旋转框的 NMS 本身已经比水平框好很多但对真正紧挨着的目标还是不够。可以考虑把 NMS 阈值从 0.5 调到 0.6或者用 soft-NMS 的变体。另外 Oriented R-CNN 这类两阶段方法在这类场景下天然比单阶段稳因为它第二阶段会对每个 RoI 重新回归减少了对初始 anchor 质量的依赖。5.3 类别不平衡与长尾自定义数据集里长尾是常态某个类可能只有几十个实例另一个类有上万。这时候 mAP 会被大类主导小类几乎学不到。我的做法是三级处理数据层面做过采样或者复制粘贴增强把小类目标贴到背景图上注意保持光照和尺度一致损失层面用类别加权或者 focal loss 调参评估层面分开看每个类的 AP 而不是只看总 mAP。这里要提醒一句类别加权不要一上来就按 1/频率 来那样小类权重会大到离谱把大类学崩。我一般用sqrt(1/freq)归一化效果更温和。5.4 一些提高稳定性的工程细节训练中断恢复这件事配置里要把resumeTrue打开并且 checkpoint 保存间隔别设太大我一般设每 1 个 epoch 存一次。旋转框训练动辄十几个小时中断一次从头来很痛苦。随机种子固定也很重要尤其是你要做多种算法的横向对比。同一个 config、不同随机种子mAP 波动 1~2 个点是常事不固定种子你根本分不清是算法差异还是随机性。日志方面除了自带的 tensorboard 记录我会额外把每次实验的 config 完整拷一份到 work_dir 下过一个月回头看的时候光看实验名根本记不住当时改了什么。5.5 常见问题速查表现象最可能原因排查动作训练不报错但 mAP 接近 0类别名不匹配 / 坐标顺序错打印类别列表对比可视化标注框前几个 iter loss 变 NaN学习率过大 / AMP 数值溢出降学习率 10 倍关闭 AMP分类 loss 降、回归 loss 不降angle_version 与标注角度定义不符换 le90 / le135 各训短轮对比验证 mAP 虚高、测试崩切图时训练验证重叠检查 split 是否按大图分组大图推理框错位窗口偏移量计算错误用单窗口推理对齐坐标验证相邻目标被压掉NMS 阈值过低调到 0.6 或用 soft-NMS小目标召回极低尺度增强范围过宽 / anchor 过大收窄尺度范围调小 anchor某个类 AP 恒为 0该类样本少于阈值或被误标为背景统计各类实例数检查难例标记最后分享一个小技巧换新数据集的时候先只用 200 张图、训 6 个 epoch 跑一个最小闭环把从标注到评估的整条链路走通再上全量数据。我现在的习惯是每接一个新项目都这么干一遍两小时能暴露 80% 的格式和配置问题比直接上全量数据等一晚上再发现报错要划算得多。数据格式这件事永远是旋转目标检测里最值得先花时间的地方。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询