picpocket / README.md
chawin.chen
增加昵称初始化状态与条件写入接口保护用户资料
20c1888
|
Raw History Blame Contribute Delete
18.1 kB
metadata
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 岁四个年龄节点的颜值预测分数。

分制自适应逻辑说明:

  • 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、关键点检测失败时,主响应照常成功,五官分析节点为:

{
  "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 或颜值模型):

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,不启动本地服务器,不导入业务配置、数据库或模型):

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 并非登录凭证,后续账号化时需接入认证用户身份。

自动化验证(隔离测试,不连接业务数据库):

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):

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)同步按用户最高分口径聚合统计,保证测完打分结果页的排位与排行榜数据完全自洽。
  • 本次改动纯逻辑优化,不需要数据库迁移。