
简介Wan2.1 FunCamera镜头运动控制工作流JSON文件专为ComfyUI用户提供适合在AI视频生成中需要精确调整镜头移动、转场与运镜方式的创作者和AIGC开发者也适用于短视频运镜设计、动态分镜预览、影视风格镜头模拟等创作场景。资源包非常轻量仅包含1个JSON文件体积约4KB可直接导入ComfyUI加载运行无需安装额外复杂依赖JSON内预设了镜头运动控制的相关节点、连线与参数组合既可立即复用到自己的视频生成流程中也可作为学习样本深入理解FunCamera在ComfyUI中的接入方式和配置逻辑。作者还提供了ComfyUI使用教程及TauriDjango开源AIGC工具平台介绍下载者可结合这些配套文章快速掌握调用方法节省自行摸索的时间。目前已有218人学习下载推荐给具备一定ComfyUI基础、希望扩展Wan2.1视频生成镜头表现力的用户参考使用。1. 用 Wan2.1 FunCamera 控制镜头运动到底在控什么很多人在 ComfyUI 里第一次加载 Wan2.1 的镜头控制工作流会下意识以为它跟写「镜头缓慢推进环绕主体旋转」这类提示词是一回事。不是。FunCamera 走的是一条显式的数值控制路线把每一帧的相机位姿写成矩阵序列编码后作为条件注入扩散过程生成时虚拟摄像机就按这条轨迹走。前者靠文本先验去猜模型给你什么算什么后者是给定轨迹模型负责在轨迹约束下把画面补出来。差别在可复现性——同一个种子、同一段轨迹推拉摇移的幅度和节奏是能对齐的做分镜、做产品环绕、做漫剧转场时这一点很关键。这套玩法适合已经把 ComfyUI 本地部署跑通、显存至少能扛住 1.3B 视频模型的人如果你还在纠结 comfyui 配置要求或者刚下完整合包先把基础文生视频跑顺再回来。2. FunCamera 的相机位姿条件是怎么注入 Wan2.1 的2.1 从文本运镜提示到显式相机外参文本运镜提示的失败模式很固定写「镜头左移」模型可能给主体左移也可能给背景左移还可能两边都动。原因是文本条件只约束语义不约束几何。FunCamera 这类相机控制方案的做法是把相机运动拆成逐帧的刚体变换用一个[F, 4, 4]的相机到世界c2w矩阵序列描述F 是帧数。每个矩阵里的旋转部分决定机位朝向平移部分决定机位位置。这套思路最早在 CameraCtrl 一类工作里成型把相机位姿转成 Plücker 嵌入也就是对每条从相机中心出发的射线用方向和叉积编码得到一个稠密的逐像素条件张量。相较于直接把 12 维位姿向量拼进文本嵌入Plücker 形式保留了「像素和射线的对应关系」模型更容易学会「画面里某个点应该往哪个方向移动」。Wan2.1 的 FunCamera 沿用了这个方向差异主要在注入位置和训练数据规模上——它是在 Wan2.1 底座基础上用带相机标注的数据做过对齐训练的所以能接住位姿条件而直接用原版 I2V 权重喂位姿基本没反应。我一般会这么判断如果你的需求是「大致往某个方向动一下」文本提示加低幅度运镜就够如果需求是「严丝合缝绕一圈回到原点」或者「和另一段素材的机位对上」那必须上显式位姿。2.2 内参 fx/fy/cx/cy 与外参 c2w 的坐标系约定真正让人翻车的是坐标系。相机控制里同时涉及三套约定混用一套就能让画面朝天上飞。名称常见取值作用混淆后果世界坐标朝向y 向上描述场景上下颠倒、地平线翻面相机坐标朝向OpenCVx 右、y 下、z 前描述成像前进变后退内参单位归一化除以宽高决定焦距感视场角突变、物体膨胀内参里的 fx、fy 控制视场角数值越大画面越「长焦」越小越「广角」cx、cy 是主点一般取 0.5、0.5 就相当于光轴对准画面中心。绝大多数工作流的内参节点接受的是归一化值如果你从标定文件里抄了一组像素单位的 fx1200直接填进去会得到极端长焦的画面正确做法是除以图像宽度。外参这边输入是 c2w 还是 w2c 一定要确认两者互为逆矩阵搞反了等于把相机放在了场景对面。提示轨迹跑出来画面完全静止时先查外参序列是不是所有帧都一样再看旋转矩阵是否正交。数值漂移积累久了会退化成非刚体变换模型会把它理解成「相机在抽搐」。2.3 控制粒度相机运动、主体运动、环境运动要分开FunCamera 只负责相机。主体动没动、背景动没动是另外的条件在管。做产品环绕时常见需求是「产品不动相机绕一圈」那主体部分交给首帧图或者参考图去锁相机位姿交给 FunCamera。如果你把两者混在一句提示词里写「镜头环绕产品缓慢旋转」大概率得到的是两股运动互相打架产物像果冻。分层的做法是首帧图定主体姿态和构图FunCamera 位姿序列定机位文本提示只写材质、光照、风格这类不涉及运动的描述。这套分工在做 comfyui 漫剧工作流时尤其明显——角色站位靠参考图镜头切换靠位姿段落拼接一旦把运动写进提示词跨镜头的角色一致性就开始掉。模型规模上1.3B 版本在消费级显卡上更容易跑起来位姿跟随的精度会粗一些14B 版本对大幅度运镜的还原更稳但显存和时间的代价摆在那。我的建议是先拿 1.3B 把轨迹和参数调对再换大模型出终版。3. ComfyUI 里跑通 FunCamera 镜头控制的最小工作流3.1 模型文件与节点依赖Wan2.1 的视频模型、文本编码器和 VAE 三件套要放对目录这是最容易被整合包目录结构搞混的一步。常见的放置位置如下扩散模型含 FunCamera 对齐权重ComfyUI/models/diffusion_models/文本编码器ComfyUI/models/text_encoders/VAEComfyUI/models/vae/相机位姿 JSON 或缓存随便一个工作目录靠节点路径参数读节点侧原生 ComfyUI 提供了 Wan 的加载与采样节点而相机嵌入的构建节点在社区插件里更成熟通常由视频封装类插件提供。装插件走 ComfyUI Manager 最省事手动装就进custom_nodes目录拉取后重启看控制台有没有报依赖缺失。如果你用的是中文整合包Manager 一般已经内置搜索框里敲关键字就能装。注意插件升级后节点名或输入口可能变化工作流报红色节点时先看控制台的 ImportError十有八九是依赖没跟上而不是工作流本身坏了。3.2 从相机嵌入到采样的连线顺序最小工作流的数据流是这样一条链顺序错了就是黑屏或者报维度错误Load Diffusion Model (FunCamera 权重) │ ├── ModelSample 采样器 ◄── 潜空间 │ CLIP Text Encode (正向/负向) ──► 条件 │ Camera Pose Loader / Embeds ──► 相机嵌入 ──► 条件拼接 │ Empty Latent Video (宽/高/帧数) ──► 潜空间 │ VAE Decode ──► 图像序列 ──► 保存/合成关键在中间那步相机嵌入不是单独一个输入口而是拼进正负条件里的。常见接法是位姿节点输出一个 embeds用一个条件合并节点把它和文本条件合成再送进采样器的 positive/negative。漏掉合并这一步位姿就是死数据画面只会按文本自由发挥。下面是用 API 方式提交同一套工作流的最小脚本适合批量跑不同轨迹import json import requests SERVER http://127.0.0.1:8188 # 从 ComfyUI 界面右上角「导出API格式」拿到工作流 JSON with open(funcamera_api.json, r, encodingutf-8) as f: wf json.load(f) # 1) 改帧数与分辨率 wf[10][inputs][width] 832 wf[10][inputs][height] 480 wf[10][inputs][length] 81 # 81 帧约 5 秒 16fps # 2) 指向这一步要用的相机轨迹文件 wf[21][inputs][pose_path] poses/orbit_360.json # 3) 换种子方便对比不同轨迹下的稳定性 wf[3][inputs][seed] 20260214 resp requests.post(f{SERVER}/prompt, json{prompt: wf, client_id: funcamera-batch}, timeout30) print(resp.status_code, resp.json())这段代码做的事很直白读一份 API 格式的工作流按节点 ID 改写输入然后 POST 到本地 8188 端口的/prompt。节点 ID10、21、3要换成你自己工作流里的实际编号改之前先在界面里点开节点看编号。length是帧数Wan 系列常见的 81 帧对应 16fps 下约 5 秒想拉长就按 4n1 的节奏递增别随便填 80 或 100潜空间下采样对不齐会直接报错。seed固定住位姿文件换掉才能横向比轨迹本身的效果。3.3 关键参数表与首帧锁定参数没有万能值但有几组区间是反复验证过比较稳的先按这张表起手再按现象微调。参数起步值调大后果调小后果分辨率832×480显存吃紧、速度骤降细节糊位姿跟随变钝帧数81长轨迹易漂移运动做不完整采样步数2030收益递减时间线性涨画面噪、边缘脏CFG56颜色过饱和、动作僵硬不跟文本画面跑偏位姿缩放1.0运镜幅度夸张、出画几乎看不出运动运动强度1.0主体也跟着乱动静帧感强首帧锁定是另一条经常被忽略的线FunCamera 控制的是机位不控制第一帧长什么样。想让整段视频从某个确定构图出发得把首帧图走参考图或首帧条件那条路跟位姿条件同时接进去。只给位姿不给首帧模型会自己编一个起始构图后面整段都跟着这个随机起点走重跑一次结果完全不同。4. 用脚本生成推拉摇移的相机轨迹4.1 orbit / dolly / crane 三类轨迹的 numpy 实现手动在节点里一帧帧拧相机位置不现实轨迹一定要用脚本生成。三类最常用的运镜用 numpy 几十行就能写出来import numpy as np import json def look_at(eye, target, upnp.array([0.0, 1.0, 0.0])): 构造 c2w 矩阵相机在 eye看向 target世界上方向为 up fwd target - eye fwd fwd / np.linalg.norm(fwd) # 相机 z 轴OpenCV 约定向前 right np.cross(fwd, up) right right / np.linalg.norm(right) # 相机 x 轴 down np.cross(fwd, right) # 相机 y 轴 c2w np.eye(4) c2w[:3, 0] right c2w[:3, 1] down c2w[:3, 2] fwd c2w[:3, 3] eye return c2w def orbit(radius3.0, height1.0, frames81, turns1.0): 环绕相机绕原点转 turns 圈 poses [] for i in range(frames): theta 2 * np.pi * turns * i / (frames - 1) eye np.array([radius * np.sin(theta), height, radius * np.cos(theta)]) poses.append(look_at(eye, np.array([0.0, 0.6, 0.0]))) return poses def dolly(start_z4.0, end_z1.6, frames81): 推镜沿 z 轴匀速靠近视线始终朝原点 poses [] for i in range(frames): t i / (frames - 1) eye np.array([0.0, 1.0, start_z (end_z - start_z) * t]) poses.append(look_at(eye, np.array([0.0, 0.8, 0.0]))) return poses def save_poses(poses, path): data [p.reshape(-1).tolist() for p in poses] # 每帧展平成 16 个数 with open(path, w, encodingutf-8) as f: json.dump({c2w: data, convention: opencv}, f) save_poses(orbit(turns1.0), poses/orbit_360.json) save_poses(dolly(), poses/dolly_in.json)逻辑上分三块。look_at负责把「相机在哪、看哪、上方向朝哪」翻译成 4×4 齐次矩阵这是所有轨迹的地基注意这里的 y 轴是朝下的符合 OpenCV 成像约定改错符号会让画面上下翻转。orbit用极坐标扫一圈radius是环绕半径height是机位高度turns是圈数取 0.5 就是半圈环绕做转场素材很顺手。dolly只改 z 值起点 4.0 落到 1.6模拟推镜把起止值对调就是拉镜。save_poses把每帧矩阵展平成 16 个浮点数存 JSON并额外写一个convention字段方便自己在工作流里确认坐标系。位姿文件里帧数必须和采样时的length一致少一帧多一帧都会让最后一段运动塌掉。4.2 轨迹平滑与幅度控制机器生成的轨迹往往「太急」。匀速环绕看着机械实际拍摄里摄影师会有缓起缓停。给角度加一条缓动曲线观感立刻不一样def ease_in_out(t): 三次缓动t 在 0~1输出也在 0~1两端变化慢 return t * t * (3 - 2 * t) # 在 orbit 里把线性进度换成缓动进度 # theta 2 * np.pi * turns * ease_in_out(i / (frames - 1))缓动函数只是把「时间进度」重新映射一遍不改变轨迹形状。缓入缓出的半径不变所以不会出现突然加速撞向主体的感觉。做环绕加推近的复合镜头可以把radius和height也写成 t 的函数比如半径从 3.5 收到 2.0同时高度从 0.8 升到 1.5就是一段带升降的螺旋推进。幅度控制上有个经验阈值单帧位移超过主体尺寸的 3% 到 5%画面就容易出现拖影和几何撕裂。做 81 帧的环绕半径 3.0 转一整圈时每帧角度约 4.5 度这已经接近上限想转更快就加帧数别硬加圈数。4.3 画面撕裂、主体漂移的排查表跑出来的结果不对别急着重跑先按现象对号入座。下面这张表是实际工作里最常撞到的几类现象大概率原因处理方式画面整体静止位姿没接进条件或序列全同查条件合并节点打印首尾矩阵对比上下颠倒相机 y 轴方向反了翻转down的符号重存 JSON前进变后退c2w / w2c 搞反对矩阵取逆后重跑边缘拉扯撕裂单帧位移过大加帧数或减圈数控制在 3% 阈值内主体跟着乱漂提示词里写了运动删掉运镜描述只留材质与光照末帧突然跳变帧数与 length 不匹配对齐两者或裁掉多余帧显存溢出报错分辨率与帧数同时拉高先降分辨率再考虑分块或降帧调试时最省时间的做法是先生成低分辨率、少帧数的版本只看运动方向对不对确认没问题再上完整参数。位姿这条链路一旦方向搞错再高的采样步数也救不回来。5. 多段轨迹拼接与镜头节奏的一个实用技巧单段轨迹跑通了接下来多半要做多镜头。直接在一个工作流里塞两段位姿是行不通的——采样器只认一条序列中间切开会让运动在接缝处断掉。稳妥做法是分段生成、后期拼接接缝处用首尾帧做锚点第一段的末帧导出成图作为第二段的首帧条件同时把第二段的轨迹起点设成第一段的末位姿这样两段之间的机位是连续的观感上就是一次长镜头里的转向。更省事的一种思路是「一条轨迹讲两个动作」。比如先环绕四分之一圈再沿半径方向推近用同一个脚本把两段函数接起来中间用一个几十帧的过渡段做缓动混合import numpy as np def blend(poses_a, poses_b, overlap12): 把 a 的尾部与 b 的头部按线性权重叠化得到连续位姿 head poses_a[:-overlap] tail_a poses_a[-overlap:] tail_b poses_b[:overlap] mid [] for i in range(overlap): w i / (overlap - 1) # 0 - 1 的混合权重 m tail_a[i] * (1 - w) tail_b[i] * w # 旋转部分重新正交化避免权重混合后不再是刚体 r m[:3, :3] u, _, vh np.linalg.svd(r) m[:3, :3] u vh mid.append(m) return head mid poses_b[overlap:]这段代码解决的问题是「两段轨迹硬拼会抖」。位姿矩阵不能像像素那样直接线性插值旋转部分混合后要重新做正交化也就是 SVD 分解后取u vh否则矩阵会带上缩放和剪切分量模型读到的是非刚体变换画面会扭。overlap控制过渡长度12 到 24 帧之间比较自然太短显得生硬太长会让两段运动在中间糊成一团。节奏上有个反直觉的点镜头运动不是越快越有冲击力。把 81 帧里的前 20 帧留得很慢中间 40 帧走完主要行程最后 20 帧再收慢比全程匀速的观感强很多。前面那段ease_in_out正好可以做这件事把它套在整条拼接后的时间轴上而不是每段单独套一次整条镜头的呼吸感就出来了。做漫剧分镜时一集里三到四个镜头每个镜头内部都用这种慢-快-慢的节奏观众不会觉得镜头在「赶」转场也更容易接。本文还有配套的精品资源点击获取