#include <stdint.h>
//===================== 量产可调宏配置 =====================
// 直方图亮度过滤阈值
#define Y_VALID_MIN 16
#define Y_VALID_MAX 248
#define Y_RELAX_MIN 8
#define Y_RELAX_MAX 252
// Offset自适应线性步长核心配置(解决原MAX_STEP=4收敛弱问题)
#define HYSTERESIS_THR 2 // 滞回死区,差值小于阈值不更新
#define OFFSET_MIN_STEP 1 // 单帧最小调整幅度,保证不会无限微调整
#define OFFSET_MAX_STEP 8 // 放大最大步长,提升大差异场景收敛速度
#define OFFSET_LINEAR_MAX_DIFF 30 // 线性映射差值区间,差值≥30直接使用MAX_STEP
#define OFFSET_MIN -40 // Offset整体上下限同步放大匹配大步长
#define OFFSET_MAX 40
#define OFFSET_FILTER_SHIFT 4 // 低通系数 15/16,抑制晃动闪烁
// Ygain固定1.0,全程不做动态更新
#define Q8_BASE 256
#define FIX_YGAIN_Q8 Q8_BASE
// 运动、无效帧配置
#define MOTION_VAR_THR 1500
#define INVALID_FADE_STEP 1
//=========================================================
// 全局状态缓存
static int16_t g_last_offset = 0;
static uint8_t g_reg_update_cnt = 0;
static uint8_t g_last_valid_master = 128;
static uint8_t g_last_valid_slave = 128;
static uint16_t g_master_invalid_cnt= 0;
static uint16_t g_slave_invalid_cnt = 0;
/**
* @brief 线性自适应步长插值函数
* 差值越小步长越小、差值越大步长线性放大;差值超过阈值直接满步长
* 解决原MAX_STEP=4收敛力度不足问题
*/
static int32_t GetAdaptiveStep_Linear(int32_t diff_raw)
{
int32_t abs_diff = (diff_raw >= 0) ? diff_raw : -diff_raw;
// 超出死区的有效差值区间
int32_t range_diff = abs_diff - HYSTERESIS_THR;
if (range_diff < 0)
range_diff = 0;
// 线性映射:range_diff [0 ~ OFFSET_LINEAR_MAX_DIFF] → step [MIN_STEP ~ MAX_STEP]
int32_t step_range = OFFSET_MAX_STEP - OFFSET_MIN_STEP;
int32_t step = OFFSET_MIN_STEP + (range_diff * step_range) / OFFSET_LINEAR_MAX_DIFF;
// 限制最大步长
if (step > OFFSET_MAX_STEP)
step = OFFSET_MAX_STEP;
// 恢复正负符号
if (diff_raw < 0)
step = -step;
// 防过冲:单次调整不超过真实差值,避免来回震荡
if (abs_diff < abs(step))
step = diff_raw;
return step;
}
/**
* @brief 直方图转有效平均亮度
* 两级阈值筛选 + 连续无效帧缓慢向128衰减兜底
*/
static uint8_t Hist2Luma(uint32_t y_hist[256], uint32_t hist_pixel_cnt,
uint8_t *last_valid, uint16_t *invalid_cnt)
{
// 内部计算指定阈值区间均值,返回-1代表无有效像素
auto InnerCalc = [&](uint8_t ymin, uint8_t ymax)->int32_t
{
uint32_t sum = 0;
uint32_t cnt = 0;
for (int y = ymin; y <= ymax; y++)
{
sum += (uint32_t)y * y_hist[y];
cnt += y_hist[y];
}
if (cnt == 0)
return -1;
return sum / cnt;
};
// 1.标准严格区间 [16, 248]
int32_t mean_std = InnerCalc(Y_VALID_MIN, Y_VALID_MAX);
if (mean_std != -1)
{
*last_valid = (uint8_t)mean_std;
*invalid_cnt = 0;
return (uint8_t)mean_std;
}
// 2.标准区间无像素,放宽阈值二次统计 [8,252]
int32_t mean_relax = InnerCalc(Y_RELAX_MIN, Y_RELAX_MAX);
if (mean_relax != -1)
{
*last_valid = (uint8_t)mean_relax;
*invalid_cnt = 0;
return (uint8_t)mean_relax;
}
// 3.两级区间全部无有效像素,多帧缓慢衰减至128
(*invalid_cnt)++;
int32_t val = *last_valid;
if (val > 128)
val -= INVALID_FADE_STEP;
else if (val < 128)
val += INVALID_FADE_STEP;
return (uint8_t)val;
}
/**
* @brief 核心Offset计算逻辑
* 公式:Yout = Yin * FIX_YGAIN - Offset
* 内置:滞回死区 + 线性自适应变步长 + 一阶低通平滑 + 上下限钳位
*/
static int16_t CalcSmoothOffset(uint8_t Ym, uint8_t Ys)
{
// Ygain固定1.0,不再动态调整
int32_t Ys_gain = ((uint32_t)Ys * FIX_YGAIN_Q8) / Q8_BASE;
int32_t offset_raw = Ys_gain - Ym;
int32_t diff_raw = offset_raw - g_last_offset;
// 滞回死区:微小差值直接保持原值,消除高频微小抖动
if (diff_raw >= -HYSTERESIS_THR && diff_raw <= HYSTERESIS_THR)
{
return g_last_offset;
}
// 获取线性自适应调整步长
int32_t adaptive_delta = GetAdaptiveStep_Linear(diff_raw);
// 定点一阶低通滤波:old * 15/16 + new * 1/16,大幅抑制帧跳变
int32_t temp_off = g_last_offset + adaptive_delta;
temp_off = ((g_last_offset * 15) + temp_off) >> OFFSET_FILTER_SHIFT;
// Offset全局上下限保护
if (temp_off < OFFSET_MIN)
temp_off = OFFSET_MIN;
if (temp_off > OFFSET_MAX)
temp_off = OFFSET_MAX;
g_last_offset = (int16_t)temp_off;
return g_last_offset;
}
/**
* @brief 双目亮度对齐顶层入口,每帧调用
* 固定Ygain不更新,仅输出平滑后的Offset
* @return 1:需要写入ISP寄存器 0:暂不更新
*/
uint8_t BinocularLumaAlign(uint32_t master_hist[256], uint32_t slave_hist[256],
uint32_t master_pix, uint32_t slave_pix, uint32_t motion_var,
int16_t *out_offset)
{
// 1.分别计算两路稳健平均亮度,自带无效像素兜底衰减
uint8_t Ym = Hist2Luma(master_hist, master_pix, &g_last_valid_master, &g_master_invalid_cnt);
uint8_t Ys = Hist2Luma(slave_hist, slave_pix, &g_last_valid_slave, &g_slave_invalid_cnt);
// 双目专属兜底:单路连续多帧无有效像素,借用另一路亮度同步对齐
if (g_master_invalid_cnt > 3) Ym = Ys;
if (g_slave_invalid_cnt > 3) Ys = Ym;
// 2.线性自适应计算平滑Offset,Ygain全程固定1.0
*out_offset = CalcSmoothOffset(Ym, Ys);
// 3.寄存器刷新限速,晃动场景减少硬件改写频次,抑制闪烁
g_reg_update_cnt++;
uint8_t update_flag = 0;
uint8_t is_static = (motion_var < MOTION_VAR_THR) ? 1 : 0;
if (is_static)
{
// 静态3帧更新一次
if (g_reg_update_cnt >= 3)
{
g_reg_update_cnt = 0;
update_flag = 1;
}
}
else
{
if (motion_var > MOTION_VAR_THR * 3)
{
// 剧烈镜头晃动,暂停更新寄存器,防止大幅度明暗跳动
update_flag = 0;
}
else
{
// 轻微运动2帧更新一次
if (g_reg_update_cnt >= 2)
{
g_reg_update_cnt = 0;
update_flag = 1;
}
}
}
return update_flag;
}
//==================== 业务调用示例 ====================
/*
void FrameProcessTask(void)
{
uint32_t master_y_hist[256], slave_y_hist[256];
uint32_t m_pix_cnt, s_pix_cnt;
uint32_t frame_motion_variance;
int16_t send_offset;
// 读取ISP两路硬件直方图(硬件已过滤Y<16/Y>248)
ISP_ReadHistData(ISP_MASTER, master_y_hist, &m_pix_cnt);
ISP_ReadHistData(ISP_SLAVE, slave_y_hist, &s_pix_cnt);
frame_motion_variance = ISP_GetMotionVariance();
uint8_t need_update = BinocularLumaAlign(master_y_hist, slave_y_hist,
m_pix_cnt, s_pix_cnt, frame_motion_variance,
&send_offset);
if (need_update)
{
ISP_RegWrite(REG_YUVEFFECT_YGAIN, FIX_YGAIN_Q8);
ISP_RegWrite(REG_YUVEFFECT_OFFSET, send_offset);
}
}
*/