畅联云平台丨07-Lens与Crop跟拍几何

Lens 与 Crop 跟拍几何:从人脸锚点到成片窗口
只讲坐标、公式、数值例子与跟拍几何。
一、两种跟拍各自交付什么
模式 观感 几何本质

二者共用同一套 源坐标轨迹 (cx_src, cy_src)(来自锁定后的人脸/人体锚点),差异在「如何把锚点变成取样窗口」。

图:Lens 用圆心与半径定义取样;zoom 放大后再裁进圆内。
二、镜头几何模块:时间轴上的分段线性
成片时间轴用输出帧号 n(25fps)。轨迹是关键点序列 [(n, value), …]。查询任意 n:
任意帧 n 的值由相邻关键点线性插值得到。
滤镜图里 FFmpeg 表达式 与此同构(分段线性表达式),保证 C++ 预览与成片一致。
关键点抽稀把过密点抽到上限(路人虚化常用 24 点),避免滤镜图爆炸。
三、锚点:脸框如何变成「该跟的点」
扫描得到的是人脸框 (face_x, face_y, face_w, face_h) 与可选人体框。跟拍中心不是随便取框中心:
- 轨迹用 cy:偏上半身,避免圆心掉到胸口;
- Lens overlay cy:有人体时锚在人体上部,圆顶高于头顶。
人头保护区(防路人椭圆误伤、也用于几何估计)有硬约束:
由脸估头高:
头高 ≈ 限制在人体高度的 12%~32% 之间(由脸短边×1.38 估计)
数值例子
人体高 800 像素、脸短边 140 时:
头高估计约 193,并限制在人体高的 12%~32% 内。
0.12×800 = 96;0.32×800 = 256。
193.2 落在 [96,256],取 193。若脸异常大(误检),0.32H 会盖帽,避免「头区吞掉半个身子」。
四、Lens:圆镜取样坐标
圆镜叠加原点计算与 FFmpeg 侧圆镜 zoom 叠加坐标一致。成片侧先算:
zoom_x = lens_d / crop_w,zoom_y = lens_d / crop_h
(lens_d = 2 * lens_r),再把放大图层拖到圆窗内。几何核心:
void lens_overlay_src_xy(int lens_r, double cx, double cy,
double zoom_x, double zoom_y,
int canvas_w, int canvas_h,
int &src_x, int &src_y) {
const int lens_d = lens_r * 2;
const int ix = (int)std::floor((double)lens_r - cx * zoom_x);
const int iy = (int)std::floor((double)lens_r - cy * zoom_y);
const int scaled_w = (int)std::lround((double)canvas_w * zoom_x);
const int scaled_h = (int)std::lround((double)canvas_h * zoom_y);
// clamp overlay origin so zoomed layer covers the circular window
src_x = std::max(lens_d - scaled_w, std::min(0, ix));
src_y = std::max(lens_d - scaled_h, std::min(0, iy));
}直觉:
- 把源画布按 (zoom_x, zoom_y) 放大;
- 希望人物锚点 (cx,cy) 落在圆镜中心;
- overlay 偏移把放大图「拖」到正确位置,并 clamp 防止露黑边。
输出侧半径有地板:圆镜半径大约不小于输出短边的 1/6(且至少约 28 像素),避免远景小脸时圆镜缩成豆子。
数值例子(720p 输出)
out=1280×720,lens_r=120,zoom=1.8,锚点源坐标映射后圆心希望在输出 (640,280)。
放大后有效图宽 1280×1.8=2304。src_x 大致满足「圆心落在圆窗中心」,
再被 [lens_d - scaled_w, 0] 夹住,保证圆内总能采到像素。
五、 Crop:移动矩形窗

图:源图上 half_w×half_h 的窗以锚点为中心,clamp 进画幅后缩放到输出。
流程:
- 收集 lens 有效区间内的 raw 点;
- 时间域高斯平滑;
- 密采样到 25fps;
- 把脸/人装进窗;
- 死区 + 速度门控;
- 微位移 hold,消灭 1~2 px 残抖。
窗左上角:
裁切窗口左上角:把中心减半宽半高后,夹紧到画面内,避免越界。
clamp 会在人靠近边缘时「顶死」,若不做后处理,轨迹会出现折线拐角——这正是离线平滑与死区要抹平的对象。
六、离线高斯平滑:公式与默认 σ
离线高斯平滑:用邻近关键点按时间距离加权平均,抑制检测抖动。
默认配置:

数值例子
σ=0.72 s @25fps,半高宽时间约 1.7 s,即约 42 帧 的权重仍明显。站立微晃会被抹平;快速横穿会有拖影感——这是有意取舍。
Lens σ=0.12 → 有效约 7 帧,保留更多即时位移。
七、Crop 死区与意图门控

图:死区内冻结;超出 enter 阈值才开始跟随;速度不够则不跟。
死区半径:
d = max(dmin, 脸短边 × 0.45)
滞回:
全片位移若 ≤ d×2.20,直接冻在中心——「人几乎没动就别跟」。
数值例子
脸短边 s=100:
人原地晃 ±30 px:落在死区内,输出中心不变。横移累计 80 px 且速度超过意图阈值:开始以 最大步进 限速追随。
八、Push-in:单人推进变焦
单人素材可走:固定锚点,zoom 从 1 拉到 最大变焦,同时保证主体不离开 crop。
某输出帧 zoom z 时,源窗:
放大倍率为 z 时,窗口宽高约为画面宽高除以 z;再按中心夹紧到画面内。
推进时用主体框与 margin 检查主体是否仍在裁切窗内;若推进会导致出窗则跳过推进,避免「越推越把人脸裁掉」。
数值例子
1080p,z=1.6,锚点 (960,420):
x0=360, y0=82.5
脸若在 y=50 且高 180,顶边可能贴窗;margin≈0.28(f_w,f_h) 会判危险。
九、Lens / Crop 轨迹构建对照

主角轨迹有效帧范围 取 cx/cy 点列的首尾 n,供滤镜 enable='between(n,…)'。
十、源帧号 ↔ 输出 25fps
定位结果带 源帧率(非法则 25)。输出帧:
若源帧率不是 25,先把源帧号换算到 25 帧/秒时间轴再查轨迹。
本项目大量路径直接按 25 处理成片;扫描用源 fps 驱动 ByteTrack 。几何层用 t_sec = frame_idx / fps 做高斯,再用 25fps 密采样写点列——时间在秒域平滑,索引在 25 域交付滤镜。
十一、常见几何故障与心算排查
- 圆镜裁头:overlay cy 偏下或头高估计过矮 → 查锚点函数,勿只调 zoom。
- 贴边抖:crop clamp 引起 → 看是否绕过了死区/平滑。
- 站立仍跟拍晃:死区过小或 σ 过短 → 对照 Crop 默认 0.72 / 0.45。
- Lens 与 Crop 选人不同心:是否混用了 face_cy 与 body_cy;Lens overlay 有人体上部锚点加权。
十二、公式卡
分段线性:
v(n)=v0+α(v1-v0), α=(n-t0)/(t1-t0)
高斯:
hat v_i = Σ_j w_ij v_j / Σ_j w_ij,
w_ij = exp(-(t_j-t_i)²/(2σ²))
头高上下限:人体高的 12%~32%。
Crop 死区:
d=max(48, 0.45×脸短边)
Lens 取样偏移(概念):
ix = lens_r - cx·zoom_x,再 clamp 到 [lens_d-scaled_w, 0]
掌握以上,即可从一段 cx_pts/cy_pts 反推成片窗口每一帧的几何来源。
十三、锚点函数对照
本项目里几条函数决定「跟哪一点」与「圆镜采哪一块」:
- 圆镜轨迹纵向取人脸 / 由人脸框推圆镜 cy:轨迹 cy 偏上;
- Crop 装人窗锚定(人体框软约束 + 已平滑跟拍点);
- 圆镜叠加原点计算(见上文代码);
- 成片跟拍:zoom_x = lens_d / crop_w(及 zoom_y 对称)。
权重含义:轨迹更跟「已平滑的跟拍点」,人体框只做软约束,避免单帧检测框把窗猛拽。
源帧 → 输出 25:做源帧索引映射到 25fps(非法 fps 按 25)。
再算一遍 push-in 锚点
脸 y=200,h=120:
例如:人体框顶在 140、身高 360 时,跟拍中心可取约 140+252=392(偏上半身)。
相对纯脸心 260,锚点下移约 132 px——推进变焦时胸口以上更稳地留在画面里。
十四、Lens 有效窗与尾 hold
离线 Pass 写出圆镜有效截止帧与圆镜结束原因。轨迹点构建止于该帧(含片尾保持秒数,出画原因可几乎为 0)。因此:
- 扫描期 Kalman 仍可能在后段给出框;
- 成片圆镜却在有效截止后停用(enable 关掉)。
这是几何层与跟踪层的第二次分工:跟踪尽量不断,镜头有意识结束,避免跟错人或跟到空镜。
