037、人脸AE与场景识别:基于人脸检测的曝光补偿与背光场景优化 037、人脸AE与场景识别基于人脸检测的曝光补偿与背光场景优化一、一个让我加班到凌晨三点的bug2019年某款旗舰机的前摄调试我至今记忆犹新。用户反馈在逆光自拍时人脸要么过曝成“白板”要么欠曝成“黑炭”。我们当时的AE策略很简单检测到人脸就把人脸区域作为ROI做曝光权重。结果呢实验室里一切正常一到真实场景就翻车。后来抓了上百组log才发现问题当人脸处于强背光时人脸区域的亮度可能只有背景的1/10AE算法拼命提亮人脸结果背景完全过曝人脸虽然亮了但噪点爆炸肤色失真。更坑的是某些场景下人脸检测框抖动导致AE在亮暗之间来回跳变画面像在“呼吸”。这个案例让我意识到人脸AE不是简单的“检测到人脸就加权”这么简单它需要一套完整的场景识别与曝光补偿策略。二、人脸AE的核心矛盾局部与全局的博弈传统AE算法基于全局亮度统计用直方图或平均亮度计算目标曝光。人脸AE引入后问题变成了如何在保证人脸曝光准确的同时不破坏整体画面的观感这里有个关键参数——人脸曝光权重。权重设高了人脸准了但背景崩了权重设低了人脸又拉不回来。我见过不少工程师直接给100%权重结果就是上面说的翻车现场。实际调试中我通常把权重控制在30%-70%之间具体取决于场景。但光有权重不够你得先判断当前是什么场景。三、场景识别背光、顺光、侧光与混合光人脸AE的第一步不是调曝光而是识别光照场景。我常用的判断逻辑// 场景判断伪代码 - 别直接复制要根据sensor特性调阈值intdetect_light_scene(face_roi*roi,frame_stats*stats){floatface_lumaget_face_avg_luma(roi);// 人脸平均亮度floatbg_lumaget_bg_avg_luma(stats,roi);// 背景平均亮度排除人脸区域floatface_bg_ratioface_luma/(bg_luma0.01);// 这里加0.01防除零踩过坑的都知道if(face_bg_ratio0.3){// 背光场景人脸亮度远低于背景// 这里踩过坑0.3这个阈值在暗光下要调低否则误判returnSCENE_BACKLIGHT;}elseif(face_bg_ratio2.0){// 顺光场景人脸比背景亮比如舞台聚光灯returnSCENE_FRONTLIGHT;}else{// 正常或侧光场景returnSCENE_NORMAL;}}这个判断看似简单实际坑很多。比如当人脸和背景都在暗光下时face_bg_ratio可能接近1.0但实际是暗光场景而非正常场景。所以我会再加一个全局亮度判断if(stats-global_luma30face_bg_ratio0.5){// 全局暗光不要误判为正常场景returnSCENE_LOWLIGHT;}四、背光场景的曝光补偿策略背光是人脸AE最头疼的场景。我的策略分三步走第一步强制提升人脸亮度但设定上限不要试图把人脸拉到18%灰标准中性灰背光下强行拉亮会导致噪点爆炸。我一般设定目标亮度为正常场景的70%-80%。比如正常场景人脸目标亮度是1200-255范围背光下设为90-100就够了。第二步动态调整曝光时间与增益的配比背光场景通常需要更长曝光时间或更高增益。这里有个取舍曝光时间长了人脸会糊尤其是小孩或运动场景增益高了噪点多。我常用的策略是先拉曝光时间到安全上限比如1/30s如果还不够再拉增益。// 背光场景曝光参数计算 - 注释写清楚免得后来人看不懂voidcalc_backlight_exposure(face_info*face,ae_params*params){floattarget_luma90;// 背光目标亮度比正常低floatcurrent_lumaface-avg_luma;floatgain_ratiotarget_luma/(current_luma0.01);// 限制最大增益倍数别这样写死要根据sensor能力来constfloatMAX_GAIN4.0;if(gain_ratioMAX_GAIN){// 增益不够拉曝光时间params-exposure_time*(gain_ratio/MAX_GAIN);params-gainMAX_GAIN;}else{params-gain*gain_ratio;}// 这里踩过坑曝光时间不能超过帧率的限制否则掉帧if(params-exposure_timeMAX_EXPOSURE_TIME){params-exposure_timeMAX_EXPOSURE_TIME;// 提示这种情况下只能接受人脸欠曝或者开启HDR}}第三步引入局部对比度增强单纯调曝光不够背光下的人脸即使亮度拉上去了对比度也偏低看起来“灰蒙蒙”的。我通常在ISP的gamma或LTM局部色调映射模块做手脚在人脸区域提升对比度背景区域压低对比度避免过曝。五、人脸检测框抖动问题这是另一个让人抓狂的问题。人脸检测框在帧与帧之间抖动导致AE参数也跟着抖。解决方案方案一时间域滤波对检测框坐标做低通滤波比如用一阶IIR// 人脸框平滑 - 别用太强的滤波否则跟踪延迟staticface_rect smooth_rect{0};floatalpha0.3;// 平滑系数越大越平滑但延迟越大smooth_rect.xalpha*smooth_rect.x(1-alpha)*current_rect.x;smooth_rect.yalpha*smooth_rect.y(1-alpha)*current_rect.y;smooth_rect.walpha*smooth_rect.w(1-alpha)*current_rect.w;smooth_rect.halpha*smooth_rect.h(1-alpha)*current_rect.h;方案二扩大ROI区域不要只用人脸框作为ROI而是向外扩展20%-30%。这样即使检测框抖动ROI内的亮度统计也相对稳定。方案三锁定曝光参数当检测到人脸且场景稳定时锁定AE参数一段时间比如0.5秒避免频繁调整。这个策略在视频录制中尤其重要。六、多张人脸场景的处理自拍杆合影、多人会议场景多张人脸同时出现。策略很简单取所有人脸亮度的加权平均权重可以按人脸大小或置信度分配。但有个特殊情况如果一张脸在背光另一张在顺光怎么办我倾向于优先照顾背光的那张脸因为顺光那张本身已经够亮了再亮也不会太差。但如果两张脸亮度差异超过3档那就只能取舍了——这时候可以提示用户“请调整位置”。七、与HDR的联动背光场景的最佳搭档是HDR。当检测到背光且人脸与背景亮度差超过一定阈值时自动开启HDR模式。HDR的曝光策略也要调整短帧曝光用于背景长帧曝光用于人脸。具体来说短帧的曝光时间按背景亮度计算长帧的曝光时间按人脸亮度计算。这样合成后的画面人脸和背景都能看清。// HDR模式下的人脸AE - 这里踩过坑别把长短帧搞反了voidcalc_hdr_exposure_for_face(face_info*face,frame_stats*stats){// 短帧保证背景不过曝floatshort_target180;// 背景目标亮度稍微欠曝一点保留细节floatshort_gainshort_target/(stats-bg_luma0.01);// 长帧保证人脸够亮floatlong_target100;// 人脸目标亮度floatlong_gainlong_target/(face-avg_luma0.01);// 限制长短帧的曝光比别这样写死要根据HDR能力来constfloatMAX_HDR_RATIO16.0;if(long_gain/short_gainMAX_HDR_RATIO){// 差异太大HDR也救不了需要补光// 提示可以开启屏幕补光或闪光灯}}八、实战经验总结不要迷信人脸检测检测到人脸不等于AE就解决了。人脸检测的置信度、大小、位置都要纳入考量。比如一张侧脸或部分遮挡的脸权重应该降低。场景切换要平滑从背光场景切换到顺光场景曝光参数不要突变。我一般用3-5帧做渐变过渡避免画面闪烁。预留手动调节空间自动曝光再智能也架不住用户有特殊需求。在相机APP中保留曝光补偿滑块让用户可以±2档调节。不同sensor差异巨大同样的算法在IMX586上跑得好换到OV64B可能就翻车。因为不同sensor的动态范围、噪声特性、响应曲线都不一样。每次换sensor都要重新标定参数。测试场景要覆盖全室内背光窗户前、室外逆光夕阳下、混合光源舞台灯光环境光、暗光背光夜晚路灯下……每个场景都要实测别只依赖实验室的灯箱。log要打全调试阶段把场景判断结果、人脸亮度、背景亮度、最终曝光参数都打出来。我习惯用“TAG_FACE_AE”作为log标签方便过滤。没有log出了问题只能靠猜。最后说一句人脸AE没有银弹。每个场景、每颗sensor、每款镜头都有自己的脾气。调试的本质是理解这些脾气的规律然后找到那个“够用”的平衡点。别追求完美追求“用户不骂娘”就够了。