畅联云平台丨08-路人头部椭圆虚化

路人头部椭圆虚化:观测、排主角、椭圆 mask 与一次 FFmpeg 合成
本文讲清:非主角脸如何变成有限条 椭圆虚化轨迹,再经 crop → boxblur → geq(alpha) → overlay 叠回成片,且与 Lens/Crop 合并进同一滤镜图,与跟拍同一遍转码完成,避免二次解码。

图:轴对齐椭圆半轴由脸框与 pad 决定;外为清晰,内为模糊。
一、观测从哪来:other_face_obs
扫描阶段,在人体检测与 ByteTrack 之外,收集 非主角脸观测:
写入路径包括:
- 检测帧上,对非主角人体 ROI 跑 SCRFD(for_stranger_blur=true,ROI 阈值放宽);
- 离线从 ByteTrack 历史回填;
- 再剥一次主角;
- 丢掉闪现。
最小脸尺寸与占比:短边至少约 16 px,且不超过画幅短边约 26%。
连续帧间隙容忍与检测步长挂钩:
stride=3 时,隔帧观测仍可串成一条路人轨。
二、排除主角:三道闸

图:任一闸命中则该观测不得进入虚化轨迹。
2.1 track_id
若 byte_track_id == hero_byte_track_id,直接排除。这是最干净的信号。
2.2 thumb 相似度
锁定阈值常见为 0.32。
轨迹级:若多点 拇指图相似度 达到「像主角」比例,整轨丢弃。
2.3 几何 IoU / 重叠
输出空间椭圆与主角保护区 IoU 过高 → 丢弃该关键点。
源空间也有类 似保护。
策略:宁可漏糊路人,不可误伤主角。
三、椭圆参数:半轴与 pad≈1.08
脸框 (x,y,w,h) → 输出椭圆,默认 face_pad ≈ 1.08:
椭圆半轴:a ≈ 脸宽/2×1.08,b ≈ 脸高/2×1.08。
相对轴对齐脸框,面积放大 1.08²≈1.17(约 +17%),盖住发际与下巴外缘。
椭圆方程(patch 局部坐标,圆心在 (a,b)):
v = ((X-a)/a)² + ((Y-b)/b)²
- v ≤ 1:完全在椭圆内;
- 1 < v ≤ 1+f:羽化带;
- 其它:透明。
数值例子
脸 80×100,pad=1.08:
外接矩形约 86×108。高分检测(det_score ≥ 0.50)还会再外扩(cover expand),再乘 margin=1.05,确保糊区盖住源脸。
由人体估头时,宽向也用 1.08:
与虚化 pad 同数量级,语义一致:「头比脸略宽」。
四、聚类、排序与「约 4 条」
按时空邻近串轨;再排序取显著轨。
硬上限 32。成片滤镜每个椭圆要 split + crop + boxblur + geq + overlay,节点线性膨胀。实务经验与科普约定:同时服务约 4 条显著路人轨 已够店内/合影场景;更短的路过会被排序挤掉。
最短连续:min_consec=2,避免单帧闪糊。
尾部 hold:离开后继续糊约 tail_hold=6 源帧(≈0.24 s @25),防止人刚出框椭圆瞬间消失。
五、中心平滑与半轴稳定
路人轨迹高斯 σ:Crop 约 0.18 s,Lens 约 0.12 s(稠密观测更短)。
半轴若帧间跳变超过半轴跳变切断比例(稀疏 0.15 / 稠密 0.28),切断为新 segment,避免一个巨大椭圆跨剧变。
实务技巧:segment 内对 semi 取较大稳定值,中心仍跟随——消除「呼吸感」。
六、 Alpha 合成公式

图:椭圆内用模糊图,外保留清晰图,边缘羽化过渡。
逐像素 alpha∈[0,1] 由椭圆 geq 给出(存成 0~255 再归一化)。
羽化宽度 feather_px(默认常为 1)。本项目实现:
std::string ellipse_alpha_geq_expr(int semi_a, int semi_b, int feather_px) {
const std::string v = "((X-a)*(X-a)/a2+(Y-b)*(Y-b)/b2)"; // conceptual
if (feather_px <= 0) return "if(lte(" + v + ",1),255,0)";
return "if(lte(v,1),255, if(lte(v,1+f), floor(255*(1+f-v)/f), 0))";
}七、FFmpeg 滤镜串:boxblur + geq + overlay
追加路人虚化滤镜(与滤镜构建同构)逐步:
- split:一路做补丁,一路保持清晰底;
- crop:按椭圆外接矩形从当前位置裁出;
- boxblur:用 boxblur 滤镜表达式(半径受限时链式多次近似更强糊);
- geq:写椭圆 alpha;
- overlay:贴回,enable 限制活跃帧区间。
中心用分段线性:
任意输出帧的中心,由关键点序列做分段线性插值得到。
点列常关键点抽稀到约 24 点。
数值例子:算力粗估
1080p25,4 条椭圆,各平均半轴 50→补丁约 100×100:
- 每帧 4 次小图 boxblur + overlay,远小于全图糊;
- 滤镜图节点约 4×(split+crop+blur+geq+overlay)≈20+ 个标签。
若强行上 20 条长轨,字符串 与图编译时间会明显变差——这是「约 4 条」的工程动机。
八、与 Lens/Crop 合并为一遍
在 lens/crop 滤镜末尾:若有路人轨则先 format=yuv420p[vfocus],再串 stranger blur,最终 [vout]。
即:跟拍几何与路人虚化同一滤镜图、一次解码一次编码。
合并出片若分步,也可能先 focus 再 blur 独立转码;优先路径是 merged one-pass。
映射注意:路人椭圆必须变到 输出坐标系。Lens 下要乘 zoom 并减 overlay 偏移;Crop 下要减窗原点再乘缩放。专门做这件事——在源坐标糊一发再拉伸,椭圆会错位。
九、覆盖率与丢弃
椭圆落到输出后,与有效画布(或 crop 内框)交集比例过低则丢点,避免糊在黑边/画外。
高分检测强制扩椭圆:det_score >= 0.50 时 cover expand(margin 1.05)。
十、GPU 路径对照
用同一套按帧取椭圆轨迹取圆心半轴,在 CUDA 上做 ROI blur + feather mask。参数语义与 FFmpeg 一致:radius / power / feather。调试可用叠加视频画椭圆轨迹验证。
十一、参数表

十二、 端到端心算例
场景:1280×720@25,crop 跟拍,路人脸源框 60×70,窗缩放后输出脸约 72×84,pad=1.08:
例如脸宽 72、脸高 84:半轴 a≈39、b≈45。
椭圆内像素约 πab≈5510。boxblur luma_radius=5、power=1,局部足够毁容级模糊;主角若 IoU 重叠被 guard 掉,则该关键点不入轨。
时间:观测在 frame 100、103、106(stride=3),max_gap 与 stride 挂钩,可聚成一条;平滑 σ=0.18 s 后写入圆心点列;成片 n 从对应 25fps 索引启用到尾 hold。
十三、公式卡
椭圆:
((X−cx)/a)² + ((Y−cy)/b)² ≤ 1
pad:
半轴公式:a=脸宽/2×pad,b=脸高/2×pad,pad≈1.08。
合成:椭圆内取 blur,外取 sharp,羽化带线性过渡。
排除主角(逻辑或):
排除主角:跟踪号相同,或相似度够高,或与主角框重叠过大——任一成立就不虚化。
合并:
把观测清洗、椭圆几何、滤镜串三件事分开调试,路人虚化问题通常一次就能定位到层。
十四、输出坐标映射:为何不能在源图糊完再拉伸
Lens 下源脸 (f_x,f_y,f_w,f_h) 到输出椭圆,核心步骤:
- 计算当前帧英雄圆心 (hero_cx, hero_cy) 与 zoom;
- 用与成片相同的圆镜叠加原点计算得到放大图层偏移;
- 把脸中心变到放大图层坐标,再变到圆镜 canvas,最后到输出 overlay 位置;
- 半轴乘 zoom * pad。
Crop 下:
若同时做了裁切/缩放,椭圆中心与半轴都要变换到最终输出坐标系。
若错误地「源图椭圆 → 整帧 scale」,窗在移动时路人会相对主角滑动,出现「糊斑追错人」。因此构建时就按输出 n 采样映射,写入的圆心点列已是输出像素。
