接入流程
以下路径都相对于 v1 服务根地址。当前本机地址为 http://127.0.0.1:8017;其他设备须换成这台机器可访问的 IP 和端口。请求和响应使用 HTTP;创建任务采用 multipart/form-data,其他 JSON 接口返回 application/json。
读取光效 key
上传 B 并获得 job_id
每约 1 秒查询状态
取 A、结果1/2与图5
POST /api/jobs 返回 HTTP 202 表示已接收,并不表示出图成功;API 1.1 新任务只有结果1、结果2和图5全部落盘后才返回 status=completed。推荐客户端最终展示/下载 /final,可保留结果1/2供对照。返回 {"model_ready":true,"fusion_loaded":true,"warmup_error":null,"queued":0,"active":0}。model_ready 只检查 v1 融合权重;融合模型启动时自动加载和预热;fusion_loaded 表示权重已驻留,warmup_error 非空表示预热失败。Qwen 光效服务是否可用,以 GET /api/lights 为准。
光效表
从 Qwen 光效服务读取当前预设。调用端使用每项的 key 作为 light_preset,显示名称使用 name。不要把光效 key 写死。
{
"lora_exists": true,
"lora": {"lid": "...", "rid": "...", "ckpt": "..."},
"presets": [
{"key": "afternoon_front", "name": "午后顺光", "prompt": "...", "tier": "...", "via": "...", "note": "..."},
{"key": "custom", "name": "自定义", "prompt": null}
]
}
上方是字段示意,完整列表以接口实时返回为准。prompt=null 的预设需要同时传 light_prompt;lora_exists=false 时不能创建 Qwen 光效任务。
创建任务
请求体为 multipart/form-data。成功返回 HTTP 202 和任务对象。content 是高分辨率原片 B,必须恰好选择一种打光来源。
| 字段 | 类型 / 默认值 | 说明 |
|---|---|---|
| content | 文件 · 必填 | 原片 B,宽和高均至少 448px;单张文件最多 120 MiB。 |
| light_preset | 字符串 · 二选一 | 从 /api/lights 取得的 key;服务默认生成约 2MP 的 A(按 B 比例、边长对齐到 32)。 |
| reference | 文件 · 二选一 | 已有 LoRA 打光图 A;与 B 的宽高比相差不能超过 1%。 |
| light_prompt | 字符串 · 可选 | 仅在所选预设没有内置 prompt 时必填。 |
| light_steps | 整数 · 12 | Qwen 步数 1–50;省略时 v1 明确请求 12 步。 |
| seed | 整数 · 可选 | Qwen 随机种子 0–2147483647;省略则由服务生成。 |
| batch | 整数 · 8 | v1 融合批量:1、2、4 或 8。 |
| amp | 字符串 · bf16 | v1 精度:bf16、fp16 或 off。 |
| light_strength | 浮点 · 1.0 | 融合光照强度,0–2。 |
| detail_strength | 浮点 · 0.2 | B 细节回灌强度,0–1。 |
| face_safe | 固定 true · 强制执行 | 脸部结构优先。兼容旧客户端布尔参数,但省略或传 false 均不能关闭,任务返回 true。可减少 A 对五官的改动传入结果,但脸上的斑驳光、波光可能变弱。 |
| contour_safe | 固定 true · 强制执行 | 人物边缘二次修复。旧布尔参数继续接收,但省略或传 false 均不能关闭,任务返回 true。本地 BiRefNet 从 B 生成遮罩,对外侧 12px 代理画幅轮廓带中的异常亮边或暗边做软修正。主体核心与远处背景不变;宽背光、内侧发丝毛刺不在本次处理域。完成后可下载遮罩检查。 |
上传文件经过 EXIF 方向修正并转为 RGB PNG。输出像素尺寸与修正后的 B 相同。客户端用 FormData 发送文件;不要手动指定 multipart 的 Content-Type 边界。
任务状态
创建时和每次查询返回同一结构。阶段为 queued → relighting → fusing → compositing → completed,上传 A 模式直接从 queued 进入 fusing;任一阶段可变为 failed。
{
"job_id": "a1b2c3d4e5f6",
"status": "queued",
"source": "qwen",
"batch": 8,
"amp": "bf16",
"light_strength": 1.0,
"detail_strength": 0.2,
"face_safe": true,
"contour_safe": true,
"light_preset": "afternoon_front",
"light_name": "午后顺光",
"reference_ready": false,
"output_ready": false,
"pipeline_required": true,
"direct_ready": false,
"final_ready": false,
"final_status": "queued",
"final_error": null,
"final_elapsed_s": null,
"final_metrics": null,
"reference_size": null,
"content_size": [6000, 4000],
"output_size": null,
"light_steps": 12,
"generated_seed": null,
"relight_elapsed_s": null,
"fusion_elapsed_s": null,
"elapsed_s": null,
"model_cached": null,
"content_features_cached": null,
"failed_stage": null,
"runtime_target_s": 10.0,
"runtime_domain": "24.00MP / 单张 RTX 5090 / 工作线程开始至结果1/2和图5全部落盘;含 Qwen/SAM,不含上传、归一化与排队",
"error": null
}
pipeline_required=true表示新任务必须生成三个结果。reference_ready=true后可先下载 A;output_ready/direct_ready分别表示结果1/2文件已准备好,output_size在此时给出。SAM 失败时整单返回status=failed, failed_stage=composite,结果1/2仍可下载诊断。- 图5状态为
queued → running → completed/failed,旧任务是not_started。新任务自动执行;只有旧任务补做或失败重试才调用POST /api/jobs/{job_id}/final,HTTP 202;重复请求在排队、运行或完成时不会重复生成。final_error单独报告 SAM/合成失败。 status=failed时读取failed_stage和error。任务执行失败通过 HTTP 200 的状态对象报告。elapsed_s是工作线程开始后的总耗时(包括图5),不包含上传、图像归一化与排队;relight_elapsed_s是 Qwen 回报的推理时间;fusion_elapsed_s到内存结果1/2完成为止;含需要时的冷启动加载,不包括 SAM 图5;final_elapsed_s是 SAM 抠图、原尺寸合成和图5文件保存耗时。content_features_cached=true表示同一原片的 v1 编码已自动复用;首次原片为 false。编码以原精度保存在 CPU,仅保留一张原片且最多 1GiB;像素、tile 布局、batch 或精度改变都会失效。每张新原片的首次耗时和复用耗时应分别统计。runtime_target_s是完整后台流程的工程目标值,不是请求超时或性能保证;其统计范围见runtime_domain。
图片与检测
| 接口 | 返回 | 可调用时机 |
|---|---|---|
| GET /api/jobs/{job_id}/preview/{kind} | JPEG,最长边 ≤1600px | kind 为 content、reference、output(结果1)、direct(结果2)或 final(图5)。 |
| GET /api/jobs/{job_id}/image/{kind} | 完整 PNG | 同上。A 保留生成尺寸;B、结果1/2和图5保留 B 尺寸。图5接口须 final_ready=true。 |
| GET /api/jobs/{job_id}/quality | JSON A 质量报告 | reference_ready=true 后;首次计算,后续按任务缓存。A 未完成时 409。 |
| GET /api/jobs/{job_id}/output | 客户端最终图:SAM人物合成图5,完整 PNG 附件 | final_ready=true 后;保留原文件名 CroCoDiLight-{job_id}.png。与 /final 像素及文件内容相同。SAM未完成或失败返回HTTP409,绝不退回结果1。结果1对比图使用 /image/output。 |
| GET /api/jobs/{job_id}/direct | 结果2完整 PNG 附件 | 完成后且 direct_ready=true;文件名为 CroCoDiLight-{job_id}-direct.png。它取自 tile 解码拼接,不经过 B 像素回填、A 二次校正、细节回灌和边缘修复。 |
| GET /api/jobs/{job_id}/final | 图5完整 PNG 附件 | final_ready=true 后。SAM 3.1 用 person 提示从结果1提取人物,贴回结果2;阈值 0.5,原生 6px 向内羽化。 |
| GET /api/jobs/{job_id}/final-mask | 图5实际合成 alpha PNG | final_ready=true 后。原尺寸灰度图,255 取结果1,0 取结果2,中间值作融合。 |
| GET /api/jobs/{job_id}/mask | 人物灰度遮罩 PNG | 仅当 contour_safe=true 且遮罩已生成;白色代表人物,供人工检查误分割。 |
| GET /api/jobs/{job_id}/crop/{kind}?x=.5&y=.5&size=512 | 原生 JPEG 裁切 | kind 可为 content、output、direct 或 final;x,y 为 0–1 中心位置,size 为 128–1024px。 |
| GET /api/jobs/{job_id}/structure?x=.5&y=.5&size=512 | JSON 检测值 | 完成后;B 与结果的局部结构相关性,目标 ≥0.90。 |
| GET /api/jobs/{job_id}/pattern?x=.5&y=.5 | JSON 检测值 | 完成后;局部光斑的恢复强度和方向相关性。 |
| GET /api/jobs/{job_id}/lighting?x=.5&y=.5 | JSON 检测值 | 完成后;结果向 A 光效接近的程度,目标 ≥0.80。 |
图5的 final_metrics 包含 background_changed_pixels 和 person_core_changed_pixels,两项均按 measured / target / domain 返回,目标都是 0。前者对比遮罩外图5与结果2,后者对比 alpha=255 内图5与结果1。它们验证像素来源,不能验证 SAM 是否完整分出发丝、衣袖或头饰。
质量报告给出 face_count_b、matched_face_count、landmark_shift、face_overexposure_ratio、risk_level、thresholds 和 domain。位移是对齐代理图中五点平均位移除以 B 脸框最大边;过曝是 B 脸框内 A 灰度 >0.95 的比例。提示线为未标定的初始值,仅用于人工复查;face_id_score、silhouette_shift 为 null,原因见 unavailable。其他三个检测接口也返回 domain;null 不等于 0。
错误处理
| HTTP | 含义 | 客户端动作 |
|---|---|---|
| 404 | 任务 ID、图片 kind 不存在,或旧任务没有结果2 | 检查路径和 job_id;结果2先看 direct_ready。 |
| 409 | A 或结果尚未生成 | 继续轮询任务;按 reference_ready 和 status 下载。 |
| 413 | 任一上传文件超过 120 MiB | 减小文件。 |
| 422 | 缺字段、无效参数、无法读取图片或 A/B 比例不符 | 展示 detail 并修正请求。 |
| 503 | v1 权重缺失、Qwen 光效服务或 A 质量检测不可用 | 检查服务状态后重试。 |
业务错误通常为 {"detail":"错误说明"};FastAPI 字段校验失败时 detail 是数组。网络中断且创建请求是否成功不明时,不要自动重复上传,以免重复生成任务。
浏览器客户端示例
以下 JavaScript 可放在与 API 同源的网页里。调用 run(contentFile) 时传入用户文件选择器拿到的 File;预设 key 从 /api/lights 读取。
const api = location.origin;
async function checkedJson(response) {
const data = await response.json();
if (!response.ok) throw new Error(
typeof data.detail === 'string' ? data.detail : JSON.stringify(data.detail)
);
return data;
}
async function createAndWait(contentFile, presetKey) {
const form = new FormData();
form.append('content', contentFile);
form.append('light_preset', presetKey);
// 现成 A 模式:删去 light_preset,改用 form.append('reference', aFile)
const created = await checkedJson(await fetch(`${api}/api/jobs`, {
method: 'POST', body: form
}));
const id = created.job_id;
for (;;) {
const job = await checkedJson(await fetch(`${api}/api/jobs/${id}`));
if (job.reference_ready) {
// 可先显示 `${api}/api/jobs/${id}/preview/reference`
}
if (job.status === 'failed') throw new Error(job.error || '任务失败');
if (job.status === 'completed' && job.final_ready) return {
job,
referenceUrl: `${api}/api/jobs/${id}/image/reference`,
outputUrl: `${api}/api/jobs/${id}/output`,
directUrl: job.direct_ready ? `${api}/api/jobs/${id}/direct` : null,
finalUrl: `${api}/api/jobs/${id}/final`,
finalError: job.final_error
};
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
async function run(contentFile) {
const lights = await checkedJson(await fetch(`${api}/api/lights`));
const presetKey = lights.presets[0].key;
return createAndWait(contentFile, presetKey);
}
// const result = await run(fileInput.files[0]);
// result.finalUrl 是最终图5;result.outputUrl/directUrl 是对照结果1/2。
若预设 prompt=null,需额外 form.append('light_prompt', 用户输入)。页面按钮里调用 run(fileInput.files[0]) 即可。
curl 示例
BASE=http://127.0.0.1:8017
curl -sS "$BASE/api/lights"
# 用预设生成 A,再交给 v1 融合。把路径换成自己的原片。
curl -sS -X POST "$BASE/api/jobs" \
-F 'content=@/path/to/B.jpg' \
-F 'light_preset=afternoon_front' \
-F 'light_steps=12' -F 'batch=8' -F 'amp=bf16'
# 从上一步的 JSON 复制 job_id,持续查询直至 completed。
JOB_ID=a1b2c3d4e5f6
curl -sS "$BASE/api/jobs/$JOB_ID"
curl -sS "$BASE/api/jobs/$JOB_ID/image/reference" -o A.png
curl -sS "$BASE/api/jobs/$JOB_ID/output" -o result.png
curl -sS "$BASE/api/jobs/$JOB_ID/direct" -o result2-direct.png
curl -sS "$BASE/api/jobs/$JOB_ID/final" -o final.png
# 若已有 A,创建时改用 reference,不传 light_preset。
curl -sS -X POST "$BASE/api/jobs" \
-F 'content=@/path/to/B.jpg' \
-F 'reference=@/path/to/A.png'
部署注意
固定依赖:本机 Qwen LoRA 服务和 SAM 3.1 TensorRT 服务均常驻;doc/start_web.sh 先加载/预热 Qwen(含 2MP),再启动 SAM 和 v1;避免首次量化的显存峰值重叠。v1 的模型和 CUDA 缓存持续保留。
- 服务当前无登录鉴权;仅在可信网络提供访问。
127.0.0.1只能从服务本机访问。跨机器使用时由部署方设置可访问地址和网络入口。 - 当前未配置跨域 CORS。浏览器网页请与 API 同源部署,或由自己的后端转发;原生桌面/移动客户端可直接请求 HTTP API。
- 已完成的任务图片和状态保存在服务数据目录,可在服务重启后读取;排队和处理中任务不会恢复。当前没有取消任务或按客户端枚举任务的接口,请保存
job_id。 - 机器可读契约见 /openapi.json;可在 /docs 交互测试。文档列出的图片接口返回二进制图片,不是 JSON。