picpocket / README.md
chawin.chen
增加昵称初始化状态与条件写入接口保护用户资料
20c1888
|
Raw History Blame Contribute Delete
18.1 kB
---
title: Picpocket
emoji: 🔥
colorFrom: yellow
colorTo: red
sdk: docker
pinned: false
---
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
# PicPocket 图像处理后台 API 扩展说明
本项目在面部人脸评分(`/analyze` 与 `/app/analyze`)接口的返回数据结构中,新增了面部局部特征分析与颜值寿命预测功能,参数均挂载在人脸信息的 `facial_features` 节点下。以下是这些新增参数的具体含义与设计注释:
## 1. 五官美学评分 (`facial_features`)
包含各个五官单项的美学几何得分,均为十分制;分析失败时省略辅助评分字段。整体颜值先归一化为十分制,再参与融合;默认融合权重 `FACIAL_FUSION_ALPHA=0.35`,分差拉伸系数 `FACIAL_SPREAD_FACTOR=2.5`:
* **`eyes`** (float): 眼部美学得分。基于眼部开合比例、左右眼对称度以及眼距偏差计算。
* **`nose`** (float): 鼻部美学得分。基于鼻梁长度、鼻翼宽度及黄金长宽比偏差计算。
* **`mouth`** (float): 嘴部美学得分。基于唇部厚度、嘴角上扬弧度及横向嘴宽比例计算。
* **`eyebrows`** (float): 眉毛美学得分。基于双眉对称性、斜率及眉眼间距偏差计算。
* **`jawline`** (float): 下颌轮廓得分。基于下颌骨边缘平滑度与左右颌角对称度计算。
## 2. 综合表现指标
* **`harmony_score`** (float): 面部整体协调性得分。评估面部整体几何比例与中心对称性的协调度。
* **`overall_facial_score`** (float): 五官平均综合得分。各单项五官得分的算术平均值。
## 3. 面部雷达图指标 (`radar_analysis`)
用于客户端绘制五维雷达图的各项细分美学指标。分值均采取十分制(自适应总分归一化后靠拢):
* **`symmetry`** (float): 对称指数。评估受试者脸部左右侧结构(如眉、眼、轮廓)的几何对称水平。
* **`proportions`** (float): 三庭五眼得分。严格对比中国古典美学“纵向三庭、横向五眼”黄金比例的契合程度。
* **`contour`** (float): 轮廓线条得分。结合下颌线柔和度与两颊折角的平顺程度得出,折角越平缓分数越高。
* **`eyes_brows`** (float): 眉眼精神得分。融合眼睛和眉毛的打分,反映眼周区域的神态与聚光度。
* **`nose_mouth`** (float): 鼻唇协调得分。融合鼻子与嘴唇的打分,反映中下庭在视觉上的协调与平衡。
## 4. 颜值巅峰预测与抗衰生命曲线 (`beauty_timeline`)
基于当前的年龄、下颌骨坚实度(骨相)与对称性预测的颜值时间演变模型:
* **`peak_age`** (int): 预测的颜值巅峰年龄(单位:岁)。通常根据当前年龄推估在 24~32 岁之间。
* **`peak_score`** (float): 颜值巅峰时的预测分数。
* **`aging_resistance`** (float): 骨相抗衰指数。基于下颌线条骨骼坚实度与面部对称度计算,骨骼感和对称性越好抗衰能力越强。
* **`future_curve`** (Dict): 未来颜值走势曲线。包含 20 岁、30 岁、40 岁、50 岁四个年龄节点的颜值预测分数。
> [!NOTE]
> **分制自适应逻辑说明**:
> * `peak_score`、`aging_resistance` 以及 `future_curve` 中的各年龄预测分数均支持**十分制(10分制)与百分制(100分制)自动识别**。
> * 系统通过输入综合颜值的量级(阈值为 `15.0`)自动适配,保证客户端展示的数据分制与全局颜值分保持完美一致。
## 5. 算法方法标识
* **`analysis_method`** (str): 分析所采用的方法。
* `"mediapipe_landmarks"`: 关键点检测成功,使用 68 点面部特征网格模型进行精细化评估。
* `"unavailable"`: 关键点检测或评分失败,仅返回辅助分析状态,不返回五官评分及相关数值对象,也不生成兜底分数。
* `"basic_estimation"`: 旧版本曾使用的统一估算分模式,新版本不再返回。
## 五官评分兼容性修复(2026-09-26)
五官评分使用 MediaPipe FaceMesh 关键点与自定义几何公式,独立于 DeepFace。
依赖锁定为 `mediapipe==0.10.21`,适配现有 `mp.solutions.face_mesh.FaceMesh`;
MediaPipe 0.10.31 起移除了此接口,原来的 `>=0.10.0` 可能使新构建一直进入统一默认分的降级分支。
Docker 构建增加接口可用性检查;发布必须重新构建并安装依赖,仅重启旧容器不会修复版本不匹配。
`/facescore/analyze` 和 `/facescore/app/analyze` 均在 `faces[].face.facial_features` 新增:
- `scores_available`:正常关键点评分为 `true`,分析失败为 `false`。
- `is_estimated`:保留字段,新版本不再生成降级估算分,始终为 `false`;客户端应使用 `scores_available` 判断是否有分数。
- `fallback_reason`:正常为 `null`;降级原因包括 `mediapipe_unavailable`、`incompatible_mediapipe_api`、`model_initialization_failed`、`model_not_ready`、`no_landmarks_detected`、`landmark_detection_failed`、`landmark_scoring_failed`、`auxiliary_analysis_failed`。
**五官评分是可选辅助数据(2026-09-26 更新)**:遮挡等导致关键点检测失败、模型不可用或评分异常时,在主评分及正常保存流程成功的前提下,接口仍返回 HTTP 200、`success=true`、总颜值、年龄、性别和图片信息。每个人脸的外层 `facial_features` 只保留状态和原因,省略其中的五官分、协调性、综合分、雷达图及时间线对象;不再返回默认分或包含 `null` 元素的数值映射。即使辅助模块有未捕获异常,也不会中断主评分或丢弃其他人脸。
原来的 `future_curve={"age_20":null,...}` 与 Android 的 `Map<String, Float>` 遍历不兼容;此次改为省略整个可选时间线,避免把空元素传入非空 `Float`。客户端应在 `scores_available=false` 时隐藏辅助区域或显示“—”,不可自行补默认分。当前旧版 Android 映射逻辑仍会自行补分,彻底取消 UI 默认值需要客户端配合。
例如总颜值为 `72.87`、关键点检测失败时,主响应照常成功,五官分析节点为:
```json
{
"analysis_method": "unavailable",
"scores_available": false,
"is_estimated": false,
"fallback_reason": "no_landmarks_detected"
}
```
该行为针对检测/评分失败,不代表新增了遮挡检测模型;MediaPipe 若仍能输出关键点,仍会进入几何评分流程。
`GET /facescore/health` 新增 `facial_analysis`,含 `ready`、`mediapipe_version`、`initialization_error`。
原 `mediapipe_available` 仍仅表示依赖可导入,判断模型是否初始化成功应检查 `facial_analysis.ready`。
懒加载且尚未初始化时返回 `ready=false`、`initialization_error=analyzer_not_initialized`;健康检查不会触发模型加载。
线上验收:重新构建部署后确认 `facial_analysis.ready=true`、版本为 `0.10.21`,再用不同的清晰正脸照片检查 `analysis_method=mediapipe_landmarks`、`scores_available=true` 及五官分数。已检测到人脸但无法检测五官关键点时,主响应应为 HTTP 200、`success=true`,辅助节点为 `analysis_method=unavailable`、`scores_available=false`,没有辅助数值字段;预期的未检测到关键点只记录 INFO。
若仍出现同分,查看 `fallback_reason` 和对应日志;正常关键点分支还需排除将 `FACIAL_FUSION_ALPHA` 设为 `1` 或 `FACIAL_SPREAD_FACTOR` 设为 `0` 的配置。
隔离回归测试(不启动服务、不导入业务配置、不加载真实 MediaPipe 或颜值模型):
```sh
python3 -m unittest discover -s test -p test_facial_analysis.py -v
python3 -m unittest discover -s test -p test_optional_facial_analysis.py -v
```
测试覆盖旧 API 初始化、新 API 不兼容、依赖缺失、模型加载失败、未检测到关键点、推理/单项评分异常、失败时省略辅助数值对象、72.87 分不再派生默认分,以及几何变化影响分数。额外隔离执行真实主评分编排及两个接口函数,验证辅助数据缺失/抛异常时仍正常返回、多脸互不影响和成功辅助结果透传。真实依赖安装与人脸模型推理仍需线上构建和验收确认。
## 图片表单字段大小限制(2026-09-26)
`Field exceeded maximum size of 1024KB.` 表示框架在解析表单时拒绝了超过 1MiB 的文本字段。
评分接口的 `images` 是 base64 图片列表,编码后体积通常比原图大约三分之一,容易触发该限制;错误发生在进入业务函数之前。
`/facescore/analyze` 与 `/facescore/app/analyze` 通过 `FaceAnalysisFormRoute` 在 FastAPI 参数解析前设置字段上限,默认 **32MiB**。
可在部署环境设置 `FACE_SCORE_MAX_FORM_FIELD_MB`(正整数,单位为 1024×1024 字节)。`images` 中全部图片的 base64 JSON 合计占用一个字段,共享该上限。
适用于 multipart 和 URL-encoded 表单;超过配置上限返回 HTTP 413 及压缩图片、减少数量或改用 `file/files` 的提示。
此限制针对文本字段,不是文件分片的大小限制;原 `file/files` 二进制上传方式及其他接口默认限制保持不变。
使用 Starlette 的 `Request.form(max_part_size=...)` 公共接口,依赖最低版本调整为 0.44.0,部署需重新安装依赖。
隔离请求测试(内存 TestClient,不启动本地服务器,不导入业务配置、数据库或模型):
```sh
python -m unittest discover -s test -p test_upload_form_limits.py -v
```
覆盖两个评分接口接收超过 1MiB 的 base64、URL-encoded 表单、上限边界和 HTTP 413、普通接口默认限制、单/多文件上传、异常时文件关闭及非法 multipart。
## Android 用户自定义昵称(2026-09-24)
设备资料表新增 `display_name`(utf8mb4、最多 12 个 Unicode 码点、默认空字符串)。原图片表 `nickname` 继续存设备标识,勿改作公开昵称。
**发布顺序:先对现有数据库执行 `sql/add_user_display_name.sql` 一次,再部署后端,最后发布 Android。** 新建库使用已更新的 `sql/create_star_table.sql`。
- `GET /facescore/app/profile`:请求头 `x-device-id`,读取 `{"success":true,"display_name":""}`;无设备资料时返回空昵称。
- `PUT /facescore/app/profile`:同一请求头,JSON `{"display_name":"小星星"}`。传 `""` 清空;去首尾空白,拒绝超长、换行和控制字符。未提供/非法设备 ID 返回 400/422,非法昵称返回 422,存储异常返回 503,不返回保存成功。
- `/facescore/leaderboard` 条目新增 `display_name`。昵称按历史记录的 Android 设备标识批量关联,实时查询,避免排名缓存导致旧昵称残留。保留每次评分记录、排名和评分时间。
- 常规设备信息上报不覆盖昵称;首次评分前也可以保存昵称。昵称无需唯一。接口沿用现有设备定位机制;设备 ID 并非登录凭证,后续账号化时需接入认证用户身份。
自动化验证(隔离测试,不连接业务数据库):
```sh
PYTHONPATH=test python -m unittest test_ranking_consistency test_user_profile -v
```
2026-09-24:13 项测试通过,涵盖昵称契约、读写失败、Unicode 限制、缓存更新和排名回归。真实 MySQL 迁移和 Android 设备联调尚待部署后进行。
### 引导保存昵称补齐设备资料(2026-10-02)
- 修复 `PUT /facescore/app/profile` 只写入设备 ID、昵称和来源的问题。Android 已在公共请求头携带完整设备信息,无需更新 App。
- 保存昵称时读取 `x-device-type`、`x-device-model`、`x-os-version`、`x-app-version`、`x-timezone`、`x-region` 和 `Accept-Language`,在同一条 upsert 中写入 `tpl_app_user_devices`;地区支持 `cf-ipcountry`、`x-country-code`、`x-geo-country` 顺序回退。
- 设备字段去除首尾空白并限制在表字段长度内;未传或空白的字段保留原值。昵称传空字符串仍会清空昵称,来源由接口确定,新建记录为 Android,已有明确来源不覆盖。读资料接口不创建记录,评分记录与排名不变。
- 无需新增数据库字段或迁移。部署后,历史不完整设备记录在下次保存昵称或正常设备上报时补齐;不会凭空回填未知设备信息。
- 隔离验证:`PYTHONPATH=test python -m unittest test_user_profile test_ranking_consistency test_client_source -v`,27 项通过;覆盖完整请求从 HTTP 到数据库写入、缺省/空白字段保护、地区回退、字段长度、原子写入失败及昵称/排名/来源回归。测试隔离数据库连接,未连接线上 MySQL,尚未部署。
### 老用户随机昵称初始化(2026-10-03)
**发布顺序:先执行 `sql/add_display_name_initialized.sql` 一次,再部署后端,最后发布 Android。** 新建数据库结构已经包含新列。迁移保留历史非空昵称;经用户确认,历史空昵称统一允许补随机名,不尝试猜测其是否曾被主动清空。
- `GET /facescore/app/profile` 和 PUT 响应新增 `nickname_initialized` 布尔值。无设备记录、空且未初始化时为 false;已有昵称或主动清空后为 true。旧客户端可忽略这个新增字段。
- 普通 `PUT /facescore/app/profile` 保存或清空昵称时均设置 `display_name_initialized=1`,同时保留设备资料写入与空字段保护。
- `POST /facescore/app/profile/initialize` 接收同样的设备请求头与 `{"display_name":"GlowBoss4826"}`,候选必须是 1–8 个英文字母加四位数字(1000–9999)。只有昵称为空且初始化标记为 0 时才能写入;在同一条 upsert 中判断并写入,避免先查后写的并发覆盖。
- 接口返回实际已存昵称和状态;已有昵称、用户主动清空、重复请求或响应丢失后重试时,都不会用候选覆盖已有决定。初始化仍保存设备信息,不修改图片记录和排行榜口径。
- 日常设备上报不修改昵称初始化标记。GET 不创建资料,未知字段/旧后端时 Android 不执行自动初始化;不能用普通 PUT 替代初始化接口。
- 测试:`PYTHONPATH=test python -m unittest test_user_profile test_ranking_consistency test_client_source -v`,34 项隔离测试通过,另通过 Python 语法与差异检查。数据库执行层为 mock,未验证真实 MySQL 并发,未执行线上迁移或部署。
## 安卓与小程序数据来源隔离(2026-09-29)
设备表和图片表新增 `client_source`:`android`、`wechat`、`ios`、`web`、`unknown`。图片写入继承服务端请求来源,包括异步保存任务;离线任务未显式指定来源时为 `unknown`,不进入两端榜单。更新已有图片不会覆盖已确定的来源。
现有数据库先暂停写入及清理任务、备份,再执行一次 `sql/add_client_source.sql`,部署新后端并重启全部进程后恢复服务。不能先部署代码再加字段,也不能迁移后继续运行旧写入代码。新建库使用 `sql/create_star_table.sql`。迁移根据当前仅安卓和小程序的历史情况,把昵称以 `android_` 开头的图片归为安卓,其余归为小程序;现有设备归为安卓,保留所有记录原有 `updated_at`,不刷新榜单时间窗。未来不能复用此历史推断规则。
| 接口 | 来源判断 |
| --- | --- |
| `/facescore/app/analyze` | 安卓 |
| `/facescore/analyze` | 微信小程序 |
| `/facescore/app/rank`、`/facescore/app/leaderboard` | 安卓,支持 GET/POST |
| `/facescore/wechat/rank`、`/facescore/wechat/leaderboard` | 微信小程序,支持 GET/POST |
| 原 `/facescore/rank`、`/facescore/leaderboard` 及其他共用处理接口 | 有非空 `x-device-id` 为安卓,无设备头为小程序,兼容当前客户端 |
明确的平台路由优先于请求头;不接受查询参数覆盖来源。排名统计(含全球与地区)、榜单、缓存及后台平台筛选均按来源隔离。小程序榜单不关联安卓设备昵称。旧版客户端无需升级即可继续使用;安卓和小程序源码已切换至各自明确的平台榜单路由(包含评分结果、趋势和排行榜中的排名查询)。必须先完成数据库迁移并部署后端,再发布这两个客户端的新版本。预留的 iOS/web 来源还需要接入对应业务路由和设备身份设计,不能沿用旧的共用路由判断。来源区分用于数据分组,不替代用户认证。
隔离测试(不连接真实 MySQL):
```sh
python -m unittest discover -s test -p test_client_source.py -v
python -m unittest discover -s test -p test_ranking_consistency.py -v
python -m unittest discover -s test -p test_user_profile.py -v
python -m unittest discover -s test -p test_upload_form_limits.py -v
```
上线后用两端各新增一条评分,检查图片来源及两个来源的全球/地区榜单;真实数据库迁移与部署联调需在部署环境执行。
### 排行榜图片路径
`/facescore/leaderboard`、`/facescore/app/leaderboard`、`/facescore/wechat/leaderboard` 的 GET/POST 响应中,`items[]` 增加 `file_path` 字符串,原样返回图片表中的路径(例如 `face/example.webp`)。新查询和缓存命中均包含该字段;不是新增完整 URL,也不改变前端当前头像展示。无路径时返回空字符串。本次字段扩展不需要数据库迁移。
### 排行榜按用户去重(2026-09-30)
为避免单一用户多次上传高分照片霸占排行榜前排(“一人霸榜”):
- 排行榜查询(`/facescore/leaderboard`、`/facescore/app/leaderboard`、`/facescore/wechat/leaderboard`)按用户标识(`nickname`)去重,每个用户仅保留最高分的一条记录进榜;若未提供昵称(匿名上传)则独立保留每张记录。
- 排名计算(`/facescore/rank`、`/facescore/app/rank`、`/facescore/wechat/rank`)同步按用户最高分口径聚合统计,保证测完打分结果页的排位与排行榜数据完全自洽。
- 本次改动纯逻辑优化,不需要数据库迁移。