1111import os as _os
1212import threading
1313import time
14+ from dataclasses import dataclass , field
1415from pathlib import Path
16+ from typing import Any , Optional
1517
1618import numpy as np
1719
@@ -37,6 +39,30 @@ def _ocr_batch_size() -> int:
3739 return config .OCR_BATCH_SIZE
3840
3941
42+ @dataclass
43+ class ExtractedSegment :
44+ """引擎输出的单个文本字段段(原有字段区间 + 代表帧 + 原始文本)。"""
45+
46+ start : int # 段首帧号
47+ end : int # 段末帧号
48+ frames : tuple = () # 段内帧号序列
49+ rep_frame : int = - 1 # 代表帧号(段内最清晰帧)
50+ text : Optional [str ] = None # OCR 原始文本(None=未读出)
51+ confidence : float = 0.0 # OCR 置信度 0-1
52+ rep_crop : Any = None # 代表帧 ROI 图像(YUV420 或 RGB)
53+
54+
55+ @dataclass
56+ class ExtractionResult :
57+ """引擎通用提取结果(无领域语义)。"""
58+
59+ segments : list = field (default_factory = list ) # list[ExtractedSegment]
60+ frames : list = field (default_factory = list ) # 全部采样帧号
61+ fps : float = 0.0 # 自测帧率
62+ timing : dict = field (default_factory = dict ) # 各阶段耗时
63+ meta : dict = field (default_factory = dict ) # backend/codec/引擎版本等
64+
65+
4066class FieldExtractor :
4167 """从视频固定区域提取文本的通用引擎(识别链:解码∥分段∥OCR)。
4268
@@ -55,7 +81,10 @@ def __init__(self, video_path: str, roi: tuple, *, frame_start=None,
5581 yuv_output : bool = False ):
5682 self ._video_path = Path (video_path )
5783 self ._roi = tuple (roi )
58- self ._fps = fps # 外部给定时直接用(truth 头),否则识别链推导
84+ # fps 强制自测:open decoder 后从 get_avg_fps/get_fps 读,忽略外部
85+ # 传入(truth 头的 fps 可能与视频实际帧率偏离;自测无额外解码开销,
86+ # 只在打开时读一次元数据)。fps 参数保留仅为 API 兼容(已废弃)。
87+ self ._fps = None
5988 self ._frame_start = frame_start or 0
6089 self ._frame_end = frame_end
6190 self ._force_aspect = force_aspect
@@ -100,8 +129,31 @@ def __init__(self, video_path: str, roi: tuple, *, frame_start=None,
100129 # 后处理参数由子类(SegmentPipeline)在构造时设置;引擎识别链不读。
101130
102131 def extract (self ):
103- """通用文本提取入口(待精修:解码∥分段∥OCR → 每段 text/conf 结果)。"""
104- raise NotImplementedError
132+ """通用文本提取:解码∥分段∥OCR → 结构化结果(每段原始文本+置信度)。
133+
134+ 引擎的正式通用入口(无任何领域语义)。返回 ExtractionResult:
135+ - segments: list[ExtractedSegment](start/end/rep_frame/text/confidence/
136+ rep_crop)
137+ - frames / fps / timing / meta
138+ 识别层不解析文本含义(速度/数值由上层应用处理)。ffis 强制自测。
139+ """
140+ frames , segs , texts , confs , rep_frames = self ._run_pipelined ()
141+ segments = [
142+ ExtractedSegment (
143+ start = seg [0 ], end = seg [- 1 ], frames = tuple (seg ),
144+ rep_frame = rep_frames [i ],
145+ text = texts [i ] if i < len (texts ) else None ,
146+ confidence = confs [i ] if i < len (confs ) else 0.0 ,
147+ rep_crop = self .crops .get (rep_frames [i ]))
148+ for i , seg in enumerate (segs )
149+ ]
150+ return ExtractionResult (
151+ segments = segments , frames = frames , fps = self ._fps or 0.0 ,
152+ timing = dict (self .timing ),
153+ meta = {"backend" : self ._backend ,
154+ "ocr_backend" : self ._ocr_backend_used ,
155+ "codec" : self ._codec ,
156+ "n_segments" : len (segments )})
105157
106158 @property
107159 def frames (self ) -> list :
0 commit comments