GPT-Image-2 MVP — 不訓 base 模型,純 API,先看見 (image, mask, JSON) 三件套
關鍵領悟:樣例顯示 GPT-Image-2 多 instance、屬性綁定、場景元素一致性都很強。之前我擔心的「多 instance 串色」問題在實測樣例中不嚴重。對 SVGE 大多數下游任務(detection / scene understanding / robot perception / VLA training / benchmark),Quick Mode 一條路就夠。
用戶實測 6 張高速公路場景(多車輛 + 路障 + 維修設備 + 不同時段/天氣),每張展示 GPT-Image-2 在 SVGE 目標場景下的真實能力:
| 維度 | 樣例證據 | 對 SVGE 的意義 |
|---|---|---|
| 多 instance(4–6 物體) | 每張 4–6 車輛同時存在、各自獨立 | Quick Mode 多 instance 可行,不需要 InstanceDiffusion-style adapter |
| 屬性綁定 | 白 truck / 銀 sedan / 灰 SUV / 深色 sedan 在同圖無串色;圖 2 全白系是 prompt 顯式指定 | 屬性 prompt 有效,per-instance caption 可控 |
| 場景元素一致 | 6 張都有錐形路障 + 右側警戒區 + 至少 1 個維修設備(箭頭板/維修車/照明車) | 場景模板量產可行,prompt 能穩定召喚行業專屬元素 |
| 時段控制 | 黃昏(1, 5, 6)/ 白天(2)/ 夜雨(3)/ 雨天(4)/ 冷藍 twilight(7, 8)— 光線方向、色溫、強度都對 | 光線軸可控,「sunset / daytime / night / rainy / twilight」直接 prompt,至少 5 檔 |
| 天氣控制 | 雨天反射、濕路面、夜雨頭燈眩光、夕陽長影都符合物理 | 天氣軸可控 |
| 視角一致性 | 6 張都是「高架斜俯瞰、3–4 車道、視野往遠方收斂」 | camera angle 可控,能保持構圖風格 |
| 物理細節 | 陰影方向、紅色尾燈滲色、路面斑剝、輪胎水霧都真實 | 細節品質高,下游模型不容易學到 artifact |
| 行業 domain 元素 | 道路維修場景的箭頭板、警示斜紋、維修車形態、照明車都對 | 基底訓練分布覆蓋此 domain,無需額外 LoRA |
下圖是用戶已經提供的 mask 標註範例 — 對樣例 6(夕陽 cinematic)類構圖的 7 個車輛 instance 做了二值前景分離。這正是 SVGE Pipeline Stage 4(SAM 3.1 後驗 + 細化)的目標輸出格式:
| Mask 屬性 | 本範例 | SVGE 規格目標 | 狀態 |
|---|---|---|---|
| 解析度 | 1672×941(與 GPT-Image-2 輸出 1:1 對齊) | 1:1 與生成圖一致 | ✅ 對齊 |
| Instance 分離 | 7 個獨立白色 blob | 每 instance 獨立通道 | ⚠️ 此圖是合成單通道;實際 pipeline 需拆成 N 通道 |
| 邊緣品質 | 連續、無洞、無雜點 | SAM 3.1 級別 | ✅ 達標 |
| 覆蓋完整性 | 含車身輪胎 + 後輪罩 | 不丟細節 | ✅ 達標 |
| 背景純淨度 | 純黑、無偽前景 | 無假陽性 | ✅ 達標 |
SVGE 主文件 §1.3 列了 6 類下游服務。並非所有下游都需要嚴格 mask 邊界精度:
| 下游任務 | 對 mask 邊界精度需求 | Quick Mode 是否足夠 |
|---|---|---|
| Detection(YOLO 等) | bbox 為主,mask 粗細可接受 | ✅ 完全夠 |
| Instance Segmentation(Mask R-CNN) | mask IoU > 0.5 | ✅ SAM 3.1 後驗能達標 |
| Semantic Segmentation 細粒度 | boundary 像素級精確 | ⚠️ 邊緣 case,視任務 |
| Scene Understanding / VLM 訓練 | 不需要精確 mask | ✅ 完全夠 |
| Robot Perception / VLA Training | bbox + 粗 mask 即可 | ✅ 完全夠 |
| Benchmark / Demo / 邊角案例擴展 | 視覺合理即可 | ✅ 完全夠 |
結論:SVGE 主文件列的 6 類下游,5 類用 Quick Mode 就夠。Strict Mode 只在做純語義分割訓練資料時才必要。這是「Quick Mode 為主路徑、Strict 退到後備」的合理性所在。
| 輪次 | 我答了什麼 | 狀態 |
|---|---|---|
| 第 1 輪 | image-first:GPT-Image-2 純 prompt 生圖 → SAM 3.1 反推 (class, bbox, mask) | 部分對(Quick Mode 路徑),但沒明說違反 SVGE mask-first 主路徑 |
| 第 2 輪(用戶駁) | 用戶問「(class, bbox, mask) 不是應該一起定好嗎」 | — |
| 我改答 | strict mask-first:用戶提供 mask → GPT edit → 系統不抽,只驗證 | 部分對(Strict Mode 路徑),但對「先要點結果」太重 |
| 用戶再駁 | 「不對,再看 HTML」 | — |
| 第 3 輪(這次) | 承認兩種模式都有效,按目標選;output mask 本來就該是 image-aligned 不是用戶 spec mask | 應該對齊正確了 |
真正答案:輸出 mask = Stage 4 image-aligned 的 refined mask。但用 IoU(refined, stage2_intent) > 0.7 作為「服從度」門檻,IoU 太低就 reject 整個樣本。(class, bbox) 從用戶意圖保留,mask 對齊實際生成圖。
(image, mask 對齊到 image, JSON 描述 image)
images.edit API 是 單 mask + 單 prompt,沒有 per-instance prompt 概念。對 SVGE 多 instance 場景:
樣例顯示 GPT-Image-2 不擅長精確 bbox 控制,但很擅長「語義位置」(左/中/右車道、近/中/遠視野)。所以 v0 的 prompt 構造把 bbox 翻成自然語言位置:
def build_prompt_from_spec(spec):
parts = []
# 1) 場景骨架(樣例都吃這套)
parts.append(f"{spec.scene.style} aerial view of a highway")
parts.append(spec.scene.lighting) # "sunset" / "daytime" / "night with rain"
if getattr(spec.scene, 'weather', None):
parts.append(f"in {spec.scene.weather} weather")
# 2) per-instance 描述(GPT-Image-2 能處理的精度)
for obj in spec.objects:
color = obj.attributes.get('color', '')
location = bbox_to_lane(obj.bbox) # ★ 關鍵轉換
parts.append(
f"a {color} {obj.class_name} in the {location}"
)
# 3) 場景一致性元素(樣例都有:cones / arrow board / maintenance vehicle)
if spec.scene.background == "road_inspection":
parts.append("orange traffic cones lining the right shoulder")
parts.append("yellow arrow signal board on the right")
if spec.scene.background == "highway_construction":
parts.append("road maintenance vehicles with red and white chevron warning patterns")
return ", ".join(parts)
def bbox_to_lane(bbox):
"""把 normalized bbox 轉成「車道 + 視野位置」的自然語言。
Args:
bbox: (x, y, w, h) all in [0, 1]
Returns:
str like "left lane in middle view" / "right shoulder in foreground"
"""
x, y, w, h = bbox
cx = x + w / 2
cy = y + h / 2
# 水平位置 → 車道
if cx < 0.25: lane = "left lane"
elif cx < 0.45: lane = "left-middle lane"
elif cx < 0.55: lane = "middle lane"
elif cx < 0.75: lane = "right-middle lane"
elif cx < 0.92: lane = "right lane"
else: lane = "right shoulder"
# 垂直位置 → 視野遠近
if cy < 0.30: depth = "far view"
elif cy < 0.55: depth = "middle view"
elif cy < 0.80: depth = "near view"
else: depth = "foreground"
return f"{lane} in {depth}"
不同下游任務對 mask 精度的容忍度不同。Quick Mode 的 SAM-bbox-IoU 過濾門檻應依下游任務動態調整,避免「一刀切 0.5」帶來的浪費或品質不足:
| 下游任務 | 建議 IoU 門檻 | 預期通過率 | 備註 |
|---|---|---|---|
| Detection(YOLO 等 bbox-only) | 0.3(鬆) | ~85% | bbox 才是 ground truth,mask 只是輔助 |
| Instance Segmentation(COCO-style) | 0.5(標準) | ~70% | 標準 COCO mAP 門檻 |
| Scene Understanding / VLM 訓練 | 0.4 | ~80% | 不直接用 mask 訓練 |
| Robot Perception / VLA Training | 0.4 | ~80% | 抓取需大致位置即可 |
| Semantic Segmentation 細粒度 | 0.7(嚴) | ~30% | 應切 Strict Mode 或 FLUX.2 + ControlNet |
| Demo / Benchmark 多樣性 | 0.3(鬆) | ~85% | 視覺合理即可 |
filter.mask_bbox_iou_threshold 欄位,按下游任務動態設置。預設 0.4(兼顧通過率與品質),生產 segmentation 訓練資料時切 0.5 或 0.7。
多數時候你不需要切過來。Quick Mode 的 SAM 3.1 後驗抽 mask 對 6 個下游任務的 5 個都夠用。
| 維度 | Quick Mode | Strict Mode |
|---|---|---|
| (class, bbox, mask) 在哪定? | Stage 4 後驗(SAM 抽) | Stage 2 派生鎖定 |
| 用戶 spec 的 mask 角色 | 選用,沒給也不影響 | 核心輸入(沒給就派生) |
| 用戶 spec 的 bbox 角色 | 驗證 SAM 結果(鬆 IoU 0.5) | 強制條件 + 驗證 IoU 0.7 |
| GPT-Image-2 用法 | images.generate 純文字 | images.edit 帶 mask |
| 多 instance 處理 | 較好(單次 generate,無 mask 串色) | 較差(mask edit 屬性串色,需逐 instance) |
| 適合 spec 複雜度 | 1–4 instance | 1–2 instance(單次)/ 1–3 instance(逐 instance) |
| 輸出 mask | SAM 抽(自然對齊 image) | SAM verify_mask(對齊 image,且 IoU > 0.7 確認對得住意圖) |
| Reject rate(預期) | 30-50% | 70-80% |
| 每 1k 通過樣本的 GPT-Image-2 成本 | 1.5k 張 × $0.05 = ~$75 | 4k 張 × $0.05 = ~$200 |
| 適合目標 | 看點結果、demo、samples | 下游 detection / segmentation 訓練資料 |
| 對應 SVGE 主文件 | 更接近 B 子系統範式 | 對齊 A 子系統 §8.A |
| API | 輸入 | 實際行為 |
|---|---|---|
images.generate | prompt 文字 | 無位置控制 |
images.edit | image + mask + prompt | mask alpha=0 區域「鼓勵」編輯,但邊界會軟化,不像 ControlNet 強制 |
| FLUX.2 / SD 3.5 + ControlNet | image + ControlNet 條件 + prompt | 嚴格 mask 條件,邊界精度高 |
| 方案 | 適合 | 缺點 |
|---|---|---|
| A. 全 composite + 全局 prompt | 屬性不衝突("3 cars in parking lot") | 屬性互衝會串色 |
| B. 逐 instance edit | 強制屬性精確 | N 次 API call、後面改前面、貴又不穩 |
| C. 切 Gemini 2.5 Flash Image | 多 reference 原生支援 | 不是 GPT-Image-2,但用戶可能要重新評估 |
| D. 切本地 FLUX.2 + ControlNet | 最嚴格 + InstanceDiffusion 風格 per-instance | 需 GPU、需自己 host |
| 項目 | 量 | 單價 | 小計 |
|---|---|---|---|
| GPT-Image-2 generate(reject 25%,樣例校準) | 1.3k 張 | ~$0.04-0.05/張 | $52-65 |
| SAM 3.1 inference(本地 RTX 4090) | 1.3k 張 | 免費 | $0 |
| Qwen3-VL 8B 本地 | ~3-5k instance | 免費 | $0 |
| SigLIP 2 評分 | 1.3k 張 | 免費 | $0 |
| 合計 | — | — | $52-65 |
樣例顯示 GPT-Image-2 在道路維修 domain 命中率高,reject 預期從原估的 33% 下調到 25%。實際數字以 D2(100 張壓力測)為準。
| 項目 | 量 | 單價 | 小計 |
|---|---|---|---|
| GPT-Image-2 edit(reject 75%) | 4k 張 | ~$0.05/張 | $200 |
| 多 instance 逐 edit 加成(平均 1.5×) | +50% | — | +$100 |
| SAM 3.1 + Qwen3-VL(本地) | — | 免費 | $0 |
| 合計 | — | — | $200-300 |
| 項目 | 量 | 單價 | 小計 |
|---|---|---|---|
| FLUX.2 [klein] 9B 本地推理 | 3k 張(reject 67%) | RTX 4090 ~6 sec/張 | 5 hr 電費 ≈ $0 |
| 初始模型下載 | ~18 GB | — | — |
| SAM 3.1 / Qwen3-VL / SigLIP 2 | — | 免費 | $0 |
| 合計 | — | — | ~$0(電費忽略) |
結論:Quick Mode 是「先看點結果」最便宜的路($75 / 1k 樣本,半天)。Strict Mode 是中介選項。真要規模化生產訓練資料,本地 FLUX.2 邊際成本歸零是長期解。
對應 prototype_v0/src/,新增 v0_workflow.py 統一兩模式 entry point:
from openai import OpenAI
from sam3 import SAM3Predictor
from transformers import AutoModel, AutoProcessor
import numpy as np
class V0Workflow:
def __init__(self, mode: str = "quick", config: dict = None):
assert mode in ["quick", "strict"]
self.mode = mode
self.openai = OpenAI()
self.sam3 = SAM3Predictor.from_pretrained("facebook/sam3.1")
self.qwen3vl = self._load_qwen3vl()
self.siglip = self._load_siglip2()
self.config = config or {}
def generate(self, spec):
if self.mode == "quick":
return self._quick_pipeline(spec)
else:
return self._strict_pipeline(spec)
# ============== Quick Mode ==============
def _quick_pipeline(self, spec):
prompt = self._build_prompt(spec)
# Stage 3: 純文生圖
resp = self.openai.images.generate(
model="gpt-image-2",
prompt=prompt,
size="1024x1024",
quality="high",
n=3,
)
candidates = [self._download(r.url) for r in resp.data]
for img in candidates:
# Stage 4: SAM 3.1 後驗抽 (class, bbox, mask)
sam_results = self.sam3.predict(
img,
concepts=[obj.class_name for obj in spec.objects],
)
# Stage 5: Qwen3-VL 抽屬性
for inst in sam_results:
inst.attrs = self.qwen3vl.extract_attrs(img, inst.bbox)
# Stage 6: 鬆過濾
if self._quick_filter(img, sam_results, spec):
yield self._build_output(img, sam_results, spec, mode="quick")
# ============== Strict Mode ==============
def _strict_pipeline(self, spec):
# Stage 2: 鎖三元組
locked = self._lock_triples(spec) # 每個 obj → (class, bbox, mask)
composite_mask = self._composite_masks(locked)
# Stage 3: GPT-Image-2 mask edit
prompt = self._build_prompt(spec)
resp = self.openai.images.edit(
image=self._blank_canvas(),
mask=composite_mask,
prompt=prompt,
size="1024x1024",
n=3,
)
candidates = [self._download(r.url) for r in resp.data]
for img in candidates:
# Stage 4: 驗證 & 細化
verify_masks = self.sam3.predict(
img,
concepts=[t.class_name for t in locked],
)
ious = [self._iou(v.mask, l.mask)
for v, l in zip(verify_masks, locked)]
if min(ious) < 0.7:
continue # reject
# Stage 5: 過濾
if self._strict_filter(img, verify_masks, locked, spec):
yield self._build_output(
img, verify_masks, spec,
mode="strict",
intent_ious=ious,
)
def _lock_triples(self, spec):
triples = []
for obj in spec.objects:
if obj.mask:
m = self._load_mask(obj.mask)
elif obj.ref_image:
m = self._sam3_from_ref(obj.ref_image, obj.bbox)
else:
m = self._bbox_to_blob(obj.bbox)
triples.append(LockedTriple(obj.class_name, obj.bbox, m))
return triples
def _build_output(self, img, masks, spec, mode, intent_ious=None):
return {
"image": img,
"masks": [m.mask for m in masks], # image-aligned
"annotation": {
"instances": [
{
"class_name": m.class_name,
"bbox": m.bbox, # SAM 抽的或 verify 的
"mask_pixel_grounding": True,
"spec_intent_iou": intent_ious[i] if intent_ious else None,
}
for i, m in enumerate(masks)
],
"scene": spec.scene.dict(),
"mode": mode,
},
}