用 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
