Metadata-Version: 2.4
Name: character-ocr
Version: 0.1.1
Summary: Lightweight character recognition engine based on PP-OCRv5_mobile from PaddleOCR (Apache 2.0). Copyright (c) 2026 pronoobe.
Author: pronoobe
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/pronoobe/character-ocr
Project-URL: Repository, https://github.com/pronoobe/character-ocr
Project-URL: Issues, https://github.com/pronoobe/character-ocr/issues
Keywords: ocr,character-recognition,paddleocr,text-detection,digit-recognition
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Image Recognition
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE-2.0.txt
Requires-Dist: paddleocr>=3.0
Requires-Dist: opencv-python>=4.5
Requires-Dist: numpy>=1.19
Dynamic: license-file

# CharacterOCR — Lightweight Character Recognition Engine

# CharacterOCR — 轻量字符识别引擎

[English](#english) | [中文](#中文)

---

## English

A lightweight OCR character recognition library based on **PP-OCRv5_mobile** from [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR). Single-file wrapper, ready to use out of the box. Pre-trained models (~15MB) are bundled in the `models/` directory — no extra downloads needed.

**Supported scenarios:** Chinese/English printed & handwritten text recognition, digit recognition, vertical/rotated/curved text.

### Requirements

- Python 3.8+
- Dependencies: `paddleocr`, `opencv-python`, `numpy`

```bash
# CPU version
pip install paddlepaddle==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/
pip install paddleocr opencv-python numpy

# GPU version (CUDA 11.8)
pip install paddlepaddle-gpu==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu118/
pip install paddleocr opencv-python numpy
```

### Project Structure

```
ocr_module_realese/
├── character_ocr/
│   ├── __init__.py                 # Package entry point
│   ├── ocr_engine.py               # Core engine (the only source file)
│   └── models/
│       ├── PP-OCRv5_mobile_det/    # Text detection model
│       └── PP-OCRv5_mobile_rec/    # Text recognition model
├── pyproject.toml                  # Package build config
├── LICENSE-2.0.txt                 # Apache License 2.0
└── README.md
```

### Quick Start

#### 1. Recognize a Single Image

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()                          # Auto-loads models on first use

img = cv2.imread("test.jpg")
results, drawn = ocr.recognize(img)           # Returns (results, annotated_image)

for r in results:
    print(f"Text: {r.text}  Score: {r.score:.2f}  BBox: {r.bbox}")

cv2.imwrite("test_result.jpg", drawn)
```

#### 2. Batch Processing Multiple Images

```python
import cv2, os
from character_ocr import CharacterOCR

ocr = CharacterOCR()
image_dir = "./images"

for filename in os.listdir(image_dir):
    if not filename.lower().endswith((".png", ".jpg", ".jpeg", ".bmp")):
        continue
    img = cv2.imread(os.path.join(image_dir, filename))
    if img is None:
        continue

    results, drawn = ocr.recognize(img)
    print(f"{filename}: {len(results)} text regions found")
    for r in results:
        print(f"  [{r.score:.2f}] {r.text!r}")
```

#### 3. Recognize Only (Skip Drawing — Faster for Batch)

```python
results = ocr.recognize_only(img)   # Returns list[OCRResult] only
for r in results:
    print(f"[{r.score:.2f}] {r.text!r}")
```

#### 4. Real-Time Camera Recognition

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()
cap = cv2.VideoCapture(0)  # 0 = default camera

print("Press Q to quit...")
while True:
    ret, frame = cap.read()
    if not ret:
        break

    results, drawn = ocr.recognize(frame)

    cv2.putText(drawn, f"Detected: {len(results)}", (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)
    cv2.imshow("OCR Camera", drawn)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
```

#### 5. Camera with Frame Skipping (Lower CPU Usage)

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()
cap = cv2.VideoCapture(0)

frame_count = 0
skip_interval = 10          # Run OCR every 10 frames
last_results = []

while True:
    ret, frame = cap.read()
    if not ret:
        break

    if frame_count % skip_interval == 0:
        last_results, drawn = ocr.recognize(frame)
    else:
        drawn = ocr.draw(frame, last_results)  # Reuse last results

    cv2.imshow("OCR Camera", drawn)
    frame_count += 1
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
```

#### 6. Global Singleton (Convenience API)

```python
import cv2
from character_ocr import get_ocr, recognize

# Method A: get the global singleton
ocr = get_ocr()
results, drawn = ocr.recognize(cv2.imread("test.jpg"))

# Method B: one-liner
results, drawn = recognize(cv2.imread("test.jpg"))
```

#### 7. Recognize Image from URL

```python
import cv2, numpy as np, requests
from character_ocr import CharacterOCR

ocr = CharacterOCR()
url = "https://example.com/sample.jpg"
resp = requests.get(url)
img_array = np.frombuffer(resp.content, dtype=np.uint8)
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)

results, drawn = ocr.recognize(img)
```

#### 8. ROI-based Recognition (Crop then Recognize)

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()
img = cv2.imread("receipt.jpg")
roi = img[100:300, 50:500]          # Crop region (y:y+h, x:x+w)

results, drawn = ocr.recognize(roi)
for r in results:
    print(f"ROI: {r.text} ({r.score:.2f})")
```

### API Reference

#### `OCRResult` dataclass

| Attribute | Type | Description |
|-----------|------|-------------|
| `text` | `str` | Recognized text content |
| `score` | `float` | Confidence score (0.0 ~ 1.0) |
| `box` | `list[list[int]]` | Four corner points `[[x0,y0],[x1,y1],[x2,y2],[x3,y3]]`, order: top-left→top-right→bottom-right→bottom-left |
| `center` | `tuple[float, float]` | Quadrilateral center `(cx, cy)`, computed from box |
| `bbox` | `tuple[int, int, int, int]` | Axis-aligned bounding box `(x, y, width, height)`, computed from box |
| `to_dict()` | `dict` | Convert result to dictionary |

#### `CharacterOCR` class

```python
ocr = CharacterOCR(
    score_threshold=0.3,       # Drop results below this confidence
    det_limit_side_len=960,    # Max side length for detection
    rec_batch_size=1,          # Recognition batch size
)
```

| Method | Signature | Description |
|--------|-----------|-------------|
| `load()` | `-> None` | Explicitly load models (auto-called on first `recognize`) |
| `recognize` | `(img_bgr: np.ndarray) -> tuple[list[OCRResult], np.ndarray]` | Detect + recognize, returns results & annotated image |
| `recognize_only` | `(img_bgr: np.ndarray) -> list[OCRResult]` | Detect + recognize, returns results only (no drawing) |
| `draw` | `(img_bgr: np.ndarray, results: list[OCRResult]) -> np.ndarray` | Manually draw detection boxes & labels on image copy |

| Property | Description |
|----------|-------------|
| `loaded` | `bool` — whether models are loaded |

#### Module-level Functions

| Function | Description |
|----------|-------------|
| `get_ocr(**kwargs)` | Get global singleton `CharacterOCR` instance |
| `recognize(img_bgr)` | Recognize using global singleton, equivalent to `get_ocr().recognize(img_bgr)` |

### Command Line

```bash
python character_ocr/ocr_engine.py test.jpg
# Prints recognition results and saves annotated image as test_result.jpg
```

### Model Info

Bundled PP-OCRv5_mobile models (~15MB total):

| Model | Directory | Purpose |
|-------|-----------|---------|
| `PP-OCRv5_mobile_det` | `models/PP-OCRv5_mobile_det/` | Text detection (locating text regions) |
| `PP-OCRv5_mobile_rec` | `models/PP-OCRv5_mobile_rec/` | Text recognition (reading text content) |

If `models/` is missing or the path contains non-ASCII characters, the engine will auto-download from HuggingFace.

### Notes

- Input must be **OpenCV BGR color image** (`np.ndarray`, shape `(H, W, 3)`, dtype `uint8`)
- For grayscale images, convert first: `cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)`
- Non-ASCII characters in the model path may cause fallback to auto-download
- Adjust `score_threshold` (default 0.3): increase for higher precision (e.g. 0.5), decrease for higher recall (e.g. 0.1)

---

## 中文

基于 [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) **PP-OCRv5_mobile** 的轻量级 OCR 字符识别库，单文件封装，开箱即用。模型文件（约 15MB）已随库附带在 `models/` 目录中，无需额外下载。

**支持场景：** 中英文印刷体/手写体识别、数字识别、竖排文字、旋转文字、弯曲文字等。

### 环境要求

- Python 3.8+
- 依赖包：`paddleocr`, `opencv-python`, `numpy`

```bash
# CPU 版
pip install paddlepaddle==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/
pip install paddleocr opencv-python numpy

# GPU 版 (CUDA 11.8)
pip install paddlepaddle-gpu==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu118/
pip install paddleocr opencv-python numpy
```

### 目录结构

```
ocr_module_realese/
├── character_ocr/
│   ├── __init__.py                 # 包入口
│   ├── ocr_engine.py               # 核心引擎（唯一源码文件）
│   └── models/
│       ├── PP-OCRv5_mobile_det/    # 文字检测模型
│       └── PP-OCRv5_mobile_rec/    # 文字识别模型
├── pyproject.toml                  # 包构建配置
├── LICENSE-2.0.txt                 # Apache 2.0 许可证
└── README.md
```

### 快速上手

#### 1. 读取单张图片并识别

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()                          # 首次调用自动加载模型

img = cv2.imread("test.jpg")
results, drawn = ocr.recognize(img)           # 返回 (结果列表, 标注图像)

for r in results:
    print(f"文字: {r.text}  置信度: {r.score:.2f}  位置: {r.bbox}")

cv2.imwrite("test_result.jpg", drawn)
```

#### 2. 批量处理多张图片

```python
import cv2, os
from character_ocr import CharacterOCR

ocr = CharacterOCR()
image_dir = "./images"

for filename in os.listdir(image_dir):
    if not filename.lower().endswith((".png", ".jpg", ".jpeg", ".bmp")):
        continue
    img = cv2.imread(os.path.join(image_dir, filename))
    if img is None:
        continue

    results, drawn = ocr.recognize(img)
    print(f"{filename}: 检测到 {len(results)} 个文字区域")
    for r in results:
        print(f"  [{r.score:.2f}] {r.text!r}")
```

#### 3. 只识别不绘图（提升批量处理性能）

```python
results = ocr.recognize_only(img)   # 只返回 list[OCRResult]
for r in results:
    print(f"[{r.score:.2f}] {r.text!r}")
```

#### 4. 摄像头实时识别

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()
cap = cv2.VideoCapture(0)  # 0 = 默认摄像头

print("按 Q 键退出...")
while True:
    ret, frame = cap.read()
    if not ret:
        break

    results, drawn = ocr.recognize(frame)

    cv2.putText(drawn, f"Detected: {len(results)}", (10, 30),
                cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)
    cv2.imshow("OCR Camera", drawn)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
```

#### 5. 摄像头——间隔帧识别（降低 CPU 占用）

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()
cap = cv2.VideoCapture(0)

frame_count = 0
skip_interval = 10          # 每 10 帧识别一次
last_results = []

while True:
    ret, frame = cap.read()
    if not ret:
        break

    if frame_count % skip_interval == 0:
        last_results, drawn = ocr.recognize(frame)
    else:
        drawn = ocr.draw(frame, last_results)  # 复用上一次结果

    cv2.imshow("OCR Camera", drawn)
    frame_count += 1
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
```

#### 6. 使用全局单例（快捷调用）

```python
import cv2
from character_ocr import get_ocr, recognize

# 方式 A：获取全局单例
ocr = get_ocr()
results, drawn = ocr.recognize(cv2.imread("test.jpg"))

# 方式 B：一行调用
results, drawn = recognize(cv2.imread("test.jpg"))
```

#### 7. 读取 URL 图片

```python
import cv2, numpy as np, requests
from character_ocr import CharacterOCR

ocr = CharacterOCR()
url = "https://example.com/sample.jpg"
resp = requests.get(url)
img_array = np.frombuffer(resp.content, dtype=np.uint8)
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)

results, drawn = ocr.recognize(img)
```

#### 8. 识别特定区域（ROI 裁剪后识别）

```python
import cv2
from character_ocr import CharacterOCR

ocr = CharacterOCR()
img = cv2.imread("receipt.jpg")
roi = img[100:300, 50:500]          # 裁剪区域 (y:y+h, x:x+w)

results, drawn = ocr.recognize(roi)
for r in results:
    print(f"ROI 区域: {r.text} ({r.score:.2f})")
```

### API 参考

#### `OCRResult` 数据类

| 属性 | 类型 | 说明 |
|------|------|------|
| `text` | `str` | 识别出的文字内容 |
| `score` | `float` | 置信度 (0.0 ~ 1.0) |
| `box` | `list[list[int]]` | 四点坐标 `[[x0,y0],[x1,y1],[x2,y2],[x3,y3]]`，顺序：左上→右上→右下→左下 |
| `center` | `tuple[float, float]` | 四边形中心点 `(cx, cy)`，由 box 自动计算 |
| `bbox` | `tuple[int, int, int, int]` | 轴对齐包围盒 `(x, y, width, height)`，由 box 自动计算 |
| `to_dict()` | `dict` | 将结果转为字典 |

#### `CharacterOCR` 类

```python
ocr = CharacterOCR(
    score_threshold=0.3,       # 低于此置信度的结果将被丢弃
    det_limit_side_len=960,    # 检测阶段长边尺寸上限
    rec_batch_size=1,          # 识别批大小
)
```

| 方法 | 签名 | 说明 |
|------|------|------|
| `load()` | `-> None` | 显式加载模型（`recognize` 首次调用时也会自动加载） |
| `recognize` | `(img_bgr: np.ndarray) -> tuple[list[OCRResult], np.ndarray]` | 检测+识别，返回结果列表和标注图像 |
| `recognize_only` | `(img_bgr: np.ndarray) -> list[OCRResult]` | 检测+识别，只返回结果列表（不绘图） |
| `draw` | `(img_bgr: np.ndarray, results: list[OCRResult]) -> np.ndarray` | 手动在图片副本上绘制检测框和标签 |

| 属性 | 说明 |
|------|------|
| `loaded` | `bool`，模型是否已加载 |

#### 模块级函数

| 函数 | 说明 |
|------|------|
| `get_ocr(**kwargs)` | 获取全局单例 `CharacterOCR` 实例 |
| `recognize(img_bgr)` | 使用全局单例识别一张图，等同于 `get_ocr().recognize(img_bgr)` |

### 命令行

```bash
python character_ocr/ocr_engine.py test.jpg
# 输出识别结果，并将标注图保存为 test_result.jpg
```

### 模型说明

库附带 PP-OCRv5_mobile 模型（移动端轻量版），模型总大小约 15MB：

| 模型 | 目录 | 说明 |
|------|------|------|
| `PP-OCRv5_mobile_det` | `models/PP-OCRv5_mobile_det/` | 文字检测（定位文字区域） |
| `PP-OCRv5_mobile_rec` | `models/PP-OCRv5_mobile_rec/` | 文字识别（识别文字内容） |

若 `models/` 目录不存在或路径包含非 ASCII 字符，引擎会自动从 HuggingFace 下载模型。

### 注意事项

- 输入图片格式必须为 **OpenCV BGR 彩色图**（`np.ndarray`，shape 为 `(H, W, 3)`，dtype 为 `uint8`）
- 灰度图请先用 `cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)` 转为三通道
- 模型路径中包含中文可能导致自动回退到网络下载
- `score_threshold` 默认 0.3，可根据场景调整：对精度要求高则调高（如 0.5），对召回要求高则调低（如 0.1）

---

## Acknowledgments / 致谢

This project is built upon the excellent work of the **PaddleOCR** team.
Original Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.

The bundled models `PP-OCRv5_mobile_det` and `PP-OCRv5_mobile_rec` are part of the [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) project, licensed under Apache 2.0.

本项目基于 **PaddleOCR** 团队的杰出成果构建。
原始版权 (c) 2020 PaddlePaddle Authors. All Rights Reserved.

附带的 `PP-OCRv5_mobile_det` 和 `PP-OCRv5_mobile_rec` 模型来自 [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) 项目，基于 Apache 2.0 协议。

- PaddleOCR GitHub: [https://github.com/PaddlePaddle/PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)
- PaddleOCR Documentation: [https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html](https://paddlepaddle.github.io/PaddleOCR/latest/en/index.html)

## License / 许可证

```
Copyright (c) 2026 pronoobe
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

Based on PaddleOCR (Apache 2.0)
Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.

You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
```

See [LICENSE-2.0.txt](LICENSE-2.0.txt) for the full license text.

详见 [LICENSE-2.0.txt](LICENSE-2.0.txt)。
