|
|
|
|
@ -36,6 +36,22 @@ from utils.config import VisualizeConfig
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/ocr", tags=["OCR 识别"])
|
|
|
|
|
|
|
|
|
|
# 模块级别的 Visualizer 实例(避免重复创建)
|
|
|
|
|
_visualizer: Optional[OCRVisualizer] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_visualizer() -> OCRVisualizer:
|
|
|
|
|
"""
|
|
|
|
|
获取 Visualizer 实例(单例模式,避免重复创建)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
OCRVisualizer 实例
|
|
|
|
|
"""
|
|
|
|
|
global _visualizer
|
|
|
|
|
if _visualizer is None:
|
|
|
|
|
_visualizer = OCRVisualizer(VisualizeConfig())
|
|
|
|
|
return _visualizer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _convert_ocr_result_to_response(
|
|
|
|
|
result: OCRResult,
|
|
|
|
|
@ -75,19 +91,63 @@ def _convert_ocr_result_to_response(
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _convert_express_result_to_response(
|
|
|
|
|
result: OCRResult,
|
|
|
|
|
annotated_image_base64: Optional[str] = None,
|
|
|
|
|
) -> ExpressResultData:
|
|
|
|
|
"""
|
|
|
|
|
将 OCRResult 转换为快递单响应数据模型
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
result: OCR 处理结果
|
|
|
|
|
annotated_image_base64: 标注图片的 Base64 编码
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
快递单响应数据模型
|
|
|
|
|
"""
|
|
|
|
|
# 解析快递单信息
|
|
|
|
|
express_info = result.parse_express()
|
|
|
|
|
merged_text = result.merge_text()
|
|
|
|
|
|
|
|
|
|
return ExpressResultData(
|
|
|
|
|
processing_time_ms=result.processing_time_ms,
|
|
|
|
|
express_info=ExpressInfoData(
|
|
|
|
|
tracking_number=express_info.tracking_number,
|
|
|
|
|
sender=ExpressPersonData(
|
|
|
|
|
name=express_info.sender_name,
|
|
|
|
|
phone=express_info.sender_phone,
|
|
|
|
|
address=express_info.sender_address,
|
|
|
|
|
),
|
|
|
|
|
receiver=ExpressPersonData(
|
|
|
|
|
name=express_info.receiver_name,
|
|
|
|
|
phone=express_info.receiver_phone,
|
|
|
|
|
address=express_info.receiver_address,
|
|
|
|
|
),
|
|
|
|
|
courier_company=express_info.courier_company,
|
|
|
|
|
confidence=express_info.confidence,
|
|
|
|
|
extra_fields=express_info.extra_fields,
|
|
|
|
|
raw_text=express_info.raw_text,
|
|
|
|
|
),
|
|
|
|
|
merged_text=merged_text,
|
|
|
|
|
annotated_image_base64=annotated_image_base64,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _process_ocr(
|
|
|
|
|
image_bytes: bytes,
|
|
|
|
|
pipeline: OCRPipeline,
|
|
|
|
|
roi: Optional[ROIParams] = None,
|
|
|
|
|
drop_score: Optional[float] = None,
|
|
|
|
|
return_annotated_image: bool = False,
|
|
|
|
|
) -> tuple[OCRResult, Optional[str]]:
|
|
|
|
|
"""
|
|
|
|
|
执行 OCR 处理
|
|
|
|
|
执行 OCR 处理(线程安全)
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
image_bytes: 图片字节数据
|
|
|
|
|
pipeline: OCR 管道
|
|
|
|
|
roi: ROI 参数
|
|
|
|
|
drop_score: 置信度阈值,低于此值的结果将被过滤
|
|
|
|
|
return_annotated_image: 是否返回标注图片
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
@ -96,27 +156,23 @@ def _process_ocr(
|
|
|
|
|
# 解码图片
|
|
|
|
|
image = decode_image_bytes(image_bytes)
|
|
|
|
|
|
|
|
|
|
# 构建管道配置
|
|
|
|
|
# 构建管道配置(每次请求独立的配置,线程安全)
|
|
|
|
|
pipeline_config = build_pipeline_config(roi)
|
|
|
|
|
|
|
|
|
|
# 临时更新管道配置
|
|
|
|
|
original_config = pipeline._pipeline_config
|
|
|
|
|
pipeline._pipeline_config = pipeline_config
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# 执行 OCR
|
|
|
|
|
result = pipeline.process(image)
|
|
|
|
|
# 执行 OCR(传递临时配置,不修改共享状态)
|
|
|
|
|
result = pipeline.process(
|
|
|
|
|
image=image,
|
|
|
|
|
pipeline_config=pipeline_config,
|
|
|
|
|
drop_score=drop_score,
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise OCRProcessingError(f"OCR 处理失败: {str(e)}")
|
|
|
|
|
finally:
|
|
|
|
|
# 恢复原始配置
|
|
|
|
|
pipeline._pipeline_config = original_config
|
|
|
|
|
|
|
|
|
|
# 生成标注图片
|
|
|
|
|
annotated_image_base64 = None
|
|
|
|
|
if return_annotated_image and result.text_count > 0:
|
|
|
|
|
visualizer = OCRVisualizer(VisualizeConfig())
|
|
|
|
|
annotated = visualizer.draw_result(image, result)
|
|
|
|
|
annotated = _get_visualizer().draw_result(image, result)
|
|
|
|
|
annotated_image_base64 = encode_image_base64(annotated)
|
|
|
|
|
|
|
|
|
|
return result, annotated_image_base64
|
|
|
|
|
@ -153,6 +209,7 @@ async def recognize_multipart(
|
|
|
|
|
image_bytes=image_bytes,
|
|
|
|
|
pipeline=pipeline,
|
|
|
|
|
roi=params.get_roi(),
|
|
|
|
|
drop_score=params.drop_score,
|
|
|
|
|
return_annotated_image=params.return_annotated_image,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -198,38 +255,14 @@ async def express_multipart(
|
|
|
|
|
image_bytes=image_bytes,
|
|
|
|
|
pipeline=pipeline,
|
|
|
|
|
roi=params.get_roi(),
|
|
|
|
|
drop_score=params.drop_score,
|
|
|
|
|
return_annotated_image=params.return_annotated_image,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 解析快递单信息
|
|
|
|
|
express_info = result.parse_express()
|
|
|
|
|
merged_text = result.merge_text()
|
|
|
|
|
|
|
|
|
|
# 构建响应
|
|
|
|
|
return ExpressResponse(
|
|
|
|
|
success=True,
|
|
|
|
|
data=ExpressResultData(
|
|
|
|
|
processing_time_ms=result.processing_time_ms,
|
|
|
|
|
express_info=ExpressInfoData(
|
|
|
|
|
tracking_number=express_info.tracking_number,
|
|
|
|
|
sender=ExpressPersonData(
|
|
|
|
|
name=express_info.sender_name,
|
|
|
|
|
phone=express_info.sender_phone,
|
|
|
|
|
address=express_info.sender_address,
|
|
|
|
|
),
|
|
|
|
|
receiver=ExpressPersonData(
|
|
|
|
|
name=express_info.receiver_name,
|
|
|
|
|
phone=express_info.receiver_phone,
|
|
|
|
|
address=express_info.receiver_address,
|
|
|
|
|
),
|
|
|
|
|
courier_company=express_info.courier_company,
|
|
|
|
|
confidence=express_info.confidence,
|
|
|
|
|
extra_fields=express_info.extra_fields,
|
|
|
|
|
raw_text=express_info.raw_text,
|
|
|
|
|
),
|
|
|
|
|
merged_text=merged_text,
|
|
|
|
|
annotated_image_base64=annotated_base64,
|
|
|
|
|
),
|
|
|
|
|
data=_convert_express_result_to_response(result, annotated_base64),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
@ -272,6 +305,7 @@ async def recognize_base64(
|
|
|
|
|
image_bytes=image_bytes,
|
|
|
|
|
pipeline=pipeline,
|
|
|
|
|
roi=body.roi,
|
|
|
|
|
drop_score=body.drop_score,
|
|
|
|
|
return_annotated_image=body.return_annotated_image,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -316,38 +350,14 @@ async def express_base64(
|
|
|
|
|
image_bytes=image_bytes,
|
|
|
|
|
pipeline=pipeline,
|
|
|
|
|
roi=body.roi,
|
|
|
|
|
drop_score=body.drop_score,
|
|
|
|
|
return_annotated_image=body.return_annotated_image,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 解析快递单信息
|
|
|
|
|
express_info = result.parse_express()
|
|
|
|
|
merged_text = result.merge_text()
|
|
|
|
|
|
|
|
|
|
# 构建响应
|
|
|
|
|
return ExpressResponse(
|
|
|
|
|
success=True,
|
|
|
|
|
data=ExpressResultData(
|
|
|
|
|
processing_time_ms=result.processing_time_ms,
|
|
|
|
|
express_info=ExpressInfoData(
|
|
|
|
|
tracking_number=express_info.tracking_number,
|
|
|
|
|
sender=ExpressPersonData(
|
|
|
|
|
name=express_info.sender_name,
|
|
|
|
|
phone=express_info.sender_phone,
|
|
|
|
|
address=express_info.sender_address,
|
|
|
|
|
),
|
|
|
|
|
receiver=ExpressPersonData(
|
|
|
|
|
name=express_info.receiver_name,
|
|
|
|
|
phone=express_info.receiver_phone,
|
|
|
|
|
address=express_info.receiver_address,
|
|
|
|
|
),
|
|
|
|
|
courier_company=express_info.courier_company,
|
|
|
|
|
confidence=express_info.confidence,
|
|
|
|
|
extra_fields=express_info.extra_fields,
|
|
|
|
|
raw_text=express_info.raw_text,
|
|
|
|
|
),
|
|
|
|
|
merged_text=merged_text,
|
|
|
|
|
annotated_image_base64=annotated_base64,
|
|
|
|
|
),
|
|
|
|
|
data=_convert_express_result_to_response(result, annotated_base64),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|