当前位置: 首页
AI教程
微调LocateAnything-3B实现超高密度目标检测实战指南

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

热心网友 时间:2026-06-26
转载

针对图像中密集、重叠目标的检测难题,提出了一种半监督微调方案。该方法基于LocateAnything-3B模型及其并行解码的高效特性,通过少量点标注转化为边界框,训练轻量检测器生成伪标签,最终微调主模型。该流程极大降低了人工标注成本,实现了对成百上千个目标的快速精准定位。

想象一下这样一个场景:你手头有一批种子发芽托盘、谷物质检图像,或者植物学调查的样本照片。每张图像里密密麻麻挤着100到500粒种子,它们互相重叠、彼此遮挡,而你的老板或导师要求模型能精准定位每一粒。这事儿听起来就让人头大,不是吗?

问题在于,要在每张图像里手动给三百个互相重叠的目标画精确的边界框,人工标注这条路根本走不通。算一笔账就清楚了——标注一个框大约5秒钟,处理一张图就要花掉25分钟;要是有一千张图,光是标注就得耗上超过400个小时,这显然不现实。

那么,有没有更聪明的解法?本文就来聊聊如何借助NVIDIA的LocateAnything-3B——一个支持并行框解码的视觉语言定位模型——搭配一套半监督流水线,把人工标注量压缩到最低。

为什么选择LocateAnything-3B?

市面上大多数视觉语言定位模型,像Grounding DINO或OWL-ViT,走的是自回归路线,一个Token一个Token地生成边界框。如果要检测300个目标,速度会慢到让人难以忍受。而LocateAnything-3B采用了一种叫并行框解码的技术,一次性并行输出完整的边界框,而不是逐Token生成。这就让它在处理成百上千个目标的密集场景时,效率优势非常明显。

它的核心构成如下:

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

整体策略:四步走

整个方案分为四个阶段,本质上是一条半监督流水线:

  • 先做最少量点标注,而非框标注
  • 用SAM2从点生成初始边界框
  • 在合成数据和伪标签数据上训练一个轻量检测器
  • 对全部未标注数据生成伪标签,再用高置信度的伪标签配合特定训练策略微调LocateAnything-3B

阶段一:最少量标注——点转框

点击目标的中心点,速度比画边界框快大约10倍。标注300粒种子,打点只需大概5分钟,而画框需要25分钟左右。这笔账怎么算都划算。

具体操作是这样的:用SAM 2(Segment Anything Model 2)把每个点击点扩展成分割掩码,再从掩码反推出边界框。

先安装依赖:

pip install sam2 torch torchvision transformers opencv-python

点转框的流水线实现如下:

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把两粒相邻种子合并成一个掩码,试着把点位稍微下移再试一次,或者在提取边界框前对掩码做一次分水岭分割后处理。

阶段二:构建初始检测器

在正式接触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产生的明显误检,比如背景里的杂点。

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

接下来,把训练好的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": "Locate 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"<>{int(x1)} {int(y1)} {int(x2)} {int(y2)}<<>")
    return "".join(lines)

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

在密集场景中,标准NMS(非极大值抑制)可能会过度抑制那些互相重叠的种子。这里有个替代方案:采用尺寸感知的过滤策略。如果两个框的尺寸相近,就保留重叠的框。因为种子的大小大致均匀,若一个大框和一个小框重叠,大概率是误检。

\

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

阶段四:微调LocateAnything-3B

伪标签数据准备就绪,接下来就是对主模型进行微调了。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 \
    --sa ve_steps 2000 \
    --logging_steps 10 \
    --report_to tensorboard \
    --deepspeed "deepspeed_configs/zero_stage2_config.json"

如果显存紧张,可以用LoRA替代。LocateAnything支持use_backbone_lorause_llm_lora参数。LoRA可以把可训练参数从大约38亿压缩到约2亿,这样一来,单张A100或两张RTX 4090上就能完成微调。

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
)

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

如果连点标注都无法提供,只有图像级别的计数(比如"这个托盘大约有320粒种子"),那该怎么办?可以通过以下方式引导定位:

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和置信度过滤,理论上足够为整个流水线提供初始数据。

使用微调后的模型进行推理

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("<>")
    if n_boxes > max_boxes:
        max_boxes = n_boxes
        best_result = result

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

集成提示:对于关键应用场景,比如种子质检,可以用多个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://developer.aliyun.com/article/1742641

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
Claude Code 必知的14个高效工作流,让你的开发效率提升300%

Claude Code 必知的14个高效工作流,让你的开发效率提升300%

Claude Code 常用工作流 先分享几项核心判断:Claude Code 真正强大的地方,并非仅仅在于它能编写代码——而是它让“编码”这件事本身变得更加高效且可控。你大概率遇到过这类场景:接手一个陌生项目,花了一整天才能理清架构;线上出现报错,翻遍日志也找不到根本原因;想要重构遗留代码,又担心

时间:2026-07-09 17:49
阿里云通义AIGC平台完全指南:设计师AI生产力革命

阿里云通义AIGC平台完全指南:设计师AI生产力革命

一、写在前面:为什么设计师需要关注AIGC? 如果你还在手动一张一张制作海报、反复修改客户口中“感觉不对”的配色方案、为电商详情页准备几十张不同场景的产品图——那么你一定经历过这些痛点: 创意瓶颈:脑海中有画面,但手绘无法呈现 重复劳动:调整尺寸、更换背景、批量生成变体消耗了大量时间精力 成本焦虑:

时间:2026-07-09 16:48
零基础毕设代码二次开发:3文件定位法及Vue/Java修改对照表

零基础毕设代码二次开发:3文件定位法及Vue/Java修改对照表

每年一到毕业季,计算机专业的同学总会陷入一个共同的怪圈:从 GitHub 上扒下来一套代码,或者用 AI 生成一个项目,看起来挺完整的,可导师一句“加个筛选条件”或“换个页面颜色”,瞬间就懵了——不敢改,不会改,怕改崩。是不是很熟悉? 一、为什么AI生成的毕设代码你 "不敢改 "? 1 1 毕业生的三大

时间:2026-07-09 16:46
反向海淘订单系统:状态机与分布式事务实战设计

反向海淘订单系统:状态机与分布式事务实战设计

先分享一个反直觉的结论:反向海淘订单管理的真正挑战,往往不在于业务逻辑本身,而在于状态流转。一个订单的生命周期拉长到跨国运输,中间涉及的环节多、系统多、参与者多,状态稍有错乱就可能引发连锁事故。Taocarts团队在实践中踩了不少坑,最终沉淀下来的这套状态机与分布式事务方案,成功解决了这一复杂难题。

时间:2026-07-09 16:46
AI并未抢走程序员饭碗而是更新了编程菜单

AI并未抢走程序员饭碗而是更新了编程菜单

AI并未大规模替代程序员,而是改变了职业结构。重复性编码岗位需求下降35%至15%,而AIAgent开发等岗位需求激增187%。开发者焦虑从“被替代”转向“跟不上变化”,60%程序员已使用AI辅助编程。人的核心价值转向架构设计、技术决策和审查AI生成代码,AI技能带来16%薪资溢价。

时间:2026-07-09 16:46
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 热门数据榜