# OCR 识别流水线：从图片到文字

> 一张验证码图片如何经过加载、预处理、模型推理、CTC 解码，最终变成一行文字。

- Repository: sml2h3/ddddocr
- GitHub: https://github.com/sml2h3/ddddocr
- Human wiki: https://grok-wiki.com/public/wiki/sml2h3-ddddocr-a34dd45d9f63
- Complete Markdown: https://grok-wiki.com/public/wiki/sml2h3-ddddocr-a34dd45d9f63/llms-full.txt

## Source Files

- `ddddocr/core/ocr_engine.py`
- `ddddocr/models/model_loader.py`
- `ddddocr/models/charset_manager.py`
- `ddddocr/utils/image_io.py`

---

<details>
<summary>相关源文件</summary>
以下文件用于生成此维基页面：
- [ddddocr/core/ocr_engine.py](ddddocr/core/ocr_engine.py)
- [ddddocr/core/base.py](ddddocr/core/base.py)
- [ddddocr/models/model_loader.py](ddddocr/models/model_loader.py)
- [ddddocr/models/charset_manager.py](ddddocr/models/charset_manager.py)
- [ddddocr/utils/image_io.py](ddddocr/utils/image_io.py)
- [ddddocr/preprocessing/image_processor.py](ddddocr/preprocessing/image_processor.py)
- [ddddocr/preprocessing/color_filter.py](ddddocr/preprocessing/color_filter.py)
</details>

# OCR 识别流水线：从图片到文字

本文介绍 ddddocr 的核心 OCR 识别流程——一张验证码图片如何经过**加载、预处理、模型推理、CTC 解码**四个阶段，最终变成一行可读文字。理解这条流水线有助于排查识别不准、自定义模型接入、以及字符集范围限制等问题。

---

## 整体流程概览

```
输入图片（bytes / base64 / 文件路径 / PIL Image / numpy）
    │
    ▼
┌─────────────┐
│ 1. 图片加载   │  load_image_from_input()
└─────┬───────┘
      │  PIL Image (可能为 RGBA)
      ▼
┌─────────────┐
│ 2. 颜色过滤   │  ColorFilter（可选，按 HSV 范围过滤干扰色）
└─────┬───────┘
      │  PIL Image
      ▼
┌─────────────┐
│ 3. 预处理     │  resize → grayscale → normalize → 维度调整
└─────┬───────┘
      │  numpy array, shape = (1, C, H, W), float32, 值域 [0,1]
      ▼
┌─────────────┐
│ 4. ONNX 推理  │  session.run()
└─────┬───────┘
      │  output shape = (seq_len, 1, num_classes) 或类似
      ▼
┌─────────────┐
│ 5. CTC 解码   │  去连续重复 → 去 blank(索引 0) → 映射字符
└─────┬───────┘
      │
      ▼
    识别文字 "aB3x"
```

---

## 第一阶段：图片加载

`load_image_from_input()` 函数是统一入口，接受五种输入格式并返回标准的 `PIL.Image` 对象：

| 输入类型 | 处理方式 |
|---------|---------|
| `bytes` | 用 `Image.open(io.BytesIO(...))` 解码 |
| `str`（文件路径） | 直接 `Image.open(path)` |
| `str`（base64） | 先 `base64.b64decode`，再 `Image.open` |
| `PIL.Image` | 调用 `.copy()` 避免外部修改 |
| `numpy.ndarray` | 按 shape/dtype 转为灰度或 RGB 的 PIL Image |

对于 PNG 透明背景的验证码，`png_rgba_black_preprocess()` 会创建白色背景并把原图 alpha 通道贴上去，避免透明像素导致文字丢失。

Sources: [ddddocr/utils/image_io.py:82-119]()，[ddddocr/utils/image_io.py:59-79]()

---

## 第二阶段：颜色过滤（可选）

当调用者传入 `color_filter_colors` 或 `color_filter_custom_ranges` 参数时，`ColorFilter` 会在 HSV 颜色空间对图片做掩码过滤——只保留指定颜色范围内的像素，其余变白。这在验证码含有干扰线或干扰色块时特别有用。

内置预设覆盖 `red`、`blue`、`green`、`yellow` 等常见颜色。例如 `red` 需要两个 HSV 区间（0-10 和 170-180），因为红色在色相环上跨越了 0° 边界。

Sources: [ddddocr/preprocessing/color_filter.py:23-34]()，[ddddocr/core/ocr_engine.py:133-139]()

---

## 第三阶段：图像预处理

这是流水线中最关键的一步，`_preprocess_image()` 负责把任意尺寸的 PIL Image 变成模型能吃的张量。

### 3.1 尺寸调整

默认模型要求**高度固定为 64 像素，宽度按比例缩放**：

```python
target_height = 64
target_width = int(image.size[0] * (target_height / image.size[1]))
```

自定义模型则根据 `charset_info['image']` 配置决定：若第一个维度为 `-1` 表示宽度自适应；否则使用固定尺寸。

Sources: [ddddocr/core/ocr_engine.py:176-192]()

### 3.2 灰度转换

默认模型和 `channel=1` 的自定义模型会将图片转为灰度（`mode='L'`），单通道。3 通道模型则保留彩色。

### 3.3 归一化与维度调整

```python
img_array = np.array(image).astype(np.float32) / 255.0  # 像素值归一化到 [0,1]

# 2D 灰度 → (1, H, W)，添加通道维
# 3D 彩色 → (C, H, W)，HWC 转 CHW
# 最后 → (1, C, H, W)，添加 batch 维
```

最终张量 shape 为 `(1, 1, 64, W)` 或 `(1, 3, H, W)`，dtype 为 `float32`。

Sources: [ddddocr/core/ocr_engine.py:199-212]()

---

## 第四阶段：ONNX 模型推理

模型通过 `onnxruntime.InferenceSession` 加载和执行。`ModelLoader` 负责：

- **选择模型文件**：默认 `common_old.onnx`，beta 模式用 `common.onnx`，自定义模型由用户指定路径
- **配置执行提供者**：优先 CUDA（GPU），失败回退 CPU
- **创建推理会话**：`onnxruntime.InferenceSession(model_path, providers=...)`

推理只需一次调用：

```python
input_name = self.session.get_inputs()[0].name
outputs = self.session.run(None, {input_name: image_array})
```

输出 `outputs[0]` 的典型 shape 为 `(sequence_length, 1, num_classes)`，其中 `num_classes` 等于字符集大小。

Sources: [ddddocr/models/model_loader.py:55-81]()，[ddddocr/core/ocr_engine.py:228-234]()

---

## 第五阶段：CTC 解码

这是将模型的"概率矩阵"变成最终文字的核心步骤，分为三个子步骤：

### 5.1 取最大概率索引

对输出的最后一维（字符类别维度）取 `argmax`，得到每个时间步的预测索引：

```python
predicted_indices = np.argmax(output[:, 0, :], axis=1)
# 例如：[0, 3, 3, 0, 7, 7, 12, 0] （0 代表 blank）
```

### 5.2 CTC 去重与去 blank

CTC（Connectionist Temporal Classification）的核心规则：

1. **去连续重复**：相邻相同的索引只保留一个
2. **去 blank**：索引 0 代表"无字符"（blank token），直接跳过

```python
# [0, 3, 3, 0, 7, 7, 12, 0]
# → 去连续重复后: [0, 3, 0, 7, 12, 0]
# → 去 blank 后: [3, 7, 12]
```

Sources: [ddddocr/core/ocr_engine.py:300-329]()

### 5.3 索引映射字符 & 范围过滤

解码后的索引通过字符集（charset）映射回文字。如果调用者设置了 `charset_range`（如只允许数字），不在有效索引内的字符会被跳过。

字符集是一个 Python 列表，索引 0 是空字符串 `""`（blank 对应位），后续索引依次对应各可识别字符。默认旧版字符集约有数千个中文、字母、数字、符号。

Sources: [ddddocr/core/ocr_engine.py:280-295]()，[ddddocr/models/charset_manager.py:237-238]()

---

## 字符集管理

`CharsetManager` 统一管理字符集的加载、索引映射和范围限制：

| 功能 | 方法 | 说明 |
|------|------|------|
| 加载默认字符集 | `load_default_charset()` | 内置 old/beta 两套字符列表 |
| 加载自定义字符集 | `load_custom_charset(path)` | 从 JSON 文件读取，需含 `charset`、`word`、`image`、`channel` 字段 |
| 设置范围限制 | `set_ranges(range)` | 支持按索引（int）、按字符串、按列表三种方式 |
| 获取有效索引 | `get_valid_indices()` | 返回 `charset_range` 中各字符在完整 charset 里的索引 |

范围限制会自动追加空字符串 `""`，确保 blank token 始终存在。

Sources: [ddddocr/models/charset_manager.py:83-124]()

---

## 自定义模型接入

用户可以通过 `import_onnx_path` 和 `charsets_path` 参数加载自定义 ONNX 模型。字符集 JSON 文件格式：

```json
{
  "charset": ["", "a", "b", "c", ...],
  "word": false,
  "image": [64, 64],
  "channel": 1
}
```

- `charset`：字符列表，索引 0 必须为空字符串（CTC blank）
- `word`：是否为单词级别识别（影响 resize 策略）
- `image`：目标尺寸，`[-1, H]` 表示宽度自适应
- `channel`：通道数，1 为灰度，3 为彩色

Sources: [ddddocr/models/model_loader.py:170-204]()，[ddddocr/core/ocr_engine.py:64-78]()

---

## 概率模式

当 `probability=True` 时，引擎会在 CTC 解码之外额外计算 softmax 概率，返回字典：

```python
{
    'text': 'aB3x',           # 识别文字
    'probabilities': [...],   # 每个时间步对所有字符的概率
    'charset': [...],         # 完整字符集
    'confidence': 0.95        # 平均最高概率
}
```

Sources: [ddddocr/core/ocr_engine.py:331-363]()

---

## 总结

ddddocr 的 OCR 流水线是一条经典的**端到端文字识别管线**：图片加载 → 可选颜色过滤 → 尺寸/灰度/归一化预处理 → ONNX Runtime 推理 → CTC 贪心解码 → 字符映射。整条链路代码量不大，但每一步都有明确的职责划分——`image_io` 负责格式统一，`ImageProcessor` 负责像素变换，`ModelLoader` 负责模型生命周期，`CharsetManager` 负责字符映射和范围约束，`OCREngine` 则将它们串成完整的预测管线。
