Files
hair/static/integration.html
T
Ubuntu a3f4c822cb feat: 补接口4测试页,更新索引和接入说明
- static/test_interface4.html: 上传照片 → 英文优先字段 + 全部42项特征表格 + 原始JSON
- gateway/app.py: 首页索引增加 if4_features 测试页
- static/integration.html: 接入说明补充接口4测试页链接
2026-06-15 21:36:40 +08:00

278 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>旷视五接口 — 前端接入说明</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f8f9fb; color: #1e293b; line-height: 1.6; }
.wrap { max-width: 960px; margin: 0 auto; padding: 32px 24px; }
h1 { font-size: 26px; margin-bottom: 8px; }
.subtitle { color: #64748b; font-size: 14px; margin-bottom: 32px; }
.card { background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,.05); margin-bottom: 24px; overflow: hidden; }
.card h2 { font-size: 16px; padding: 16px 20px; border-bottom: 1px solid #f1f5f9; background: #fafbfc; }
.card-body { padding: 18px 20px; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #f1f5f9; }
th { background: #f8fafc; font-weight: 700; color: #475569; font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
tr:last-child td { border-bottom: none; }
code { font-family: "SF Mono", "Fira Code", monospace; font-size: 12.5px; background: #f1f5f9; padding: 2px 6px; border-radius: 4px; color: #0f172a; }
pre { background: #1e293b; color: #e2e8f0; padding: 16px 20px; border-radius: 8px; font-family: "SF Mono", "Fira Code", monospace; font-size: 12.5px; line-height: 1.5; overflow-x: auto; }
.badge { display: inline-block; padding: 2px 10px; border-radius: 10px; font-size: 11px; font-weight: 700; }
.badge.post { background: #dbeafe; color: #1e40af; }
.badge.get { background: #d1fae5; color: #065f46; }
.badge.warn { background: #fef3c7; color: #92400e; }
.endpoint { margin-bottom: 20px; }
.endpoint .url { font-size: 15px; font-weight: 700; margin-bottom: 6px; }
.endpoint .desc { color: #64748b; font-size: 13px; margin-bottom: 10px; }
.endpoint .params { font-size: 12px; color: #94a3b8; margin-bottom: 8px; }
.link { color: #2563eb; text-decoration: none; font-size: 13px; }
.link:hover { text-decoration: underline; }
.toc { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 24px; }
.toc a { font-size: 13px; color: #2563eb; text-decoration: none; padding: 4px 12px; background: #eff6ff; border-radius: 6px; }
.toc a:hover { background: #dbeafe; }
hr { border: none; border-top: 1px solid #e2e8f0; margin: 20px 0; }
</style>
</head>
<body>
<div class="wrap">
<h1>📡 旷视五接口 — 前端接入说明</h1>
<p class="subtitle">Base URL: <code>https://hair.xiangsilian.com</code> &nbsp;|&nbsp; 在线文档: <a href="/docs" class="link">/docs</a> &nbsp;|&nbsp; 在线测试页见底部</p>
<div class="toc">
<a href="#overview">通用约定</a>
<a href="#if1">接口1</a>
<a href="#if2">接口2</a>
<a href="#if3">接口3</a>
<a href="#if4">接口4</a>
<a href="#if5">接口5</a>
<a href="#errors">错误码</a>
<a href="#test">在线测试</a>
</div>
<!-- ======== 通用约定 ======== -->
<div class="card" id="overview">
<h2>🔧 通用约定</h2>
<div class="card-body">
<table>
<tr><td>协议</td><td>HTTPS</td></tr>
<tr><td>请求方式</td><td>全部 <span class="badge post">POST</span></td></tr>
<tr><td>编码</td><td>UTF-8</td></tr>
<tr><td>Content-Type</td><td><code>multipart/form-data</code></td></tr>
<tr><td>图片参数</td><td>三选一:<code>image_file</code>(文件上传)/ <code>image_url</code>URL/ <code>image_base64</code>base64 + 前缀)</td></tr>
<tr><td>图片格式</td><td>JPG / PNG,≤ 1 MB,单人正面照</td></tr>
</table>
<p style="margin-top:14px;font-size:13px"><strong>统一响应结构</strong></p>
<pre>{
"code": 0, // 0=成功,非0=错误
"message": "success",
"request_id": "...", // 请求追踪 ID
"data": { ... } // 业务数据,各接口不同
}</pre>
</div>
</div>
<!-- ======== 接口1 ======== -->
<div class="card" id="if1">
<h2>1. 四庭七眼测量 &nbsp;<span class="badge post">POST</span> &nbsp;<code>/api/v1/face/measure</code></h2>
<div class="card-body">
<p class="desc">上传正面照 → 返回标注好的 PNG(仅标注图层,透明底)+ 四庭七眼厘米数值 + 关键点像素坐标。</p>
<p><strong>入参</strong></p>
<table>
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>image_file</td><td>file</td><td>三选一</td><td>上传图片文件</td></tr>
<tr><td>image_url</td><td>string</td><td>三选一</td><td>图片 URL</td></tr>
<tr><td>image_base64</td><td>string</td><td>三选一</td><td>base64 字符串,需带 data:image/...;base64, 前缀</td></tr>
</table>
<p style="margin-top:12px"><strong>data 字段</strong></p>
<table>
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td><code>annotated_image_url</code></td><td>string</td><td>标注 PNG URL(仅标注图层,透明底,叠加到原图上显示)</td></tr>
<tr><td><code>face_total_height_cm</code></td><td>number</td><td>全脸高度(cm</td></tr>
<tr><td><code>four_courts</code></td><td>object</td><td>四庭:top/upper/middle/lower,各含 _cm 和 ratios</td></tr>
<tr><td><code>seven_eyes</code></td><td>object</td><td>七眼:eye_width/face_width/inter_eye_distance,各含 _cm 和 ratios</td></tr>
<tr><td><code>landmarks</code></td><td>object</td><td>5 个关键点像素坐标:hair_top/hairline/brow_center/nose_bottom/chin_tip</td></tr>
</table>
<p style="margin-top:12px;font-size:12px;color:#64748b">💡 前端把标注图叠加到原图上即可呈现测量效果(标注图白色线条 #FFFFFF,透明底)。</p>
<p style="margin-top:8px"><strong>请求示例</strong>fetch):</p>
<pre>const fd = new FormData();
fd.append('image_file', file); // 或 image_url / image_base64
const res = await fetch('https://hair.xiangsilian.com/api/v1/face/measure', {
method: 'POST',
body: fd
});
const { code, data } = await res.json();
// data.annotated_image_url → 标注 PNG
// data.face_total_height_cm → 全脸高度
// data.four_courts.ratios → { top_court: 0.25, ... }
// 前端叠加显示:原图 + annotated_image_urlabsolute 叠加)</pre>
</div>
</div>
<!-- ======== 接口2 ======== -->
<div class="card" id="if2">
<h2>2. C端生发 &nbsp;<span class="badge post">POST</span> &nbsp;<code>/api/v1/hair/grow</code></h2>
<div class="card-body">
<p class="desc">上传正面照 + 性别 → 多组发际线方案。每组含发际线叠加预览图 + ComfyUI 生发效果图。</p>
<p><strong>入参</strong></p>
<table>
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>image_file / image_url / image_base64</td><td></td><td>三选一</td><td>用户正面照</td></tr>
<tr><td>gender</td><td>string</td><td>✅ 必填</td><td><code>"male"</code> / <code>"female"</code></td></tr>
</table>
<p style="margin-top:12px"><strong>data.results[] 元素</strong></p>
<table>
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td><code>image_url</code></td><td>string</td><td>发际线叠加预览图(曲线叠在原图上)</td></tr>
<tr><td><code>grown_image_url</code></td><td>string</td><td>生发后效果图(ComfyUI/Flux「植发3个月」)⚠ 可空</td></tr>
<tr><td><code>hairline_type</code></td><td>string</td><td>发际线类型 key</td></tr>
<tr><td><code>order</code></td><td>int</td><td>排序(1=最佳,当前按贴图顺序)</td></tr>
</table>
<p style="margin-top:8px;font-size:12px;color:#64748b">
Female 5 种:ellipse/flower/heart/straight/wave &nbsp;|&nbsp;
Male 4 种:ellipse/m/straight/inverse_arc<br>
⚠ 生发图由本机 ComfyUI 生成,耗时可达数分钟,fetch 超时需放大(≥5min)。
</p>
</div>
</div>
<!-- ======== 接口3 ======== -->
<div class="card" id="if3">
<h2>3. B端生发(医生端)&nbsp;<span class="badge post">POST</span> &nbsp;<code>/api/v1/hair/grow-b</code></h2>
<div class="card-body">
<p class="desc">医生用马克笔在用户照片额头画线 → 拍照上传 → 系统生成生发效果图。</p>
<p><strong>入参</strong></p>
<table>
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>marked_image_file / marked_image_url / marked_image_base64</td><td></td><td>✅ 三选一</td><td>已用马克笔标注发际线的图片</td></tr>
</table>
<p style="margin-top:12px"><strong>data 字段</strong></p>
<table>
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td><code>hair_growth_image_url</code></td><td>string</td><td>生发后效果图 URL ⚠ 可空</td></tr>
<tr><td><code>hairline_type</code></td><td>string</td><td>固定 <code>"custom"</code>(手绘定制)</td></tr>
</table>
<p style="font-size:12px;color:#64748b">⚠ 划线图未检测到划线(或无人脸)→ code=1001</p>
</div>
</div>
<!-- ======== 接口4 ======== -->
<div class="card" id="if4">
<h2>4. 用户特征分析 &nbsp;<span class="badge post">POST</span> &nbsp;<code>/api/v1/face/features</code></h2>
<div class="card-body">
<p class="desc">上传照片 → 火山方舟豆包视觉模型分析 → 返回几十项面部特征(脸型/眉形/肤色/四季色彩…)。</p>
<p><strong>入参</strong>image_file / image_url / image_base64 三选一。无其他参数。</p>
<p style="margin-top:12px"><strong>data 字段</strong></p>
<table>
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td><code>features</code></td><td>string</td><td><strong>JSON 字符串</strong>(不是对象!客户端需 <code>JSON.parse()</code></td></tr>
</table>
<p style="margin-top:8px"><strong>features 英文优先字段</strong>(其余中文字段同时返回,共~42个):</p>
<table>
<tr><th>字段</th><th>说明</th><th>字段</th><th>说明</th></tr>
<tr><td>face_shape</td><td>脸型</td><td>eyebrow_shape</td><td>眉形</td></tr>
<tr><td>facial_age</td><td>面部年龄区间</td><td>gender</td><td>性别</td></tr>
<tr><td>dynamic_static_type</td><td>动静类型</td><td>gene_style</td><td>基因风格</td></tr>
</table>
<pre>const res = await fetch('https://hair.xiangsilian.com/api/v1/face/features', {
method: 'POST',
body: fd
});
const { code, data } = await res.json();
const features = JSON.parse(data.features); // ← 注意:data.features 是字符串!
console.log(features.face_shape); // "鹅蛋脸"
console.log(features['四季色彩季型']); // "冷夏型"(中文字段也保留)</pre>
</div>
</div>
<!-- ======== 接口5 ======== -->
<div class="card" id="if5">
<h2>5. 发际线 PNG 生成 &nbsp;<span class="badge post">POST</span> &nbsp;<code>/api/v1/hairline/generate</code></h2>
<div class="card-body">
<p class="desc">上传正面照 + 性别 → N 张发际线叠加图 + 最佳发际线中心点坐标。</p>
<p><strong>入参</strong></p>
<table>
<tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr>
<tr><td>image_file / image_url / image_base64</td><td></td><td>三选一</td><td>用户正面照</td></tr>
<tr><td>gender</td><td>string</td><td>✅ 必填</td><td><code>"male"</code> / <code>"female"</code></td></tr>
</table>
<p style="margin-top:12px"><strong>data 字段</strong></p>
<table>
<tr><th>字段</th><th>类型</th><th>说明</th></tr>
<tr><td><code>hairline_images[]</code></td><td>object[]</td><td>发际线叠加图列表,每项含 image_url + order</td></tr>
<tr><td><code>best_hairline_center_point</code></td><td>object</td><td>最佳发际线中心点像素坐标 <code>{ x: number, y: number }</code></td></tr>
</table>
</div>
</div>
<!-- ======== 错误码 ======== -->
<div class="card" id="errors">
<h2>⚠ 错误码</h2>
<div class="card-body">
<table>
<tr><th>code</th><th>message</th><th>说明</th></tr>
<tr><td>1001</td><td>无法识别人像</td><td>未检测到人脸</td></tr>
<tr><td>1002</td><td>人像分辨率过低</td><td>低于最低分辨率</td></tr>
<tr><td>1003</td><td>角度问题,非正面照</td><td>非正面 / 角度过大</td></tr>
<tr><td>1005</td><td>检测到多张人脸</td><td>仅支持单人</td></tr>
<tr><td>1006</td><td>文件超出大小限制</td><td>单文件 > 1 MB</td></tr>
<tr><td>1007</td><td>图片参数错误 / 后端不可用</td><td>参数传错 / 服务繁忙请稍后重试</td></tr>
<tr><td>1008</td><td>图片格式不支持</td><td>非 JPG/PNG / base64 解码失败</td></tr>
</table>
<p style="font-size:12px;color:#94a3b8;margin-top:8px">1004 已废弃(接口2 不再自动判性别,改由客户端传 gender 参数)。</p>
</div>
</div>
<!-- ======== 在线测试页 ======== -->
<div class="card" id="test">
<h2>🧪 在线测试页面</h2>
<div class="card-body">
<table>
<tr><th>接口</th><th>测试页</th><th>功能</th></tr>
<tr><td>1. 四庭七眼</td><td><a href="/static/test_interface1.html" class="link">/static/test_interface1.html</a></td><td>上传照片 → 原图+标注叠加,底图/标注开关,指标卡片</td></tr>
<tr><td>2. C端生发</td><td><a href="/static/test_interface2.html" class="link">/static/test_interface2.html</a></td><td>上传+性别 → 方案一覧(原图/叠加/生发),双图对比</td></tr>
<tr><td>3. B端生发</td><td><a href="/static/test_interface3.html" class="link">/static/test_interface3.html</a></td><td>划线图上传 → 生发效果图</td></tr>
<tr><td>4. 用户特征</td><td><a href="/static/test_interface4.html" class="link">/static/test_interface4.html</a></td><td>上传照片 → 42项面部特征表格 + 原始JSON</td></tr>
<tr><td>5. 发际线PNG</td><td><a href="/static/test_interface5.html" class="link">/static/test_interface5.html</a></td><td>上传+性别 → 发际线方案+中心点坐标</td></tr>
</table>
<p style="font-size:12px;color:#94a3b8;margin-top:12px">
完整 API 文档:<a href="/docs" class="link">/docs</a>Swagger UI
</p>
</div>
</div>
<p style="text-align:center;font-size:12px;color:#cbd5e1;margin-top:32px">
Base: https://hair.xiangsilian.com &nbsp;|&nbsp; Swagger: /docs &nbsp;|&nbsp; 接入说明: /static/integration.html
</p>
</div>
</body>
</html>