lovart3/docs/feature-object-picker.md

574 lines
21 KiB
Markdown
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.

# 「物品拾取器」功能开发方案 v2.0
> 📅 更新日期:2024-02
> 🔍 基于行业调研和技术分析的优化方案
---
## 一、需求分析
### 1.1 用户原始需求
- 用户选中画布图片后,点击"获取物品"按钮
- 鼠标变成红色小手图标
- 点击图片中的物品,小手图标贴在点击位置
- 后台调用 QWEN-VL 模型识别物品名称和位置
- 生成包含物品信息的标签到对话框
### 1.2 深度理解:用户的真实目标
**核心痛点:** 用户想要精准地告诉 AI "我要处理图片中的这个东西"
**当前问题:**
1. 用户只能用文字描述物品(如"左边的椅子"),AI 可能理解错误
2. 复杂场景中,文字描述不够精准(如"第二排右边第三个人")
3. 用户不知道 AI 能否正确识别他想要的物品
**用户期望:**
- 像"指哪打哪"一样精准指定目标物品
- AI 能准确理解并反馈识别结果
- 后续编辑操作(替换、删除、修改)能精准作用于该物品
---
## 二、行业调研与技术选型
### 2.1 竞品功能分析
| 产品 | 功能名称 | 实现方式 | 优缺点 |
|------|----------|----------|--------|
| **Midjourney** | Vary Region | 矩形/套索工具手动选区,修改 prompt 重新生成 | ✅ 灵活 ❌ 需手动勾选,学习成本高 |
| **Adobe Firefly** | Generative Fill | 点击+自动识别边界,AI 填充 | ✅ 智能边界识别 ❌ 依赖 Adobe 生态 |
| **Canva Magic Edit** | Magic Eraser | 画笔涂抹选区 | ✅ 直观 ❌ 精度依赖用户手绘 |
| **Remove.bg** | 前景分离 | 自动识别主体 | ✅ 一键操作 ❌ 只能选主体 |
### 2.2 核心技术调研
#### SAM (Segment Anything Model) - Meta
- **能力**:点击即可分割任意物品,生成精准蒙版
- **优势**:
- 支持点击 (Point)、框选 (Box)、文本 (Text) 三种选择方式
- 可在浏览器端运行(WebAssembly + ONNX)
- 实时响应,无需等待 API
- **API 服务**:
- Segmind API:$0.008/次,~3.8s 响应
- Fal.ai SAM2:支持 WebSocket 实时连接
#### Qwen-VL 视觉语言模型
- **能力**:识别物品名称、位置、属性
- **输出格式**:
- `bbox_2d`:边界框坐标
- `point_2d`:点坐标
- 物品描述文本
- **局限**:复杂场景准确率约 45%(需要结合分割模型提升精度)
#### Google MediaPipe Interactive Segmenter
- **能力**:点击生成分割蒙版
- **平台**:Android、Web、Python
- **优势**:轻量级,适合移动端
### 2.3 技术方案对比
| 方案 | 描述 | 延迟 | 精度 | 实现成本 |
|------|------|------|------|----------|
| **A. 纯 VLM** | 点击坐标 → Qwen-VL → 名称+位置 | ~2s | ⭐⭐ | 低 |
| **B. SAM + VLM** | 点击 → SAM 分割 → VLM 识别 | ~4s | ⭐⭐⭐⭐ | 中 |
| **C. 浏览器 SAM** | 预计算 Embedding → 点击即时分割 | <100ms | ⭐⭐⭐⭐⭐ | 高 |
| **D. SAM API + VLM** | SAM Cloud API → VLM | ~5s | ⭐⭐⭐⭐ | 中 |
### 2.4 推荐方案
**MVP 阶段**:方案 A(纯 VLM)
- 快速上线验证需求
- 1-2 天开发周期
**正式版本**:方案 B/D(SAM + VLM)
- 提供物品轮廓可视化
- 蒙版数据可用于后续精准编辑
**高级版本**:方案 C(浏览器端 SAM)
- 极致体验,鼠标悬停即可预览轮廓
- 适合高频用户场景
---
## 三、功能设计
### 3.1 功能定位
**「物品拾取器」** - 可视化的物品选择工具,让用户通过点击精准指定图片中的目标物品。
### 3.2 核心价值
| 价值点 | 说明 |
|--------|------|
| **精准指定** | 点击即选中,无需文字描述 |
| **即时反馈** | AI 识别后显示物品名称和轮廓,用户可确认 |
| **上下文传递** | 物品信息自动带入对话,后续指令更精准 |
| **降低沟通成本** | 减少"不是这个,是那个"的来回沟通 |
### 3.3 交互流程(MVP 版本)
```
┌─────────────────────────────────────────────────────────────┐
│ 1. 用户选中图片 → 工具栏显示「🎯」拾取按钮 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 2. 点击「🎯」→ 进入拾取模式 │
│ - 鼠标变成 ⊕ 准星图标 │
│ - 图片边框变成橙色虚线 │
│ - 顶部提示:「点击图片中的物品」 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 3. 用户点击图片某位置 │
│ - 点击位置显示脉冲动画圆点 ◉ │
│ - 提示:「正在识别...」 │
│ - 调用 API 识别物品 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. 识别完成 → 显示结果浮层 │
│ │
│ ┌────────────────────────────────┐ │
│ │ 🎯 识别结果 │ │
│ │ │ │
│ │ ┌──────┐ 黑色皮质沙发 │ │
│ │ │ 缩略 │ 位置:左侧中央 │ │
│ │ └──────┘ │ │
│ │ │ │
│ │ [重新选择] [添加到对话 ✓] │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 5. 用户确认 → 生成标签到对话框 │
│ │
│ 对话框: │
│ ┌─────────────────────────────────────────┐ │
│ │ 🎯 [缩略图] 黑色皮质沙发 · 左侧中央 │ │
│ └─────────────────────────────────────────┘ │
│ │
│ 用户输入:「把这个换成白色」 │
└─────────────────────────────────────────────────────────────┘
```
### 3.4 交互流程(增强版本 - 带 SAM 分割)
```
┌─────────────────────────────────────────────────────────────┐
│ 3. 用户点击图片某位置 │
│ - Step 1: SAM 分割 → 获取物品蒙版 │
│ - Step 2: VLM 识别 → 获取物品名称 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 4. 识别完成 → 图片上显示物品轮廓 │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ │ │
│ │ ╭───────────╮ ← 橙色轮廓线 │ │
│ │ │ 沙发 │ │ │
│ │ ╰───────────╯ │ │
│ │ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ 🎯 黑色皮质沙发 │ │ │
│ │ │ 左侧中央 │ │ │
│ │ │ [重新选] [添加到对话] │ │ │
│ │ └────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 3.5 数据结构
```typescript
interface PickedObject {
// 原图信息
imageUrl: string;
imageWidth: number;
imageHeight: number;
// 点击位置(相对坐标 0-1)
clickX: number;
clickY: number;
// AI 识别结果(严格格式)
objectName: string; // ≤8字,格式:[颜色/材质]+[物品],如"黑色沙发"
objectPosition: string; // 固定4字,如"左上区域"、"中央区域"
confidence: number; // 置信度 0-1
// 物品边界框(必需)
boundingBox: {
x: number; // 相对坐标 0-1,精确到小数点后2位
y: number;
width: number;
height: number;
};
// 分割蒙版(增强版,可选)
mask?: {
dataUrl: string; // Base64 PNG 蒙版图
polygon?: Array<{ x: number; y: number }>; // 轮廓点
};
}
// 位置词库常量
const POSITION_OPTIONS = [
'左上区域', '中央顶部', '右上区域',
'左侧中部', '中央区域', '右侧中部',
'左下区域', '中央底部', '右下区域'
] as const;
```
---
## 四、技术实现
### 4.1 图片压缩优化
为加速 AI 分析返回速度,前端在发送图片前需进行压缩:
```typescript
// 图片压缩配置
const PICKER_IMAGE_CONFIG = {
maxSize: 600, // 最大宽度或高度 600px
quality: 0.85, // JPEG 质量
format: 'jpeg' // 输出格式
};
```
**压缩策略:**
- 宽度 > 600px 或 高度 > 600px 时,等比缩放至最大边 600px
- 例如:1200x800 → 600x400,800x1200 → 400x600
- 保持宽高比不变,确保点击坐标映射准确
**压缩函数:**
```typescript
async function compressImageForPicker(imageUrl: string): Promise<{
compressedUrl: string; // Base64 或临时 URL
scale: number; // 缩放比例,用于坐标映射
originalWidth: number;
originalHeight: number;
}> {
const img = new Image();
img.crossOrigin = 'anonymous';
return new Promise((resolve, reject) => {
img.onload = () => {
const { naturalWidth, naturalHeight } = img;
let scale = 1;
let targetWidth = naturalWidth;
let targetHeight = naturalHeight;
// 计算缩放比例
if (naturalWidth > 600 || naturalHeight > 600) {
scale = Math.min(600 / naturalWidth, 600 / naturalHeight);
targetWidth = Math.round(naturalWidth * scale);
targetHeight = Math.round(naturalHeight * scale);
}
// Canvas 压缩
const canvas = document.createElement('canvas');
canvas.width = targetWidth;
canvas.height = targetHeight;
const ctx = canvas.getContext('2d')!;
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
const compressedUrl = canvas.toDataURL('image/jpeg', 0.85);
resolve({
compressedUrl,
scale,
originalWidth: naturalWidth,
originalHeight: naturalHeight
});
};
img.onerror = reject;
img.src = imageUrl;
});
}
```
### 4.2 API 设计
#### 端点
```
POST /api/pick-object
```
#### 请求
```json
{
"imageData": "data:image/jpeg;base64,...", // 压缩后的图片(Base64)
"clickX": 0.35,
"clickY": 0.42,
"originalImageUrl": "https://s3.xxx/image.jpg", // 原图 URL(用于生成标签)
"options": {
"includeMask": false,
"includePolygon": false
}
}
```
#### 响应
```json
{
"success": true,
"object": {
"name": "黑色皮质沙发",
"position": "图片左侧中央偏下",
"confidence": 0.92,
"boundingBox": {
"x": 0.1,
"y": 0.3,
"width": 0.4,
"height": 0.35
},
"mask": null,
"polygon": null
}
}
```
### 4.3 后端实现(MVP 版本)
#### Prompt 设计原则
- **字数限制**:名称 ≤8字,位置 ≤4字
- **格式统一**:固定词库,避免自由发挥
- **简洁清晰**:无冗余修饰词
#### 标准词库
**物品名称格式**:`[颜色/材质] + [物品]`(≤8字)
```
✅ 正确:黑色沙发、白色花瓶、木质书架、红色跑车
❌ 错误:一个非常漂亮的黑色皮质沙发、看起来像是白色的陶瓷花瓶
```
**位置描述词库**(固定4个字):
| 横向 | 纵向 | 组合示例 |
|------|------|----------|
| 左侧 | 顶部 | 左上区域 |
| 中央 | 中部 | 中央区域 |
| 右侧 | 底部 | 右下区域 |
```
✅ 正确:左上区域、中央区域、右下区域、左侧中部
❌ 错误:图片的左边偏上一点的位置、大概在中间靠右
```
#### 实现代码
```typescript
// lib/ai/pick-object.ts
export async function pickObject(
imageData: string,
clickX: number,
clickY: number,
originalImageUrl: string
): Promise<PickedObjectResult> {
const prompt = `
识别图片中坐标 (${(clickX * 100).toFixed(0)}%, ${(clickY * 100).toFixed(0)}%) 位置的物品。
【输出要求 - 严格遵守】
返回纯 JSON,无其他文字:
{
"name": "物品名称",
"position": "位置描述",
"confidence": 0.95,
"boundingBox": { "x": 0.1, "y": 0.2, "width": 0.3, "height": 0.4 }
}
【name 格式】≤8字,格式:[颜色/材质]+[物品]
正确:黑色沙发、白色花瓶、木质书架、玻璃茶几
错误:一个黑色的皮质沙发(太长)
【position 格式】固定4字,从以下选择:
横向:左侧/中央/右侧
纵向:顶部/中部/底部
组合:左上区域、左侧中部、左下区域、中央顶部、中央区域、中央底部、右上区域、右侧中部、右下区域
【boundingBox】相对坐标 0-1,精确到小数点后2位
`;
const result = await callQwenVL(imageData, prompt);
return JSON.parse(result);
}
```
**性能预估:**
| 图片尺寸 | 压缩后 | API 响应时间 |
|----------|--------|--------------|
| 2000x1500 | 600x450 | ~1-2s |
| 1200x800 | 600x400 | ~1-2s |
| 800x600 | 600x450 | ~1s |
| 600x400 | 不压缩 | <1s |
### 4.4 前端组件
```
components/
├── object-picker/
│ ├── ObjectPickerMode.tsx # 拾取模式状态管理
│ ├── ObjectPickerOverlay.tsx # 图片遮罩层
│ ├── ObjectPickerCursor.tsx # 自定义光标
│ ├── ObjectPickerResult.tsx # 识别结果弹窗
│ ├── ObjectPickerOutline.tsx # 物品轮廓显示(增强版)
│ └── PickedObjectTag.tsx # 对话框中的物品标签
```
### 4.5 Canvas Store 扩展
```typescript
interface CanvasStore {
// ... 现有状态
// 物品拾取模式
isPickerMode: boolean;
pickerTargetNodeId: string | null;
setPickerMode: (enabled: boolean, nodeId?: string) => void;
// 拾取结果
pickedObject: PickedObject | null;
setPickedObject: (obj: PickedObject | null) => void;
// 待添加到对话的拾取物品
pendingPickedObjects: PickedObject[];
addPendingPickedObject: (obj: PickedObject) => void;
clearPendingPickedObjects: () => void;
}
```
---
## 五、对话框集成
### 5.1 物品标签样式
```
┌────────────────────────────────┐
│ 🎯 [缩略图] 黑色沙发 · 左上区域 │
└────────────────────────────────┘
```
**标签格式**:`🎯 [缩略图] {名称} · {位置}`
- **图标** 🎯:区分普通图片和拾取物品
- **缩略图**:裁剪自边界框区域
- **名称**:≤8字,如"黑色沙发"
- **位置**:固定4字,如"左上区域"
**示例**:
| 完整标签 | 字数 |
|----------|------|
| 🎯 黑色沙发 · 左上区域 | 12字 |
| 🎯 白色花瓶 · 中央区域 | 12字 |
| 🎯 木质书架 · 右下区域 | 12字 |
### 5.2 发送消息时的数据
```typescript
{
type: "file",
mediaType: "image/jpeg",
url: "https://...",
// 🆕 物品拾取信息
pickedObject: {
name: "黑色皮质沙发",
position: "左侧中央",
clickX: 0.35,
clickY: 0.42,
boundingBox: { x: 0.1, y: 0.3, width: 0.4, height: 0.35 }
}
}
```
### 5.3 后端 Prompt 增强
当消息包含 `pickedObject` 时,自动增强 prompt:
```
用户想要处理图片中的「黑色皮质沙发」。
- 物品位置:左侧中央
- 边界框:(10%, 30%) - (50%, 65%)
- 用户点击坐标:(35%, 42%)
用户指令:把这个沙发换成白色
请精准地只修改该物品区域,保持其他部分不变。
```
---
## 六、扩展功能(未来迭代)
### 6.1 多物品选择
- 支持连续点击选择多个物品
- 对话框显示多个物品标签
- "把 A 和 B 都删掉"
### 6.2 负向选择
- 标记"不要这个"
- 用于排除干扰物品
### 6.3 智能建议
- 进入拾取模式时,自动高亮主要物品
- 显示可点击的热区提示
### 6.4 历史记录
- 记住用户最近拾取的物品
- 快速重新选择
### 6.5 快捷键
- 按住 Alt + 点击:直接拾取(跳过确认)
- Esc:退出拾取模式
---
## 七、开发计划
### Phase 1:MVP(1-2 天)
- [ ] 工具栏「🎯」拾取按钮
- [ ] 拾取模式 UI(光标、提示)
- [ ] `/api/pick-object` API
- [ ] 识别结果弹窗
- [ ] 物品标签生成到对话框
### Phase 2:体验优化(2-3 天)
- [ ] 点击脉冲动画
- [ ] 边界框可视化显示
- [ ] 标签样式优化
- [ ] 错误处理与重试
- [ ] 支持 ESC 退出
### Phase 3:功能增强(3-5 天)
- [ ] SAM 集成,显示物品轮廓
- [ ] 多物品选择
- [ ] 拾取历史记录
- [ ] 快捷键支持
---
## 八、总结
### 功能本质
**让用户从"描述物品"变成"指向物品"**,大幅降低人机沟通成本,提升图片编辑精准度。
### 核心创新
1. **点击即选中** - 最直观的物品选择方式
2. **AI 即时反馈** - 显示物品名称和轮廓,用户可确认
3. **上下文自动传递** - 物品信息(含坐标)无缝带入对话
### 竞争优势
- 市面上少有的"精准物品选择 + AI 编辑"组合
- 比 Midjourney 更简单(无需手动勾选)
- 比文字描述更准确(减少误解)
### 预期效果
- 用户满意度提升:减少"AI 理解错误"的挫败感
- 编辑效率提升:一次点击 > 多次文字描述
- 差异化竞争力:独特的交互体验