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