- 人工智能
- AI 技能
- 3D渲染
【免费下载链接】img2threejs
Rebuild the object in a reference image as a code-only, procedural, quality-gated, animation-ready Three.js model. Token-efficient image-to-3D.
本指南讲解 img2threejs 流水线 v1.5 中 WS4 阶段的核心拟合机制:analysis_by_synthesis_fitting(分析-合成拟合)。它把"根据参考图重建 Three.js 模型"这一过程,从自由猜测式的试错收敛为一次受硬性预算约束、结果可审计、可由 Divine Eye 视觉评估器驱动评分的确定性参数搜索。读完本文,你将掌握fit()与fit_against_divine_eye()的完整 API 契约、停止策略(plateau / oscillation / 预算上限)的判定细节、CLI 拟合目标文件的精确格式,以及如何把拟合结果接入correction_loop.decide()驱动的校正闭环。
WS4 拟合循环在流水线中的定位
img2threejs 的目标是以"代码即资产(code-only)、程序化、质量门控、动画就绪"的方式,把参考图中的物体重建为 Three.js 模型。在docs/ARCHITECTURE.md中,forge/stage4_review/fit_params.py被明确标注为 "Bounded, gate-aware analysis-by-synthesis parameter fitting"(有界、门控感知的分析-合成参数拟合)。
所谓 analysis-by-synthesis(分析-合成),指的是一条朴素的循环:给定一组参数(例如某个渲染或场景向量的坐标),先把参数合成为一张渲染图,再拿渲染图与参考图做比较分析,根据差距调整参数,如此反复。WS4 拟合循环的设计意图在 grimoire/build/analysis_by_synthesis_fitting.md 开篇写得很清楚:
它把确定性参数搜索与 Divine Eye 绑定,使循环表现得像一次有界的艺术家修正过程,而不是一个自由运行的猜测器。
这句话包含两个关键限定:
- 确定性(deterministic):候选参数的评估顺序固定,不使用任何随机源,同样的输入永远得到同样的结果——这是可复现、可审计的前提;
- 有界(bounded):迭代次数与评估次数都有硬性上限,加上 plateau 与方向翻转(thrashing)检测,拟合器不可能无限运行下去。
其中Divine Eye是forge/stage4_review/divine_eye.py中实现的确定性多信号渲染↔参考评估器(零 Token、纯 Python 像素数学)。它提供硬门控(silhouette IoU ≥ 0.85、scale delta ≤ 0.08)与软信号集成(SSIM、边缘重叠、色调对比等),是校正循环唯一信赖的"这张渲染离参考还有多远、哪里不对"的裁判。WS4 拟合正是把 Divine Eye 当作 objective 打分器来使用。
fit() API 与输入约束
核心入口是fit(),定义在 forge/stage4_review/fit_params.py 中:
result = fit(initial, bounds, objective, FitConfig())它实现的是纯标准库、确定性、有界的坐标下降(coordinate descent)。调用方只能在能提供确定性 objective 时用它来微调一个较小的渲染或场景向量。
initial / bounds / objective 的严格约束
fit()在真正开始搜索之前会通过_normalize_inputs()与_validate_config()做全面校验(见 fit_params.py),任何不满足约束的输入都会抛出FitInputError(带field与detail的结构化错误信息):
| 输入 | 约束 | 违规示例 |
|---|---|---|
initial | 1~15 个有限(finite)数值参数,且非布尔值;必须在各自 bounds 内 | 16 个参数(超MAX_DIMENSIONS = 15);math.nan;True |
bounds | 每个参数一个[lower, upper]有限区间对;lower严格小于upper;initial必须落在区间内 | [1](不是二元组);[1, 1](lower 不小于 upper);initial=2.0超出[-1, 1] |
objective | 必须可调用(callable);对任何参数返回有限分数;分数越高越好 | None;返回math.nan触发NonFiniteScoreError |
其中"分数越高越好"的方向约定需要特别留意——这与常见的损失函数(越小越好)相反。CLI 中的示例 objective 是1.0 - sum((value - target[i])**2),即越接近目标值分数越高,最优为 1.0。
FitConfig:拟合的预算与停止参数
FitConfig是一个 frozen dataclass,全部字段均有默认值(fit_params.py):
| 字段 | 默认值 | 含义 |
|---|---|---|
max_iterations | 40 | 最大迭代轮数(硬上限) |
max_evaluations | 500 | 最大 objective 评估次数(硬上限) |
min_improvement | 1e-9 | 判定一轮迭代"无净改进"的净增益阈值 |
plateau_iterations | 3 | 连续无净改进达到该次数则停止(状态plateau) |
oscillation_flips | 2 | 连续翻转次数达到该值则停止(状态oscillation) |
seed | None | 仅作元数据记录,不参与计算 |
校验规则(_validate_config):迭代/评估/plateau/oscillation 四个限制必须是正整数;min_improvement必须是有限非负数;seed必须是整数或None。
候选顺序、步长与停止语义
拟合器逐坐标推进。候选顺序是固定的:对每一个坐标,先评估有界下移(bounded lower move),再评估有界上移(bounded upper move),每轮只沿能提升分数的方向提交移动。seed仅作为元数据被记录,没有任何随机源——这正是"确定性"的来源。
步长(step size)初始为区间宽度的四分之一(upper - lower) / 4.0;若某次迭代没有任何坐标带来改进,所有步长统一减半(step / 2.0),形成标准的坐标下降括号细化(bracket refinement)行为。
fit()在以下条件满足时停止:
max_iterations:迭代轮数耗尽,状态为max-iterations;max_evaluations:评估预算耗尽,状态为max-evaluations。特别地,如果预算在某个坐标的第二个方向之前耗尽,已经评估过的、分数更优的第一个方向提案会被提交并记录后再返回结果,保证预算边界内绝不浪费已付出的评估;plateau:连续plateau_iterations轮净增益低于min_improvement;oscillation:连续迭代中出现方向翻转(thrashing)达到oscillation_flips次。
方向翻转(flip)跟踪的细节值得展开:翻转只统计净增益低于min_improvement的不稳定迭代,且每个至少发生一次方向反转的不稳定迭代使 thrash 计数 +1;稳定迭代或未发生反转的迭代会重置计数。这保证了三种正常情况不会误触发oscillation:普通括号细化、单轮多坐标反转(比如某轮同时改善了 2 个坐标但各自方向不同)、以及非连续(不相邻)的翻转。对应的回归测试在 forge/tests/test_fit_params.py 的test_smooth_quadratic_refinement_does_not_count_boundary_bracketing_as_oscillation、test_non_consecutive_direction_flips_do_not_oscillate、test_one_unstable_iteration_with_multiple_reversals_does_not_oscillate中逐一固化。
FitResult 与 FitTelemetry
fit()返回FitResult(frozen dataclass),包含:
parameters:最终参数向量;best_score:最优 objective 分数;status:max-iterations/max-evaluations/plateau/oscillation之一;iterations与evaluations:实际消耗的迭代与评估计数;seed:透传配置中的种子;history:每轮一条FitTelemetry,含 objective 分数、改进标志(improved)、累计评估次数、步长向量。
两个实用的序列化/转换方法:
to_json():输出parameters、bestScore、bestObjectiveScore(显式字段名)、status、iterations、evaluations、seed、history。其中bestScore为兼容保留,与bestObjectiveScore数值相同;to_correction_history(defect_tags):把历史分数转为校正循环可直接消费的{"fidelity", "defectTags", "reverted"}列表,但要求每轮best_score都在[0, 1]范围内,否则抛出FitInputError。
CLI 拟合目标:确定性的二次函数 fixture
CLI 入口main()(fit_params.py)只运行一个确定性的二次函数 fixture objective,用于演示、冒烟测试与契约验证,而不是给生产场景用的通用拟合接口。生产调用方应直接调用fit()并传入自己的 objective。
输入 JSON 的精确格式
CLI 通过--input <path>读取 JSON 文件。顶层对象必须恰好包含initial、bounds、target、config四个键(多一个、少一个都会以退出码 2 报错):
{ "initial": [0.0], "bounds": [[-1.0, 1.0]], "target": [0.5], "config": {"maxIterations": 20, "maxEvaluations": 200, "seed": 7} }其中initial、bounds、target必须是数组;target定义理想目标点,objective 为1.0 - sum((value - target[i])**2)。
config只接受以下键(camelCase),未知键会被拒绝并以退出码 2 退出:
| JSON 键 | 对应 FitConfig 字段 |
|---|---|
maxIterations | max_iterations |
maxEvaluations | max_evaluations |
minImprovement | min_improvement |
plateauIterations | plateau_iterations |
oscillationFlips | oscillation_flips |
seed | seed |
拼写错误(例如maxEvaluatons)会被精确地报告为 unknown key——测试test_cli_rejects_unknown_config_keys_without_traceback验证了这一点。
运行方式与退出码
python -m forge.stage4_review.fit_params --input fit.json # 输出示例:plateau score=0.99999999 evaluations=42 python -m forge.stage4_review.fit_params --input fit.json --json # 输出 JSON 化结果(sort_keys=True),含 bestScore / bestObjectiveScore / status / history--input:必填,指向二次函数 objective 的 JSON 文件;--json:可选,以 JSON 输出完整结果;缺省时输出一行摘要{status} score={bestScore} evaluations={evaluations};- 退出码:成功为 0;任何
FitInputError、NonFiniteScoreError、文件读取错误或 JSON 解析错误都会以退出码 2结束,且错误信息打印到 stderr、绝不输出 traceback(测试test_cli_rejects_malformed_bounds_without_traceback与test_cli_requires_exact_top_level_schema验证了这一行为)。
可执行的 Divine Eye 拟合:fit_against_divine_eye()
CLI 只能跑二次函数 fixture;真正把拟合接入视觉评估的是fit_against_divine_eye():
fit_against_divine_eye(initial, bounds, render_for_parameters, reference_png, evaluator=None, config=FitConfig())它把"确定性参数→渲染"的回调(render_for_parameters)变成一个有界的保真度 objective(见 fit_params.py):
- 对每个候选参数调用
render_for_parameters(parameters),得到渲染图路径; - 以
(reference_png, render_path)调用 evaluator 得到 Divine Eye 评估结果; - 把评估结果深拷贝后打上适配器字段
fitCandidateParameters、fitReferencePng、fitRenderPath,追加到结果列表; - 将结果换算成"门控感知"的 objective 分数返回给
fit()。
门控感知的 objective 分数
_divine_eye_objective_score()的规则是:
- 干净(clean)结果:没有 hard gate 失败且路由字段
action="continue"、verdict="pass"(或缺失)的候选,直接使用其原始 Divine Eye 保真度[0, 1]作为 objective 分数; - 带 hard gate 的结果:objective 分数固定为
-1.0,低于所有干净分数。
批准判定实现在 forge/stage4_review/_fit_divine_eye.py:approved = not hard_gates and action in (None, "continue") and verdict in (None, "pass")。
关键设计是原始保真度与 objective 分数分离:即使一个候选的原始保真度高达 0.90 但触发了 hard gate,它也不会以-1.0之外的分数参与拟合,无法挤掉一个干净的低分候选——测试test_fit_against_divine_eye_rejects_higher_fidelity_hard_gate_as_best直接验证了这一点:参数-0.5处保真度 0.90 但 gate 失败,最终最优参数仍是 0.90 的干净候选之外的 0.0(保真度 0.85)。同理,action="probe"的待审(pending)结果也不会取代已批准的基线。
evaluator 的惰性导入与可替换性
evaluator参数是可选的。缺省时使用_default_divine_eye_evaluator:惰性导入divine_eye.evaluate(fit_params.py),因此仅导入拟合模块本身不会加载任何图像分析依赖,模块保持轻量。调用方也可以注入自己的(reference_path, render_path) -> Mapping评估器,这在测试中被大量使用(用预置分数表代替真实 Divine Eye)。
DivineEyeFitResult:结果、原始保真度与修正历史
fit_against_divine_eye()返回DivineEyeFitResult,包含:
fit_result:底层FitResult(best_score/bestObjectiveScore是objective 分数,全门控运行时为-1.0);best_raw_fidelity/ JSONbestRawFidelity:被选中的干净候选的原始 Divine Eye 保真度([0, 1]);若没有任何被批准的候选(全门控运行),为None——测试test_fit_against_divine_eye_bounds_all_gated_runs_with_raw_provenance验证了这一点;divine_eye_results:每次评估结果的深拷贝记录,携带适配器字段;correction_history/ JSONcorrectionHistory:由原始(拷贝的)Divine Eye 记录派生的归一化历史,而非 objective 分数,因此对全门控运行同样有效、可审计。
to_json()输出fitResult、bestObjectiveScore、bestRawFidelity、divineEyeResults、correctionHistory五个字段。整个集成不会修改 evaluator 返回的结果映射:适配器总是对结果做深拷贝再打字段,测试test_fit_against_divine_eye_snapshots_reused_evaluator_mapping用"复用同一映射的 evaluator"验证了快照隔离,test_divine_eye_history_copies_rich_provenance验证了原始记录后续被外部改动也不会污染历史。
Divine Eye 适配层与校正循环集成
divine_eye_fidelity():标量分数适配
divine_eye_fidelity(result)(fit_params.py)从原始 Divine Eye 结果中读取fidelity作为标量 objective 分数,条件严格:必须是映射(mapping)、fidelity存在、为有限数且落在[0, 1]。布尔值、越界、非有限值、缺失字段都会触发带字段名的FitInputError。它只读不改,不会触碰结果中的 gate 键(测试test_divine_eye_adapter_reads_fidelity_without_changing_gate_keys)。
divine_eye_correction_history():归一化为校正循环历史
divine_eye_correction_history(results)委托给_fit_divine_eye.py的normalize_history(),把未修改的 Divine Eye 结果序列归一化为校正循环的历史条目。每个条目包含:
fidelity:原始保真度分数;defectTags与hardGateFailures:hard gate 失败标签的拷贝;reverted:布尔值,表示该轮相比"最后一次被批准的最优分数"是否下降;pendingReview:是否处于待审状态(即未被批准);divineEye:原始结果上下文的深拷贝(含 fidelity、gate failures、action、signals、reference/render 路径等);divineEyeAction/divineEyeVerdict:存在时镜像路由字段。
基线(baseline)更新规则是:只有被批准(无 hard gate 且action="continue"、verdict="pass")且分数更高的轮次才会更新accepted_fidelity。因此 pending(probe/非 pass)、reverted 或 hard-gated 的尝试既不会替换基线,也不会赢得门控感知的拟合。测试test_pending_high_fidelity_does_not_replace_approved_baseline与test_hard_gated_result_does_not_replace_accepted_fidelity分别覆盖了这两种"高保真但不被批准"的场景。
correction_loop.decide():停止策略状态机
校正循环的停止策略是 forge/stage4_review/correction_loop.py 中的纯逻辑函数:
decision = decide(history, target_fidelity=0.85, max_iter=6, min_delta=0.02)decide()返回{"stop": bool, "action": str, "reason": str}。停止条件按严格优先级顺序评估,第一个命中的生效,顺序为(源码注释中标注为 priority 1–8):
- EMPTY:历史为空 → 继续迭代;
- HARD GATE:存在 hard gate 失败 →
stop=True,action="refine-code"; - PENDING REVIEW:保留 Divine Eye 评估器的非 continue 路由(
refine-code/refine-spec/request-input); - SUCCESS:
fidelity >= target_fidelity且无未闭合 defect →action="continue"; - REPEATED_DEFECT:同一 defect 标签连续两轮存活 →
action="refine-spec"; - OSCILLATION:尾部连续两轮
reverted→action="refine-spec"; - PLATEAU:在目标以下且增量
< min_delta→action="request-input"; - HARD_CEILING:
len(history) >= max_iter→action="request-input"。
这个顺序本身承载了设计语义:hard gate 永远路由到refine-code,硬上限永远能终止循环——即使分数仍在爬升。后者是该模块最重要的不变量:调用方只要按while not decide(history)["stop"]: history.append(...)的写法循环,就绝不可能超过max_iter轮,任何"单调改进但永远到不了目标"的死循环都会被天花板截断。
decide()对历史条目的校验同样严格:fidelity必须是[0, 1]内的有限数、defectTags必须是字符串列表、reverted必须是布尔。当历史条目含有嵌套divineEyeprovenance 时,其有限 fidelity、hard gates、action、verdict 具有权威性,顶层的镜像 fidelity 与路由字段必须与之匹配,否则校验拒绝该条目(_routing_state中的冲突检测);fidelity-only 的历史条目(无嵌套 provenance)仍然受支持,保持向后兼容。
budget_exceeded():Token 预算断路器
budget_exceeded(spent_tokens, budget)(correction_loop.py)是 §3.6 的预算熔断:spent_tokens必须是有限非负数值,budget必须是非负整数,畸形输入抛出ValueError。返回spent_tokens >= budget时,调用方应当halt-and-ask-the-user(停下并询问用户),绝不静默继续——这是对昂贵 VLM 驱动校正循环的成本护栏。
测试契约:确定性、单调性与门控语义
forge/tests/test_fit_params.py 是这套拟合器行为契约的最完整文档,值得作为实现细节的权威参考:
| 测试 | 固化的契约 |
|---|---|
test_seeded_metadata_and_result_are_deterministic | 相同输入 + 相同 seed 产生完全相同的FitResult;seed 仅记录于元数据 |
test_best_score_history_is_monotonic_and_normalizes_for_correction_loop | 历史分数单调不减;to_correction_history()产生合法条目 |
test_stops_at_max_evaluations/test_budget_exhaustion_commits_evaluated_coordinate_improvement | 预算硬上限生效;耗尽前已评估的更优方向被提交(parameters=(-0.5,),best_score=0.5) |
test_stops_on_plateau/test_stops_on_direction_oscillation | plateau 与 oscillation 停止条件按配置触发 |
test_fit_against_divine_eye_runs_evaluator_and_preserves_provenance | evaluator 被逐候选调用;fitCandidateParameters/fitReferencePng/fitRenderPath正确写入;结果映射不被外部改动污染 |
test_fit_against_divine_eye_rejects_higher_fidelity_hard_gate_as_best | 高保真但 hard-gated 的候选不能赢得拟合 |
test_fit_against_divine_eye_rejects_higher_fidelity_probe_as_best | 高保真但action="probe"的候选不替换基线,且pendingReview=True |
test_fit_against_divine_eye_bounds_all_gated_runs_with_raw_provenance | 全门控运行best_score=-1.0、best_raw_fidelity=None,但原始保真度全部保留在结果与历史中 |
test_divine_eye_history_preserves_per_iteration_hard_gates_for_correction_loop | 归一化历史把 hard gate 同时写入defectTags与hardGateFailures,且能驱动decide()返回refine-code |
test_rejects_invalid_bounds_and_non_finite_scores/test_fit_config_rejects_invalid_limits... | 输入校验覆盖面(超维、越界、NaN、非法 config 值) |
实战接入指南:如何在自己的拟合目标中使用这套机制
生产环境的使用模式可以概括为三条原则:
CLI 只用于演示与冒烟:二次函数 fixture 的 JSON 输入只验证机制本身。真正的拟合调用直接
from forge.stage4_review.fit_params import fit, fit_against_divine_eye, FitConfig,传入自己的 objective 或 render 回调。用 fit() 拟合纯数值目标:如果你的渲染/场景向量有 1~15 个可微调参数,且有确定性的打分函数,直接构造
initial、bounds、objective即可。objective 记得"越高越好",并对任何非法输入返回有限值(否则NonFiniteScoreError会中断拟合)。用 fit_against_divine_eye() 拟合视觉目标:实现
render_for_parameters(parameters) -> str | Path(把参数渲染成 PNG)与参考图路径,其余交给拟合器:它会逐候选调用 Divine Eye(或你的注入 evaluator)、维护门控感知的 objective、产出可审计的DivineEyeFitResult。得到结果后,把correction_history喂给correction_loop.decide(),按返回的action(refine-code/refine-spec/request-input/continue)驱动下一轮,并用budget_exceeded()在 Token 预算耗尽时停下询问用户。
需要注意的是适用边界:fit 只做局部坐标下降,适合微调小参数向量;它依赖调用方提供的确定性 objective,Divine Eye 的全局像素对齐信号在"照片参考 vs 程序化重建"场景下会受到取景/背景/光照的干扰(详见 grimoire/review/self_correction.md 的 Divine Eye caveat 与 grimoire/review/divine_eye_microscope.md 的微观尺度限制),因此在接入前应确认你的 objective 确实能区分"好"与"坏"的候选——这正是 WS4 拟合把搜索与门控感知的评估器绑定的原因所在。
小结
Analysis-by-synthesis fitting 是 img2threejs WS4 阶段的关键机制,它把"逼近参考图"从自由猜测变成一次确定、有界、可审计的搜索:fit()提供纯标准库的有界坐标下降与四类停止条件,fit_against_divine_eye()将参数→渲染→Divine Eye 评估串成门控感知的 objective,correction_loop.decide()则给出带优先级的状态机停止策略与动作路由,budget_exceeded()兜住 Token 成本。三者组合,就构成了文档开头所说的"有界的艺术家修正过程"——每一次参数调整都有依据、有上限、有记录,这正是可复现、可调试、可交付的程序化 Three.js 重建流水线所依赖的品质。
- 人工智能
- AI 技能
- 3D渲染
【免费下载链接】img2threejs
Rebuild the object in a reference image as a code-only, procedural, quality-gated, animation-ready Three.js model. Token-efficient image-to-3D.
相关推荐
img2threejs 质量门禁体系全解:从 Divine Eye 到装配门的确定性质量契约
img2threejs 质量门禁体系全解:从 Divine Eye 到装配门的确定性质量契约 本文是 img2threejs 项目 质量门禁总契约 https:
人工智能AI 技能3D渲染手写 Agentic Loop:用 while 循环把函数调用变成真正的 Agent(LLM Zoomcamp 实战)
手写 Agentic Loop:用 while 循环把函数调用变成真正的 Agent(LLM Zoomcamp 实战) 在 LLM Zoomcamp 的 202
示例工程教程人工智能大模型Sealed Secrets 校验实战:使用 kubeseal --validate 验证已有 SealedSecret 的正确性
Sealed Secrets 校验实战:使用 kubeseal validate 验证已有 SealedSecret 的正确性 导读 Sealed Secret
云原生应用安全运维
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考