17、Camera 视频防抖:VideoStabilizationMode、OIS/EIS区别、防抖对FOV的影响、防抖开启后的裁剪处理
视频防抖,说白了就是让画面“稳”下来。
你拿着手机边走边拍,画面晃得像地震,这时候防抖就派上用场了。我这些年调过的防抖方案,从OIS到EIS,再到两者混合,踩过的坑真不少。今天咱们就把这块彻底聊透。
17.1 防抖的两大流派:OIS 与 EIS
先搞清楚两个概念:OIS(光学防抖)和 EIS(电子防抖)。
OIS(Optical Image Stabilization),是硬件层面的。镜头模组里有个磁悬浮结构,能根据陀螺仪数据实时移动镜头,补偿手抖。好处是物理层面就稳了,不影响画质。坏处是——贵,而且只能补偿低频抖动。
EIS(Electronic Image Stabilization),是软件层面的。通过裁剪画面、分析帧间运动,用算法把画面“拉”回来。好处是成本低,所有SoC都能做。坏处是——会裁剪画面,FOV会变小。
我个人的习惯是:旗舰机上用OIS+EIS混合方案,中低端机用纯EIS。为什么?因为OIS能兜住低频抖动,EIS处理高频残留,两者互补,效果最好。
核心区别总结:
- OIS:硬件补偿,不损失画质,不裁剪FOV,但成本高
- EIS:软件补偿,会裁剪FOV,依赖算法,成本低
- 混合方案:OIS兜底 + EIS精修,目前旗舰标配
17.2 VideoStabilizationMode 枚举
在Android Camera2 API里,防抖模式通过 CONTROL_VIDEO_STABILIZATION_MODE 控制。这个枚举值不多,但每个都有讲究。
| 模式 | 值 | 说明 |
|---|---|---|
| OFF | 0 | 关闭视频防抖 |
| ON | 1 | 开启EIS(电子防抖) |
| PREVIEW_STABILIZATION | 2 | 预览流防抖(Android 12+) |
嗯,这里要注意:ON 模式只代表“开启EIS”,不代表OIS。OIS的控制在 CONTROL_OIS_MODE 里,两者是独立的。
我曾经遇到过一个坑:某款手机开启了 VIDEO_STABILIZATION_MODE_ON,但预览画面依然抖得厉害。查了半天才发现,OIS没开,纯靠EIS扛不住大抖动。后来我建议他们同时开启OIS,效果立竿见影。
我的建议:
如果设备支持OIS,一定要同时开启 CONTROL_OIS_MODE 为 OPTIMIZE。别只依赖EIS,否则大抖动场景下画面会崩。
17.3 防抖对FOV的影响
这是很多人忽略的点。EIS开启后,FOV会变小。为什么?
你想想看,EIS需要“裁剪”画面边缘作为缓冲区域。当检测到抖动时,算法会移动这个裁剪窗口,把画面“拉”回来。这个缓冲区域越大,防抖效果越好,但FOV损失也越大。
我实测过一组数据:
| EIS裁剪比例 | FOV损失 | 防抖效果 |
|---|---|---|
| 5% | 约3° | 轻微 |
| 10% | 约6° | 中等 |
| 15% | 约9° | 较强 |
| 20% | 约12° | 极强 |
说白了,防抖和FOV是跷跷板。你要画面稳,就得牺牲视角。你要广角,就得接受抖动。
我在项目中遇到过产品经理要求“既要防抖好,又要FOV不损失”的情况。我只能告诉他:物理定律摆在那,要么加OIS,要么接受裁剪。最后他们选了OIS+EIS混合方案,FOV损失控制在5%以内,效果能接受。
17.4 防抖开启后的裁剪处理
EIS的裁剪处理,核心流程是这样的:
- 陀螺仪数据采集:每帧都记录手机的姿态变化
- 运动估计:计算帧与帧之间的位移量
- 裁剪窗口计算:根据位移量,动态调整裁剪区域
- 图像重采样:将裁剪后的图像缩放回原始分辨率
- 输出稳定帧:最终输出给编码器
这里面最核心的是第三步——裁剪窗口的计算。算法需要预测未来几帧的运动趋势,提前调整窗口位置,否则会出现“滞后感”。
我给你们看一段伪代码,理解一下这个逻辑:
// 伪代码:EIS裁剪窗口计算
void calculateCropWindow(Frame currentFrame, GyroData gyro) {
// 1. 获取当前帧的陀螺仪数据
float pitch = gyro.pitch;
float yaw = gyro.yaw;
float roll = gyro.roll;
// 2. 计算运动补偿量
float deltaX = yaw * FOCAL_LENGTH;
float deltaY = pitch * FOCAL_LENGTH;
// 3. 应用低通滤波,防止突变
deltaX = lowPassFilter(deltaX, prevDeltaX);
deltaY = lowPassFilter(deltaY, prevDeltaY);
// 4. 计算裁剪窗口(保留10%缓冲区域)
int cropWidth = sensorWidth * 0.9f;
int cropHeight = sensorHeight * 0.9f;
int cropX = (sensorWidth - cropWidth) / 2 + deltaX;
int cropY = (sensorHeight - cropHeight) / 2 + deltaY;
// 5. 限制裁剪窗口不超出传感器边界
cropX = clamp(cropX, 0, sensorWidth - cropWidth);
cropY = clamp(cropY, 0, sensorHeight - cropHeight);
// 6. 输出裁剪参数
setCropRegion(cropX, cropY, cropWidth, cropHeight);
}
这段代码看着简单,但实际调优时很痛苦。低通滤波的系数、缓冲区域的大小、运动预测的算法,每个参数都影响最终效果。
注意:
裁剪窗口的计算必须在每一帧的VSYNC中断内完成,否则会出现帧率抖动。我曾经因为裁剪计算耗时过长,导致预览帧率从30fps掉到24fps,用户反馈“画面卡卡的”。后来优化了算法,把计算量压到1ms以内才解决。
17.5 知识体系总览
我把本章的核心逻辑画成了一张图,方便你理解整个防抖体系:
17.6 实际调优经验
最后分享几个我在项目中积累的调优经验:
- 陀螺仪采样率要够:至少200Hz,否则运动估计不准。我见过用100Hz的,画面像抽风一样。
- 裁剪缓冲区域不要固定:动态调整,抖动大时扩大缓冲,抖动小时缩小,能平衡FOV和防抖效果。
- OIS和EIS的协同:OIS负责低频(0-5Hz),EIS负责高频(5-20Hz),中间有重叠区域需要做融合滤波。
- 预览和录制分开处理:预览流可以降低防抖强度以保持低延迟,录制流可以加强防抖以追求画质稳定。
避坑指南:
我曾经在某个项目里,把EIS的裁剪缓冲设成了20%,结果FOV损失太大,用户投诉“视角变窄了”。后来改成动态缓冲,平时10%,抖动大时15%,用户反馈好多了。所以,别一刀切,要根据场景动态调整。
好了,视频防抖这块就聊到这。记住:OIS是硬件兜底,EIS是软件精修,两者配合才能出好效果。FOV的损失是不可避免的,但可以通过动态裁剪来优化。裁剪处理的核心是运动估计和窗口计算,这块的算法调优决定了最终体验。