双目拼接亮度差优化

一、亮度差消除优化点

  • 优化线性自适应步长逻辑,解决 OFFSET_MAX_STEP=4 收敛慢、作用弱问题,提供可放大的量程配置;
  • 保留全部鲁棒逻辑:硬件直方图两级阈值统计、无有效像素多级兜底、双目互相借用亮度、滞回死区、线性插值自适应变步长、帧低通平滑、寄存器限速刷新;
  • 全整数无浮点,适配 ISP 嵌入式;
  • 线性插值区间拉大,最大步长可灵活配置(默认给到 8,兼顾收敛速度与防闪烁),同时保留最小步长、滞回约束。

二、代码

cpp 复制代码
#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);
    }
}
*/
相关推荐
大熊背2 个月前
双目拼接竖缝消除(ISP 分区锐化实操方案) 优化方案
人工智能·算法·双目拼接
大熊背2 个月前
安防摄像头 白天 ↔ 全彩 模式切换逻辑设计
软光敏·isppipeline
大熊背3 个月前
ISP Pipeline中Lv实现方式探究之七--lv值计算框架final_version
自动曝光·对数函数·定点计算·isppipeline
大熊背3 个月前
Serial over TCP实现原理
网络·tcp·isppipeline
大熊背3 个月前
如何利用Lv值实现三级降帧
算法·自动曝光·lv·isppipeline
大熊背3 个月前
ISP Pipeline中Lv实现方式探究之三--lv计算定点实现
数据结构·算法·自动曝光·lv·isppipeline
大熊背3 个月前
ISP Pipeline中Lv实现方式探究之二
自动白平衡·自动曝光·lv·isppipeline·bv
大熊背3 个月前
ISP中Lv和ISO系统并存的意义
自动曝光·iso·lv·isppipeline
大熊背3 个月前
根据ISP各个ISO节点标定后的参数,如何插值生成当前增益下对应的ISP参数
iso·isppipeline·isp插值·log域