CROCODILIGHT · V1 API
返回整图测试台

客户端接入文档

上传原片 B,生成或上传打光图 A。固定流程输出三张与 B 同尺寸的图片:结果1为现有融合,结果2为 CroCo 解码直出,图5为 SAM 3.1 人物合成。新任务无需额外开关或请求。

接入流程

以下路径都相对于 v1 服务根地址。当前本机地址为 http://127.0.0.1:8017;其他设备须换成这台机器可访问的 IP 和端口。请求和响应使用 HTTP;创建任务采用 multipart/form-data,其他 JSON 接口返回 application/json。

1GET /api/lights
读取光效 key
2POST /api/jobs
上传 B 并获得 job_id
3GET /api/jobs/{job_id}
每约 1 秒查询状态
4GET 图片接口
取 A、结果1/2与图5
任务异步排队。POST /api/jobs 返回 HTTP 202 表示已接收,并不表示出图成功;API 1.1 新任务只有结果1、结果2和图5全部落盘后才返回 status=completed。推荐客户端最终展示/下载 /final,可保留结果1/2供对照。
GET /api/health

返回 {"model_ready":true,"fusion_loaded":true,"warmup_error":null,"queued":0,"active":0}。model_ready 只检查 v1 融合权重;融合模型启动时自动加载和预热;fusion_loaded 表示权重已驻留,warmup_error 非空表示预热失败。Qwen 光效服务是否可用,以 GET /api/lights 为准。

光效表

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 光效任务。

创建任务

POST /api/jobs

请求体为 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整数 · 12Qwen 步数 1–50;省略时 v1 明确请求 12 步。
seed整数 · 可选Qwen 随机种子 0–2147483647;省略则由服务生成。
batch整数 · 8v1 融合批量:1、2、4 或 8。
amp字符串 · bf16v1 精度:bf16、fp16 或 off。
light_strength浮点 · 1.0融合光照强度,0–2。
detail_strength浮点 · 0.2B 细节回灌强度,0–1。
face_safe固定 true · 强制执行脸部结构优先。兼容旧客户端布尔参数,但省略或传 false 均不能关闭,任务返回 true。可减少 A 对五官的改动传入结果,但脸上的斑驳光、波光可能变弱。
contour_safe固定 true · 强制执行人物边缘二次修复。旧布尔参数继续接收,但省略或传 false 均不能关闭,任务返回 true。本地 BiRefNet 从 B 生成遮罩,对外侧 12px 代理画幅轮廓带中的异常亮边或暗边做软修正。主体核心与远处背景不变;宽背光、内侧发丝毛刺不在本次处理域。完成后可下载遮罩检查。

上传文件经过 EXIF 方向修正并转为 RGB PNG。输出像素尺寸与修正后的 B 相同。客户端用 FormData 发送文件;不要手动指定 multipart 的 Content-Type 边界。

任务状态

GET /api/jobs/{job_id}

创建时和每次查询返回同一结构。阶段为 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,最长边 ≤1600pxkind 为 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}/qualityJSON 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 PNGfinal_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=512JSON 检测值完成后;B 与结果的局部结构相关性,目标 ≥0.90。
GET /api/jobs/{job_id}/pattern?x=.5&y=.5JSON 检测值完成后;局部光斑的恢复强度和方向相关性。
GET /api/jobs/{job_id}/lighting?x=.5&y=.5JSON 检测值完成后;结果向 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。
409A 或结果尚未生成继续轮询任务;按 reference_ready 和 status 下载。
413任一上传文件超过 120 MiB减小文件。
422缺字段、无效参数、无法读取图片或 A/B 比例不符展示 detail 并修正请求。
503v1 权重缺失、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。
CroCoDiLight v1 · 客户端 API 文档