File size: 5,784 Bytes
319f4cb
 
 
 
 
 
 
 
57bc6ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319f4cb
 
57bc6ef
 
1eff75e
57bc6ef
 
319f4cb
57bc6ef
 
 
 
 
 
 
 
 
 
 
 
 
319f4cb
 
57bc6ef
319f4cb
 
 
57bc6ef
 
319f4cb
57bc6ef
 
 
1eff75e
 
 
57bc6ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319f4cb
 
57bc6ef
319f4cb
57bc6ef
 
 
319f4cb
 
 
 
 
57bc6ef
319f4cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57bc6ef
319f4cb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57bc6ef
 
 
 
 
319f4cb
 
57bc6ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""
PaddleOCR ラッパー

【重要】PaddleOCR 3.x API変更:
- ocr() → predict() に変更
- use_angle_cls は廃止
- use_doc_orientation_classify, use_doc_unwarping 等を使用
"""

from typing import List, Tuple, Optional
from dataclasses import dataclass
import numpy as np


@dataclass
class OCRResult:
    """OCR検出結果"""

    text: str
    confidence: float
    bbox: List[List[int]]  # [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]


class OCREngine:
    """
    PaddleOCRラッパークラス
    日本語テキスト抽出に最適化

    PaddleOCR 3.x 対応
    """

    def __init__(self, lang: str = "japan"):
        """
        Args:
            lang: 言語設定 (3.xでは使用されない可能性あり)
        """
        self.lang = lang
        self._ocr = None
        self._initialized = False

    def _init_ocr(self) -> None:
        """OCRエンジンの遅延初期化"""
        if self._initialized:
            return

        try:
            from paddleocr import PaddleOCR

            # PaddleOCR 3.x 用の初期化
            # 高速化のためドキュメント補正系は無効化
            self._ocr = PaddleOCR(
                use_doc_orientation_classify=False,
                use_doc_unwarping=False,
                use_textline_orientation=False,
            )
            self._initialized = True
            print("[OCR] PaddleOCR 3.x initialized successfully")
        except ImportError:
            print("Warning: PaddleOCR not installed. OCR will not work.")
            self._initialized = False
        except Exception as e:
            print(f"Warning: PaddleOCR init error: {e}")
            self._initialized = False

    def detect(self, frame: np.ndarray) -> List[OCRResult]:
        """
        フレームからテキストを検出

        Args:
            frame: 入力画像(BGR形式)

        Returns:
            OCRResult のリスト
        """
        self._init_ocr()

        if self._ocr is None:
            return []

        try:
            # PaddleOCR 3.x: predict() を使用
            result = self._ocr.predict(frame)

            if result is None:
                return []

            ocr_results = []

            # PaddleOCR 3.x の結果形式を解析
            # 結果はジェネレータまたはリストで返される
            for item in result:
                if item is None:
                    continue

                # 結果がdict形式の場合
                if isinstance(item, dict):
                    rec_texts = item.get("rec_texts", [])
                    rec_scores = item.get("rec_scores", [])
                    dt_polys = item.get("dt_polys", [])

                    for i, text in enumerate(rec_texts):
                        if text and len(text.strip()) > 0:
                            confidence = rec_scores[i] if i < len(rec_scores) else 0.0
                            bbox = dt_polys[i] if i < len(dt_polys) else [[0,0],[0,0],[0,0],[0,0]]
                            ocr_results.append(
                                OCRResult(
                                    text=text,
                                    confidence=float(confidence),
                                    bbox=bbox,
                                )
                            )
                # 旧形式のタプル/リストの場合
                elif isinstance(item, (list, tuple)):
                    for line in item:
                        if line is None:
                            continue
                        if isinstance(line, (list, tuple)) and len(line) >= 2:
                            bbox = line[0]
                            text_info = line[1]
                            if text_info and len(text_info) >= 2:
                                text = text_info[0]
                                confidence = float(text_info[1])
                                ocr_results.append(
                                    OCRResult(
                                        text=text,
                                        confidence=confidence,
                                        bbox=bbox,
                                    )
                                )

            return ocr_results

        except Exception as e:
            print(f"OCR error: {e}")
            import traceback
            traceback.print_exc()
            return []

    def detect_text_only(self, frame: np.ndarray) -> List[str]:
        """
        テキストのみを抽出(信頼度でフィルタリング)

        Args:
            frame: 入力画像

        Returns:
            検出されたテキストのリスト
        """
        results = self.detect(frame)
        # 信頼度0.5以上のテキストのみ
        return [r.text for r in results if r.confidence >= 0.5]

    def detect_with_positions(
        self, frame: np.ndarray
    ) -> List[Tuple[str, float, Tuple[int, int]]]:
        """
        テキストと位置情報を抽出

        Returns:
            (テキスト, 信頼度, 中心座標) のリスト
        """
        results = self.detect(frame)
        output = []

        for r in results:
            if r.confidence < 0.5:
                continue
            # バウンディングボックスの中心を計算
            xs = [p[0] for p in r.bbox]
            ys = [p[1] for p in r.bbox]
            center_x = int(sum(xs) / 4)
            center_y = int(sum(ys) / 4)
            output.append((r.text, r.confidence, (center_x, center_y)))

        return output

    @property
    def is_available(self) -> bool:
        """OCRエンジンが利用可能かどうか"""
        self._init_ocr()
        return self._initialized and self._ocr is not None