宝可梦卡牌识别服务文档接口.md 18 KB

宝可梦卡牌识别服务文档接口

三合一文档,取代原 后端对接文档.md(8010) / 8000接口文档.md / 接口启动与验证.md。 模型版本 card_seg_v0904(双区级联 DINOv2-large,8.19 权重 + 91,116 张图库)。 部署机 192.168.77.249,框架根 ~/顾工交接/wzj

总览

端口 脚本 环境 谁调用 功能
8010 serve_recognition_api.py pytorch 后端服务(主用) 图片 URL → Top-K card_id + 匹配分,结构化错误码契约
8000 serve_card_match.py pytorch 后端服务 / 小程序前端 图片 → 单张卡的业务字段(卡名/系列/卡号/稀有度…)
8100 serve_lang_judge.py paddleocr 仅 8010 内部调用(监听 127.0.0.1) 卡面图 → 语种 + 方向角(PaddleOCR sidecar)
  • 8010 与 8000 是两套独立契约,不是同一接口的两个版本:8010 返回"候选列表 + 匹配分",8000 返回"一张卡的业务字段"。
  • 8100 是内部件,不对后端暴露;它挂掉时 8010 自动降级(仍可用,只是 language 不进日志)。
  • 三个服务共用同一份权重与图库,识别结果同源。

一、8010 主识别接口(后端对接主用)

1.1 功能

POST http://192.168.77.249:8010/recognize —— 给一张卡牌图片 URL,返回最像的若干张卡的 card_id 及匹配分。后端拿 card_id 去 ES 表 ptcg_cardscard_id 字段)查卡详情做展示。

后端要做的 3 件事:① 发请求带 task_id + image_url;② 取 data.matches[].card_id(通常取 rank=1);③ 判 code==200 && data.status=="success",失败时按 data.error_code 枚举分支处理。

⚠️ 不要algorithm_card_name / algorithm_series_name 展示给终端用户——那是算法侧调试信息,可能不准。展示一律以后端用 card_id 查 ES 的结果为准。

1.2 请求头

Header 必填 说明
Content-Type: application/json JSON 请求
X-Request-Id 后端生成的追踪 ID,算法侧原样写日志
Authorization: Bearer <token> 当前未开启鉴权,不用传(仅当服务带 API_TOKEN 环境变量启动时才校验)

1.3 请求参数(JSON body)

参数 类型 必填 默认 说明
task_id string - 后端任务 ID,原样回写到响应
image_url string - 待识别图片 URL,算法侧自行下载,必须公网可访问
top_k int 5 返回候选数,上限 20
min_match_rate int 0 最低匹配分 0-100。默认 0 = 不过滤,始终返回 top_k(哪怕最高分 <60)。只要高置信可自行传 80
return_embedding bool false 调试/入库用,见下

1.4 成功响应字段(code == 200

字段 类型 说明
code int 固定 200
message string 固定 success
data.task_id string 回写请求的 task_id
data.status string success
data.model_version string 算法版本,当前固定 card_seg_v0904
data.processing_time_ms int 算法处理耗时(毫秒)
data.matches array 候选列表,已按 match_rate 降序排好(可能为空数组
data.matches[].card_id string ★后端主要用这个,对应 ES ptcg_cards.card_id
data.matches[].match_rate int 匹配分 0-100,越高越像
data.matches[].rank int 排名,从 1 开始
data.matches[].algorithm_card_name string 算法识别的卡名,仅调试用,勿展示
data.matches[].algorithm_series_name string 算法识别的系列名,同上
data.embedding array return_embedding=true 时出现,1024 个 float(上半区特征);同时附带 data.embedding_lower(下半区 1024 维)与 data.embedding_dim=1024

match_rate 语义:100 ≈ 完全一致;实拍图 top1 一般 75-95;<60 属低置信。是否采纳由后端决定,算法侧默认不卡门槛、全部返回。

{
  "code": 200,
  "message": "success",
  "data": {
    "task_id": "9988776655",
    "status": "success",
    "model_version": "card_seg_v0904",
    "processing_time_ms": 486,
    "matches": [
      { "algorithm_card_name": "亚克诺姆", "algorithm_series_name": "补充包 勇魅群星 魅",
        "card_id": "0110001", "match_rate": 100, "rank": 1 },
      { "algorithm_card_name": "迷布莉姆", "algorithm_series_name": "强化包 怒炎灼天",
        "card_id": "017526", "match_rate": 71, "rank": 2 }
    ]
  }
}

1.5 失败响应(★重点)

只要 code != 200,响应结构就变data 只保留 4 个字段,不再有 matches / model_version / processing_time_ms

{
  "code": 422,
  "message": "no card detected in image",
  "data": {
    "task_id": "9988776655",
    "status": "failed",
    "error_code": "NO_CARD_DETECTED",
    "error_message": "no card detected in image"
  }
}
  • HTTP 状态码 = code(400/415/422/500 直接体现在 HTTP status 上)。
  • messagedata.error_message 内容一致,展示提示用 error_message
  • error_code 是稳定枚举,后端用它判分支(不要用 message 文案判断)。
HTTP / code error_code 含义 典型触发 & 建议处理
400 INVALID_REQUEST 请求参数错误 task_id/image_url;检查参数
400 INVALID_IMAGE_URL 图片 URL 非法或不可访问 下载失败;检查 URL 是否公网可达
415 UNSUPPORTED_IMAGE_TYPE 不支持的图片格式 下载到了但不是有效图像;提示用户换图
422 IMAGE_TOO_BLURRY 图片过于模糊 预留枚举,当前版本不会主动触发
422 NO_CARD_DETECTED 未检测到卡牌主体 YOLO 检不出卡;提示用户重拍/更清晰
500 MODEL_INFERENCE_ERROR 模型推理异常 特征提取失败或未分类异常;可重试
504 MODEL_TIMEOUT 模型处理超时 预留枚举,当前版本不会主动触发

后端约定:上面这些"业务性失败"建议后端转成前端 HTTP 200 + data.status=failed;而算法服务超时 / HTTP 5xx / 网络失败等基础设施异常,转成 HTTP 502 报警。

1.6 调用示例

curl -X POST http://192.168.77.249:8010/recognize \
  -H 'Content-Type: application/json' \
  -H 'X-Request-Id: order-20260707-001' \
  -d '{"task_id":"9988776655","image_url":"https://cdn.yourdomain.com/card-001.jpg","top_k":5,"min_match_rate":0}'

二、8000 卡牌字段接口(后端服务 + 小程序)

2.1 功能

传一张图片,返回最匹配那一张卡的业务字段。两个入口输入完全一样,只是返回结构不同:

接口 方法 用途
http://192.168.77.249:8000/match GET / POST 单对象返回,给后端服务调用
http://192.168.77.249:8000/match_fields GET / POST 数组返回(多一个图片字段),给小程序前端调用

2.2 请求头

场景 Header
GET 传参 无需特殊头
JSON POST Content-Type: application/json
上传文件 Content-Type: multipart/form-data(curl 用 -F 自动带)

无鉴权。

2.3 请求参数(图片来源三选一)

方式 参数 说明
上传文件 multipart 字段 image 前端本机图片,-F "image=@xxx.jpg"
图片 URL image_url 必须公网可下载
服务器本地路径 image_url 服务器上已有文件的绝对路径,一般用不到

可选 top_k(默认 5)。注意:top_k 只影响内部召回,两个入口都只返回 Top-1 那张卡,不返回候选列表;要候选列表请用 8010。

2.4 响应字段

/match 返回单对象 9 字段;/match_fields 返回数组(内含 1 个对象),多一个 frontImageUrl

字段 说明
card_id 卡牌唯一编号,后端主键
card_name_ch 中文名
pg_label 卡组 / 系列
year 年份
card_no 卡号
rarity 稀有度(来自 data/card_master_fields.csv
language 语种:tcg us / tcg jp / 简中 / 繁中
card_type 固定 1
trace_id 本次调用随机 ID,仅供追踪,不代表卡牌信息
frontImageUrl 标准图地址,/match_fields
// GET /match
{ "card_id": "04101495", "card_name_ch": "褪色小镇", "pg_label": "XY - Ancient Origins",
  "year": "2015", "card_no": "73/98", "rarity": "Uncommon", "language": "tcg us",
  "card_type": 1, "trace_id": "923de4f4c4e146ccae5b5988452f7134" }

// GET /match_fields
[ { "card_id": "04101495", "card_name_ch": "褪色小镇", "...": "...",
    "frontImageUrl": "https://.../04101495/xxx.jpg", "trace_id": "22bf456a..." } ]

内部推理用 fusion/upper/lower 相似度(与入库同源),但对外 9 字段契约不变card_id 之外的字段来自算法侧图库快照,如与业务库有出入,以业务库为准

2.5 失败响应

与 8010 不同,8000 返回的是裸 error 文本,没有 error_code 枚举,靠 HTTP 状态码区分:

{ "error": "缺少图片:请上传 image 文件或传 image_url" }      // 400
{ "error": "上传文件为空" }                                   // 400
{ "error": "图片下载失败(公网 URL 访问不到): https://..." }  // 500
{ "error": "未检出卡牌区域或特征提取失败" }                    // 500
状态码 含义 处理
400 请求参数问题 检查是否传了 imageimage_url
500 图片下载失败 / 未识别出卡牌 换图重试;无需额外特殊处理

2.6 调用示例

curl "http://192.168.77.249:8000/match?image_url=https://xxx.jpg"
curl "http://192.168.77.249:8000/match_fields?image_url=https://xxx.jpg"
curl -X POST http://192.168.77.249:8000/match -F "image=@D:/某张卡.jpg"
curl -X POST http://192.168.77.249:8000/match -H "Content-Type: application/json" -d '{"image_url":"https://xxx.jpg"}'

三、8100 语种/方向 sidecar(内部,不对后端暴露)

3.1 功能

POST http://127.0.0.1:8100/lang_judge —— 卡面裁剪图 → 语种 + 方向角

为什么要单独一个进程:主接口跑在 pytorch 环境,而 PaddleOCR 需要 paddleocr==3.2.0 + paddlepaddle 3.2.0,两套包互不兼容、无法共存于同一环境,故拆成独立进程由主接口 HTTP 调用(PaddleOCR 常驻内存,单图延迟最低)。

监听 127.0.0.1,仅本机可达,后端不要直接调。

判定规则(ocr_judge.py 原样逻辑):

  1. 假名 ≥ 10 → tcg jp(日文铁证,最高优先级)
  2. 汉字×3 > 拉丁字母 → 中文卡,再用 hanzidentifier 投票判 简中 / 繁中
  3. 否则 → tcg us

卡面英文元素(HP/Weakness/版权/编号)天然拉高 latin,故中文阈值放宽到 25%。

3.2 请求头

Header 说明
Content-Type: multipart/form-data 必须。multipart 字段名 image,值为卡面裁剪图

无鉴权,无其它参数。

3.3 响应字段

字段 类型 说明
language string / null tcg us / tcg jp / 简中 / 繁中异常时为 null
angle int 方向角 0 / 90 / 180 / 270(输入相对正立的顺时针旋转),来自 doc_preprocessor_res.angle
detail object 字符统计明细,随语种分支不同:日文 {kana,cjk,latin}、中文 {kana,s,t,cjk}、英文 {kana,latin,cjk};异常时为 {err: "..."}
text string OCR 文本,截断到 500 字符
{ "language": "tcg us", "angle": 0, "detail": {"kana":0,"latin":86,"cjk":2}, "text": "Fading Town..." }

3.4 失败响应

情况 响应 说明
没传图 / 字段名不对 400 {"error":"缺少图片:请以 multipart 字段 image 上传"}
上传文件为空 400 {"error":"上传文件为空"}
OCR 内部异常 200 {"language":null,"angle":0,"detail":{"err":"..."},"text":""} ★注意:不是 HTTP 错误,而是降级返回 language=null,调用方需判空

3.5 它挂掉的影响

8010 主接口自动降级仍可用(走全库检索、angle=0),日志出现 [warn] OCR sidecar 不可达。可选环境变量 LANG_JUDGE_URL(默认 http://127.0.0.1:8100/lang_judge)、LANG_JUDGE_TIMEOUT(默认 8 秒,超时即降级)。

验证 sidecar 是否被真实调用:

grep -a 'POST /lang_judge' logs/serve_8100.log | tail -3     # 有 200 行 = 真被调用了

四、健康检查(三个服务都有 GET /health

服务 预期返回
8010 {"status":"ok","gallery_size":91116,"model_version":"card_seg_v0904"}
8000 {"status":"ok","gallery_size":91116,"model":"card_seg_v0904","feature_dim":1024,"alpha":0.5,"top_k_recall":30}
8100 {"status":"ok"}

两条硬性判据:

  1. gallery_size 必须是 91116。若是 78272,说明特征库是旧版、权重与图库混版了,立即停服(新老下半区权重向量余弦仅 0.058,检索会全废)。
  2. /health 会"假 ok"(模型没真正就绪也可能返回 ok),必须再打一个真实请求才算验过。

五、失败响应差异速查

8010 8000 8100
错误结构 {code,message,data{task_id,status,error_code,error_message}} {"error":"文本"} {"error":"文本"}
稳定枚举 error_code 7 种 ❌ 只有 400/500 ❌ 只有 400
判分支依据 data.error_code HTTP 状态码 HTTP 状态码
推理失败时 422 NO_CARD_DETECTED 500 {"error":"未检出卡牌区域…"} 200 + language=null(降级,非报错)

附:部署与运维(后端可忽略)

环境:服务器 192.168.77.249(用户 martin),框架根 ~/顾工交接/wzj,GPU V100。 重要:必须 CUDA_VISIBLE_DEVICES=1(GPU0 是 GTX1060,cu128 不支持)。

启动

cd ~/顾工交接/wzj

# ① 先起 OCR sidecar(paddleocr 环境,8010 依赖它)
CUDA_VISIBLE_DEVICES=1 setsid nohup \
  ~/miniconda3/envs/paddleocr/bin/python serve_lang_judge.py --port 8100 \
  </dev/null >logs/serve_8100.log 2>&1 &

# ② 主接口(pytorch 环境)
CUDA_VISIBLE_DEVICES=1 setsid nohup \
  ~/miniconda3/envs/pytorch/bin/python serve_recognition_api.py --port 8010 \
  </dev/null >logs/serve_8010.log 2>&1 &

# ③ 字段接口(pytorch 环境,按需)
CUDA_VISIBLE_DEVICES=1 setsid nohup \
  ~/miniconda3/envs/pytorch/bin/python serve_card_match.py --port 8000 \
  </dev/null >logs/serve_8000.log 2>&1 &

setsid nohup … </dev/null >log 2>&1 & 让进程脱离 SSH 常驻。加载:8010 约 25-30s(图库 91116 + DINOv2 + YOLO + 预热),8000 约 10-20s,8100 约 15-20s。启动预热完后第一个真实请求就 ~0.5s

自检

ps aux | grep -E 'serve_lang_judge|serve_recognition_api|serve_card_match' | grep -v grep
ss -ltnp 2>/dev/null | grep -E ':8000|:8010|:8100'    # 8000/8010 是 0.0.0.0,8100 是 127.0.0.1
curl -s http://127.0.0.1:8100/health; curl -s http://127.0.0.1:8010/health; curl -s http://127.0.0.1:8000/health

# 冒烟识别(图库里已知卡,应命中同 card_id、match_rate≈96)
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}'

通过标志:code:200card_id == "04101495"match_rate ≈ 96processing_time_ms ~0.5-1s。

停止 / 重启

pkill -9 -f serve_recognition_api.py     # 本机直接执行可用 pkill
pkill -9 -f serve_card_match.py
pkill -9 -f serve_lang_judge.py

通过 ssh host sh -c 远程执行时勿用 pkill -f(会杀掉自己),改按 PID: for p in $(pgrep -f 'serve_card_match.py --port 8000'); do [ "$p" != "$$" ] && kill -9 "$p"; done

sidecar 一般不用频繁重启,除非改了它的代码。

常见问题

现象 原因 & 处理
Address already in use 端口被占;ss -ltnp \| grep 8010 看是谁,kill 后重启
日志 [warn] OCR sidecar 不可达 sidecar 挂了/没起。接口仍可用(降级),curl 127.0.0.1:8100/health 排查后重启 sidecar
首次请求特别慢(>3s) 预热没跑完;看日志有无 [启动] 预热完成,没有就重启
No module named flask 对应环境装:~/miniconda3/envs/<env>/bin/pip install flask
Field 'mlp_ratio' expected int, got float DINOv2 config.jsonmlp_ratio4.0,新版 huggingface_hub 严格校验,改成 4 即可(备份 config.json.bak.20260717
Ultralytics requirement ['onnx'] not found ultralytics 会自动装,等 30-60s 后重启接口生效
DualCascadeMatcher import 失败 确认 scripts/cascade_match.pymodules/feature_extractor_dual.py 在框架根下
top1 rate 特别低(<30) 图像质量差/无卡牌,属正常低置信,不是服务故障
no CUDA-capable device 忘了 CUDA_VISIBLE_DEVICES=<UUID>
显存不够 OOM 5090 是共享的,nvidia-smi 看后换另一张卡的 UUID
gallery_upper/lower 特征库 宝可梦/上线/README_上线清单.md 落位表补齐

相关文档

  • 上线交付包与资产清单:宝可梦/上线/README_上线清单.md
  • 算法链路总览:宝可梦整体流程框架详解.md
  • 裁卡/摆正细节:卡牌区域检测裁切与旋转正立流程.md
  • 语种判定规则详解:语种判断逻辑说明.md