SAM3 标注交通牌主体:正负点提示策略

用 SAM3 标注交通标志时,默认分割经常会把灯杆、支架、阴影一起圈进来。关键在于提示点的位置和正负样本的配合。

单点提示(最简方案)

把提示点打在牌面中央,SAM 通常只会分割牌面:

牌型提示点位置
圆形限速牌圆心
三角警示牌三角形中间
矩形指示牌牌面中央
points = [[x_center, y_center]]
labels = [1]  # 1 = foreground

正点 + 负点过滤杆子

如果单点还是把杆子带入,加负样本点排除:

points = [
    [牌面中心x, 牌面中心y],  # 正样本
    [杆子中间x, 杆子中间y],  # 负样本
]
labels = [1, 0]  # 1=前景, 0=背景

实际调用:

masks, scores, logits = predictor.predict(
    point_coords=np.array(points),
    point_labels=np.array(labels),
    multimask_output=True,
)

# 取得分最高的 mask
best_mask = masks[np.argmax(scores)]

检测框缩小(配合 Grounding DINO)

Grounding DINO 给出的 bbox 通常包含了灯杆,传给 SAM 时手动收缩:

def shrink_box(box, ratio=0.85):
    x1, y1, x2, y2 = box
    cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
    w, h = (x2 - x1) * ratio, (y2 - y1) * ratio
    return [cx - w/2, cy - h/2, cx + w/2, cy + h/2]

tight_box = shrink_box(dino_box, ratio=0.8)

masks, scores, _ = predictor.predict(
    point_coords=None,
    point_labels=None,
    box=np.array(tight_box),
    multimask_output=False,
)

缩小 15~20% 通常能去掉杆子区域。

mask 面积过滤

有时 SAM 返回多个候选 mask,按面积过滤掉太大或太小的:

img_area = image.shape[0] * image.shape[1]

valid_masks = []
for mask in masks:
    area = mask.sum()
    ratio = area / img_area

    if 0.01 < ratio < 0.4:  # 占图片面积 1%~40%
        valid_masks.append(mask)

交通牌通常不超过画面的 40%,过滤掉背景误判。

完整管线(Grounding DINO + SAM3)

from groundingdino.util.inference import load_model, predict
from segment_anything import SamPredictor

# Step 1: DINO 定位交通牌
boxes, logits, phrases = predict(
    model=gd_model,
    image=image,
    caption="traffic sign",
    box_threshold=0.35,
    text_threshold=0.25,
)

# Step 2: 收缩框
tight_boxes = [shrink_box(b) for b in boxes]

# Step 3: SAM 精确分割
predictor.set_image(image)
for box in tight_boxes:
    masks, scores, _ = predictor.predict(
        box=np.array(box),
        multimask_output=False,
    )
    # 保存 mask...

调参建议

杆子还是出现 → 加负样本点在杆子上,或再缩小 bbox 5%

牌面被切掉 → bbox 缩小太多,调回 ratio=0.9

误检到路面标线 → 提高 DINO 的 box_threshold(0.35 → 0.45)

多块牌聚在一起multimask_output=True 后手动选面积最合适的 mask