jiachenlong/backend/app/routers/ocr.py

189 lines
6.8 KiB
Python
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.

# OCR 识别路由 - 专业人民币生肖纪念钞鉴定
import os
import base64
import httpx
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.auth import get_current_user
from app.models.models import User
router = APIRouter(prefix="/api/ocr", tags=["OCR 识别"])
# 阿里云 DashScope API 配置
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "sk-9389024a37da4f7bb455ac9a6b28776f")
# 专业提示词
PROFESSIONAL_PROMPT = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。
【识别流程】
1. 判断类型是否评级钞:首先确认是否为裸钞还是评级钞(有封装盒和标签)
2. 验证纪念钞特征:对照生肖纪念钞特征进行确认
3. 验证评级类型:有'标十'字眼的为标十,有'百连'字眼的为标百,其他为单张
4. 提取信息:仔细阅读标签上的所有文字内容
【版别格式要求】
只需要:年份 + 属相,例如:
- 2024 龙
- 2025 蛇
- 2026 马
【输出要求】
严格按照以下格式输出,每个字段必须填写具体值:
✅ 1 发行机构:中国人民银行
✅ 2 发行版别2024 龙
✅ 3 面额:贰拾圆
✅ 4 是否评级:是/否
✅ 5 封装类型:裸钞/单张/标十/标百
✅ 6 冠字序号J0xxxxxxxx
✅ 7 评级机构ACG/PCGS/PMG
✅ 8 评级分数67/68/69
✅ 9 是否三星:是/否
✅ 10 特殊标识:金山标/天马标/红绳版等
✅ 11 号码特征:金山号 2 张,天马号 3 张等
现在请仔细分析提供的图片,按上述格式输出结果。"""
@router.post("/recognize")
async def recognize_image(
image: UploadFile = File(...),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""OCR 图片识别"""
try:
image_data = await image.read()
image_base64 = base64.b64encode(image_data).decode('utf-8')
headers = {
"Authorization": f"Bearer {DASHSCOPE_API_KEY}",
"Content-Type": "application/json"
}
# 阿里云 DashScope API 格式 (qwen-vl-plus 视觉模型)
payload = {
"model": "qwen-vl-plus",
"input": {
"messages": [{
"role": "user",
"content": [
{
"image": f"data:{image.content_type};base64,{image_base64}"
},
{
"text": PROFESSIONAL_PROMPT
}
]
}]
},
"parameters": {
"max_tokens": 1000
}
}
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
json=payload,
headers=headers
)
if response.status_code != 200:
raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}")
ocr_result = response.json()
text_content = ""
# 新版API返回格式
if "output" in ocr_result and "choices" in ocr_result["output"]:
choices = ocr_result["output"]["choices"]
if choices and len(choices) > 0:
content = choices[0].get("message", {}).get("content", [])
if content and len(content) > 0:
text_content = content[0].get("text", "")
fields = extract_fields(text_content)
# 调试日志:打印提取的字段
import logging
logging.info(f"OCR 提取的字段:{fields}")
return {"success": True, "text": text_content, "fields": fields}
except Exception as e:
import traceback
error_detail = f"识别失败:{str(e)}\n{traceback.format_exc()}"
raise HTTPException(status_code=500, detail=error_detail)
def extract_fields(text: str) -> dict:
"""从 OCR 文本中提取字段 - 直接返回 AI 识别结果"""
import re
fields = {}
# 解析结构化输出
patterns = {
'issuer': r'✅.*?1.*?发行机构.*?[:]\s*(.+?)(?:\n|$)',
'version': r'✅.*?2.*?发行版别.*?[:]\s*(.+?)(?:\n|$)',
'denomination': r'✅.*?3.*?面额.*?[:]\s*(.+?)(?:\n|$)',
'is_graded_text': r'✅.*?4.*?是否评级.*?[:]\s*(.+?)(?:\n|$)',
'packaging': r'✅.*?5.*?封装类型.*?[:]\s*(.+?)(?:\n|$)',
'prefix_serial': r'✅.*?6.*?冠字序号.*?[:]\s*(.+?)(?:\n|$)',
'grading_company': r'✅.*?7.*?评级机构.*?[:]\s*(.+?)(?:\n|$)',
'grading_score': r'✅.*?8.*?评级分数.*?[:]\s*(.+?)(?:\n|$)',
'three_star_text': r'✅.*?9.*?是否三星.*?[:]\s*(.+?)(?:\n|$)',
'special_mark': r'✅.*?10.*?特殊标识.*?[:]\s*(.+?)(?:\n|$)',
'serial_feature': r'✅.*?11.*?号码特征.*?[:]\s*(.+?)(?:\n|$)'
}
for field, pattern in patterns.items():
match = re.search(pattern, text, re.IGNORECASE | re.DOTALL)
if match:
value = match.group(1).strip()
# 保留所有值,包括"无"和"未识别",让前端处理
fields[field] = value
# 处理是否评级
if 'is_graded_text' in fields:
fields['is_graded'] = '' in fields.pop('is_graded_text')
# 处理是否三星
if 'three_star_text' in fields:
fields['three_star'] = '' in fields.pop('three_star_text')
# 简化版别字段2024 龙年贺岁纪念钞(标十) → 2024 龙)
if 'version' in fields:
version = fields['version']
# 提取年份和生肖
year_match = re.search(r'(20\d{2})', version)
animal = ''
if '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
elif '' in version:
animal = ''
if year_match and animal:
fields['version'] = f"{year_match.group(1)}{animal}"
return fields