jiachenlong/backend/app/routers/ocr.py

182 lines
6.5 KiB
Python
Raw Normal View History

# 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-max 视觉模型)
payload = {
"model": "qwen-vl-max",
"messages": [{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}"
}
},
{
"type": "text",
"text": PROFESSIONAL_PROMPT
}
]
}],
"max_tokens": 1000
}
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
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 = ""
if "choices" in ocr_result and len(ocr_result["choices"]) > 0:
text_content = ocr_result["choices"][0]["message"]["content"]
fields = extract_fields(text_content)
# 调试日志:打印提取的字段
import logging
logging.info(f"OCR 提取的字段:{fields}")
return {"success": True, "text": text_content, "fields": fields}
except Exception as e:
raise HTTPException(status_code=500, detail=f"识别失败:{str(e)}")
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