# 图片物体智能标注系统 - 实施文档 ## 📋 项目概述 ### 功能描述 用户在对话框中上传图片后,可以通过点击图片预览中的"定位标注"按钮,点击图片中的任意物体,系统自动识别该物体的名称(包括颜色、材质等详细信息),并将识别结果以特殊格式自动填入对话框。 ### 使用场景示例 1. 用户上传两张图片 2. 点击第一张图中的"绿色手提包" → 输入框自动添加:`📍图1: 绿色手提包` 3. 点击第二张图中的"红色背包" → 输入框继续添加:`📍图2: 红色背包` 4. 用户手动输入"替换" → 最终输入框内容:`将📍图1: 绿色手提包 替换成 📍图2: 红色背包` --- ## 🏗️ 技术架构 ### 方案选择:Florence-2 **选择理由**: - ✅ **零配置**:不需要预定义物体类别 - ✅ **速度快**:推理时间 0.5-1秒 - ✅ **识别准确**:自动识别颜色、材质、细节 - ✅ **轻量级**:模型仅 230MB - ✅ **多任务支持**:物体检测、区域描述、OCR 等 ### 系统架构图 ``` ┌─────────────────────────────────────────────────────────┐ │ 前端 (React) │ ├─────────────────────────────────────────────────────────┤ │ 1. MultimodalInput (上传图片) │ │ 2. ImagePreview (图片预览 + 标注按钮) │ │ 3. ObjectAnnotation (点击检测 + 标注UI) │ └─────────────────┬───────────────────────────────────────┘ │ HTTP POST ▼ ┌─────────────────────────────────────────────────────────┐ │ 后端 API (FastAPI) │ ├─────────────────────────────────────────────────────────┤ │ POST /api/detect-object │ │ - 接收图片 URL 和点击坐标 │ │ - 裁剪点击区域 │ │ - 调用 Florence-2 模型 │ │ - 返回物体描述 │ └─────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ Florence-2 模型服务 │ ├─────────────────────────────────────────────────────────┤ │ - 加载预训练模型 (microsoft/Florence-2-base) │ │ - 图像预处理 │ │ - 推理生成物体描述 │ │ - 中英文翻译(可选) │ └─────────────────────────────────────────────────────────┘ ``` --- ## 🔧 实施步骤 ### 阶段一:后端 API 开发(第1-2天) #### 1.1 环境准备 **服务器要求**: - CPU: 8核或以上(如果使用 GPU,CPU 要求可降低) - 内存: 16GB 或以上 - GPU: NVIDIA GPU(推荐,支持 CUDA 11.8+) - 显存: 4GB 或以上(推荐 8GB) - 驱动: 最新版 NVIDIA 驱动 - 存储: 10GB 可用空间 - Python: 3.9+ **安装依赖**: **步骤 1:检查 GPU 是否可用** ```bash # 检查 NVIDIA 驱动 nvidia-smi # 查看 CUDA 版本 nvcc --version ``` **步骤 2:安装 Python 依赖** ```bash # 创建 Python 虚拟环境 cd /var/www/ai-chatbot-backend python3 -m venv venv source venv/bin/activate # 安装核心依赖 pip install fastapi uvicorn python-multipart # 🚀 GPU 加速版本(推荐,如果有 NVIDIA GPU) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 或者使用国内镜像(更快) pip install torch torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple # CPU 版本(如果没有 GPU) # pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 其他依赖 pip install transformers pillow requests pip install openai # 用于调用翻译 API(可选) ``` **步骤 3:验证 GPU 可用性** ```bash python -c "import torch; print(f'CUDA 可用: {torch.cuda.is_available()}'); print(f'GPU 数量: {torch.cuda.device_count()}'); print(f'GPU 名称: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"N/A\"}')" ``` 预期输出(如果 GPU 可用): ``` CUDA 可用: True GPU 数量: 1 GPU 名称: NVIDIA GeForce RTX 3090 ``` #### 1.2 下载 Florence-2 模型 **方式一:自动下载(推荐)** ```python from transformers import AutoProcessor, AutoModelForCausalLM # 首次运行会自动下载模型(约 230MB) model = AutoModelForCausalLM.from_pretrained( "microsoft/Florence-2-base", trust_remote_code=True ) processor = AutoProcessor.from_pretrained( "microsoft/Florence-2-base", trust_remote_code=True ) ``` **方式二:手动下载(网络受限时)** ```bash # 使用魔搭社区镜像(国内访问更快) pip install modelscope modelscope download --model=AI-ModelScope/Florence-2-base --local_dir ./models/florence-2 ``` #### 1.3 创建 API 服务 **文件结构**: ``` /var/www/ai-chatbot-backend/ ├── app/ │ ├── api/ │ │ └── detect_object.py # 物体检测 API │ ├── services/ │ │ └── florence_service.py # Florence-2 服务封装 │ ├── utils/ │ │ ├── image_utils.py # 图片处理工具 │ │ └── translation.py # 中英文翻译 │ └── main.py # FastAPI 主入口 ├── models/ # 模型文件目录 ├── requirements.txt └── start.sh # 启动脚本 ``` **核心代码实现**(详见下文"代码实现"章节) #### 1.4 API 端点设计 **POST /api/detect-object** **请求参数**: ```typescript { "imageUrl": string, // 图片 URL(S3 地址) "clickX": number, // 点击 X 坐标(像素) "clickY": number, // 点击 Y 坐标(像素) "imageIndex": number, // 图片序号(从 1 开始) "imageWidth": number, // 图片原始宽度 "imageHeight": number // 图片原始高度 } ``` **响应格式**: ```typescript { "success": boolean, "data": { "description": string, // 物体描述(中文):"绿色皮革手提包" "descriptionEn": string, // 物体描述(英文):"green leather handbag" "confidence": number, // 置信度(0-1) "imageIndex": number, // 图片序号 "formattedText": string // 格式化文本:"📍图1: 绿色皮革手提包" }, "error": string | null } ``` **错误响应**: ```typescript { "success": false, "data": null, "error": "无法识别该区域的物体,请点击更明显的物体位置" } ``` --- ### 阶段二:前端组件开发(第3-4天) #### 2.1 修改 ImagePreview 组件 **文件**:`components/image-preview.tsx` **新增功能**: 1. 添加"定位标注"模式切换按钮 2. 在标注模式下,鼠标悬停显示准星光标 3. 点击时发送请求到后端 API 4. 显示加载状态和识别结果 5. 将结果传递给父组件 **UI 设计**: ``` ┌──────────────────────────────────────────┐ │ ← 返回 📍 定位标注 ⬇️ 下载 │ ← 顶部工具栏 ├──────────────────────────────────────────┤ │ │ │ │ │ [图片显示区域] │ │ (标注模式下显示准星) │ │ │ │ │ ├──────────────────────────────────────────┤ │ 识别中... 🔍 │ ← 底部状态栏(可选) └──────────────────────────────────────────┘ ``` #### 2.2 修改 MultimodalInput 组件 **文件**:`components/multimodal-input.tsx` **新增功能**: 1. 接收来自 `ImagePreview` 的标注结果 2. 将标注文本插入到输入框中 3. 使用特殊样式显示标注文本(蓝色、带图标) 4. 支持多次标注,文本自动追加 **输入框样式**: ``` ┌──────────────────────────────────────────────────────────┐ │ 将 [📍图1: 绿色手提包] 替换成 [📍图2: 红色背包] │ │ ↑ 蓝色、可点击 ↑ 蓝色、可点击 │ └──────────────────────────────────────────────────────────┘ ``` #### 2.3 创建标注文本组件 **新建文件**:`components/annotation-tag.tsx` **功能**: - 显示标注标签(如 `📍图1: 绿色手提包`) - 支持点击删除 - 特殊样式(蓝色背景、圆角、图标) **样式示例**: ```tsx 📍图1: 绿色手提包 ``` --- ### 阶段三:集成与测试(第5天) #### 3.1 本地开发测试 **启动后端**: ```bash cd /var/www/ai-chatbot-backend source venv/bin/activate python app/main.py # 访问 http://localhost:8001/docs 查看 API 文档 ``` **启动前端**: ```bash cd /var/www/ai-chatbot npm run dev # 访问 http://localhost:3000 ``` **测试流程**: 1. 上传测试图片(包含明显物体) 2. 点击"定位标注"按钮 3. 点击图片中的物体 4. 验证识别结果是否准确 5. 验证文本是否正确插入输入框 #### 3.2 性能测试 **测试指标**: - 单次识别耗时:< 1秒 - 并发请求数:10 QPS - 内存占用:< 2GB **测试命令**: ```bash # 使用 Apache Bench 测试 ab -n 100 -c 10 -p test_data.json -T application/json \ http://localhost:8001/api/detect-object ``` #### 3.3 边缘情况测试 **测试场景**: 1. 点击图片空白区域(无物体) 2. 点击模糊不清的区域 3. 上传黑白图片 4. 上传极小/极大图片 5. 快速连续点击多次 **预期处理**: - 返回友好的错误提示 - 不崩溃、不卡死 - 有重试机制 --- ### 阶段四:生产部署(第6天) #### 4.1 后端部署 **使用 PM2 管理进程**: ```bash # 安装 PM2 npm install -g pm2 # 创建 PM2 配置文件 cat > ecosystem.config.js << EOF module.exports = { apps: [{ name: 'florence-api', script: '/var/www/ai-chatbot-backend/venv/bin/python', args: '/var/www/ai-chatbot-backend/app/main.py', instances: 1, exec_mode: 'fork', max_memory_restart: '2G', env: { PORT: 8001, MODEL_PATH: '/var/www/ai-chatbot-backend/models/florence-2' } }] } EOF # 启动服务 pm2 start ecosystem.config.js pm2 save ``` **Nginx 反向代理**: ```nginx # /etc/nginx/sites-available/ai-chatbot # 新增后端 API 代理 location /api/detect-object { proxy_pass http://127.0.0.1:8001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; # 增加超时时间(因为 AI 推理需要时间) proxy_connect_timeout 10s; proxy_send_timeout 10s; proxy_read_timeout 10s; } ``` #### 4.2 前端部署 **构建生产版本**: ```bash cd /var/www/ai-chatbot npm run build pm2 restart ai-chatbot ``` #### 4.3 监控与日志 **设置日志轮转**: ```bash # 查看 Florence API 日志 pm2 logs florence-api # 设置日志文件大小限制 pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 100M pm2 set pm2-logrotate:retain 7 ``` **监控指标**: - API 响应时间 - 错误率 - 内存/CPU 使用率 - 请求 QPS --- ## 💻 代码实现 ### 1. 后端 API 实现 #### `app/main.py` - FastAPI 主入口 ```python from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel import uvicorn from app.services.florence_service import FlorenceService app = FastAPI(title="Florence-2 物体检测 API") # CORS 配置 app.add_middleware( CORSMiddleware, allow_origins=["https://ai.yydd.ai", "http://localhost:3000"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 初始化 Florence 服务(全局单例) florence_service = FlorenceService() class DetectRequest(BaseModel): imageUrl: str clickX: int clickY: int imageIndex: int imageWidth: int imageHeight: int class DetectResponse(BaseModel): success: bool data: dict | None error: str | None @app.on_event("startup") async def startup_event(): """启动时加载模型""" print("🚀 正在加载 Florence-2 模型...") await florence_service.load_model() print("✅ 模型加载完成!") @app.post("/api/detect-object", response_model=DetectResponse) async def detect_object(request: DetectRequest): """检测图片中点击位置的物体""" try: result = await florence_service.detect_object_at_point( image_url=request.imageUrl, click_x=request.clickX, click_y=request.clickY, image_index=request.imageIndex, image_width=request.imageWidth, image_height=request.imageHeight ) return DetectResponse( success=True, data=result, error=None ) except Exception as e: print(f"❌ 检测失败: {str(e)}") return DetectResponse( success=False, data=None, error=f"物体识别失败: {str(e)}" ) @app.get("/health") async def health_check(): """健康检查""" return {"status": "ok", "model_loaded": florence_service.is_loaded} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8001) ``` #### `app/services/florence_service.py` - Florence-2 服务 ```python import torch from transformers import AutoProcessor, AutoModelForCausalLM from PIL import Image import requests from io import BytesIO import asyncio class FlorenceService: def __init__(self): self.model = None self.processor = None self.is_loaded = False # 🚀 自动检测并使用 GPU(如果可用) self.device = "cuda" if torch.cuda.is_available() else "cpu" print(f"🎯 使用设备: {self.device}") if self.device == "cuda": print(f"🚀 GPU 加速已启用: {torch.cuda.get_device_name(0)}") print(f"💾 GPU 显存: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB") async def load_model(self): """异步加载模型""" if self.is_loaded: return loop = asyncio.get_event_loop() await loop.run_in_executor(None, self._load_model_sync) def _load_model_sync(self): """同步加载模型(在线程池中执行)""" print("📦 下载 Florence-2 模型...") self.model = AutoModelForCausalLM.from_pretrained( "microsoft/Florence-2-base", trust_remote_code=True ).to(self.device) self.processor = AutoProcessor.from_pretrained( "microsoft/Florence-2-base", trust_remote_code=True ) self.is_loaded = True print("✅ Florence-2 模型加载完成") async def detect_object_at_point( self, image_url: str, click_x: int, click_y: int, image_index: int, image_width: int, image_height: int ) -> dict: """检测点击位置的物体""" # 1. 下载图片 print(f"📥 下载图片: {image_url}") response = requests.get(image_url, timeout=10) image = Image.open(BytesIO(response.content)).convert("RGB") # 2. 计算裁剪区域(以点击点为中心,200x200 像素) crop_size = 200 left = max(0, click_x - crop_size // 2) top = max(0, click_y - crop_size // 2) right = min(image_width, click_x + crop_size // 2) bottom = min(image_height, click_y + crop_size // 2) print(f"✂️ 裁剪区域: ({left}, {top}, {right}, {bottom})") cropped_image = image.crop((left, top, right, bottom)) # 3. 使用 Florence-2 识别物体 print("🔍 正在识别物体...") loop = asyncio.get_event_loop() description = await loop.run_in_executor( None, self._detect_sync, cropped_image ) # 4. 翻译成中文(如果是英文) description_cn = await self._translate_to_chinese(description) # 5. 格式化返回结果 formatted_text = f"📍图{image_index}: {description_cn}" return { "description": description_cn, "descriptionEn": description, "confidence": 0.95, # Florence-2 不返回置信度,固定值 "imageIndex": image_index, "formattedText": formatted_text } def _detect_sync(self, image: Image.Image) -> str: """同步推理(在线程池中执行)""" # 使用