scan, split, OCR, prepare for LLM
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

paddle_engine.py 2.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. from __future__ import annotations
  2. import importlib.resources
  3. from dataclasses import dataclass, field
  4. from typing import Any
  5. import cv2
  6. import yaml
  7. from paddlex import create_pipeline
  8. @dataclass
  9. class ParsedBlock:
  10. label: str
  11. content: str
  12. bbox: tuple[int, int, int, int]
  13. confidence: float = 0.9
  14. @dataclass
  15. class OcrPageResult:
  16. blocks: list[ParsedBlock] = field(default_factory=list)
  17. raw_json: dict[str, Any] = field(default_factory=dict)
  18. width: int = 0
  19. height: int = 0
  20. def _load_config() -> dict[str, Any]:
  21. ref = importlib.resources.files("paddlex") / "configs" / "pipelines" / "PP-StructureV3.yaml"
  22. with importlib.resources.as_file(ref) as path, open(path) as f:
  23. config: dict[str, Any] = yaml.safe_load(f)
  24. config["SubPipelines"]["GeneralOCR"]["SubModules"]["TextRecognition"]["model_name"] = (
  25. "eslav_PP-OCRv5_mobile_rec"
  26. )
  27. table_ocr = config["SubPipelines"]["TableRecognition"]["SubPipelines"]["GeneralOCR"]
  28. table_ocr["SubModules"]["TextRecognition"]["model_name"] = "eslav_PP-OCRv5_mobile_rec"
  29. return config
  30. class OcrEngine:
  31. def __init__(self) -> None:
  32. self._config: dict[str, Any] = _load_config()
  33. self._pipeline: Any = None
  34. def process(self, image_path: str) -> OcrPageResult:
  35. image = cv2.imread(image_path)
  36. if image is None:
  37. raise FileNotFoundError(f"Не удалось загрузить изображение: {image_path}")
  38. h, w = image.shape[:2]
  39. if self._pipeline is None:
  40. self._pipeline = create_pipeline(
  41. config=self._config,
  42. use_doc_orientation_classify=False,
  43. use_doc_unwarping=False,
  44. )
  45. raw_results = list(self._pipeline.predict(image))
  46. blocks, raw_json = self._parse_results(raw_results)
  47. return OcrPageResult(blocks=blocks, raw_json=raw_json, width=w, height=h)
  48. @staticmethod
  49. def _parse_results(raw_results: list[Any]) -> tuple[list[ParsedBlock], dict[str, Any]]:
  50. if not raw_results:
  51. return [], {}
  52. raw_json: dict[str, Any] = raw_results[0].json["res"]
  53. blocks: list[ParsedBlock] = []
  54. for item in raw_json.get("parsing_res_list", []):
  55. text = item.get("block_content", "")
  56. label = item.get("block_label", "text")
  57. bbox = item.get("block_bbox", [0, 0, 0, 0])
  58. if not text.strip():
  59. continue
  60. blocks.append(ParsedBlock(
  61. label=label, content=text,
  62. bbox=(int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3])),
  63. ))
  64. return blocks, raw_json
  65. _engine: OcrEngine | None = None
  66. def ocr_image(image_path: str) -> OcrPageResult:
  67. global _engine
  68. if _engine is None:
  69. _engine = OcrEngine()
  70. return _engine.process(image_path)