411 lines
21 KiB
Markdown
411 lines
21 KiB
Markdown
# 身份证识别 API(免费 · 离线 · Docker · 极限低配可用)
|
||
|
||
上传身份证图片 → 返回结构化 JSON。OCR 用 **RapidOCR**(PaddleOCR 的 ONNX 版,Apache-2.0 可商用),服务用 **FastAPI**,全程**不需要 GPU、不需要联网、不需要任何云服务密钥**。
|
||
|
||
目标硬件:**1 核 CPU / 2G 内存配额**即可运行。速度高度依赖 CPU 单核性能:现代 x86 NAS(J4125 及以上)单张约 0.5–1.5s;**老旧低端机型(如 Celeron 1007U @1.5GHz)约 2–6s**,属于正常范围。
|
||
|
||
## 能力
|
||
|
||
- 正面:姓名、性别、民族、出生日期、住址、公民身份号码
|
||
- 反面:签发机关、有效期限
|
||
- 自动判正反面(也可用 `side=front/back` 强制指定)
|
||
- 身份证号 **ISO 7064 MOD 11-2 校验位验证**,识别错了会被标记 `id_number_valid=false`
|
||
- 返回每字段平均置信度与耗时
|
||
- 可选脱敏返回(`mask=true`)
|
||
|
||
## 1 核 2G 极限低配适配(重点)
|
||
|
||
2G 内存的真正风险不是模型本身(det/cls/rec 三个 onnx 权重合计约 15MB),而是三件事:
|
||
|
||
1. **onnxruntime 的线程风暴**——Docker 的 `cpus: 1.0` 限额**不会**改变容器内 `os.cpu_count()`,onnxruntime / OpenBLAS 仍会按宿主机核数建线程池。64 核宿主机上容器能起出几十个线程,1 核机器上直接 OOM 或被 cgroup 杀。必须显式钉死。
|
||
2. **大图的解码缓冲 + det 中间张量**——内存随图片面积平方增长。
|
||
3. **多 worker**——每个 worker 独立加载一份模型,直接翻倍。
|
||
|
||
本项目已做六层控制:
|
||
|
||
| 层 | 手段 | 效果 |
|
||
|---|---|---|
|
||
| 进程 | `--workers 1`,引擎全局单例 | 不会加载多份模型;每多一个 worker 多 400–600MB |
|
||
| 线程 | `OMP/OPENBLAS/MKL_NUM_THREADS=1` + `cv2.setNumThreads(0)` | 三重钉死,杜绝线程风暴 |
|
||
| 模型 | 关闭方向分类器 `use_angle_cls=False` | 身份证基本正向,少一个模型、少一次推理 |
|
||
| 输入 | 图片最长边 960px + `det_limit_side_len=736`(低端机型下调过,通用默认 1200/960) | 解码缓冲和 det 张量双降 |
|
||
| 推理 | `rec_batch_num=1` | batch=6 在单核下只涨峰值内存,不省时间 |
|
||
| 容器 | `cpus: 1.0`、`mem_limit: 1024m` | 留 1G 给系统 / Docker 本身 / 其他服务 |
|
||
| 入口 | content-length 预检 + 5MB 上限 + 6000px 尺寸硬上限 | 三层拦超大图,防解压炸弹打爆内存 |
|
||
|
||
**内存预算(估算,建议你本地 `docker stats` 实测校准)**
|
||
|
||
| 阶段 | RSS |
|
||
|---|---|
|
||
| Python 3.11 + FastAPI + uvicorn | ~150–200 MB |
|
||
| numpy + opencv-headless + pillow | ~120–150 MB |
|
||
| onnxruntime 运行时 | ~80–100 MB |
|
||
| 三个 onnx 模型常驻 + 图优化副本 | +150–250 MB |
|
||
| 单张 960px 图推理峰值 | +80–150 MB |
|
||
| **稳态 / 峰值** | **约 500–650 MB / 800MB–1.0GB** |
|
||
|
||
容器限 1024m 缓冲不多,**峰值贴着上限时先加 swap,再考虑降 `MAX_SIDE=736`**。低端机型(4G 单条)强烈建议提前加 2G swap,这是最便宜的保险:
|
||
|
||
## 快速开始
|
||
|
||
```bash
|
||
cd idcard-api
|
||
docker compose up -d --build # 首次构建会预热模型,稍慢
|
||
docker stats idcard-api # 看内存是否在预期区间
|
||
curl http://127.0.0.1:8000/api/v1/idcard/health # 直接看 mem_mb 字段
|
||
```
|
||
|
||
接口文档:`http://127.0.0.1:8000/docs`
|
||
|
||
> 💡 根路径 `/` 返回服务信息与端点清单,不是 404;`/api/v1/idcard/health` 返回状态和内存。
|
||
> 若 `/docs` 打开是白屏,说明页面依赖的外网 CDN(jsdelivr)被墙或超时——**不是服务故障**,直接用 curl / Postman / Apifox 调接口即可,请求格式见下方「调用」。
|
||
|
||
## 发布到飞牛 fnOS(Docker 应用)
|
||
|
||
适合把服务放到 NAS 上、给局域网其他应用调用。**主推 SSH 路径**(多文件项目必须整体上传,Compose 界面只能贴单个 yaml,上下文文件传不上去)。
|
||
|
||
### 第 0 步:三项前置检查(SSH 进 fnOS 后执行)
|
||
|
||
```bash
|
||
uname -m # 必须是 x86_64;若是 aarch64 见下方说明
|
||
free -h # 看可用内存,关键!见下方警告
|
||
ss -tlnp | grep 8000 # 确认 8000 端口没被别的容器占用
|
||
```
|
||
|
||
> ⚠️ **内存警告**:`mem_limit: 1024m` 是给**容器**的配额,fnOS 系统本身要占 1G+。当前默认值已针对 **Celeron 1007U / 4GB 单条内存**这类低端机型调好;NAS 总内存 ≥8G 可调回 `1280m` 并把 `MAX_SIDE` 调回 `1200`。4G 机型务必加 2G swap 兜底。
|
||
|
||
> ⏱️ **速度预期(Celeron 1007U @1.5GHz)**:单张身份证约 **2–6 秒**。这块 ULV 单核比常见 NAS 的 J4125 弱 3 倍以上,`MAX_SIDE=960` 就是为速度做的妥协(文字仍有约 25px 高,清晰印刷体识别率影响很小)。慢是正常的,不是没调好。
|
||
|
||
### 第 1 步:把整个项目目录传到 NAS
|
||
|
||
```bash
|
||
# fnOS 上建目录(存储池编号按实际改,/volume1 或 /vol1 都见过)
|
||
mkdir -p /volume1/docker/idcard-api
|
||
```
|
||
|
||
用 WinSCP / FileZilla 把本地的 `idcard-api` 目录**整个**拖上去(`app/`、`Dockerfile`、`docker-compose.yml`、`requirements.txt`、`client_example.py`、`.env.example`),保持目录结构。或者用 scp:
|
||
|
||
```bash
|
||
scp -r idcard-api root@飞牛IP:/volume1/docker/
|
||
```
|
||
|
||
### 第 2 步:配置 .env
|
||
|
||
```bash
|
||
cd /volume1/docker/idcard-api
|
||
cp .env.example .env
|
||
vi .env # 配 API_TOKENS(每方一把 token),见「鉴权」一节
|
||
```
|
||
|
||
### 第 3 步:构建并启动(最容易卡的是模型下载)
|
||
|
||
```bash
|
||
docker compose config # 先检查 yaml 语法,没问题再往下
|
||
docker compose up -d --build
|
||
docker compose logs -f # 看到 "engine ready" 且无 Traceback 即成功
|
||
```
|
||
|
||
**构建卡住/失败的两种原因和对策**:
|
||
|
||
1. **pip 慢**:Docker Hub 基础镜像可在 fnOS「设置 → Docker → 镜像仓库」配轩辕等加速源;pip 慢就在 Dockerfile 的 `pip install` 后加 `-i https://pypi.tuna.tsinghua.edu.cn/simple`。
|
||
2. **onnx 模型下载失败**(构建到预热那步报错,国内访问 GitHub 不稳):在能上网的 Windows 上先执行
|
||
```powershell
|
||
pip install rapidocr-onnxruntime
|
||
python -c "from rapidocr_onnxruntime import RapidOCR; RapidOCR()"
|
||
```
|
||
把生成模型目录(一般在 `C:\Users\<你>\.rapidocr_model`)传到 NAS `/volume1/docker/idcard-api/models`,compose 已挂载 `/app/models`,重跑 `docker compose up -d --build` 即可。
|
||
|
||
### 第 4 步:验证
|
||
|
||
```bash
|
||
curl http://127.0.0.1:8000/api/v1/idcard/health # 看 status / mem_mb
|
||
curl -X POST http://127.0.0.1:8000/api/v1/idcard/recognize \
|
||
-F "file=@/volume1/docker/idcard-api/test.jpg" -F "side=auto" \
|
||
-H "X-API-Key: 你的密钥"
|
||
docker stats idcard-api # 看内存峰值是否贴着 1280m
|
||
```
|
||
|
||
建议先拿一张真实身份证正反面各测一次,确认字段和 `id_number_valid=true` 再交给调用方。
|
||
|
||
### 第 5 步:接入调用方
|
||
|
||
- **同 NAS 上的其他 Docker 容器**:把调用方容器加入 `idcard-api_idcard-api` 网络(或在调用方 compose 里加 `external_links` / 同一自定义网络),地址写 `http://idcard-api:8000`;图省事直接用 `http://飞牛IP:8000`。
|
||
- **局域网其他设备**:`http://飞牛IP:8000`,带 `Authorization: Bearer <token>` 头。
|
||
- **要域名 + HTTPS**:用 fnOS 自带的反向代理/远程访问把域名指到 `127.0.0.1:8000`,不要在路由器上映射公网端口直连。
|
||
- **ARM 机型(aarch64)**:python:3.11-slim 和 opencv-headless 有 ARM 版,但 onnxruntime 需 ≥1.19 才有完善的 ARM wheel,先在 NAS上 `docker compose build` 试;若 onnxruntime 装不上,只能换 x86 机型或用云侧部署。
|
||
|
||
### 第 6 步:以后更新代码怎么发(重要)
|
||
|
||
**fnOS 的「重新部署 / 重建容器」只会 recreate 容器,不会 rebuild 镜像**——environment 变了会把新变量注进新容器,但代码改动完全不会进去。所以改完代码必须显式 build。
|
||
|
||
```bash
|
||
cd /vol1/1000/Docker/idcard-api # 换成你的实际目录
|
||
sudo docker compose -p icd up -d --build
|
||
```
|
||
|
||
> `-p icd` 不能省:镜像名 `icd-idcard-api` 说明 compose 项目名是 `icd`,不带这个参数会建成另一个项目,跟现有容器对不上。宿主机用户如果不在 docker 组,命令前都要 `sudo`(或执行一次 `sudo usermod -aG docker $USER && newgrp docker`)。
|
||
|
||
| 你改了什么 | 命令 | 耗时 |
|
||
|---|---|---|
|
||
| `app/*.py` | `up -d --build` | 10–30 秒 |
|
||
| `requirements.txt` | `up -d --build`(pip 层失效重跑) | 1–3 分钟 |
|
||
| `.env` / `docker-compose.yml` | `up -d`(自动 recreate,无需 build) | 秒级 |
|
||
| 怀疑缓存脏 / 要刷基础镜像 | `build --no-cache` + `up -d --force-recreate` | 3–5 分钟 |
|
||
|
||
**`--no-cache` 不是日常操作**:Docker 的层缓存按内容哈希失效,`COPY app ./app` 依赖代码内容,代码一变这一层和后续层必然重跑,前面的 apt/pip 层照常命中缓存。
|
||
|
||
也可以直接用 `deploy.sh`(已随项目提供),它会增量构建、等就绪、校验镜像内代码真的更新了,失败自动降级全量重建:
|
||
|
||
```bash
|
||
bash /vol1/1000/Docker/idcard-api/deploy.sh
|
||
```
|
||
|
||
> 如果脚本报 `\r: command not found`,是 Windows 换行符问题,执行 `sed -i 's/\r$//' deploy.sh` 修一下。
|
||
|
||
## 调用
|
||
|
||
```bash
|
||
curl -X POST http://127.0.0.1:8000/api/v1/idcard/recognize \
|
||
-F "file=@/path/idcard.jpg" -F "side=auto"
|
||
```
|
||
|
||
base64 方式:
|
||
|
||
```bash
|
||
curl -X POST http://127.0.0.1:8000/api/v1/idcard/recognize/base64 \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"image_base64": "<base64>", "side": "auto", "mask": false}'
|
||
```
|
||
|
||
**返回示例**
|
||
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"side": "front",
|
||
"name": "张三",
|
||
"gender": "男",
|
||
"nation": "汉",
|
||
"birth": "19900101",
|
||
"address": "北京市朝阳区某某路1号",
|
||
"id_number": "110101199001011237",
|
||
"id_number_valid": true,
|
||
"issuer": null,
|
||
"validity": null,
|
||
"field_count": 6,
|
||
"confidence": 0.96,
|
||
"elapsed_ms": 980.5,
|
||
"raw_text": "姓名张三 | 性别男 | ..."
|
||
}
|
||
}
|
||
```
|
||
|
||
Python 示例见 `client_example.py`。
|
||
|
||
### C# / .NET 调用示例(server-to-server)
|
||
|
||
要点三个:**必须带鉴权头**、**必须检查 HTTP 状态码**(否则 401 会被当成"未识别到"静默吞掉)、**超时要给足**(低端 NAS 首张要加载模型 10–20 秒)。
|
||
|
||
```csharp
|
||
// 配置里加一项 IdCardOcrToken,与服务端 .env 的 API_TOKENS 对应
|
||
string token = ServiceSettingsPolicy.GetIdCardOcrToken(service);
|
||
|
||
using var request = new HttpRequestMessage(HttpMethod.Post, apiUrl);
|
||
if (!string.IsNullOrWhiteSpace(token))
|
||
{
|
||
// 新版服务(推荐):标准 Bearer 头
|
||
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||
// 或旧版服务(仅设置了 API_KEY):改用下面这行
|
||
// request.Headers.TryAddWithoutValidation("X-API-Key", token);
|
||
}
|
||
request.Content = form;
|
||
|
||
using var response = client.SendAsync(request).GetAwaiter().GetResult();
|
||
|
||
// 关键:先判状态码,别让 401/413/422 掉进 JSON 解析分支变成"静默失败"
|
||
if (!response.IsSuccessStatusCode)
|
||
{
|
||
string body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
|
||
Console.WriteLine($"[UploadIdCardFile] OCR HTTP {(int)response.StatusCode}: {body}");
|
||
return data; // 前端提示"未识别到信息",后端日志里有真实原因
|
||
}
|
||
```
|
||
|
||
`HttpClient` 建议用 `IHttpClientFactory` 或静态单例,不要每次 `new`——高频调用会耗尽 socket。
|
||
|
||
## 鉴权(Token 验证)
|
||
|
||
支持三种方式,全部用**标准库实现**(HMAC-SHA256),不引入 PyJWT 等依赖,对 1C/2G 无额外开销。
|
||
|
||
### 方式一:多 token(推荐)
|
||
|
||
`.env` 里按 `名称:token` 配置,多个用逗号分隔:
|
||
|
||
```bash
|
||
API_TOKENS=frontend:abc123def456,win-service:xyz789uvw012
|
||
```
|
||
|
||
好处是**每个调用方一把,能单独吊销**(删掉对应那段重启即可),日志里也会打出调用方名称,出问题能定位是谁在调。
|
||
|
||
调用时带标准 Bearer 头:
|
||
|
||
```bash
|
||
curl -X POST http://10.1.1.163:8000/api/v1/idcard/recognize \
|
||
-H "Authorization: Bearer xyz789uvw012" \
|
||
-F "file=@idcard.jpg"
|
||
```
|
||
|
||
旧的 `X-API-Key` 头仍然有效(兼容不改代码的老调用方);`API_KEY` 单密钥配置也继续支持。
|
||
|
||
### 方式二:短期 token(前端场景推荐)
|
||
|
||
设置 `CLIENT_SECRET` 后,多出一个换 token 的接口:
|
||
|
||
```bash
|
||
# ① 用 client_secret 换 token(默认 1 小时有效)
|
||
curl -X POST http://10.1.1.163:8000/api/v1/auth/token \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"client_id":"frontend","client_secret":"你的密钥"}'
|
||
|
||
# 返回
|
||
{"token":"eyJzdWIiOiJmcm9udGVuZCIsImlhdCI6...9f2a","expires_in":3600,"token_type":"Bearer"}
|
||
|
||
# ② 后续请求带这个 token
|
||
curl -X POST http://10.1.1.163:8000/api/v1/idcard/recognize \
|
||
-H "Authorization: Bearer eyJzdWIi..." \
|
||
-F "file=@idcard.jpg"
|
||
```
|
||
|
||
token 是 `base64(payload) + "." + HMAC签名` 的结构(类似 JWT 但没有第三段),服务端校验签名和 `exp` 过期时间。`TOKEN_TTL` 可调有效期。
|
||
|
||
适合前端的原因:页面里不硬编码长期密钥,泄露了也就一小时的窗口。
|
||
|
||
### 方式三:不配置(本地调试)
|
||
|
||
`API_TOKENS`、`API_KEY`、`CLIENT_SECRET` 全为空时,接口完全开放。**只适合本机调试**——`/api/v1/idcard/health` 会返回 `auth_enabled: false`,启动日志也会警告。
|
||
|
||
### 几个实现细节
|
||
|
||
- **`/api/v1/idcard/health` 不鉴权**,方便探活和监控;它不返回任何身份证数据。
|
||
- **401 响应带 `WWW-Authenticate: Bearer` 头**,符合 HTTP 标准,前端库能自动识别。
|
||
- **签名比较用 `hmac.compare_digest`**(常数时间),避免时序侧信道攻击。
|
||
- **`/docs` 不做鉴权**,方便调试;要锁起来就在 Nginx 反代层加 basic auth。
|
||
- **日志只记调用方名称和耗时,不记姓名/身份证号**。
|
||
- 静态 token **以明文存在容器环境变量里**,能读 `docker inspect` 的人就能看到。安全要求更高的话,用短期 token 模式 + 只把 `CLIENT_SECRET` 挂在服务端,客户端只拿短时 token。
|
||
|
||
## 跨域(CORS)
|
||
|
||
**先分清调用方**:
|
||
|
||
| 调用方 | 有跨域限制吗 | 需要做什么 |
|
||
|---|---|---|
|
||
| 后端服务(curl、Python requests、其他 Docker 容器、Java/Go 等) | ❌ 没有 | 什么都不用配 |
|
||
| 浏览器前端(Vue / React / 原生 JS 页面) | ✅ 有 | 服务端开 CORS + 请求头带 `Authorization: Bearer <token>` |
|
||
|
||
CORS 是**浏览器的同源策略**,只在浏览器环境生效。所以「别的应用调用」如果是后端服务,完全不受影响;如果是网页前端,才会撞到。
|
||
|
||
服务已内置 `CORSMiddleware`,由环境变量 `CORS_ORIGINS` 控制:
|
||
|
||
```bash
|
||
# .env 里配置,逗号分隔;* 表示允许所有来源(开发方便)
|
||
CORS_ORIGINS=http://10.1.1.163:5173,http://10.1.1.163:8080
|
||
```
|
||
|
||
几个要点:
|
||
|
||
1. **预检请求**:前端带 `X-API-Key` 自定义头会触发 `OPTIONS` 预检,中间件已自动处理(返回 204 + 允许头),无需额外路由。
|
||
2. **鉴权头必须在 `allow_headers` 里**:已设 `allow_headers=["*"]`,`X-API-Key` 可通过。
|
||
3. **`allow_credentials=False` 是刻意的**:本项目用请求头鉴权、不用 Cookie,而 `allow_origins=["*"]` 与 credentials=True 是非法组合,浏览器会直接拒绝。
|
||
4. **安全提醒**:身份证是敏感数据。`CORS_ORIGINS=*` 且未配置任何 token 时,局域网内任何一个网页的 JS 都能悄悄调用你的接口读数据。**生产必须二选一**:配 `API_TOKENS`,或把 `CORS_ORIGINS` 收紧到具体前端地址。
|
||
5. **生产更推荐同源**:用 Nginx / fnOS 反向代理把前端和 API 放在同一域名下(`/api` 转发到 8000),浏览器视为同源,连 CORS 都不需要,也少一层预检开销。
|
||
|
||
## 如果内存还是吃紧(降级路径)
|
||
|
||
按代价从低到高:
|
||
|
||
1. `MAX_SIDE=960` + `DET_LIMIT_SIDE_LEN=736`:身份证文字在 960px 下仍有 ~25px 高,识别基本无损,通常再省 100–150MB
|
||
2. 换 PP-OCR **mobile** 版识别模型(`ch_PP-OCRv4_mobile_rec`),体积和内存都小于 server 版,身份证这种清晰印刷体精度损失很小
|
||
3. 加 swap(2–4G)作为兜底——会拖慢速度,但能防 OOM 被杀。**这是 2G 机器强烈建议做的**:
|
||
```bash
|
||
sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile
|
||
sudo mkswap /swapfile && sudo swapon /swapfile
|
||
```
|
||
4. 极端情况换 Tesseract + `chi_sim`:内存可压到 200MB 内,但中文证件识别率明显下降,不推荐
|
||
|
||
**并发上不去时**:低配下不要加 worker,改成前面挂 Nginx 限流排队。1 核的现实吞吐大约 **1–2 QPS**,超过就会堆积。要扛量就横向多开几个容器 + 前面 LB,而不是在单机上加进程。
|
||
|
||
## 准确率与调优
|
||
|
||
- 清晰扫描件 / 规整拍照:字段级 95%+;模糊、反光、强倾斜、复印件会明显下降。
|
||
- 掉得最多的通常是**住址**(长文本)和**名字里的生僻字**。建议对 `id_number_valid=false` 的结果走人工复核,不要直接采信。
|
||
- 拍照场景强烈建议前端先做一次**方向校正 + 边缘裁剪**,比在服务端补救有效得多。
|
||
- 想加"先检测身份证区域再 OCR"(防复杂背景),可接 YOLOv8n 做前置裁剪,挂在 `_load_image` 之后即可——**1C/2G 下不建议加**,会再吃 100MB 内存和一大截 CPU。
|
||
|
||
## 合规与安全(重要)
|
||
|
||
身份证号属于**敏感个人信息**,受《个人信息保护法》《数据安全法》约束:
|
||
|
||
1. 这个方案的价值就在**数据不出你自己的机器**。别为了省事换成公有云 API,那等于把 PII 交给第三方。
|
||
2. 服务**不落盘、不缓存**图片,`raw_text` 含明文,生产环境建议要么关掉、要么 `mask=true`。
|
||
3. 日志里**不要打印身份证号和姓名**。当前实现只打加载信息。
|
||
4. 对外必须 HTTPS + 鉴权 + 限流;已做两层大小防护(content-length 预检 + 惰性尺寸检查),`MAX_IMAGE_BYTES=5MB`。
|
||
5. 收集前应有用户授权与隐私政策,明确保留期限——最安全的做法是识别完即丢弃。
|
||
|
||
## 目录
|
||
|
||
```
|
||
idcard-api/
|
||
├── app/
|
||
│ ├── main.py # FastAPI 服务、鉴权、图片校验、低配参数
|
||
│ └── parser.py # 关键词锚点解析 + 身份证校验位
|
||
├── Dockerfile
|
||
├── docker-compose.yml
|
||
├── requirements.txt
|
||
└── client_example.py
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
**构建时报 `401 Unauthorized`(拉不下 `python:3.11-slim` 元数据)**:Docker 镜像源问题,与项目无关。注意 **fnOS 的 Docker 应用会用自己的界面配置覆盖 `/etc/docker/daemon.json`**,手工改文件常常不生效(报错 URL 仍是 `docker.fnnas.com` 就是没生效)。本项目已改走第三条路——**在 Dockerfile 里直接写国内镜像仓库全路径**,不依赖 daemon 配置:
|
||
|
||
```dockerfile
|
||
ARG BASE_IMAGE=docker.m.daocloud.io/library/python:3.11-slim
|
||
FROM ${BASE_IMAGE}
|
||
```
|
||
|
||
切换源只需在 `.env` 里设 `BASE_IMAGE`,构建前先探活:
|
||
```bash
|
||
for m in docker.m.daocloud.io docker.1ms.run mirror.ccs.tencentyun.com; do
|
||
echo -n "$m -> "; curl -s -o /dev/null -w "%{http_code}\n" --max-time 8 \
|
||
"https://$m/v2/library/python/manifests/3.11-slim"
|
||
done
|
||
```
|
||
返回 `200` 可用;`401` 且带 `WWW-Authenticate` 头属于 Registry 正常认证流程,Docker 能自动处理;连接超时/`5xx` 则换下一个。全部不通就走离线导入兜底:
|
||
|
||
```bash
|
||
# 1) 在能联网且有 Docker 的机器上(你的 Windows 本机装了 Docker Desktop 即可)
|
||
docker pull python:3.11-slim
|
||
docker save python:3.11-slim -o python311slim.tar
|
||
# 2) 传到 NAS 后导入
|
||
docker load -i python311slim.tar
|
||
# 3) 构建时指向本地 tag
|
||
# 在 .env 里设 BASE_IMAGE=python:3.11-slim,然后 docker compose up -d --build
|
||
```
|
||
|
||
**pip 装包慢/断**:与镜像源无关,是另一条网络路径。Dockerfile 已默认清华源(`PIP_INDEX_URL`),需要时构建覆盖。
|
||
|
||
**构建时模型下载失败**:国内访问模型源不稳。先在能上网的机器上 `python -c "from rapidocr_onnxruntime import RapidOCR; RapidOCR()"` 生成模型目录,再拷进 `/app/models` 卷;pip 可加 `-i https://pypi.tuna.tsinghua.edu.cn/simple`。
|
||
|
||
**容器起不来 / 报 OOM**:先 `docker stats` 看峰值,或 `curl .../health` 看 `mem_mb`。若峰值贴着 `mem_limit`,按上面降级路径降 `MAX_SIDE`;反复被杀就先加 swap。
|
||
|
||
**`RapidOCR rejected {...}` 警告**:正常,代码在逐级降级尝试构造参数,最终会用可用组合启动,不会失败。锁死 `requirements.txt` 版本后这条警告会消失。
|
||
|
||
**CPU 跑满但吞吐很低**:典型症状是线程在抖。检查容器内 `echo $OMP_NUM_THREADS` 必须是 1;如果宿主机是多核而 compose 里只写了 `cpus: 1.0`,环境变量没生效就会这样。
|
||
|
||
**返回 422 no text detected**:图片不是身份证、或分辨率过低/文字太小。
|
||
|
||
**性别/民族识别成空**:正面右侧那一行字小,常见于低清图。提高图片质量,或换 mobile/server 识别模型。
|
||
|
||
**numpy 相关报错**:`requirements.txt` 里锁了 `numpy<2`,不要手动升。onnxruntime 与 numpy 2 存在兼容问题。
|