Files
Mini-Nav/notebooks/proposal_segament.py

384 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import marimo
__generated_with = "0.21.1"
app = marimo.App()
@app.cell
def _():
import marimo as mo
import numpy as np
import polars as pl
from PIL import Image, ImageDraw, ImageFont
import torch
import io
import requests
return Image, ImageDraw, ImageFont, mo, np, torch
@app.cell
def _(np):
from scenegraph import RoomNode
from simulator import (
HabitatSimulatorConfig,
TopDownSceneElements,
create_habitat_simulator,
render_topdown_scene_map,
)
from habitat.utils.visualizations import maps
scene_path = "data/scene_datasets/habitat-test-scenes/skokloster-castle.glb"
image_size = 768
num_rooms = 3
views_per_room = 6
meters_per_pixel = 0.05
sim, agent = create_habitat_simulator(
HabitatSimulatorConfig(
scene_path=scene_path,
views_per_room=views_per_room,
image_size=image_size,
sensor_height=1.5,
move_forward_step=0.25,
enable_physics=False,
)
)
room_nodes = []
for _idx in range(num_rooms):
point = sim.pathfinder.get_random_navigable_point()
room_nodes.append(
RoomNode(
room_id=f"room_{_idx:02d}",
center=np.asarray(point, dtype=np.float32),
bbox_extent=np.asarray([1.5, 2.0, 1.5], dtype=np.float32),
)
)
render_topdown_scene_map(
pathfinder=sim.pathfinder,
elements=TopDownSceneElements(room_nodes=room_nodes),
meters_per_pixel=meters_per_pixel,
maps_module=maps,
)
return agent, room_nodes, sim, views_per_room
@app.cell
def _(agent, room_nodes, sim, views_per_room):
from rich.progress import track
from compressors import MaskScoringConfig, rank_masks
from compressors.pipeline import HashPipeline
from compressors.proposal import (
extract_masked_region,
generate_proposals_batch,
)
from simulator import save_object_image, save_room_view
from simulator import collect_room_views_by_room
all_room_views = collect_room_views_by_room(
agent=agent,
sim=sim,
room_nodes=room_nodes,
views_per_room=views_per_room,
)
return (all_room_views,)
@app.cell
def _(ImageDraw, ImageFont, all_room_views, mo, torch):
from transformers import Owlv2Processor, Owlv2ForObjectDetection
from utils import get_device, numpy_to_pil
device = get_device()
# 你真正想控制的显示阈值
score_threshold = 0.25
# 1. 加载模型
processor = Owlv2Processor.from_pretrained(
"google/owlv2-base-patch16-ensemble"
)
model = Owlv2ForObjectDetection.from_pretrained(
"google/owlv2-base-patch16-ensemble"
).to(device)
model.eval()
# 2. 读取图片
image = numpy_to_pil(all_room_views["room_00"][1])
# 3. 文本查询
text_labels = [
[
"a cat",
"a dog",
"a TV remote control",
"a chair",
"a table",
"a vase",
"a painting",
"a window"
]
]
# 4. 推理
inputs = processor(text=text_labels, images=image, return_tensors="pt").to(
device
)
with torch.no_grad():
outputs = model(**inputs)
# 5. 后处理
target_sizes = torch.tensor([(image.height, image.width)])
results = processor.post_process_grounded_object_detection(
outputs=outputs,
target_sizes=target_sizes,
threshold=0.1, # 模型后处理的第一层粗筛
text_labels=text_labels,
)
result = results[0]
boxes = result["boxes"]
scores = result["scores"]
pred_labels = result["text_labels"]
# 6. 二次过滤:只保留高分目标
filtered_items = [
(_box, _score, _text_label)
for _box, _score, _text_label in zip(boxes, scores, pred_labels)
if _score.item() >= score_threshold
]
# 7. 画框
_vis_image = image.copy()
_draw = ImageDraw.Draw(_vis_image)
try:
font = ImageFont.truetype("arial.ttf", 18)
except Exception:
font = ImageFont.load_default()
for _box, _score, _text_label in filtered_items:
_x1, _y1, _x2, _y2 = [float(v) for v in _box.tolist()]
_label = f"{_text_label}: {_score.item():.3f}"
# 边框
_draw.rectangle([_x1, _y1, _x2, _y2], outline="red", width=3)
# 文本背景框
try:
_tx1, _ty1, _tx2, _ty2 = _draw.textbbox((_x1, _y1), _label, font=font)
except Exception:
_w = _draw.textlength(_label, font=font)
_h = 20
_tx1, _ty1, _tx2, _ty2 = _x1, _y1, _x1 + _w + 6, _y1 + _h
text_bg = [_tx1, max(0, _ty1 - 2), _tx2 + 4, _ty2 + 2]
_draw.rectangle(text_bg, fill="red")
_draw.text((_x1 + 2, max(0, _y1)), _label, fill="white", font=font)
# 8. 结果文本
detection_lines = []
for _box, _score, _text_label in filtered_items:
box_rounded = [round(_v, 2) for _v in _box.tolist()]
detection_lines.append(
f"- {_text_label}: score={_score.item():.3f}, box={box_rounded}"
)
if not detection_lines:
detection_text = f"没有检测到 score >= {score_threshold:.2f} 的目标"
else:
detection_text = "\n".join(detection_lines)
# 9. marimo 输出
mo.vstack(
[
mo.md(
f"## OWLv2 检测可视化结果\n\n过滤阈值:`score >= {score_threshold:.2f}`"
),
mo.image(_vis_image, width=700),
mo.md(detection_text),
]
)
return device, filtered_items, image
@app.cell
def _(Image, ImageDraw, device, filtered_items, image, mo, np, torch):
from transformers import Sam2Processor, Sam2Model
# -----------------------------
# 0. 加载 SAM2
# -----------------------------
sam2_model_id = "facebook/sam2.1-hiera-large"
sam2_processor = Sam2Processor.from_pretrained(sam2_model_id)
sam2_model = Sam2Model.from_pretrained(sam2_model_id).to(device)
sam2_model.eval()
# -----------------------------
# 1. 从上一格的 OWLv2 结果中取框
# filtered_items: [(box, score, text_label), ...]
# SAM2 需要的 box 格式:
# [[[x1, y1, x2, y2], [x1, y1, x2, y2], ...]]
# 这里最外层是 batch 维度,因为我们当前只处理 1 张图
# -----------------------------
input_boxes = [
[
[float(v) for v in box.tolist()]
for box, score, text_label in filtered_items
]
]
# 没有框就直接提前返回
if len(input_boxes[0]) == 0:
mo.vstack(
[
mo.md("## SAM2 分割结果"),
mo.md(
"没有可用于分割的检测框,请先降低 OWLv2 的 score_threshold 或检查检测结果。"
),
]
)
else:
# -----------------------------
# 2. 预处理并推理
# 官方文档支持 bounding box prompt
# 并且 multimask_output=False 时,每个框只返回一张最佳 mask
# -----------------------------
sam2_inputs = sam2_processor(
images=image,
input_boxes=input_boxes,
return_tensors="pt",
).to(device)
with torch.no_grad():
sam2_outputs = sam2_model(**sam2_inputs, multimask_output=False)
# -----------------------------
# 3. 后处理到原图尺寸
# 文档说明 pred_masks 需要 post_process_masks
# -----------------------------
all_masks = sam2_processor.post_process_masks(
sam2_outputs.pred_masks.cpu(),
sam2_inputs["original_sizes"],
)
# 当前只有 1 张图,所以取第 0 个
# 形状通常是 [num_objects, num_masks, H, W]
# 由于 multimask_output=Falsenum_masks 通常为 1
masks_for_image = all_masks[0]
# 兼容处理:压掉单 mask 维度
# 目标形状变成 [num_objects, H, W]
if masks_for_image.ndim == 4 and masks_for_image.shape[1] == 1:
masks_for_image = masks_for_image[:, 0]
# IoU 分数,文档中也给出了 outputs.iou_scores
# 形状通常是 [batch_size, point_batch_size, num_masks]
iou_scores = sam2_outputs.iou_scores.detach().cpu()[0]
if iou_scores.ndim == 2 and iou_scores.shape[1] == 1:
iou_scores = iou_scores[:, 0]
# -----------------------------
# 4. 可视化:把 mask 半透明叠加到原图上
# -----------------------------
base_rgba = image.convert("RGBA")
overlay = Image.new("RGBA", image.size, (0, 0, 0, 0))
overlay_draw = ImageDraw.Draw(overlay)
# 准备一个可重复的颜色列表
colors = [
(255, 0, 0, 90),
(0, 255, 0, 90),
(0, 0, 255, 90),
(255, 255, 0, 90),
(255, 0, 255, 90),
(0, 255, 255, 90),
(255, 128, 0, 90),
(128, 0, 255, 90),
]
# 先画 mask再画 box
_vis_image = base_rgba.copy()
for _idx, ((_box, _score, _text_label), mask) in enumerate(
zip(filtered_items, masks_for_image)
):
color = colors[_idx % len(colors)]
# mask -> numpy bool
mask_np = mask.numpy() > 0
# 做一层彩色 mask
mask_rgba = np.zeros((image.height, image.width, 4), dtype=np.uint8)
mask_rgba[mask_np] = color
mask_img = Image.fromarray(mask_rgba, mode="RGBA")
_vis_image = Image.alpha_composite(_vis_image, mask_img)
# 画框和标签
_draw = ImageDraw.Draw(_vis_image)
for _idx, ((_box, _score, _text_label), mask) in enumerate(
zip(filtered_items, masks_for_image)
):
_x1, _y1, _x2, _y2 = [float(v) for v in _box.tolist()]
iou_val = (
float(iou_scores[_idx].item())
if _idx < len(iou_scores)
else float("nan")
)
_label = (
f"{_text_label} | owl={_score.item():.3f} | sam2_iou={iou_val:.3f}"
)
_draw.rectangle([_x1, _y1, _x2, _y2], outline=(255, 0, 0, 255), width=3)
try:
_tx1, _ty1, _tx2, _ty2 = _draw.textbbox((_x1, _y1), _label)
except Exception:
_tx1, _ty1, _tx2, _ty2 = _x1, _y1, _x1 + 220, _y1 + 20
_draw.rectangle(
[_tx1, max(0, _ty1 - 2), _tx2 + 4, _ty2 + 2],
fill=(255, 0, 0, 220),
)
_draw.text((_x1 + 2, max(0, _y1)), _label, fill="white")
# -----------------------------
# 5. 输出摘要
# -----------------------------
summary_lines = []
for _idx, ((_box, _score, _text_label), mask) in enumerate(
zip(filtered_items, masks_for_image)
):
mask_area = int((mask.numpy() > 0).sum())
iou_val = (
float(iou_scores[_idx].item())
if _idx < len(iou_scores)
else float("nan")
)
summary_lines.append(
f"- {_text_label}: owl_score={_score.item():.3f}, sam2_iou={iou_val:.3f}, mask_area={mask_area}"
)
mo.vstack(
[
mo.md("## SAM2 分割可视化结果"),
mo.image(_vis_image, width=700),
mo.md(
"\n".join(summary_lines) if summary_lines else "没有生成任何 mask"
),
]
)
return
if __name__ == "__main__":
app.run()