作用:从 eBay 交易卡牌图片中,识别出该卡属于哪种语言版本
目标语种(4 类,对齐
cards_master_v2的language字段):
tcg us(英文卡)tcg jp(日文卡)简中(简体中文卡)繁中(繁体中文卡)
┌──────────────────────────────────────────────────────────┐
│ 第一步:卡牌区域裁剪(pytorch 环境) │
│ 原图 → card_seg_v2(yolo26s-seg) → 卡牌区域裁剪图 │
│ 脚本: scripts/yolo_crop.py(即 yolo_crop.py) │
│ 产物: data/query_crops/*.jpg │
└───────────────────────────┬────────────────--------------┘
│ 中间切换 conda 环境
▼
┌──────────────────────────────────────────────────────────┐
│ 第二步:OCR + 语种判定(paddleocr 环境) │
│ 裁剪图 → PaddleOCR 3.2.0 识别 → 统计字符 → 判定语种 │
│ 脚本: ocr_judge.py │
│ 产物: data/language_labels_320.json │
└──────────────────────────────────────────────────────────┘
为什么分两步、还切环境? YOLO 依赖
ultralytics(在pytorch环境),OCR 依赖paddleocr==3.2.0 + paddlepaddle==3.2.0(在paddleocr环境),两套包互不兼容,无法共存于同一环境,故拆成两个独立步骤。
语种判定的全部逻辑在 ocr_judge.py 中:
| 功能 | 代码位置 | 说明 |
|---|---|---|
| 字符统计 | ocr_judge.py:61-63 |
统计 kana / cjk / latin 数量 |
| 判定主逻辑 | ocr_judge.py:55-73(judge() 函数) |
按优先级返回语种 |
| 简繁判定 | ocr_judge.py:67-69 |
调用 hanzidentifier |
| OCR 文本提取 | ocr_judge.py:36-47(ocr_full_text()) |
PaddleOCR 识别 |
| 文本截断 500 字符 | ocr_judge.py:92 |
text = full_text[:500] |
代码:
ocr_judge.py:61-63
kana = sum(1 for ch in text if 0x3040 <= ord(ch) < 0x3100)
cjk = sum(1 for ch in text if 0x4E00 <= ord(ch) < 0x9FFF)
latin = sum(1 for ch in text if ch.isascii() and ch.isalpha())
| 统计量 | 含义 | Unicode 范围 / 判定方式 |
|---|---|---|
kana |
假名数量(日文) | 0x3040 ~ 0x3100,含平假名(ぁ~ゎ)+片假名(ァ~ヶ) |
cjk |
CJK 汉字数量(中日韩) | 0x4E00 ~ 0x9FFF,统一表意文字区(一~龥) |
latin |
拉丁字母数量 | ASCII 字母 A-Z a-z |
s/t(仅中文卡才计算):见下文「简繁判定」。
0x3040~0x30FF:日文独有,平假名和片假名在这个区段集中存放。出现假名几乎 100% 是日文卡(中文卡、英文卡不可能有假名)。0x4E00~0x9FFF:中、日、韩共用的汉字都挤在这一个区段。问题在于简体字和繁体字共用同一个码位(字形不同但 Unicode 相同),所以光靠码位无法区分简繁,必须靠 hanzidentifier 比对字形。代码:
ocr_judge.py:55-73
def judge(text):
kana = sum(...) # 假名数
cjk = sum(...) # 汉字数
latin = sum(...) # 拉丁字母数
# ① 假名 ≥ 10 → 日文(铁证)
if kana >= 10:
return "tcg jp", {"kana": kana, "cjk": cjk, "latin": latin}
# ② 汉字比拉丁字母多 → 是中文卡,进一步判简繁
if cjk > latin:
s = sum(1 for ch in text if hz.identify(ch) == 2) # 简体字数
t = sum(1 for ch in text if hz.identify(ch) == 1) # 繁体字数
script = "简中" if s >= t else "繁中"
return script, {"kana": kana, "s": s, "t": t, "cjk": cjk}
# ③ 拉丁字母不少于汉字 → 英文卡
if latin >= cjk:
return "tcg us", {"kana": kana, "latin": latin, "cjk": cjk}
# ④ 都不满足 → 无法判定
return None, {"kana": kana, "cjk": cjk, "latin": latin}
| 优先级 | 条件 | 结果 | 原理 |
|---|---|---|---|
| ① | kana >= 10 |
tcg jp | 假名是日文铁证 |
| ② | cjk > latin |
简中 / 繁中 | 汉字占主导 |
| ③ | latin >= cjk |
tcg us | 拉丁字母占主导 |
| ④ | 否则 | None | 无法判定 |
if kana >= 10:
return "tcg jp", ...
if cjk > latin:
s = sum(1 for ch in text if hz.identify(ch) == 2)
t = sum(1 for ch in text if hz.identify(ch) == 1)
script = "简中" if s >= t else "繁中"
cjk > latin:先确认"这是一张中文卡"(汉字比英文多),再谈简繁。英文卡虽然也可能有零星汉字误识,但 latin 占绝对多数,不会进入这里。hanzidentifier 判简繁:前面说过,简体字和繁体字在 Unicode 里共用同一个码位(如"龙/龍"是两个不同码位,但很多字简繁同码位只是字形不同)。hanzidentifier 内部维护了简繁字形对照表,能逐字判断这个字是简体写法还是繁体写法:
hz.identify(ch) == 2 → 简体字hz.identify(ch) == 1 → 繁体字s >= t 投票:一张卡上汉字很多,逐字判简繁后按数量投票,少数服从多数。简繁字数相等时偏向简中(实际几乎不会相等)。if latin >= cjk:
return "tcg us", ...
不用 GlotLID 之类的语言分类模型,而是直接数字符,原因:
detail 里的 kana/cjk/latin/s/t 全是数字,判定过程完全透明,方便人工核对。判定顺序是 日文 → 中文 → 英文,按"信号强度"从强到弱排列。每命中一条就 return,不再往下走。这样:
这是整套逻辑里最巧妙也最依赖外部库的一环。因为 Unicode 不区分简繁,必须借助 hanzidentifier 的字形表,再按数量投票,才能区分简中卡和繁中卡。
OCR 前先用 YOLO 把卡牌区域裁出来,去掉 eBay 页面边框、PSA/CGC 评级壳文字、卖家水印等干扰,让 OCR 文本更纯净,字符统计更准确。
文件:
data/language_labels_320.json,100 条
每条记录结构(键的含义见第三、四节):
{
"1": {
"language": "tcg us", // 判定结果
"detail": { // 判定依据
"kana": 0, // 假名数
"latin": 228, // 拉丁字母数
"cjk": 0 // 汉字数(中文卡还会有 s/t)
},
"text": "BASICCharmander..." // OCR 文本(≤500 字符)
}
}
注意:
detail的键不固定。日文卡只有kana/cjk/latin(命中规则①即返回);中文卡才会有s/t(进入规则②才算);英文卡是kana/latin/cjk。
| 语种 | 数量 | 占比 |
|---|---|---|
| tcg us(英文) | 81 | 81% |
| tcg jp(日文) | 18 | 18% |
| 繁中 | 1 | 1% |
| 文件 | 路径 | 用途 |
|---|---|---|
| YOLO 裁剪脚本 | yolo_crop.py |
第一步,pytorch 环境运行 |
| OCR+判定脚本 | ocr_judge.py |
第二步,paddleocr 环境运行 |
| 裁剪图产物 | data/query_crops/ |
100 张卡牌裁剪图 |
| 判定结果 | data/language_labels_320.json |
最终输出 |
| 卡牌检测模型 | ★ultralytics/runs/segment/card_seg_pn_v2/weights/best.pt(TRT: 同目录 best.engine;旧 yolov11n_card_seg01.onnx 仅 bbox_legacy 回退) |
卡牌检测分割 |
| OCR 模型 | PaddleOCR 3.2.0 内置 PP-OCRv5 | 文本识别 |
| 简繁库 | hanzidentifier (pip) |
简繁字形判定 |
环境:服务器
192.168.77.249,YOLO 步骤用pytorch环境,OCR 步骤用paddleocr环境(paddleocr 3.2.0 + paddlepaddle 3.2.0)。