OCR Worker 改用 RapidOCR/ONNX,修复 Paddle SIGILL。

This commit is contained in:
dekun
2026-06-28 15:27:59 +08:00
parent 035b65dcc8
commit 4de460c235
5 changed files with 34 additions and 108 deletions
+19 -68
View File
@@ -1,4 +1,4 @@
"""局域网 OCR 服务:在带 NVIDIA 显卡的机器上运行,供成绩档案系统调用"""
"""局域网 OCR 服务:RapidOCR(ONNX),不依赖 Paddle,避免 SIGILL/cuDNN 问题"""
import logging
import os
@@ -9,19 +9,14 @@ from pathlib import Path
from fastapi import FastAPI, File, Header, HTTPException, UploadFile
from PIL import Image
os.environ.setdefault("OPENCV_IO_ENABLE_OPENEXR", "0")
os.environ.setdefault("FLAGS_use_mkldnn", "0")
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("ocr-worker")
OCR_MAX_SIDE = int(os.getenv("OCR_MAX_SIDE", "1280"))
OCR_API_KEY = os.getenv("OCR_API_KEY", "").strip()
OCR_USE_GPU = os.getenv("OCR_USE_GPU", "true").lower() in {"1", "true", "yes"}
app = FastAPI(title="Grade Archive OCR Worker", version="1.0.0")
app = FastAPI(title="Grade Archive OCR Worker", version="2.0.0")
_engine = None
_engine_mode = "none"
def _check_key(key: str | None) -> None:
@@ -29,54 +24,15 @@ def _check_key(key: str | None) -> None:
raise HTTPException(status_code=401, detail="Invalid OCR API key")
def _create_engine(use_gpu: bool):
from paddleocr import PaddleOCR
def get_engine():
global _engine
if _engine is None:
from rapidocr_onnxruntime import RapidOCR
return PaddleOCR(
use_angle_cls=False,
lang="ch",
show_log=False,
use_gpu=use_gpu,
enable_mkldnn=False,
ir_optim=False,
det_limit_side_len=min(OCR_MAX_SIDE, 1280),
rec_batch_num=8,
)
def get_engine(force_cpu: bool = False):
global _engine, _engine_mode
if _engine is not None and not force_cpu:
return _engine
modes: list[bool] = [False] if force_cpu or not OCR_USE_GPU else [True, False]
last_err: Exception | None = None
for use_gpu in modes:
try:
logger.info("Loading PaddleOCR use_gpu=%s", use_gpu)
_engine = _create_engine(use_gpu)
_engine_mode = "gpu" if use_gpu else "cpu"
logger.info("PaddleOCR ready mode=%s", _engine_mode)
return _engine
except Exception as exc:
last_err = exc
logger.warning("PaddleOCR init failed use_gpu=%s: %s", use_gpu, exc)
_engine = None
_engine_mode = "none"
hint = ""
err_text = str(last_err or "")
if "libGL" in err_text:
hint = " 请执行: sudo bash deploy/install-ocr-deps.sh 后重启 OCR"
elif any(x in err_text.lower() for x in ("cuda", "cudnn", "gpu", "out of memory")):
hint = " 显存不足或 CUDA 异常,可设置 OCR_USE_GPU=false 用 CPU"
raise RuntimeError(f"PaddleOCR 初始化失败: {last_err}{hint}") from last_err
def _reset_engine():
global _engine, _engine_mode
_engine = None
_engine_mode = "none"
logger.info("Loading RapidOCR (ONNX CPU)…")
_engine = RapidOCR()
logger.info("RapidOCR ready")
return _engine
def _bbox_from_box(box: list) -> list[float]:
@@ -110,32 +66,31 @@ def _prepare_image_bytes(content: bytes) -> tuple[bytes, float, float, int, int]
return buf.getvalue(), scale_x, scale_y, orig_w, orig_h
def _run_ocr_impl(content: bytes) -> dict:
def run_ocr_on_bytes(content: bytes) -> dict:
engine = get_engine()
image_bytes, scale_x, scale_y, orig_w, orig_h = _prepare_image_bytes(content)
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
tmp.write(image_bytes)
tmp_path = tmp.name
try:
result = engine.ocr(tmp_path, cls=False)
result, _elapsed = engine(tmp_path)
finally:
Path(tmp_path).unlink(missing_ok=True)
lines: list[dict] = []
if result and result[0]:
for item in result[0]:
if result:
for item in result:
if not item or len(item) < 2:
continue
box, rec = item[0], item[1]
text = rec[0] if rec else ""
conf = float(rec[1]) if rec and len(rec) > 1 else 0.0
box, text = item[0], item[1]
conf = float(item[2]) if len(item) > 2 else 0.0
if not text:
continue
if scale_x != 1.0 or scale_y != 1.0:
box = _scale_box(box, scale_x, scale_y)
lines.append(
{
"text": text,
"text": str(text),
"confidence": conf,
"box": box,
"bbox": _bbox_from_box(box),
@@ -147,17 +102,13 @@ def _run_ocr_impl(content: bytes) -> dict:
"lines": lines,
"width": orig_w,
"height": orig_h,
"engine_mode": _engine_mode,
"engine_mode": "rapidocr-onnx",
}
def run_ocr_on_bytes(content: bytes) -> dict:
return _run_ocr_impl(content)
@app.get("/health")
def health():
return {"status": "ok", "gpu_requested": OCR_USE_GPU, "engine_mode": _engine_mode}
return {"status": "ok", "engine": "rapidocr-onnxruntime"}
@app.post("/api/ocr/regions")