图片识别

This commit is contained in:
李鹏宇
2026-06-12 14:55:30 +08:00
parent c29ea45542
commit bfdab920e1
2 changed files with 178 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
"""扩展 ir.attachment,增加 AI OCR 内容字段"""
from odoo import models, fields
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
ai_ocr_content = fields.Text(
string='AI OCR 识别内容',
help='上传到聊天附件时自动提取的文字内容'
)
+166
View File
@@ -0,0 +1,166 @@
# -*- coding: utf-8 -*-
"""腾讯云 OCR 适配层 — 封装通用票据识别 & 通用文字识别"""
import base64
import logging
_logger = logging.getLogger(__name__)
# tencentcloud-sdk-python 为可选依赖,导入失败时给出明确提示
try:
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.ocr.v20181119 import ocr_client, models
HAS_TENCENT_SDK = True
except ImportError:
HAS_TENCENT_SDK = False
_logger.warning(
'yuthon_ai_agent: tencentcloud-sdk-python 未安装,OCR 功能不可用。'
'请执行: pip install tencentcloud-sdk-python'
)
# 支持的图片 MIME 类型
SUPPORTED_IMAGE_TYPES = {
'image/png', 'image/jpeg', 'image/jpg', 'image/bmp', 'image/gif',
}
SUPPORTED_PDF_TYPE = 'application/pdf'
MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10MB
class TencentOCR:
"""腾讯云 OCR 适配器"""
def __init__(self, secret_id, secret_key, region='ap-guangzhou'):
if not HAS_TENCENT_SDK:
raise RuntimeError(
'tencentcloud-sdk-python 未安装,请执行: pip install tencentcloud-sdk-python'
)
if not secret_id or not secret_key:
raise ValueError('OCR SecretId / SecretKey 未配置')
self.cred = credential.Credential(secret_id, secret_key)
self.region = region
self._client = None
@property
def client(self):
if self._client is None:
self._client = ocr_client.OcrClient(self.cred, self.region)
return self._client
def _encode_image(self, raw_bytes):
"""将图片/PDF 二进制数据转为 Base64 字符串"""
return base64.b64encode(raw_bytes).decode('utf-8')
def _validate_input(self, raw_bytes, mimetype):
"""校验输入大小 & 格式"""
if len(raw_bytes) > MAX_IMAGE_SIZE:
raise ValueError(f'文件大小超过限制(最大 10MB),当前 {len(raw_bytes) / 1024 / 1024:.1f}MB')
if mimetype not in SUPPORTED_IMAGE_TYPES and mimetype != SUPPORTED_PDF_TYPE:
raise ValueError(f'不支持的文件格式:{mimetype},仅支持 PNG/JPG/BMP/GIF/PDF')
def recognize_general(self, raw_bytes, mimetype='image/png'):
"""通用文字识别 — 适用于任意图片/PDF 的文字提取。
:param raw_bytes: 文件二进制数据
:param mimetype: MIME 类型
:return: dict {success, text, error}
"""
self._validate_input(raw_bytes, mimetype)
try:
image_base64 = self._encode_image(raw_bytes)
req = models.GeneralBasicOCRRequest()
req.ImageBase64 = image_base64
# PDF 需要额外参数
if mimetype == SUPPORTED_PDF_TYPE:
req.IsPdf = True
resp = self.client.GeneralBasicOCR(req)
texts = []
for item in resp.TextDetections:
texts.append(item.DetectedText)
return {
'success': True,
'text': '\n'.join(texts),
}
except TencentCloudSDKException as e:
_logger.exception('Tencent OCR GeneralBasicOCR 调用失败')
return {'success': False, 'error': f'OCR 识别失败:{e.message}'}
except Exception as e:
_logger.exception('OCR GeneralBasicOCR 异常')
return {'success': False, 'error': f'OCR 异常:{str(e)}'}
def recognize_invoice(self, raw_bytes, mimetype='image/png'):
"""通用票据识别(高级版)— 自动识别增值税发票、火车票、机票等。
:param raw_bytes: 文件二进制数据
:param mimetype: MIME 类型
:return: dict {success, text, error}
"""
self._validate_input(raw_bytes, mimetype)
try:
image_base64 = self._encode_image(raw_bytes)
req = models.RecognizeGeneralInvoiceRequest()
req.ImageBase64 = image_base64
if mimetype == SUPPORTED_PDF_TYPE:
req.IsPdf = True
resp = self.client.RecognizeGeneralInvoice(req)
items = resp.MixedInvoiceItems or []
if not items:
return {'success': True, 'text': '(未识别到票据信息)'}
texts = []
for item in items:
item_type = getattr(item, 'Type', '未知票据')
single_invoice = item.SingleInvoiceInfos
if single_invoice:
texts.append(f'—— {item_type} ——')
for key, value in single_invoice.items():
if value:
texts.append(f'{key}:{value}')
return {
'success': True,
'text': '\n'.join(texts) if texts else '(未识别到票据内容)',
}
except TencentCloudSDKException as e:
_logger.exception('Tencent OCR RecognizeGeneralInvoice 调用失败')
return {'success': False, 'error': f'OCR 识别失败:{e.message}'}
except Exception as e:
_logger.exception('OCR RecognizeGeneralInvoice 异常')
return {'success': False, 'error': f'OCR 异常:{str(e)}'}
def smart_recognize(self, raw_bytes, mimetype='image/png'):
"""智能识别:先尝试票据识别,失败则回退到通用文字识别。
:return: dict {success, text, error}
"""
# 先尝试票据识别
result = self.recognize_invoice(raw_bytes, mimetype)
if result['success'] and result.get('text') and '(未识别到票据' not in result['text']:
return result
# 回退到通用文字
return self.recognize_general(raw_bytes, mimetype)
def get_ocr_instance(provider):
"""从 AiProvider 记录中创建 OCR 实例。
:param provider: ai.provider 记录
:return: TencentOCR 实例或 None
"""
if not provider.ocr_enabled:
return None
if not provider.ocr_secret_id or not provider.ocr_secret_key:
_logger.warning('OCR 已启用但 SecretId/SecretKey 未配置 (provider=%s)', provider.name)
return None
if not HAS_TENCENT_SDK:
return None
try:
return TencentOCR(
secret_id=provider.ocr_secret_id,
secret_key=provider.ocr_secret_key,
region=provider.ocr_region or 'ap-guangzhou',
)
except Exception as e:
_logger.exception('创建 OCR 实例失败')
return None