ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Qwen2-VL 微调实战:用 LLaMA-Factory 让视觉大模型学会无人机航拍目标检测(附避坑指南)

Qwen2-VL 微调实战:用 LLaMA-Factory 让视觉大模型学会无人机航拍目标检测(附避坑指南) 前言把检测任务交给视觉语言模型VLM做是最近很多项目的尝鲜方向——不用改架构、不用设计检测头只要构造好对话数据大模型就能看图说话式地输出目标框。但真正上手之后你会发现微调能跑通不代表推理能用。本文完整记录一次 Qwen2-VL-7B 在无人机航拍图像上的微调实践从数据构造、训练参数选择到推理阶段翻车的四类问题与切图补救方案全部踩过、全部给出解法。可直接抄配置。一、项目概况项目配置任务无人机俯视航拍城市问题识别7 类目标检测基础模型Qwen2-VL-7B-Instruct微调框架LLaMA-FactoryLoRA / SFT硬件AutoDL 单卡 vGPU 32GB原图分辨率3840 × 2160数据规模11469 张有目标 9293 空标签 21767 个类别店外经营、占道经营、屋顶乱堆物料、临街乱堆物料、暴露垃圾、工地扬尘未苫盖、交通标线不清晰。其中屋顶乱堆物料与临街乱堆物料位置不同、店外经营与占道经营核心区别是有无实体店面——这些易混淆类的设计会在 Prompt 里显式给出。二、数据准备2.1 先看数据分布再决定训练策略类别照片数框数屋顶乱堆物料501412749交通标线不清晰23816551暴露垃圾22734077占道经营15633567店外经营10441508临街乱堆物料10291412工地扬尘未苫盖306506合计1361030370两个结论直接决定了后面的坑类别极不均衡最大类是最小类的 16 倍。这是后来模型只学会猜占道经营的直接原因。空标签负样本只保留了 20%2176 张。这是后来误检率偏高的直接原因。建议空标签比例提到 40%~50%且必须是困难负样本路面、斑马线、阴影、路边堆物、停车位、广告牌、水渍、反光——这些才是误检的真正来源。2.2 标注格式转换原始标注是 YOLO 格式归一化的中心点坐标 宽高而Qwen2-VL 原生支持的是绝对像素坐标 [x1, y1, x2, y2]转换时注意两点YOLO 的 (cx, cy, w, h) 都是 0~1 归一化值要先乘原图宽高再转左上/右下角点空标签样本保留 has_issuefalse 的对话输出空列表让模型学会没有问题就输出空。2.3 数据格式ShareGPT 图像路径LLaMA-Factory 的 VLM 训练数据用 ShareGPT 格式images 字段放图片的绝对路径{ conversations: [ { from: human, value: image请分析这张无人机航拍图像…… }, { from: gpt, value: {\has_issue\: true, \issue_count\: 2, \issues\: [{\type\: \占道经营\, \bbox\: [1213, 856, 1892, 1290]}, {\type\: \暴露垃圾\, \bbox\: [2431, 1520, 2688, 1745]}]} } ], images: [/root/autodl-tmp/VLM_Fine_tuning/LLaMA-Factory/data/images/xxx.jpg] }再在 dataset_info.json 里注册 urban_train / urban_val 即可。2.4 Prompt 设计这步决定输出格式的稳定性System Prompt负责定义任务、类别边界和易混淆区分User Prompt负责强制 JSON 输出结构我这里是few-shot可以让vlm做参考你是一个专业的城市管理问题识别助手用于分析无人机俯视航拍图像。 图像来源为固定高度的无人机俯拍目标以俯视角度呈现可能较小 同一图像中可能存在多个同类或不同类问题也可能不存在任何问题。 你需要识别以下七类城市管理问题 1. 店外经营有实体店面经营物品从店铺门口向外延伸摆放…… 2. 占道经营在马路、人行道或小道上摆放经营无对应实体店面…… 3. 屋顶乱堆物料建筑屋顶存在杂乱堆放的物品…… 4. 临街乱堆物料临街区域杂乱堆放建筑材料、杂物或货物…… 5. 暴露垃圾生活垃圾、废弃物等垃圾类物品未经覆盖或容纳直接暴露在外…… 6. 工地扬尘未苫盖建筑工地的物料、土方或沙石未进行覆盖苫盖…… 7. 交通标线不清晰道路交通标线存在磨损、褪色或模糊不清…… 注意事项 - 店外经营和占道经营的核心区别在于有无实体店面 - 临街乱堆物料和暴露垃圾的核心区别在于堆放物是否为垃圾类物品 - 屋顶乱堆物料和临街乱堆物料的区别在于堆放位置是否在建筑屋顶 请严格按照指定JSON格式输出结果不要输出任何额外内容。请分析这张无人机航拍图像识别其中存在的城市管理问题 并严格按照以下JSON格式输出 { has_issue: true或false, issue_count: 问题数量, issues: [ { type: 问题类型中文名称, bbox: [x1, y1, x2, y2] } ] } 其中bbox为目标在图像中的绝对像素坐标[x1,y1]为左上角[x2,y2]为右下角。 如果图像中不存在任何问题has_issue输出falseissue_count输出0issues输出空列表。几个实测有效的细节易混淆类的判别标准要写进 Prompt核心区别在于……比让模型自己悟有效得多bbox 明确声明绝对像素坐标并在 User Prompt 里定义左上/右下角减少模型自由发挥不要输出任何额外内容这句话能显著降低 JSON 前后混入废话的概率。三、训练配置3.1 关键训练参数调了数十遍效果最佳版### model model_name_or_path: /path/to/Qwen2-VL-7B-Instruct template: qwen2_vl ### method stage: sft finetuning_type: lora lora_target: all-linear lora_rank: 64 lora_alpha: 128 lora_dropout: 0.05 ### 关键解冻视觉侧 freeze_vision_tower: false # 解冻视觉编码器 freeze_multi_modal_projector: false # 解冻投影层 freeze_language_model: false ### dataset dataset: urban_train eval_dataset: urban_val cutoff_len: 8192 image_max_pixels: 1003520 # 约 1120×896 image_min_pixels: 1024 ### train per_device_train_batch_size: 1 gradient_accumulation_steps: 8 learning_rate: 1.0e-4 lr_scheduler_type: cosine warmup_ratio: 0.05 num_train_epochs: 3.0 bf16: true gradient_checkpointing: true flash_attn: fa2 eval_steps: 500 save_steps: 1003.2 这些参数为什么这么设每一行背后都是一次翻车freeze_vision_tower: false——最重要的一行。第一版训练把视觉塔冻结了结果模型的眼睛完全没参与学习坐标定位一塌糊涂。解冻视觉编码器和投影层让视觉特征真正向定位任务对齐是坐标精度提升最明显的单点改动。lora_rank: 64初版 16。rank16 对看图描述够用对输出像素级坐标容量明显不足框的大小和位置都很随意。定位任务建议从 32 起步条件允许直接 64。alpha 保持 2 倍 rank 关系。image_max_pixels: 1003520约 1120×896。这是 32GB 显存下能稳定跑的分辨率上限。注意一个关键事实原图 3840×2160送进模型会被下采样到这个尺度——原图里 10×10 像素的小目标下采样后只剩 1~2 个 patch。这个矛盾就是后面所有推理问题的根源训练参数解决不了只能靠推理方案补救见第五节。batch_size 1 × 梯度累积 8。32GB 显存下 7B 模型 图像输入实际 batch 只能开 1用梯度累积把有效 batch 撑到 8。配合 gradient_checkpointing 和 flash_attn: fa2显存才压得住。learning_rate: 1e-4。首轮从零训 LoRA 用 1e-4 没问题但如果是从已有 checkpoint 续训这个学习率偏危险建议降到 1e-5 ~ 3e-5。3.3 训练结果单个 epoch 约 7 小时 20 分钟10321 张训练图epoch 1 结束 train_loss 0.4676eval_loss 从 0.4302 一路降到 0.3771收敛平稳训练 3 epoch中途服务器关机过一次从 checkpoint 的 LoRA 权重续跑步数从头计。四、整图推理四类问题一次翻车现场训练指标很健康但拿整张 3840×2160 原图直接推理效果离可用还差得远框不准框整齐地堆在一起、大小一致但就是不贴目标——模型在猜一个看起来合理的坐标而不是在做几何回归框偏大普遍比真实目标大 3~4 倍误检把马路纹理、阴影检成暴露垃圾占道经营漏检小目标密集的图检出数量明显不足。根因分析这部分是全文最值钱的认知VLM 不是检测模型。传统检测器YOLO、DINO有 FPN 多尺度特征金字塔和 anchor 机制专治小目标Qwen2-VL 的视觉编码器把任意尺寸输入映射到固定数量的 patch token小目标在下采样后特征几乎被淹没。它输出 bbox 的方式是让语言模型把坐标说出来——本质是序列预测不是几何回归。框整齐排列、大小一致正是语言模型在按统计规律输出坐标格式的典型症状。剩下的问题来自数据误检空标签只占 20%且缺路面/阴影/斑马线这类困难负样本类别偏科第一轮训练 1 个 epoch 时模型只输出占道经营——多数类样本多模型学到了猜多数类永远不会太差的捷径典型的类别不平衡 欠训练组合。五、切图推理不改一行训练代码效果大幅提升既然小目标的矛盾是下采样后特征被淹没那就在推理侧把目标在输入图中的相对尺寸放大——把原图切块逐块推理坐标还原后合并去重。5.1 方案设计原图 3840×2160 切成2×3 6 块每块约 1280×960目标相对尺寸放大约 2.4 倍块间保留15% overlap防止目标正好压在切割边界上被截断小目标特别密集的图进一步切成15 块每块独立推理后把坐标归一化 → 映射回子图像素尺度 → 加子图偏移量还原为原图坐标最后NMS 去重去掉 overlap 区域产生的重复框。5.2 核心代码import cv2 import numpy as np def slice_inference(image, predict_fn, rows2, cols3, overlap0.15): 切图推理逐块推理 坐标还原 NMS 合并 H, W image.shape[:2] # 带重叠的切块每块的实际起止范围 ys np.linspace(0, H, rows 1).astype(int) xs np.linspace(0, W, cols 1).astype(int) pad_y, pad_x int(H * overlap), int(W * overlap) all_boxes, all_scores, all_labels [], [], [] for i in range(rows): for j in range(cols): y0, y1 max(0, ys[i] - pad_y), min(H, ys[i 1] pad_y) x0, x1 max(0, xs[j] - pad_x), min(W, xs[j 1] pad_x) tile image[y0:y1, x0:x1] tw, th tile.shape[1], tile.shape[0] for box, score, label in predict_fn(tile): x1_, y1_, x2_, y2_ box # 模型偶尔输出 0~1 相对坐标统一转成子图像素坐标 if max(x1_, y1_, x2_, y2_) 1.5: x1_, y1_, x2_, y2_ x1_*tw, y1_*th, x2_*tw, y2_*th # 加上子图左上角偏移还原到原图坐标系 all_boxes.append([x1_x0, y1_y0, x2_x0, y2_y0]) all_scores.append(score) all_labels.append(label) keep nms(all_boxes, all_scores, iou_thr0.5) # 去掉重叠区重复框 return [(all_labels[k], all_boxes[k]) for k in keep]坐标还原的完整链路模型输出相对子图→ 归一化判断 → 乘子图实际尺寸 → 加偏移量 → 原图坐标 → NMS。实测中模型输出坐标制式偶尔漂移有时绝对像素、有时 0~1 相对坐标所以代码里保留了一次 1.5 的判断做自适应。5.3 效果小目标的定位与识别精度明显改善密集小目标场景的漏检也大幅缓解整体达到可演示水平。代价是推理次数变成 6~15 倍需要按业务延迟做权衡。六、避坑清单都是真金白银换来的坐标系要全链路对齐。训练数据是原图绝对像素坐标模型看到的是缩放后的图推理时模型输出有时是 0~1 相对坐标——每个环节都显式确认坐标制式能省掉一整天的 debug。视觉塔必须解冻。freeze_vision_tower: false不然模型的眼睛不参与学习定位全靠蒙。LoRA rank 别低于 32。定位任务对容量比描述任务敏感得多16 会明显不够用。空标签不是垃圾数据是压制误检的主力。20% 不够40%~50% 更稳且优先补充长得像目标的困难负样本。类别不平衡要在数据层处理过采样少数类 / 加权 loss否则模型会走猜多数类的捷径。密集目标的 JSON 输出可能被 max_new_tokens 截断一图 15 个目标的 JSON 很容易超 1024 token漏检排查时记得检查这一项——虽然多数时候主因还是小目标特征太弱模型根本没看见。OpenCV 画中文标签会变方块。服务器装 fonts-wqy-zenhei或者改用 PIL 绘制中文。长训会遇上断点续训。云端实例关机中断很常见从 checkpoint 的 LoRA 权重续跑即可续训时把学习率降到 1e-5 ~ 3e-5防止把已收敛的权重冲飞。训练 loss 正常 ≠ 推理可用。SFT 收敛只说明模型学会了输出格式VLM 做检测的天花板由架构决定没有 FPN就没有小目标精度上限的保证。七、后续优化方向按投入产出排序推理后处理零训练成本NMS 的 IoU 阈值从 0.5 收紧到 0.35按面积过滤明显异常的框超过原图 30% 基本是误检小于 0.01% 基本是噪声。YOLO 两阶段流水线最推荐用现成的 YOLO 格式标注直接训一个 YOLOv8负责在哪里Qwen2-VL 只对 YOLO 框出的 crop 做分类确认负责是什么。检测器的 FPN 结构天然适配小目标VLM 的语义理解能力用在刀刃上现有标注零额外处理。GRPO 强化训练长期在 SFT 基础上设计 IoU 奖励 分类奖励 格式奖励让定位从语言模型猜坐标进化到真正优化几何精度。前提是先做 rule-based reward 离线验证否则 bbox 格式还漂着就上 RL会直接学歪。总结这次实践完整的路线是ShareGPT 格式构造带严格 JSON 输出模板的检测数据 → LLaMA-Factory 上解冻视觉塔的 LoRA 微调rank64→ 面对高分辨率小目标用切图推理 坐标还原 NMS 补救。最想留给你的一句话VLM 做检测微调解决的是输出格式和语义对齐解决不了小目标几何定位——后者要么靠切图推理缓解要么老实上两阶段流水线。认清这个边界能帮你省下大量调参时间。如果这篇对你有帮助欢迎点赞收藏踩过别的坑也可以在评论区交流。我做的不一定是最好的欢迎大家讨论~环境说明Qwen2-VL-7B-Instruct / LLaMA-Factory / AutoDL vGPU 32GB / PyTorch flash-attn
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表