ai-chatbot/图片物体智能标注系统-实施文档.md
Your Name 53ddb7e585
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
Initial commit - clean repository
2025-12-17 20:15:18 +08:00

1241 lines
35 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.

# 图片物体智能标注系统 - 实施文档
## 📋 项目概述
### 功能描述
用户在对话框中上传图片后,可以通过点击图片预览中的"定位标注"按钮,点击图片中的任意物体,系统自动识别该物体的名称(包括颜色、材质等详细信息),并将识别结果以特殊格式自动填入对话框。
### 使用场景示例
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
<span className="inline-flex items-center gap-1 px-2 py-1 bg-blue-500/10 text-blue-600 rounded-md">
<span>📍图1: 绿色手提包</span>
<button onClick={onRemove}>×</button>
</span>
```
---
### 阶段三:集成与测试(第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:
"""同步推理(在线程池中执行)"""
# 使用 <CAPTION> 任务生成简短描述
prompt = "<CAPTION>"
inputs = self.processor(
text=prompt,
images=image,
return_tensors="pt"
).to(self.device)
# 生成描述
with torch.no_grad():
generated_ids = self.model.generate(
input_ids=inputs["input_ids"],
pixel_values=inputs["pixel_values"],
max_new_tokens=50,
num_beams=3,
do_sample=False
)
# 解码结果
generated_text = self.processor.batch_decode(
generated_ids,
skip_special_tokens=False
)[0]
# 提取描述(去除提示词)
description = generated_text.replace(prompt, "").strip()
print(f"✅ 识别结果: {description}")
return description
async def _translate_to_chinese(self, text: str) -> str:
"""翻译成中文(简单的关键词映射)"""
# 方案1: 简单的关键词映射(快速、离线)
keyword_map = {
"red": "红色",
"green": "绿色",
"blue": "蓝色",
"yellow": "黄色",
"black": "黑色",
"white": "白色",
"brown": "棕色",
"pink": "粉色",
"purple": "紫色",
"orange": "橙色",
"gray": "灰色",
"handbag": "手提包",
"backpack": "背包",
"bag": "包",
"shoe": "鞋子",
"hat": "帽子",
"glasses": "眼镜",
"chair": "椅子",
"table": "桌子",
"sofa": "沙发",
"bed": "床",
"person": "人",
"car": "汽车",
"cat": "猫",
"dog": "狗",
"leather": "皮革",
"cloth": "布料",
"plastic": "塑料",
"metal": "金属",
"wooden": "木制",
}
# 简单替换
result = text.lower()
for en, cn in keyword_map.items():
result = result.replace(en, cn)
# 如果没有翻译成功,返回原文
if result == text.lower():
return text
return result
# 方案2: 调用翻译 API(可选,需要额外配置)
# from app.utils.translation import translate
# return await translate(text, target_lang="zh")
```
#### `requirements.txt` - Python 依赖
```txt
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6
pydantic==2.5.3
torch==2.1.2
torchvision==0.16.2
transformers==4.37.0
pillow==10.2.0
requests==2.31.0
```
---
### 2. 前端组件实现
#### `components/image-preview.tsx` - 修改
```typescript
"use client";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { X, Download, Target } from "lucide-react";
import Image from "next/image";
import { useState } from "react";
import { toast } from "sonner";
interface ImagePreviewProps {
isOpen: boolean;
imageUrl: string;
imageIndex: number;
onClose: () => void;
onAnnotate?: (annotation: {
imageIndex: number;
description: string;
formattedText: string;
}) => void;
}
export function ImagePreview({
isOpen,
imageUrl,
imageIndex,
onClose,
onAnnotate,
}: ImagePreviewProps) {
const [isAnnotationMode, setIsAnnotationMode] = useState(false);
const [isDetecting, setIsDetecting] = useState(false);
const handleImageClick = async (e: React.MouseEvent<HTMLDivElement>) => {
if (!isAnnotationMode || isDetecting) return;
const rect = e.currentTarget.getBoundingClientRect();
const clickX = Math.round(e.clientX - rect.left);
const clickY = Math.round(e.clientY - rect.top);
setIsDetecting(true);
toast.info("正在识别物体...");
try {
const response = await fetch("/api/detect-object", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
imageUrl,
clickX,
clickY,
imageIndex,
imageWidth: rect.width,
imageHeight: rect.height,
}),
});
const result = await response.json();
if (result.success && result.data) {
toast.success(`识别成功:${result.data.description}`);
onAnnotate?.({
imageIndex: result.data.imageIndex,
description: result.data.description,
formattedText: result.data.formattedText,
});
onClose();
} else {
toast.error(result.error || "识别失败,请重试");
}
} catch (error) {
console.error("物体检测失败:", error);
toast.error("识别失败,请检查网络连接");
} finally {
setIsDetecting(false);
}
};
const handleDownload = () => {
const link = document.createElement("a");
link.href = imageUrl;
link.download = `image-${imageIndex}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[90vh] p-0">
{/* 顶部工具栏 */}
<div className="flex items-center justify-between p-4 border-b">
<Button
variant="ghost"
size="sm"
onClick={onClose}
className="gap-2"
>
<X className="h-4 w-4" />
关闭
</Button>
<div className="flex items-center gap-2">
<Button
variant={isAnnotationMode ? "default" : "outline"}
size="sm"
onClick={() => setIsAnnotationMode(!isAnnotationMode)}
className="gap-2"
disabled={isDetecting}
>
<Target className="h-4 w-4" />
{isAnnotationMode ? "标注中..." : "定位标注"}
</Button>
<Button
variant="outline"
size="sm"
onClick={handleDownload}
className="gap-2"
>
<Download className="h-4 w-4" />
下载
</Button>
</div>
</div>
{/* 图片显示区域 */}
<div
className={`relative flex items-center justify-center p-4 ${
isAnnotationMode ? "cursor-crosshair" : ""
} ${isDetecting ? "opacity-50 pointer-events-none" : ""}`}
onClick={handleImageClick}
>
<Image
src={imageUrl}
alt={`图片 ${imageIndex}`}
width={800}
height={600}
className="max-w-full max-h-[calc(90vh-120px)] object-contain"
unoptimized
/>
{isAnnotationMode && (
<div className="absolute top-4 left-1/2 transform -translate-x-1/2 bg-blue-500 text-white px-4 py-2 rounded-full text-sm">
点击图片中的物体进行标注
</div>
)}
{isDetecting && (
<div className="absolute inset-0 flex items-center justify-center">
<div className="bg-black/70 text-white px-6 py-3 rounded-lg flex items-center gap-3">
<div className="animate-spin h-5 w-5 border-2 border-white border-t-transparent rounded-full" />
<span>正在识别...</span>
</div>
</div>
)}
</div>
</DialogContent>
</Dialog>
);
}
```
#### `components/multimodal-input.tsx` - 修改(部分)
在现有的 `MultimodalInput` 组件中添加:
```typescript
// 在组件顶部添加状态
const [annotations, setAnnotations] = useState<Array<{
imageIndex: number;
description: string;
formattedText: string;
}>>([]);
// 添加标注处理函数
const handleAnnotate = (annotation: {
imageIndex: number;
description: string;
formattedText: string;
}) => {
setAnnotations((prev) => [...prev, annotation]);
// 自动插入到输入框
setInput((prev) => {
if (prev) {
return `${prev} ${annotation.formattedText}`;
}
return annotation.formattedText;
});
};
// 在 PreviewAttachment 组件调用时传递 onAnnotate
<PreviewAttachment
key={attachment.url}
attachment={attachment}
onRemove={() => handleRemoveAttachment(attachment.url)}
onAnnotate={handleAnnotate} // 新增
imageIndex={index + 1} // 新增
/>
```
#### `components/preview-attachment.tsx` - 修改
```typescript
// 添加新的 props
interface PreviewAttachmentProps {
attachment: Attachment;
onRemove: () => void;
onAnnotate?: (annotation: any) => void; // 新增
imageIndex?: number; // 新增
// ... 其他 props
}
// 在图片点击处理中打开预览
const handleImageClick = () => {
setShowPreview(true);
};
// 渲染 ImagePreview
{showPreview && (
<ImagePreview
isOpen={showPreview}
imageUrl={attachment.url}
imageIndex={imageIndex || 1}
onClose={() => setShowPreview(false)}
onAnnotate={onAnnotate}
/>
)}
```
---
## 🚀 部署脚本
### `deploy.sh` - 一键部署脚本
```bash
#!/bin/bash
echo "🚀 开始部署 Florence-2 物体检测系统..."
# 1. 部署后端
echo "📦 部署后端 API..."
cd /var/www/ai-chatbot-backend
# 激活虚拟环境
source venv/bin/activate
# 安装/更新依赖
pip install -r requirements.txt
# 下载模型(如果未下载)
python -c "from app.services.florence_service import FlorenceService; s = FlorenceService(); s._load_model_sync()"
# 重启服务
pm2 restart florence-api || pm2 start ecosystem.config.js
echo "✅ 后端部署完成"
# 2. 部署前端
echo "📦 部署前端..."
cd /var/www/ai-chatbot
# 构建
npm run build
# 重启服务
pm2 restart ai-chatbot
echo "✅ 前端部署完成"
# 3. 重启 Nginx
echo "🔄 重启 Nginx..."
sudo nginx -t && sudo systemctl reload nginx
echo ""
echo "🎉 部署完成!"
echo ""
echo "📝 检查服务状态:"
echo " pm2 status"
echo ""
echo "📊 查看日志:"
echo " pm2 logs florence-api"
echo " pm2 logs ai-chatbot"
echo ""
echo "🌐 访问地址:"
echo " https://ai.yydd.ai"
echo ""
```
---
## 🧪 测试计划
### 功能测试
| 测试项 | 测试步骤 | 预期结果 |
|--------|---------|---------|
| 基础识别 | 上传包含明显物体的图片,点击物体 | 正确识别物体名称和颜色 |
| 多图标注 | 上传多张图片,分别点击标注 | 每张图片的标注独立,序号正确 |
| 颜色识别 | 点击不同颜色的同类物体 | 能区分颜色(如红色包、绿色包) |
| 空白区域 | 点击图片空白处 | 提示"无法识别物体" |
| 模糊图片 | 上传模糊图片并点击 | 尽可能识别,或提示图片质量问题 |
| 快速点击 | 连续快速点击多次 | 不重复识别,有防抖机制 |
### 性能测试
| 指标 | 目标值 | 测试方法 |
|------|--------|----------|
| 单次推理时间 | < 1秒 | 使用计时器测量 |
| 并发处理能力 | 10 QPS | Apache Bench 压测 |
| 内存占用 | < 2GB | `top` 命令监控 |
| 错误率 | < 1% | 压测统计 |
---
## 🚀 GPU 加速配置(推荐)
如果您的服务器有 NVIDIA GPU,强烈建议启用 GPU 加速,可以将推理速度从 0.5-1 秒降低到 **0.1-0.3 秒**!
### 1. 检查 GPU 状态
```bash
# 检查 GPU 信息
nvidia-smi
# 输出示例:
# +-----------------------------------------------------------------------------+
# | NVIDIA-SMI 525.125.06 Driver Version: 525.125.06 CUDA Version: 12.0 |
# |-------------------------------+----------------------+----------------------+
# | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
# | 0 NVIDIA RTX 3090 Off | 00000000:01:00.0 Off | N/A |
# +-----------------------------------------------------------------------------+
```
### 2. 安装 CUDA 版本的 PyTorch
```bash
# 进入虚拟环境
cd /var/www/ai-chatbot-backend
source venv/bin/activate
# 安装 CUDA 版本(根据您的 CUDA 版本选择)
# CUDA 11.8
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
# CUDA 12.1
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
# 或使用国内镜像(更快)
pip install torch torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple
```
### 3. 验证 GPU 可用性
```bash
python << EOF
import torch
print(f"PyTorch 版本: {torch.__version__}")
print(f"CUDA 可用: {torch.cuda.is_available()}")
print(f"CUDA 版本: {torch.version.cuda}")
print(f"GPU 数量: {torch.cuda.device_count()}")
if torch.cuda.is_available():
print(f"GPU 名称: {torch.cuda.get_device_name(0)}")
print(f"GPU 显存: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB")
EOF
```
### 4. 性能对比
| 设备 | 推理时间 | 吞吐量 | 内存占用 |
|------|---------|--------|---------|
| CPU(8核) | 0.5-1秒 | 1-2 QPS | 1.5GB |
| GPU(RTX 3090) | 0.1-0.2秒 | 5-10 QPS | 2GB(显存) |
| GPU(T4) | 0.2-0.3秒 | 3-5 QPS | 2GB(显存) |
| GPU(V100) | 0.15-0.25秒 | 4-7 QPS | 2GB(显存) |
> 💡 **推荐配置**:RTX 3060 或以上,显存 ≥ 6GB
### 5. GPU 监控
```bash
# 实时监控 GPU 使用率
watch -n 1 nvidia-smi
# 查看 GPU 进程
nvidia-smi pmon
# 记录 GPU 使用日志
nvidia-smi dmon -s pucvmet -f gpu_usage.log
```
### 6. 故障排查
#### 问题 1:`torch.cuda.is_available()` 返回 False
**解决方案**:
```bash
# 1. 检查 NVIDIA 驱动
nvidia-smi
# 如果报错,需要重新安装驱动
# 2. 检查 CUDA 工具包
nvcc --version
# 如果找不到命令,需要安装 CUDA Toolkit
# 3. 重新安装 PyTorch(确保版本匹配)
pip uninstall torch torchvision
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
```
#### 问题 2:显存不足(CUDA out of memory)
**解决方案**:
```python
# 方法1: 减小批处理大小(在代码中已经是单张推理,无需调整)
# 方法2: 清空 GPU 缓存
import torch
torch.cuda.empty_cache()
# 方法3: 使用半精度推理(FP16)
model = model.half() # 转换为 FP16
```
#### 问题 3:推理速度慢(GPU 利用率低)
**原因**:首次推理会有模型编译开销
**解决方案**:
```python
# 在启动时预热模型
@app.on_event("startup")
async def warmup():
print("🔥 正在预热模型...")
dummy_image = Image.new("RGB", (224, 224), color="red")
await florence_service.detect_object_at_point(
image_url="dummy",
click_x=112,
click_y=112,
image_index=1,
image_width=224,
image_height=224
)
print("✅ 模型预热完成")
```
---
## ⚠️ 注意事项
### 1. 模型准确度限制
Florence-2 是通用视觉模型,可能存在以下情况:
- 小物体或远景物体识别不准确
- 罕见物体可能无法识别
- 颜色描述可能不够精确(如"深蓝"和"浅蓝")
**解决方案**:
- 引导用户点击明显、清晰的物体
- 提供"重新识别"按钮
- 允许用户手动编辑识别结果
### 2. 性能优化
**模型缓存**:
- 模型加载到内存后常驻,避免重复加载
- 使用全局单例模式
**图片缓存**:
- 对于同一张图片的多次点击,缓存下载的图片
- 使用 LRU 缓存策略,限制内存占用
**并发控制**:
- 使用队列限制并发推理数量
- 避免 OOM(内存溢出)
### 3. 用户体验优化
**加载状态**:
- 首次使用时提示"模型加载中"
- 识别过程中显示加载动画
- 预估等待时间(约 0.5-1秒)
**错误处理**:
- 网络错误:提示"网络连接失败,请重试"
- 识别失败:提示"无法识别该区域,请点击更明显的位置"
- 服务异常:提示"服务暂时不可用,请稍后再试"
---
## 📊 项目进度表
| 阶段 | 任务 | 预计时间 | 负责人 |
|------|------|---------|--------|
| 阶段一 | 后端 API 开发 | 2天 | 后端开发 |
| 阶段二 | 前端组件开发 | 2天 | 前端开发 |
| 阶段三 | 集成与测试 | 1天 | 全栈 |
| 阶段四 | 生产部署 | 1天 | 运维 |
| **总计** | - | **6天** | - |
---
## 🔮 未来扩展
### 1. 支持更多识别任务
- **OCR 文字识别**:识别图片中的文字
- **区域分割**:精确标注物体边界
- **多物体标注**:一次点击识别多个物体
### 2. 用户反馈机制
- 允许用户纠正错误的识别结果
- 收集用户反馈,优化模型
### 3. 自定义类别
- 允许用户上传自己的物体类别
- 支持小样本学习(Few-shot Learning)
---
## 📚 参考资料
1. [Florence-2 官方论文](https://arxiv.org/abs/2311.06242)
2. [Hugging Face - Florence-2 文档](https://huggingface.co/microsoft/Florence-2-base)
3. [FastAPI 官方文档](https://fastapi.tiangolo.com/)
4. [Next.js 官方文档](https://nextjs.org/docs)
---
## 📞 技术支持
如有问题,请联系开发团队。
**文档版本**: v1.0
**更新日期**: 2025-12-15
**作者**: AI Chatbot 开发团队