# 宝可梦卡牌识别 · 操作方法(每一步怎么做) > 面向接手的开发/运维。**照着做即可**。资产清单与落位见 `README_上线清单.md`,接口契约见 `docs/宝可梦卡牌识别服务文档接口.md`。 > 本文所有脚本路径以包内 `code/` 为基准;部署到服务器后即框架根 `~/顾工交接/wzj/`。 > 每条结论都标了 `脚本:行`,可自行核对。 --- ## 零、先看这张图:当前自动化识别方案的链式流程 **在线(生产,一次请求 ~0.5s,全程在内存里跑完,无中间文件):** ``` HTTP 请求 (image_url / multipart image) │ ┌────────────────┴────────────────┐ :8010 /recognize :8000 /match serve_recognition_api.py serve_card_match.py └────────────────┬────────────────┘ │ 两者都调同一个匹配器 ▼ image_downloader.download_card_image() 下载到 data/query_images/.jpg ★用完即删 (serve_recognition_api.py:162-166) │ ▼ DualCascadeMatcher.query() ← scripts/cascade_match.py:150 │ ┌────────────────────────────┴───────────────────────────┐ │ ① 裁卡 CardDetector.detect_and_crop() │ 模型① │ modules/yolo_detector.py:108 │ card_seg_pn_v2_best.pt │ seg_warp: mask → 4 角点 → warp_perspective → 竖放 │ (yolo26s-seg) │ 输出:摆正的卡面 RGB 数组(可变尺寸,不落盘) │ ├─────────────────────────────────────────────────────────┤ │ ② letterbox392() feature_extractor_dual.py:30 │ │ BICUBIC 等比缩放 + 黑边填充 → 392×392 │ ├─────────────────────────────────────────────────────────┤ │ ③ split_half() feature_extractor_dual.py:43 │ │ 上半区 crop(0,0,392,196) │ 下半区 crop(0,196,392,392) │ │ 各 196×392 │ ├──────────────────────────┬──────────────────────────────┤ │ ④上半区 extract_upper() │ ⑤下半区 extract_lower() │ 模型②③ │ dinov2-large → CLS │ dinov2-large → CLS │ best_upper_half.pth │ → L2 归一化 → (1024,) │ → L2 归一化 → (1024,) │ best_layer3_bottom.pth │ feature_extractor_dual.py:137 (F.normalize) │ ├──────────────────────────┼──────────────────────────────┤ │ ⑥ 全库召回(物种级) │ ⑦ 候选重排(版本级) │ │ gallery_upper @ q_u │ gallery_lower[cand] @ q_l │ │ argpartition 取 K=30 │ 只对这 30 个算 │ │ cascade_match.py:105-112│ cascade_match.py:110 │ └──────────────────────────┴──────────────────────────────┘ │ ▼ ⑧ 融合重排 fusion = α·upper + (1-α)·lower α=0.5 cascade_match.py:142 │ ▼ 返回 {predicted_card_id, fusion_score, upper_sim, lower_sim, top_k[]} cascade_match.py:157-164 │ │ 8010 换算 match_rate=round(f×100) 8000 拿 card_id 查 输出 matches[] card_master_fields.csv 出业务字段 ``` **为什么要分上下两区**:上半区(标题+插画)决定"是哪只精灵",下半区(技能框+卡号+稀有度符号+闪卡反光层)决定"哪个版本"。一个模型学不好两种维度,所以按卡牌版式上下各切 50%,各训一个专用模型,先召回再消歧。 **旁挂件(可选,不影响主链)**:`:8100 serve_lang_judge.py` 出语种/方向。★注意它**当前不参与检索**——`serve_recognition_api.py:18-19` 注释明确写"仅记 language 到日志,不参与检索前置",`recognize()` 函数体里没有任何 language 消歧逻辑。所以它挂了对准确率无影响。 **离线(建库/批量/其它子系统)**:见第三、四、五节,这些是**有中间文件落盘**的多步链路。 --- ## 一、日常操作:启动在线服务 ### 1.1 三个进程(各自独立,按需起) ```bash cd ~/顾工交接/wzj mkdir -p logs # ① OCR sidecar(paddleocr 环境)—— 可选,8010 的旁挂件 CUDA_VISIBLE_DEVICES=1 setsid nohup \ ~/miniconda3/envs/paddleocr/bin/python serve_lang_judge.py --port 8100 \ logs/serve_8100.log 2>&1 & # ② 主识别接口(pytorch 环境)—— 后端调这个 CUDA_VISIBLE_DEVICES=1 setsid nohup \ ~/miniconda3/envs/pytorch/bin/python serve_recognition_api.py --port 8010 \ logs/serve_8010.log 2>&1 & # ③ 业务字段接口(pytorch 环境)—— 小程序/后端要卡名卡号时起 CUDA_VISIBLE_DEVICES=1 setsid nohup \ ~/miniconda3/envs/pytorch/bin/python serve_card_match.py --port 8000 \ logs/serve_8000.log 2>&1 & ``` ### 1.2 启动时到底加载了什么(`cascade_match.py:50-86`) | 加载项 | 文件 | npy 后端 | milvus 后端 | |---|---|---|---| | 图库元数据 | `data/gallery_dual_meta.json` | ✅ 读 | ✅ 读(仍要读,取 card_ids/metas) | | 上半区特征 | `data/gallery_upper_features.npy` | ✅ `np.load` | ❌ 不读 | | 下半区特征 | `data/gallery_lower_features.npy` | ✅ `np.load` | ❌ 不读 | | Milvus 连接 | `192.168.77.249:19530` | ❌ | ✅ 连两个 collection | | 裁卡模型 | `card_seg_pn_v2/weights/best.pt` | ✅ | ✅ | | 上/下半区模型 | 两个 `.pth`(各 1.2G) | ✅ | ✅ | | 业务字段表 | `data/card_master_fields.csv` | 8000/8010 各自单独加载 | 同 | 切后端:`config.py:103` 的 `CASCADE_BACKEND`(`gpu` / `milvus`),或命令行 `--backend`。**249 现役 `gpu`**(特征库 fp32 常驻显存,召回 0.4ms/卡);`milvus` 为回滚兜底。 ### 1.3 验证(两步都要做) ```bash curl http://127.0.0.1:8010/health # 必须看到 gallery_size: 91116。若是 88384/78272 → 权重/图库混版,立刻停服。 # ★ /health 会「假 ok」,必须再打一次真实请求 curl -X POST http://127.0.0.1:8010/recognize -H 'Content-Type: application/json' \ -d '{"task_id":"smoke-1","image_url":"https://illustration-1309648802.cos.ap-guangzhou.myqcloud.com/ai_image/04101495/synthesis_result_1781513293_8eb1dce1-e93b-4c78-90f1-16c9d7251498.jpg","top_k":3}' # 预期 card_id=04101495、match_rate≈96 ``` ### 1.4 会在磁盘上留下什么 | 产物 | 路径 | 说明 | |---|---|---| | 下载的查询图 | `data/query_images/.jpg` | **用完即删**(两个 serve 的 finally 块) | | 8000 的调用历史 | `data/match_results_history.json` | `serve_card_match.py:108-124` **持续追加,会一直长大**,定期清 | | 日志 | `logs/serve_{8000,8010,8100}.log` | | --- ## 二、离线批量匹配(要跑一批图出表格时) **单图 / 小批量抽查** —— `code/scripts/cascade_match.py`: ```bash cd ~/顾工交接/wzj CUDA_VISIBLE_DEVICES=1 ~/miniconda3/envs/pytorch/bin/python scripts/cascade_match.py --image <单图路径> CUDA_VISIBLE_DEVICES=1 ~/miniconda3/envs/pytorch/bin/python scripts/cascade_match.py --image-dir <目录> CUDA_VISIBLE_DEVICES=1 ~/miniconda3/envs/pytorch/bin/python scripts/cascade_match.py --self-test 500 ``` - `--image` / `--image-dir`:结果只打到 **stdout**,不落文件。 - `--self-test N`:抽 N 张库图自检索,Top-1 应命中自己 → 报告写 `data/cascade_test_report_self_test.json`。 - 通用参数:`--alpha` / `--top-k-recall` / `--backend`。 **批量出表** —— `code/scripts/cascade_match_batch.py`(生产批量入口): ```bash CUDA_VISIBLE_DEVICES=1 ~/miniconda3/envs/pytorch/bin/python scripts/cascade_match_batch.py \ --image-dir <图目录> --out-csv <输出.csv> --top-n 5 ``` - 输入:目录下 `*.jpg/jpeg/png/webp` - 输出①:`--out-csv` 指定的 CSV,列 = `id, predicted_card_id, card_name_ch, series, language, year, master_card_no, fusion, upper_sim, lower_sim, high_conf, top5`(`high_conf` = fusion ≥ `SIMILARITY_THRESHOLD`) - 输出②:`.topk.json`,每张图的完整 Top-K 明细 > ⚠️ 用户要求**推理结果只存 .xlsx 不要 csv**。这个脚本原生出 CSV,跑完请转成 xlsx 并删掉 csv(或改脚本用 `to_excel`)。 --- ## 三、图库重建(★这里有最大的坑,务必看完) ### 3.1 建库脚本 **现役脚本**:`code/build_dual_gallery_v0904.py` | 项 | 值 | |---|---| ```bash cd ~/顾工交接/wzj CUDA_VISIBLE_DEVICES=1 ~/miniconda3/envs/pytorch/bin/python build_dual_gallery_v0904.py # --limit N 仅处理前 N 条(冒烟用,默认 0=全量) # --batch N 批大小(默认 64) ``` | 项 | 值 | |---|---| | 输入·卡列表 | `data/train_meta_v0904.json` → `["records"]` **已随包提供**(91,116 条,fname=`record.id.ext` 新命名) | | 输入·图片 | `data/gallery_images/` **不随包提供**(几十 GB),需另外准备 | | 输出目录 | `data/gallery_v0904/` —— **config.py:96-98 直接指向这里,建完无需搬主路径** | | 输出文件 | `gallery_upper_features.npy` / `gallery_lower_features.npy` / `gallery_dual_meta.json` | | 预处理入口 | `import modules.feature_extractor_dual`(与在线查询**同一份代码**)| | 断点续跑 | **无**。中断只能从头再来(全量约 85 min) | | 容错 | 无图 → `n_noimg++` 跳过;裁切/特征失败 → `n_fail++` 跳过;NaN 特征剔除 | | 自检 | 结束时 assert 上下两库形状一致(行对齐) | > **为什么建库和查询必须同一个预处理入口**:库特征和查询特征要在同一个向量空间里做点积才有意义。`letterbox392` → `split_half` → ImageNet 归一化 → CLS → L2,任何一环不一致,相似度就不可比。所以两侧都只走 `modules/feature_extractor_dual.py`,不要另写预处理。 ### 3.3 无需搬库(v0904 起) v0904 建库输出在 `data/gallery_v0904/`,**config.py 就直接指向该子目录**(`:96-98`),建完重启服务即生效。 (v819 时代"输出 gallery_v819/ 再手工搬主路径"的步骤已废除;旧主路径 npy 若存在属 v819 回滚资产,勿混用。) ### 3.4 校验(通用自检索) ```bash CUDA_VISIBLE_DEVICES=1 ~/miniconda3/envs/pytorch/bin/python scripts/cascade_match.py --self-test 200 ``` 抽 N 张库图从原图重跑完整链路,Top-1 应命中自己;报告写 `data/cascade_test_report_self_test.json`。 (读的是 config 现役库,即 `gallery_v0904/`;旧 `scripts/_verify_gallery_v819.py` 硬编码 v819 路径,仅回滚场景使用。) ### 3.5 Milvus 重灌(仅 `CASCADE_BACKEND="milvus"` 回滚场景需要) ```bash python scripts/_milvus_rebuild_v819.py --dry-run # 先只校验不写 python scripts/_milvus_rebuild_v819.py --drop # 确认后 drop 重建 ``` > ⚠️ 该脚本读 `data/gallery_v819/`,**是 v819 库的回滚工具**。v819 库文件已于 2026-09-07 从服务器删除, > 备份在本地 `宝可梦\249备份\`;回滚 = 先传回五件(权重×2→`upper_model_output_v0904` 同级的 > `upper_model_output/`/`layer3_bg_model_output/`,npy+meta→`data/` 主路径)→ 恢复旧 config → 重启。 ```bash python scripts/_milvus_rebuild_v819.py --dry-run # 先只校验不写 python scripts/_milvus_rebuild_v819.py --drop # 确认后 drop 重建 ``` | 项 | 值 | |---|---| | 目标 | `192.168.77.249:19530`(`:21`) | | Collection | `pokemon_dual_upper_dinov2l_1024` / `pokemon_dual_lower_dinov2l_1024`(`:27-28`),dim=1024,metric=COSINE | | 读取源 | `data/gallery_v819/` 下的 npy + meta(`:22-25`)——v819 库文件已删,先从 `宝可梦\249备份\` 传回 | | 安全机制 | **PROTECTED 黑名单**(`:32-34`):`pokemon_cards`(旧768维) / `vit_base_pokemon`(别的项目) **禁止操作**;只有上面两个 collection 在白名单里,动别的直接 `raise RuntimeError` | | 灌前校验 | 行数一致、维度=1024、card_id 唯一且非空且 ≤32 字符、向量已归一化 | | 写入 | BATCH=10000 分批 insert → flush → create_index → load | ### 3.6 增量加卡(★没有现成脚本) `build_dual_gallery_v0904.py` **只能全量重建,没有 append 模式**。要只加新卡不重建,需要自己改:读现有 npy → `np.vstack` 新特征 → 合并 meta 的 `card_ids`/`metas`(**必须保持行对齐**)→ 覆盖保存(gpu 后端保存后重启服务即生效;Milvus 兜底侧无增量入口,只有 `--drop` 全灌)。 好消息是:**加卡不需要重训模型**(模型与图库解耦)。只有真重训才必须"权重 + 特征库(+ Milvus 兜底)整套同步换"。 --- ## 四、card_no 编号识别线 ``` {IN_DIR}/*.jpg ──①──► {OUT_DIR}/card_no_crops/*.jpg ──②──► {OUT_DIR}/card_no_ocr.json flat_card_no_detect + card_no_crop_records.json flat_card_no_ocr (pytorch) (paddleocr37) 模型:card_seg_pn_v2 + yolo26s_card_no PP-OCRv6_medium_rec ``` | 步 | 脚本 | 环境 | 模型 | 输出 | |---|---|---|---|---| | ① | `pokemon/flat_card_no_detect.py` | pytorch | `card_seg_pn_v2`(裁卡)+ `yolo26s_card_no`(检编号框,`:29-30`) | `card_no_crops/` 裁剪图(文件名带 conf 前缀)+ `card_no_crop_records.json` | | ② | `pokemon/flat_card_no_ocr.py` | paddleocr37 | `PP-OCRv6_medium_rec`(**仅 rec,不用 det**,`:33`;`engine=transformers, device=cpu`) | `card_no_ocr.json`:`rec_text / card_no / leniency_applied / empty_reason / rec_score / det_conf` | - `MARGIN` 环境变量(默认 0.05,`:17`)控制编号框外扩幅度。 - 宽容判定:`modules/card_no_leniency.py` 的 `finalize_card_no()` 在 `flat_card_no_ocr.py:61` 被调用,单次宽容后仍不合规就置空,并用 `empty_reason` 区分 **无编号(`rec_empty`)** vs **模糊不可读(`blur_unreadable`)**。 - `pokemon/flat_card_no.py` 是"检测+可视化"的集成示例版(出 `vis_num/` + `labels_num/`),不出 OCR 结果。 - `parse_card_no.py` 是**已归档的老方案**(固定 ROI `seq_left/seq_right` + det+rec + 五格式猜号),2026-08-11 起废弃,留作历史参照。 --- ## 五、语种 / 方向判断 两个版本、同一套判定规则(`ocr_judge.py:76-95` 与 `serve_lang_judge.py:90-106` 逻辑一致): | 版本 | 脚本 | 用法 | 输出 | |---|---|---|---| | 批处理 | `yolo_crop.py`(pytorch,裁卡) → `ocr_judge.py`(paddleocr,判定) | 离线跑一批 | `language_labels_320.json`:`{language, angle, detail, text}` | | 服务 | `serve_lang_judge.py` :8100 | 常驻,8010 内部调 | 同结构 JSON | 规则:① 假名 ≥10 → `tcg jp`;② 汉字×3 > 拉丁字母 → 中文,再用 `hanzidentifier` 投票判 `简中`/`繁中`;③ 否则 → `tcg us`。 **必须拆两个 conda 环境**:裁卡要 `ultralytics`(pytorch 环境),OCR 要 `paddleocr==3.2.0 + paddlepaddle 3.2.0`,两套包互不兼容、无法共存。 ★再强调一次:**语种当前没有参与在线检索**(`serve_recognition_api.py:18-19`)。 --- ## 六、cat16 三属性(rarity / element)—— 不在本包内 这条线的脚本在 `D:\顾工交接\wzj\ebay_入库\完整流程\ebay_sync\` 和 `D:\顾工交接\wzj\ebay_sync\`,**没有随本包交付**(属 ebay 入库子系统)。模型权重在包内 `models/04_cat16三属性与编号/`,需要时按下面的流程接: ``` infer_cat16_pipeline.py --ocr-dir roi_ocr_A (pytorch) 裁卡 → 透视矫正 → 切 box1/box2(稀有度) box3(属性) ROI → 4× LANCZOS 放大 → 喂检测 模型:yolo26s_det_rarity(25类) / yolo26s_det_element(13类) ├─► infer_cat16_raw_2000.csv (box1/2/3_cls + conf) └─► roi_ocr_A/seq_{left,right}/*.jpg (编号 ROI 裁剪) │ ocr_card_no_step.py --crop-dir roi_ocr_A (paddleocr) └─► roi_ocr_A_viz/seq_{left,right}_texts.tsv │ parse_card_no_ebay.py └─► card_no_parsed_cat16.csv │ run_full_rerun_cat16.py::step5_parse_and_merge() └─► infer_cat16_result_2000.csv 列:id, crop_status, card_no, rarity_cls, rarity_conf, box3_cls, box3_conf, error ``` ⚠️ 这批脚本路径硬编码严重(服务器 `/home/user/...` 与本地 `D:\...` 混写),迁移要逐个改。 --- ## 七、新增卡片上线的标准流程(把上面串起来) | # | 做什么 | 怎么做 | 人工? | 耗时 | |---|---|---|---|---| | 1 | 更新卡列表 | 从 PG `cards_master_v2` 导出,更新 `train_meta_v0904.json` / `card_master_fields.csv` | **是** | 分钟级 | | 2 | 下图 | 图片下到 `data/gallery_images/`,**文件名必须是 `record.id.ext` 命名**(对齐 `train_meta_v0904.json` 的 fname) | **是** | ~30 min | | 3 | 建库 | `python build_dual_gallery_v0904.py` | 否 | ~90 min | | 4 | 校验 | `python scripts/cascade_match.py --self-test 200`,看 Top-1 命中率 | 否 | 5-10 min | | 5 | 重启服务 | 按 §1.1 重启,验 `gallery_size` 变成新行数 + 打真实请求 | **是** | 1 min | | 6 | (仅 milvus 兜底)灌 Milvus | `python scripts/_milvus_rebuild_v819.py --drop`(需先适配读取源) | **是**(需确认) | 10-15 min | **只加卡、不重训**是常态;一旦重训了模型,权重 / 特征库(/ Milvus 兜底)必须整套一起换。 若重训换权重:新权重进**新的版本化目录**(如 `upper_model_output_v0904/`)+ config 指过去,勿覆盖旧目录(便于回滚)。 --- ## 八、关键参数与它们在哪 | 参数 | 值 | 位置 | 含义 | |---|---|---|---| | `CASCADE_ALPHA` | 0.5 | `config.py:91` | `fusion = α·upper + (1-α)·lower` | | `CASCADE_TOP_K_RECALL` | 30 | `config.py:90` | 上半区全库召回多少个交给下半区重排 | | `SIMILARITY_THRESHOLD` | 0.50 | `config.py:75` | 高/低置信分界,只影响 `high_conf` 标记,不卡返回 | | `CASCADE_BACKEND` | `gpu` | `config.py:103` | 检索后端(gpu=特征库常驻显存;milvus=回滚兜底) | | `DUAL_FEATURE_DIM` | 1024 | `config.py:82` | dinov2-large CLS 维度 | | `CARD_CROP_MODE` | `seg_warp` | `config.py:16` | `seg_warp`=mask 4 点 warp(默认)/ `bbox_legacy`=只切矩形 | | `MARGIN` | 0.05 | 环境变量,`flat_card_no_detect.py:17` | 编号框外扩 | | `LANG_JUDGE_TIMEOUT` | 8s | 环境变量 | 调 8100 超时即降级 | --- ## 九、踩坑速查(都是实测过的) | 坑 | 现象 | 对策 | |---|---|---| | **config 指错库** | 服务起来 `gallery_size` 还是旧数 | 确认 `config.py:96-98` 指向 `data/gallery_v0904/`,重启生效 | | **权重图库混版** | 匹配全乱、相似度普遍很低 | 检查 `/health` 的 `gallery_size`;权重和特征库必须同版 | | **选错 GPU** | `no kernel image is available` | 必须使用 `CUDA_VISIBLE_DEVICES=1` | | **`/health` 假 ok** | health 通了但请求全错 | 必须再打一个真实请求验 | | **`ultralytics` 内存泄漏** | 批量 predict 越跑越占内存 | 分片跑、跑完重启进程 | | **onnxruntime 影子化** | 明明该走 GPU 却静默回退 CPU | `requirements.txt` 里的裸 `onnxruntime` 会覆盖 GPU 版 | | **PaddleOCR 环境串了** | `import paddle` 失败或 ultralytics 报错 | pytorch 与 paddleocr 两个环境的包互不兼容,别混装 | | **history 文件涨爆盘** | `data/match_results_history.json` 巨大 | 8000 每次请求都追加,定期清理 | --- ## 十一、包内脚本地图 ``` code/ (2026-09-07 全量对齐 249/73 生产) ├── serve_recognition_api.py :8010 主识别接口(后端用) ├── serve_card_match.py :8000 业务字段接口(后台/树莓派用) ├── serve_card_match_v2.py :8020 新版多卡接口(gunicorn -w1 --threads8,生产主入口) ├── serve_lang_judge.py :8100 语种/方向 sidecar(内部) ├── config.py ★所有路径/阈值/参数,所有脚本都读它 ├── build_dual_gallery_88384.py v819 建库(历史参照) ├── build_dual_gallery_v0904.py ★现役建库(91,116,输出 data/gallery_v0904/) ├── prepare_train_data_v3.py ★训练数据准备(gallery+meta→上/下半区裁片分组) ├── train_dinov2_upper_half.py ★上半区训练(bs=224 双卡 100ep)+ _v0904 变体 ├── train_dinov2_layer3_bottom.py ★下半区训练(bs=8 单卡 150ep)+ _v0904 变体 ├── mine_layer3_step1_extract.py ★下半区挖矿①提特征(+ _v0904 变体) ├── mine_layer3_step2_pairs.py ★下半区挖矿②混淆组(+ _v0904 变体) ├── ab_test_v0904.py A/B 同尺 h2h 评测(v0904 胜出判据) ├── parse_card_no.py 旧 card_no 方案(已归档) ├── yolo_crop.py / ocr_judge.py 语种方向批处理版(两环境) ├── step1_crop.py 评级区域切图工具(独立) ├── scripts/ │ ├── cascade_match.py ★双区级联核心 DualCascadeMatcher + 单图/自检入口 │ ├── cascade_match_batch.py ★批量匹配出 CSV │ ├── build_gallery.py 旧 A4 768 维建库(废弃) │ ├── _verify_gallery_v819.py v819 库自检索校验(回滚场景用) │ └── _milvus_rebuild_v819.py Milvus 重灌(带 PROTECTED 白名单,回滚场景用) ├── tools/ 8020 运维/压测/调优工具集 │ ├── loadtest_8020.py 吞吐压测 │ ├── profile_8020_pipeline.py 管线耗时剖析 │ ├── abtest_backend_gpu.py gpu/milvus 后端 A/B │ ├── test_card_merge.py 近距 mask 合并测试 │ └── … ├── modules/ │ ├── feature_extractor_dual.py ★建库与查询共用的唯一预处理入口(letterbox/split_half/提特征) │ ├── yolo_detector.py ★裁卡(mask 4 点 warp + 竖放 + 近距合并 50px + card_type) │ ├── attribute_detector.py ★属性(cat16 配方 box3 右上 ROI + 4× LANCZOS) │ ├── feature_extractor.py 旧 A4 单区 768 维(废弃) │ ├── card_no_leniency.py 编号宽容判定 │ ├── rectifier.py 透视矫正/正立 │ ├── vector_index.py 旧 768 维向量检索(废弃) │ ├── image_downloader.py 下图(md5 命名) │ ├── grading_detector.py 评级标签检测封装 │ └── capp_upload.py / csv_reader.py / db_reader.py / ultralytics_compat.py └── pokemon/ ├── flat_grading.py / flat_ocr.py ★评级线(日常,平铺目录) ├── grading_ocr.py / grading_ocr_step2.py / │ parse_grading_ocr.py / infer_test.py 评级 OCR 与测试 ├── flat_card_no_detect.py / flat_card_no_ocr.py ★card_no 线 ├── flat_card_no.py card_no 集成示例版 └── pokemon_grading.py / pokemon_ocr_step2.py / pokemon_report.py ``` --- ## 相关文档 - 资产清单 / MD5 / 落位映射:`README_上线清单.md` - 三个接口的契约(请求头/响应字段/错误码):`docs/宝可梦卡牌识别服务文档接口.md` - 算法原理与演进:`docs/宝可梦整体流程框架详解.md` - 裁卡与摆正细节:`docs/卡牌区域检测裁切与旋转正立流程.md` - 重训漂移评估方法:`docs/DINOv2重训实验总结报告.md` - 编号 OCR 宽容政策:`docs/卡牌区域OCR_rec宽容政策.md`