双目拼接亮度差优化

一、亮度差消除优化点

  • 优化线性自适应步长逻辑,解决 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);
    }
}
*/
相关推荐
大熊背21 小时前
树莓派 libcamera IPA 框架解析
linux·ipa·isppipeline
大熊背6 天前
树莓派IspPipeline LSC模块原理详解
算法·lsc·isppipeline·mesh lsc
大熊背8 天前
树莓派相机自动白平衡详解(四)
算法·树莓派·白平衡·isppipeline
大熊背11 天前
树莓派相机自动白平衡详解(二)
算法·白平衡·isppipeline
大熊背3 个月前
双目拼接竖缝消除(ISP 分区锐化实操方案) 优化方案
人工智能·算法·双目拼接
大熊背3 个月前
安防摄像头 白天 ↔ 全彩 模式切换逻辑设计
软光敏·isppipeline
大熊背4 个月前
ISP Pipeline中Lv实现方式探究之七--lv值计算框架final_version
自动曝光·对数函数·定点计算·isppipeline
大熊背4 个月前
Serial over TCP实现原理
网络·tcp·isppipeline
大熊背5 个月前
如何利用Lv值实现三级降帧
算法·自动曝光·lv·isppipeline