微调LocateAnything-3B 实现超高密度的目标检测

简介: 本文介绍如何微调NVIDIA LocateAnything-3B模型,应对300+密集重叠种子的精准定位难题。依托并行框解码(PBD)与半监督Pipeline(点标注→SAM2转框→YOLO伪标→定向微调),大幅降低人工成本,实现高精度、可落地的密集目标检测方案。

微调LocateAnything-3B,实现当图像中有 300+ 个密集重叠目标、人工标注不可行时的实用方案。

假设手头有一批种子发芽托盘、谷物质检图像或植物学调查照片。每张图像包含 100–500+ 粒种子,许多彼此重叠,部分被遮挡。老板(或导师)要求模型能精确定位每一粒。

在每张图像里手动为 300 个互相重叠的目标画紧密边界框,人工标注是根本不可行。按每个框约 5 秒计算,一张图就要花 25 分钟;1000 张图下来,标注工时超过 400 小时。

本文介绍如何借助 NVIDIA 的 LocateAnything-3B——一个支持 Parallel Box Decoding(并行框解码)的视觉语言定位模型——以及一套半监督 Pipeline,将人工标注量压缩到最低。

为什么选择 LocateAnything-3B?

大多数视觉语言定位模型(如 Grounding DINO 或 OWL-ViT)采用自回归方式逐 Token 生成边界框,检测 300 个目标的速度极慢。

LocateAnything-3B 采用 Parallel Box Decoding(PBD),以并行块预测的方式一次性输出完整边界框,而非逐 Token 生成,特别适合包含数百目标的密集场景。

核心参数:

  • 基础模型:Qwen2.5–3B-Instruct 语言模型 + MoonViT 视觉编码器
  • 解码方式:并行块级预测(非自回归)
  • 许可证:NVIDIA Open Model License

总体策略

整个方案分四个阶段,采用半监督 Pipeline:

  1. 最少量的点标注(非框标注)+ SAM2 生成初始边界框
  2. 在合成数据与伪标签数据上训练轻量级检测器
  3. 对全量未标注数据集生成伪标签
  4. 使用高置信度伪标签配合密集场景专用训练策略微调 LocateAnything-3B

阶段 1:最少量标注 + 点转框

点击目标中心点的速度比画边界框快约 10 倍。标注 300 粒种子,打点只需约 5 分钟,画框则需约 25 分钟。

具体做法:用 SAM 2(Segment Anything Model 2)将每个点扩展为分割掩码(segmentation mask),再从掩码推导出边界框。

依赖安装:

 pip install sam2 torch torchvision transformers opencv-python

点转框 Pipeline:

 import cv2
import numpy as np
import torch
from sam2.build_sam import build_sam2
from sam2.sam2_image_predictor import SAM2ImagePredictor
from PIL import Image

# 加载 SAM2
checkpoint = "./checkpoints/sam2.1_hiera_large.pt"
model_cfg = "configs/sam2.1/sam2.1_hiera_l.yaml"
predictor = SAM2ImagePredictor(build_sam2(model_cfg, checkpoint))

def points_to_boxes(image_path, seed_points):
    """
    Args:
        image_path: 原始种子图像路径
        seed_points: (x, y) 元组列表——每粒种子中心点击一次
    Returns:
        boxes: [x1, y1, x2, y2] 边界框列表
    """
    image = np.array(Image.open(image_path).convert("RGB"))
    predictor.set_image(image)

    # 将点转换为 numpy 数组
    point_coords = np.array(seed_points, dtype=np.float32)
    point_labels = np.ones(len(seed_points), dtype=np.int32)  # 1 = 前景

    # 批量预测(SAM2 支持多点输入)
    masks, scores, logits = predictor.predict(
        point_coords=point_coords,
        point_labels=point_labels,
        multimask_output=False,  # 每个点输出单一掩码
    )

    # 将掩码转换为边界框
    boxes = []
    for mask in masks:
        # mask 形状:(H, W)
        ys, xs = np.where(mask > 0)
        if len(xs) == 0:
            continue
        x1, y1, x2, y2 = int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())
        boxes.append([x1, y1, x2, y2])

    return boxes, masks

# 使用示例
seed_points = [
    (120, 340), (145, 342), (180, 338),  # ... 共 300 个点
]

 boxes, masks = points_to_boxes("tray_001.jpg", seed_points)

SAM2 擅长处理边界分离。即使种子相互重叠,点击可见前景种子上的点,通常也能正确分割出该前景目标。若两粒种子严重粘连:

  • 点击最上层种子的可见中心
  • SAM2 会优先分割包含该点的目标
  • 对于被遮挡的种子,点击其可见的边缘或角点

若 SAM2 将两粒相邻种子合并为一个掩码,可将点位下移后重试,或在提取边界框前对掩码进行 watershed 分割后处理。

阶段 2:构建初始检测器

在接触 LocateAnything-3B 之前,先基于 SAM2 生成的边界框训练一个快速的 YOLO11 或 RT-DETR 模型。训练完成后可以得到一个专针对种子的检测器,运行速度达 100+ FPS,适合对数千张图像批量生成伪标签。

 from ultralytics import YOLO

# 在 SAM2 伪标签上训练 YOLO11
model = YOLO("yolo11n.pt")  # 使用 Nano 版以提升速度,可按需换用 s/m/l

# 数据格式:YOLO 需要 .txt 文件,格式为 类别 x_center y_center width height(归一化)
# 转换边界框:
def box_to_yolo_line(box, img_w, img_h):
    x1, y1, x2, y2 = box
    x_c = ((x1 + x2) / 2) / img_w
    y_c = ((y1 + y2) / 2) / img_h
    w = (x2 - x1) / img_w
    h = (y2 - y1) / img_h
    return f"0 {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}"

# 训练
model.train(
    data="seed_pseudo_data.yaml",
    epochs=50,
    imgsz=640,
    batch=16,
    patience=10,
    augment=True,
    hsv_h=0.015, hsv_s=0.7, hsv_v=0.4,
    degrees=15, translate=0.1, scale=0.5,
    fliplr=0.5, mosaic=1.0, mixup=0.1,
 )

为什么要设计这个中间步骤?YOLO11 训练只需几分钟,每个预测结果都带有置信度分数,可按置信度过滤伪标签,也能过滤掉 SAM2 产生的明显误检(如背景杂点)。

阶段 3:大规模生成伪标签

将 YOLO11 种子检测器应用于全部未标注图像,保留高置信度预测,过滤噪声。

 import glob
import json
from ultralytics import YOLO

model = YOLO("runs/detect/seed_yolo11/weights/best.pt")
image_paths = glob.glob("raw_images/*.jpg")

pseudo_labels = []
CONF_THRESHOLD = 0.65  # 可调整——从 0.6 开始,若噪声过多可提高至 0.75
IOU_NMS_THRESHOLD = 0.5

for img_path in image_paths:
    results = model(img_path, conf=CONF_THRESHOLD, iou=IOU_NMS_THRESHOLD)

    boxes = results[0].boxes.xyxy.cpu().numpy().tolist()
    confs = results[0].boxes.conf.cpu().numpy().tolist()

    # 可选:跳过模型置信度较低的图像(平均置信度偏低)
    if len(confs) > 0 and np.mean(confs) < 0.5:
        print(f"标记为待人工复核:{img_path}")
        continue

    # 以 LocateAnything 对话格式存储
    pseudo_labels.append({
        "id": img_path.split("/")[-1].replace(".jpg", ""),
        "image": img_path,
        "conversations": [
            {
                "from": "human",
                "value": "<image>\nLocate all seeds in this image."
            },
            {
                "from": "gpt",
                "value": format_boxes_for_locateany(boxes)
            }
        ],
        "meta": {"source": "yolo_pseudo", "mean_conf": float(np.mean(confs))}
    })

def format_boxes_for_locateany(boxes):
    """将 xyxy 格式边界框转换为模型所需的特殊 Token 格式。"""
    # LocateAnything 要求坐标归一化至 [0, 999] 并使用特殊 Token 包裹
    # 具体 Token 化方式取决于 processor;此处展示逻辑结构
    lines = []
    for box in boxes:
        x1, y1, x2, y2 = box
        lines.append(f"<<box>>{int(x1)} {int(y1)} {int(x2)} {int(y2)}<<</box>>")
    return "\n".join(lines)

# 保存 recipe
with open("data/pseudo_labels.jsonl", "w") as f:
    for item in pseudo_labels:
         f.write(json.dumps(item) + "\n")

基于置信度的过滤策略:

在密集场景中,标准 NMS(Non-Maximum Suppression,非极大值抑制)可能过度抑制相互重叠的种子。替代方案如下:

 # 尺寸感知过滤:若两个框尺寸相近,则保留重叠框
# (种子大小大致均匀;若大框与小框重叠,大概率是误检)
def size_aware_filter(boxes, confs, size_tol=2.0):
    areas = [(b[2]-b[0])*(b[3]-b[1]) for b in boxes]
    median_area = np.median(areas)

    filtered = []
    for box, conf, area in zip(boxes, confs, areas):
        if area > median_area * size_tol or area < median_area / size_tol:
            continue  # 可能是误检(杂质、背景团块)
        filtered.append((box, conf))
     return filtered

阶段 4:Fine-tune LocateAnything-3B

伪标签数据已准备就绪,开始对主模型进行 Fine-tuning。

LocateAnything 使用 recipe JSON 定义数据混合比例。针对密集种子场景,有意提高密集/重叠场景的数据权重:

 {
  "datasets": [
    {
      "name": "seed_pseudo_dense",
      "path": "data/pseudo_labels.jsonl",
      "root": "data/raw_images/",
      "repeat": 3,
      "length": 15000
    },
    {
      "name": "seed_sam2_verified",
      "path": "data/human_verified_sam2.jsonl",
      "root": "data/raw_images/",
      "repeat": 8,
      "length": 2000
    },
    {
      "name": "seed_counting_captions",
      "path": "data/weak_count_captions.jsonl",
      "root": "data/raw_images/",
      "repeat": 2,
      "length": 5000
    }
  ]
 }

注意:人工核验数据的 repeat 设为 8。数量虽少,但质量更高。提高重复比例可防止模型仅学习 YOLO 的偏差。

训练脚本:

 # 脚本位置:eaglevl/train/locany_finetune_magi_stream.py

torchrun --nproc_per_node=4 \
  eaglevl/train/locany_finetune_magi_stream.py \
  --model_name_or_path nvidia/LocateAnything-3B \
  --meta_path "./recipes/seed_finetune_recipe.json" \
  --output_dir work_dirs/locany_seed_sft \
  --max_steps 10000 \
  --learning_rate 1e-5 \
  --warmup_ratio 0.1 \
  --lr_scheduler_type cosine \
  --bf16 True \
  --block_size 6 \
  --attn_implementation magi \
  --max_seq_length 8192 \
  --per_device_train_batch_size 1 \
  --gradient_accumulation_steps 8 \
  --save_steps 2000 \
  --logging_steps 10 \
  --report_to tensorboard \
   --deepspeed "deepspeed_configs/zero_stage2_config.json"

LoRA 替代方案(显存受限时):没有 4 张 A100 的话,可以走 LoRA。LocateAnything 支持

use_backbone_lora

use_llm_lora

 from transformers import AutoModel, AutoConfig

config = AutoConfig.from_pretrained(
    "nvidia/LocateAnything-3B",
    trust_remote_code=True
)

# 启用 LoRA
config.use_llm_lora = 64      # 语言模型的秩
config.use_backbone_lora = 64 # 视觉骨干网络的秩

model = AutoModel.from_pretrained(
    "nvidia/LocateAnything-3B",
    config=config,
    trust_remote_code=True,
    torch_dtype=torch.bfloat16
 )

LoRA 将可训练参数从约 38 亿压缩到约 2 亿,在单张 A100 或 2 张 RTX 4090 上即可完成 Fine-tuning。

进阶:弱监督计数辅助定位

如果连点标注都无法提供,只有图像级别的计数(例如"此托盘约有 320 粒种子"),可以通过以下方式引导定位:

 # 基于计数注意力的弱监督定位
import torch.nn.functional as F

def attention_peaks_to_points(model, processor, image_path, prompt, n_peaks=300):
    """
    利用模型中文本('seeds')与图像 patch 之间的交叉注意力,
    估计种子中心的可能位置。需要对模型进行前向 hook。
    """
    image = Image.open(image_path).convert("RGB")
    inputs = processor(text=prompt, images=image, return_tensors="pt").to("cuda")

    # 开启注意力输出的前向传播(如模型支持)
    with torch.no_grad():
        outputs = model(**inputs, output_attentions=True)

    # 提取视觉-语言注意力(层次相关,通常取最后几层)
    attn = outputs.attentions[-1]  # [batch, heads, text_tokens, image_patches]

    # 对多头取均值,并对种子相关的文本 Token 求和
    seed_token_indices = find_seed_tokens(inputs.input_ids, processor)
    seed_attn = attn[:, :, seed_token_indices, :].mean(dim=2)  # [batch, heads, patches]

    # 重塑为空间网格(MoonViT 使用合并后的 patch,448px 下约为 24x24)
    H = W = 24
    attn_map = seed_attn.mean(dim=1).reshape(H, W).cpu().numpy()

    # 寻找局部极大值
    from scipy.ndimage import maximum_filter
    local_max = (attn_map == maximum_filter(attn_map, size=3))
    peaks = np.argwhere(local_max)

    # 将 patch 索引转换为图像坐标
    img_w, img_h = image.size
    points = [(p[1] * img_w / W, p[0] * img_h / H) for p in peaks[:n_peaks]]

     return points

这些注意力极值点将作为伪点输入阶段 1 的 SAM2。不过这个结果存在一定噪声,但配合 NMS 和置信度过滤,足以为整个 Pipeline 提供初始数据(理论)。

使用 Fine-tune 后的模型进行推理

 from transformers import AutoModel, AutoProcessor
import torch

model = AutoModel.from_pretrained(
    "work_dirs/locany_seed_sft/checkpoint-10000",
    trust_remote_code=True,
    torch_dtype=torch.bfloat16
).cuda()

processor = AutoProcessor.from_pretrained(
    "nvidia/LocateAnything-3B",
    trust_remote_code=True
)

# 加载密集种子图像
image = "germination_tray_A7.jpg"

# Prompt 措辞很重要——明确说明对重叠情况的预期
prompts = [
    "Locate every single seed in this image, including overlapping ones.",
    "Detect all seeds. Do not miss any, even if they are partially covered.",
    "Count and bound each individual seed. Return all bounding boxes.",
]

best_result = None
max_boxes = 0

for prompt in prompts:
    inputs = processor(text=prompt, images=image, return_tensors="pt").to("cuda")

    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=1024,  # 允许输出大量边界框
            do_sample=False
        )

    result = processor.batch_decode(outputs, skip_special_tokens=False)[0]
    n_boxes = result.count("<<box>>")

    if n_boxes > max_boxes:
        max_boxes = n_boxes
        best_result = result

print(f"检测到 {max_boxes} 粒种子")
 print(best_result)

Ensemble Prompting

对于关键应用场景(如种子质检),可用多个 Prompt 分别推理,再通过 NMS 合并结果:

 from collections import defaultdict

def merge_prompt_results(results_list, iou_thresh=0.5):
    """
    不同 Prompt 会发现不同的种子,合并它们!
    """
    all_boxes = []
    for res in results_list:
        all_boxes.extend(parse_boxes(res))  # 自行实现解析器

    # 标准 NMS 或 soft-NMS
     return weighted_boxes_fusion(all_boxes, iou_thresh)

不同 Prompt 的召回范围往往不完全重叠,融合后通常能找到更多目标。

参考资源

  • 模型:nvidia/LocateAnything-3B
  • 论文:LocateAnything: Fast and High-Quality Vision-Language Grounding with Parallel Box Decoding
  • 代码:NVlabs/Eagle/Embodied
  • SAM2:facebook/sam2.1
  • 标注工具:CVAT(支持点标注),Label Studio

总结

密集重叠场景下的目标检测,不需要消耗数百小时手工标注。点监督、SAM2 自动分割、轻量级伪标签模型、LocateAnything-3B 的并行解码——四者结合,可以构建出生产级种子检测器,与传统框标注流程相比,标注时间减少约 90%。

让廉价模型和几何方法承担繁重工作,人力精力留给验证和边界案例。

https://avoid.overfit.cn/post/d5eae73f1cfa42688a4d3e970494702e

by Yash M Gupta, PhD

目录
相关文章
|
4天前
|
人工智能 IDE API
阿里云百炼Coding Plan指南:Lite及Pro高级版最新说明,Pro限量发售每天9:30补货
阿里云百炼Coding Plan是专为AI编程打造的订阅服务,现仅剩Pro版(200元/月),每日限量发售。含9万次/月调用额度,支持Qwen、Kimi、GLM等多模型及主流编程工具,告别Token焦虑。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
|
12天前
|
人工智能 Oracle 机器人
推理 → 行动 → 观察:用 LangChain + Python 实现一个智能体循环
智能体循环(Agentic Loop)突破单次问答局限,通过“推理→行动→观察”迭代闭环,让AI能自主分解任务、调用工具、持续优化直至目标完成,是构建真正自动化智能体的核心架构。
234 9
推理 → 行动 → 观察:用 LangChain + Python 实现一个智能体循环
|
5月前
|
机器学习/深度学习 人工智能 计算机视觉
YOLO26改进 - 注意力机制 | 多扩张通道细化器MDCR 通过通道划分与异构扩张卷积提升小目标定位能力
本文介绍了一种在YOLO26目标检测模型中引入高效解码器模块EMCAD的创新方法,以提升模型在资源受限场景下的性能与效率。EMCAD由多个模块构成,其中核心的EUCB(高效上卷积块)通过上采样、深度可分离卷积、激活归一化和通道调整等操作,兼顾了特征质量与计算成本。实验结果显示,该模块在显著减少参数与FLOPs的同时仍具备优异性能。文章还提供了完整的YOLO26模型集成流程、配置和训练实战。
YOLO26改进 - 注意力机制 | 多扩张通道细化器MDCR 通过通道划分与异构扩张卷积提升小目标定位能力
|
1天前
|
弹性计算 Linux 应用服务中间件
阿里云Linux云服务器部署Go项目:从环境配置到生产级服务全流程指南
本文提供了一份完整的阿里云Linux云服务器部署Go项目的实战指南。从ECS实例的选型与安全组配置入手,详细讲解了Go语言环境的安装与验证、项目代码的跨平台编译与文件传输、Systemd进程管理实现开机自启与异常重启、Nginx反向代理配置实现域名访问与负载均衡。同时涵盖了生产环境必备的监控告警、日志管理、GitHub Actions自动化CI/CD流水线搭建,以及部署过程中常见问题的排查与解决方案。全文贯穿了静态编译、最小权限原则、内网穿透等关键技术要点,帮助开发者将Go项目从本地开发环境平滑迁移到云端生产环境。
|
1天前
|
网络协议 网络性能优化 数据安全/隐私保护
阿里云智能接入网关对接使用完全指南:从硬件部署到混合云互联
本文系统阐述阿里云智能接入网关(Smart Access Gateway)的对接使用方法。SAG是阿里云提供的SD-WAN解决方案,包含硬件CPE、软件vCPE和APP三种产品形态,可将线下分支、门店、数据中心快速接入阿里云VPC。文章从产品选型开始,详细讲解硬件设备的购买、激活、组网配置(直挂与旁挂)、静态与动态路由(OSPF/BGP)配置、云连接网与云企业网的绑定流程,以及SAG APP的移动办公接入配置。同时深入探讨SAG与VPN网关、高速通道专线的混合组网与备份方案,以及SAG vCPE实现多云互联的实践。在高级功能方面,涵盖访问控制列表、QoS策略、应用识别与带宽保障等能力。最后提供
|
12天前
|
JSON Rust API
Pydantic v2 入门教程:模型、字段、验证器
本文详解 Pydantic v2(Python 3.10+)核心用法:模型定义、字段约束、自定义验证器(field/model)、嵌套/递归结构、序列化控制及 JSON Schema 生成,所有示例完整可运行,助你构建健壮数据验证与序列化逻辑。
103 1
Pydantic v2 入门教程:模型、字段、验证器
|
5天前
|
Java
如何编写github项目的README.md文件?
本教程面向中文用户,系统演示Markdown核心语法:标题(=、-、#)、引用块(&gt;)、代码块、列表、转义与HTML混排等,并附Java示例,简明实用,助你快速上手。(239字)
114 9
|
3天前
|
存储 运维 数据可视化
SOCKS5动态代理科普:原理、搭建方式与运维痛点解决方案
SOCKS5动态代理是兼容性强、支持全流量转发的通用代理协议。SSH动态代理无需额外部署,仅靠SSH隧道即可实现内网穿透、异地调试与安全上网。传统方案存在连接冗余、管理混乱等痛点,而SSHXTERM创新支持复用已有SSH会话创建多代理,提供可视化管理、加密存储与轻量运行,大幅提升运维效率。(239字)
112 7
|
2天前
|
自然语言处理 关系型数据库 MySQL
阿里云Elasticsearch搭建网站站内搜索功能:从零到生产级全栈实战指南
本文是一份基于阿里云Elasticsearch托管服务构建生产级站内搜索功能的完整实战指南。文章从传统数据库模糊查询的性能瓶颈出发,深入剖析了Elasticsearch倒排索引原理与BM25相关性评分机制。随后逐步演示了在阿里云控制台创建ES实例、配置Kibana访问、设计索引映射(Mapping)与IK中文分词器、通过Logstash将RDS MySQL数据全量与增量同步至ES、编写复杂DSL查询语句实现多条件组合搜索与高亮显示、在Java Spring Boot应用中集成ES官方RestClient、配置X-Pack安全权限与Kibana监控,最后系统讨论了索引设计、查询优化、集群规格选型
|
1天前
|
存储 人工智能 运维
给 AI Agent 加记忆之前,先决定它到底允许记住什么
给 AI Agent 接入记忆层之前,先区分短期上下文、长期事实和推理轨迹,并用最小 dry run 验证写入、检索、纠错和删除边界。