35 KiB
35 KiB
图片物体智能标注系统 - 实施文档
📋 项目概述
功能描述
用户在对话框中上传图片后,可以通过点击图片预览中的"定位标注"按钮,点击图片中的任意物体,系统自动识别该物体的名称(包括颜色、材质等详细信息),并将识别结果以特殊格式自动填入对话框。
使用场景示例
- 用户上传两张图片
- 点击第一张图中的"绿色手提包" → 输入框自动添加:
📍图1: 绿色手提包 - 点击第二张图中的"红色背包" → 输入框继续添加:
📍图2: 红色背包 - 用户手动输入"替换" → 最终输入框内容:
将📍图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 是否可用
# 检查 NVIDIA 驱动
nvidia-smi
# 查看 CUDA 版本
nvcc --version
步骤 2:安装 Python 依赖
# 创建 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 可用性
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 模型
方式一:自动下载(推荐)
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
)
方式二:手动下载(网络受限时)
# 使用魔搭社区镜像(国内访问更快)
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
请求参数:
{
"imageUrl": string, // 图片 URL(S3 地址)
"clickX": number, // 点击 X 坐标(像素)
"clickY": number, // 点击 Y 坐标(像素)
"imageIndex": number, // 图片序号(从 1 开始)
"imageWidth": number, // 图片原始宽度
"imageHeight": number // 图片原始高度
}
响应格式:
{
"success": boolean,
"data": {
"description": string, // 物体描述(中文):"绿色皮革手提包"
"descriptionEn": string, // 物体描述(英文):"green leather handbag"
"confidence": number, // 置信度(0-1)
"imageIndex": number, // 图片序号
"formattedText": string // 格式化文本:"📍图1: 绿色皮革手提包"
},
"error": string | null
}
错误响应:
{
"success": false,
"data": null,
"error": "无法识别该区域的物体,请点击更明显的物体位置"
}
阶段二:前端组件开发(第3-4天)
2.1 修改 ImagePreview 组件
文件:components/image-preview.tsx
新增功能:
- 添加"定位标注"模式切换按钮
- 在标注模式下,鼠标悬停显示准星光标
- 点击时发送请求到后端 API
- 显示加载状态和识别结果
- 将结果传递给父组件
UI 设计:
┌──────────────────────────────────────────┐
│ ← 返回 📍 定位标注 ⬇️ 下载 │ ← 顶部工具栏
├──────────────────────────────────────────┤
│ │
│ │
│ [图片显示区域] │
│ (标注模式下显示准星) │
│ │
│ │
├──────────────────────────────────────────┤
│ 识别中... 🔍 │ ← 底部状态栏(可选)
└──────────────────────────────────────────┘
2.2 修改 MultimodalInput 组件
文件:components/multimodal-input.tsx
新增功能:
- 接收来自
ImagePreview的标注结果 - 将标注文本插入到输入框中
- 使用特殊样式显示标注文本(蓝色、带图标)
- 支持多次标注,文本自动追加
输入框样式:
┌──────────────────────────────────────────────────────────┐
│ 将 [📍图1: 绿色手提包] 替换成 [📍图2: 红色背包] │
│ ↑ 蓝色、可点击 ↑ 蓝色、可点击 │
└──────────────────────────────────────────────────────────┘
2.3 创建标注文本组件
新建文件:components/annotation-tag.tsx
功能:
- 显示标注标签(如
📍图1: 绿色手提包) - 支持点击删除
- 特殊样式(蓝色背景、圆角、图标)
样式示例:
<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 本地开发测试
启动后端:
cd /var/www/ai-chatbot-backend
source venv/bin/activate
python app/main.py
# 访问 http://localhost:8001/docs 查看 API 文档
启动前端:
cd /var/www/ai-chatbot
npm run dev
# 访问 http://localhost:3000
测试流程:
- 上传测试图片(包含明显物体)
- 点击"定位标注"按钮
- 点击图片中的物体
- 验证识别结果是否准确
- 验证文本是否正确插入输入框
3.2 性能测试
测试指标:
- 单次识别耗时:< 1秒
- 并发请求数:10 QPS
- 内存占用:< 2GB
测试命令:
# 使用 Apache Bench 测试
ab -n 100 -c 10 -p test_data.json -T application/json \
http://localhost:8001/api/detect-object
3.3 边缘情况测试
测试场景:
- 点击图片空白区域(无物体)
- 点击模糊不清的区域
- 上传黑白图片
- 上传极小/极大图片
- 快速连续点击多次
预期处理:
- 返回友好的错误提示
- 不崩溃、不卡死
- 有重试机制
阶段四:生产部署(第6天)
4.1 后端部署
使用 PM2 管理进程:
# 安装 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 反向代理:
# /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 前端部署
构建生产版本:
cd /var/www/ai-chatbot
npm run build
pm2 restart ai-chatbot
4.3 监控与日志
设置日志轮转:
# 查看 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 主入口
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 服务
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 依赖
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 - 修改
"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 组件中添加:
// 在组件顶部添加状态
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 - 修改
// 添加新的 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 - 一键部署脚本
#!/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 状态
# 检查 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
# 进入虚拟环境
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 可用性
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 监控
# 实时监控 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
解决方案:
# 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)
解决方案:
# 方法1: 减小批处理大小(在代码中已经是单张推理,无需调整)
# 方法2: 清空 GPU 缓存
import torch
torch.cuda.empty_cache()
# 方法3: 使用半精度推理(FP16)
model = model.half() # 转换为 FP16
问题 3:推理速度慢(GPU 利用率低)
原因:首次推理会有模型编译开销
解决方案:
# 在启动时预热模型
@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)
📚 参考资料
📞 技术支持
如有问题,请联系开发团队。
文档版本: v1.0
更新日期: 2025-12-15
作者: AI Chatbot 开发团队