ISP模块参数统一对外接口插值逻辑

ISP Pipeline中Lv实现方式探究之一

ISP Pipeline中Lv实现方式探究之二

ISP Pipeline中Lv实现方式探究之三--lv计算定点实现

ISP Pipeline中Lv实现方式探究之四----正LV值定点实现

ISP Pipeline中Lv实现方式探究之五--lv值计算框架优化

ISP Pipeline中Lv实现方式探究之七--lv值计算框架final_version

根据ISP各个ISO节点标定后的参数,如何插值生成当前增益下对应的ISP参数

上述的博文讲解了如何利用增益进行ISP参数的插值基本原理。这篇博文重点讲解如何进行ISP插值具体方法。

目录

一、示例代码实现

二、文件结构总览

[三、isp_interp.h 头文件逐段解析](#三、isp_interp.h 头文件逐段解析)

[四、isp_interp.c 实现层逐段解析](#四、isp_interp.c 实现层逐段解析)

五、业务层解析

六、标定初始化代码解析

七、整体架构分层总结

八、核心优化点复盘


一、示例代码实现

  1. isp_interp.h 头文件
cpp 复制代码
#ifndef __ISP_INTERP_H__
#define __ISP_INTERP_H__

#include "isp_param.h"
#include <stdint.h>
#include <string.h>

/************************ 定点Q格式可配置宏 ************************/
#define ISP_Q_SHIFT         12
#define ISP_Q_ONE           (1U << ISP_Q_SHIFT)
#define ISP_Q_HALF          (ISP_Q_ONE >> 1)

typedef int64_t  ISP_LONG;
typedef uint64_t ISP_ULONG;

/************************ 分层错误码 ************************/
typedef enum {
    ISP_INTERP_OK                = 0,
    ISP_INTERP_ERR_NULL_OUT     = -1,  // 输出缓存为空
    ISP_INTERP_ERR_INVALID_CMD  = -2,  // 非法算法指令
    ISP_INTERP_ERR_NO_CAL_DATA  = -3,  // 无标定增益/参数池
    ISP_INTERP_ERR_CAL_CNT_LT2  = -4,  // 标定档位不足2档无法插值
    ISP_INTERP_ERR_CB_FAIL      = -5,  // 模块插值回调失败
    ISP_INTERP_ERR_GAIN_ORDER   = -6,  // 标定增益未升序,二分失效
} ISP_INTERP_ERR_E;

/************************ 插值状态枚举 ************************/
typedef enum {
    ISP_INTERP_CLAMP_MIN = 0,
    ISP_INTERP_CLAMP_MAX,
    ISP_INTERP_EXACT_MATCH,
    ISP_INTERP_LINEAR_CALC
} ISP_INTERP_ST_E;

/************************ 日志开关 量产注释关闭printf ************************/
#define ISP_INTERP_LOG_EN    1
#if ISP_INTERP_LOG_EN
#include <stdio.h>
#define ISP_LOG(fmt, ...)    printf("[ISP_INTERP]%s:%d " fmt "\r\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define ISP_LOG(fmt, ...)    do{}while(0)
#endif

/************************ 标定表头:所有ISP模块通用 ************************/
typedef struct {
    int32_t  cal_cnt;               // 标定档位总数
    uint32_t* p_gain_arr;           // 升序ISO增益数组
    uint8_t*  p_param_pool;         // 连续内存存储cal_cnt组完整参数
    uint32_t  param_single_size;    // 单组参数结构体字节大小
    uint8_t   enable_interp;        // 1=开启增益插值,0=直接拷贝标定参数
    uint8_t   gain_sorted_ok;       // 初始化校验:增益升序标记
} ISP_CAL_HEAD_T;

/************************ 统一插值回调函数原型(所有模块共用接口) ************************/
typedef ISP_INTERP_ERR_E (*ISP_INTERP_CB)(
    void*        p_out,
    const void*  p_cal_low,
    const void*  p_cal_high,
    uint32_t     alpha_q
);

/************************ cmd映射表项:下标=LIB_ALGSET_XXX指令码 ************************/
#define LIB_ALG_CMD_MAX    32
typedef struct {
    ISP_CAL_HEAD_T* p_cal_head;
    ISP_INTERP_CB   interp_cb;
} ISP_CMD_INTERP_ITEM_T;
extern const ISP_CMD_INTERP_ITEM_T g_isp_interp_reg[LIB_ALG_CMD_MAX];

/************************ 全局插值缓存联合体(容纳全部ISP模块参数) ************************/
typedef union {
    ISP_LSC_ATTR        lsc;
    ISP_DRC_REG         drc;
    ISP_RGB_GAMMA       rgb_gamma;
    ISP_YUV_GAMMA_LUT   yuv_gamma_lut;
    ISP_DPC_ATTR        dpc;
} ISP_INTERP_UNION_BUF_T;
extern ISP_INTERP_UNION_BUF_T g_interp_global_buf;

/************************ 对外唯一统一插值入口 ************************/
ISP_INTERP_ERR_E isp_get_unified_interp_param(
    int32_t cmd,
    uint32_t cur_gain,
    void*    p_out_buf,
    uint32_t out_buf_max_size
);

/************************ 工具函数声明 ************************/
static inline int32_t  isp_scalar_linear_interp(int32_t val0, int32_t val1, uint32_t alpha_q);
static inline uint32_t isp_calc_alpha_q(uint32_t g0, uint32_t g1, uint32_t cur);
static void isp_safe_param_copy(void* dst, const void* src, uint32_t copy_len, uint32_t buf_max_len);

#endif
  1. isp_interp.c 完整实现代码
cpp 复制代码
#include "isp_interp.h"

/************************ 全局标定实例声明(工程中外部初始化赋值) ************************/
extern ISP_CAL_HEAD_T g_lsc_cal_head;
extern ISP_CAL_HEAD_T g_drc_cal_head;
extern ISP_CAL_HEAD_T g_rgbgamma_cal_head;
extern ISP_CAL_HEAD_T g_yuvgamma_lut_cal_head;
extern ISP_CAL_HEAD_T g_dpc_cal_head;

/************************ 模块插值回调前置声明 ************************/
static ISP_INTERP_ERR_E interp_lsc_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q);
static ISP_INTERP_ERR_E interp_drc_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q);
static ISP_INTERP_ERR_E interp_rgbgamma_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q);
static ISP_INTERP_ERR_E interp_yuvgamma_lut_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q);
static ISP_INTERP_ERR_E interp_dpc_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q);

/************************ 全局cmd注册表:数组下标 = libisp_set cmd指令码 O(1)查表 ************************/
const ISP_CMD_INTERP_ITEM_T g_isp_interp_reg[LIB_ALG_CMD_MAX] = {
    [LIB_ALGSET_LSC_ATTR] = {
        .p_cal_head = &g_lsc_cal_head,
        .interp_cb  = interp_lsc_cb
    },
    [LIB_ALGSET_DRC_ATTR] = {
        .p_cal_head = &g_drc_cal_head,
        .interp_cb  = interp_drc_cb
    },
    [LIB_ALGSET_RGBGAMMA_ATTR] = {
        .p_cal_head = &g_rgbgamma_cal_head,
        .interp_cb  = interp_rgbgamma_cb
    },
    [LIB_ALGSET_YUVGAMMALUT_ATTR] = {
        .p_cal_head = &g_yuvgamma_lut_cal_head,
        .interp_cb  = interp_yuvgamma_lut_cb
    },
    [LIB_ALGSET_DPC_ATTR] = {
        .p_cal_head = &g_dpc_cal_head,
        .interp_cb  = interp_dpc_cb
    },
    // 其余未使用cmd默认全0空
};

/************************ 全局插值缓存 单块内存无malloc ************************/
ISP_INTERP_UNION_BUF_T g_interp_global_buf;

/************************ 底层工具函数实现 ************************/
static inline int32_t isp_scalar_linear_interp(int32_t val0, int32_t val1, uint32_t alpha_q)
{
    ISP_LONG v0 = (ISP_LONG)val0 * (ISP_Q_ONE - alpha_q);
    ISP_LONG v1 = (ISP_LONG)val1 * alpha_q;
    ISP_LONG res = (v0 + v1) >> ISP_Q_SHIFT;

    // 溢出裁剪防止寄存器参数越界花屏
    if(res > INT32_MAX) return INT32_MAX;
    if(res < INT32_MIN) return INT32_MIN;
    return (int32_t)res;
}

static inline uint32_t isp_calc_alpha_q(uint32_t g0, uint32_t g1, uint32_t cur)
{
    ISP_ULONG delta_cur = cur - g0;
    ISP_ULONG delta_total = g1 - g0;
    return (uint32_t)((delta_cur * ISP_Q_ONE) / delta_total);
}

static void isp_safe_param_copy(void* dst, const void* src, uint32_t copy_len, uint32_t buf_max_len)
{
    uint32_t real_len = (copy_len > buf_max_len) ? buf_max_len : copy_len;
    memcpy(dst, src, real_len);
}

/************************ 二分查找增益区间 ************************/
static ISP_INTERP_ST_E isp_gain_binary_search(
    const ISP_CAL_HEAD_T* p_cal,
    uint32_t cur_gain,
    int32_t* p_out_left_idx
)
{
    uint32_t* gain_arr = p_cal->p_gain_arr;
    int32_t l = 0;
    int32_t r = p_cal->cal_cnt - 1;
    *p_out_left_idx = 0;

    if(cur_gain <= gain_arr[l])
    {
        ISP_LOG("gain %u clamp min, min_cal_gain=%u", cur_gain, gain_arr[l]);
        return ISP_INTERP_CLAMP_MIN;
    }
    if(cur_gain >= gain_arr[r])
    {
        *p_out_left_idx = r - 1;
        ISP_LOG("gain %u clamp max, max_cal_gain=%u", cur_gain, gain_arr[r]);
        return ISP_INTERP_CLAMP_MAX;
    }

    while(l <= r)
    {
        int32_t mid = (l + r) / 2;
        if(gain_arr[mid] == cur_gain)
        {
            *p_out_left_idx = mid;
            ISP_LOG("gain %u exact match cal index %d", cur_gain, mid);
            return ISP_INTERP_EXACT_MATCH;
        }
        else if(gain_arr[mid] < cur_gain)
            l = mid + 1;
        else
            r = mid - 1;
    }
    *p_out_left_idx = r;
    ISP_LOG("linear interp range [%u, %u], cur_gain=%u", gain_arr[r], gain_arr[l], cur_gain);
    return ISP_INTERP_LINEAR_CALC;
}

/************************ 统一插值对外主入口 ************************/
ISP_INTERP_ERR_E isp_get_unified_interp_param(
    int32_t cmd,
    uint32_t cur_gain,
    void*    p_out_buf,
    uint32_t out_buf_max_size
)
{
    // 1. 空指针拦截
    if(NULL == p_out_buf)
    {
        ISP_LOG("err: output buffer is NULL");
        return ISP_INTERP_ERR_NULL_OUT;
    }
    // 2. O(1)下标查表,无循环遍历
    if(cmd < 0 || cmd >= LIB_ALG_CMD_MAX)
    {
        ISP_LOG("err: invalid cmd %d, range[0,%d]", cmd, LIB_ALG_CMD_MAX-1);
        return ISP_INTERP_ERR_INVALID_CMD;
    }
    const ISP_CMD_INTERP_ITEM_T* p_reg_item = &g_isp_interp_reg[cmd];
    ISP_CAL_HEAD_T* p_cal = p_reg_item->p_cal_head;
    ISP_INTERP_CB p_cb = p_reg_item->interp_cb;

    // 3. 标定合法性校验
    if(NULL == p_cal || NULL == p_cb)
    {
        ISP_LOG("err: cmd %d no interp register", cmd);
        return ISP_INTERP_ERR_INVALID_CMD;
    }
    if(NULL == p_cal->p_gain_arr || NULL == p_cal->p_param_pool)
    {
        ISP_LOG("err: cmd %d missing cal gain or param pool", cmd);
        return ISP_INTERP_ERR_NO_CAL_DATA;
    }
    if(p_cal->cal_cnt < 2)
    {
        ISP_LOG("err: cmd %d cal cnt %d < 2 cannot interp", cmd, p_cal->cal_cnt);
        return ISP_INTERP_ERR_CAL_CNT_LT2;
    }
    if(!p_cal->gain_sorted_ok)
    {
        ISP_LOG("err: cmd %d cal gain array unsorted", cmd);
        return ISP_INTERP_ERR_GAIN_ORDER;
    }

    uint32_t param_size = p_cal->param_single_size;
    int32_t left_idx = 0;
    ISP_INTERP_ST_E interp_st = isp_gain_binary_search(p_cal, cur_gain, &left_idx);
    uint8_t* p_cal_low  = p_cal->p_param_pool + left_idx * param_size;
    uint8_t* p_cal_high = p_cal->p_param_pool + (left_idx + 1) * param_size;

    // 4. 分状态处理参数输出
    switch(interp_st)
    {
        case ISP_INTERP_CLAMP_MIN:
            isp_safe_param_copy(p_out_buf, p_cal->p_param_pool, param_size, out_buf_max_size);
            break;
        case ISP_INTERP_CLAMP_MAX:
            isp_safe_param_copy(p_out_buf, p_cal_high, param_size, out_buf_max_size);
            break;
        case ISP_INTERP_EXACT_MATCH:
            isp_safe_param_copy(p_out_buf, p_cal_low, param_size, out_buf_max_size);
            break;
        case ISP_INTERP_LINEAR_CALC:
        {
            if(!p_cal->enable_interp)
            {
                isp_safe_param_copy(p_out_buf, p_cal_low, param_size, out_buf_max_size);
                ISP_LOG("cmd %d interp disabled, use low cal param", cmd);
                break;
            }
            uint32_t alpha_q = isp_calc_alpha_q(p_cal->p_gain_arr[left_idx], p_cal->p_gain_arr[left_idx+1], cur_gain);
            ISP_LOG("alpha_q = %u / %d", alpha_q, ISP_Q_ONE);
            ISP_INTERP_ERR_E cb_ret = p_cb(p_out_buf, p_cal_low, p_cal_high, alpha_q);
            if(cb_ret != ISP_INTERP_OK)
            {
                ISP_LOG("cmd %d interp callback failed ret=%d", cmd, cb_ret);
                return ISP_INTERP_ERR_CB_FAIL;
            }
            break;
        }
        default:
            isp_safe_param_copy(p_out_buf, p_cal_low, param_size, out_buf_max_size);
            break;
    }
    ISP_LOG("cmd %d interp complete, gain=%u", cmd, cur_gain);
    return ISP_INTERP_OK;
}

/************************ 各ISP模块插值回调实现(统一入参接口) ************************/
// 1. LSC镜头阴影校正插值
static ISP_INTERP_ERR_E interp_lsc_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q)
{
    ISP_LSC_ATTR* p_o = (ISP_LSC_ATTR*)out;
    const ISP_LSC_ATTR* p0 = (const ISP_LSC_ATTR*)c0;
    const ISP_LSC_ATTR* p1 = (const ISP_LSC_ATTR*)c1;

    // 开关枚举阈值切换不插值
    p_o->lsc_en = (alpha_q > ISP_Q_HALF) ? p1->lsc_en : p0->lsc_en;
    p_o->blend_mode = (alpha_q > ISP_Q_HALF) ? p1->blend_mode : p0->blend_mode;

    // 标量参数定点插值
    p_o->global_gain = isp_scalar_linear_interp(p0->global_gain, p1->global_gain, alpha_q);
    p_o->shade_thresh = isp_scalar_linear_interp(p0->shade_thresh, p1->shade_thresh, alpha_q);

    // LSC网格二维数组逐点插值
    for(int32_t i = 0; i < LSC_GRID_TOTAL; i++)
    {
        p_o->r_grid[i] = isp_scalar_linear_interp(p0->r_grid[i], p1->r_grid[i], alpha_q);
        p_o->g_grid[i] = isp_scalar_linear_interp(p0->g_grid[i], p1->g_grid[i], alpha_q);
        p_o->b_grid[i] = isp_scalar_linear_interp(p0->b_grid[i], p1->b_grid[i], alpha_q);
    }
    return ISP_INTERP_OK;
}

// 2. DRC宽动态插值
static ISP_INTERP_ERR_E interp_drc_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q)
{
    ISP_DRC_REG* p_o = (ISP_DRC_REG*)out;
    const ISP_DRC_REG* p0 = (const ISP_DRC_REG*)c0;
    const ISP_DRC_REG* p1 = (const ISP_DRC_REG*)c1;

    p_o->drc_en = (alpha_q > ISP_Q_HALF) ? p1->drc_en : p0->drc_en;
    p_o->dark_strength = isp_scalar_linear_interp(p0->dark_strength, p1->dark_strength, alpha_q);
    p_o->bright_strength = isp_scalar_linear_interp(p0->bright_strength, p1->bright_strength, alpha_q);
    p_o->compress_thresh = isp_scalar_linear_interp(p0->compress_thresh, p1->compress_thresh, alpha_q);
    return ISP_INTERP_OK;
}

// 3. RGB域Gamma插值
static ISP_INTERP_ERR_E interp_rgbgamma_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q)
{
    ISP_RGB_GAMMA* p_o = (ISP_RGB_GAMMA*)out;
    const ISP_RGB_GAMMA* p0 = (const ISP_RGB_GAMMA*)c0;
    const ISP_RGB_GAMMA* p1 = (const ISP_RGB_GAMMA*)c1;

    p_o->gamma_en = (alpha_q > ISP_Q_HALF) ? p1->gamma_en : p0->gamma_en;
    p_o->r_gamma_coef = isp_scalar_linear_interp(p0->r_gamma_coef, p1->r_gamma_coef, alpha_q);
    p_o->g_gamma_coef = isp_scalar_linear_interp(p0->g_gamma_coef, p1->g_gamma_coef, alpha_q);
    p_o->b_gamma_coef = isp_scalar_linear_interp(p0->b_gamma_coef, p1->b_gamma_coef, alpha_q);
    return ISP_INTERP_OK;
}

// 4. YUV Gamma 256点LUT插值
static ISP_INTERP_ERR_E interp_yuvgamma_lut_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q)
{
    ISP_YUV_GAMMA_LUT* p_o = (ISP_YUV_GAMMA_LUT*)out;
    const ISP_YUV_GAMMA_LUT* p0 = (const ISP_YUV_GAMMA_LUT*)c0;
    const ISP_YUV_GAMMA_LUT* p1 = (const ISP_YUV_GAMMA_LUT*)c1;

    p_o->gamma_en = (alpha_q > ISP_Q_HALF) ? p1->gamma_en : p0->gamma_en;
    for(int32_t i = 0; i < GAMMA_LUT_POINT; i++)
    {
        p_o->y_lut[i] = isp_scalar_linear_interp(p0->y_lut[i], p1->y_lut[i], alpha_q);
    }
    return ISP_INTERP_OK;
}

// 5. DPC坏点校正(关闭插值,直接拷贝标定参数)
static ISP_INTERP_ERR_E interp_dpc_cb(void* out, const void* c0, const void* c1, uint32_t alpha_q)
{
    memcpy(out, c0, sizeof(ISP_DPC_ATTR));
    return ISP_INTERP_OK;
}
  1. 插值调用业务示例
cpp 复制代码
#include "isp_param.h"
#include "isp_interp.h"

S32 libisp_set(VOID *obj, S32 cmd, VOID *arg)
{
    struct libisp_obj *p_libisp_obj = (struct libisp_obj *)obj;
    VOID *ispReg;
    S32 ret = FALSE;

    if((NULL == p_libisp_obj) || (NULL == p_libisp_obj->isp_cacheaddr))
        return FALSE;

    ispReg = p_libisp_obj->isp_cacheaddr;

    switch (cmd)
    {
        case LIB_ALGSET_LSC_ATTR:
            ret = _isp_algset_lsc_attr(ispReg, (ISP_LSC_ATTR*)arg);
            break;
        case LIB_ALGSET_DRC_ATTR:
            ret = _isp_algset_drc_attr(ispReg, (ISP_DRC_REG*)arg);
            break;
        case LIB_ALGSET_RGBGAMMA_ATTR:
            ret = _isp_algset_rgbgamma_attr(ispReg, (ISP_RGB_GAMMA*)arg);
            break;
        case LIB_ALGSET_YUVGAMMALUT_ATTR:
            ret = _isp_algset_yuvgammalut_attr(ispReg, (ISP_YUV_GAMMA_LUT*)arg);
            break;
        case LIB_ALGSET_DPC_ATTR:
            ret = _isp_algset_dpc_attr(ispReg, (ISP_DPC_ATTR*)arg);
            break;
        default:
            break;
    }
    return ret;
}

/************************ AE线程业务调用示例:插值 + 下发libisp_set ************************/
extern struct libisp_obj* g_isp_handle;

void ae_task_loop_run(void)
{
    // AE实时获取sensor总增益
    uint32_t cur_total_gain = ae_get_sensor_total_gain();
    ISP_INTERP_ERR_E interp_ret;

    // 1. LSC插值下发
    interp_ret = isp_get_unified_interp_param(
        LIB_ALGSET_LSC_ATTR,
        cur_total_gain,
        &g_interp_global_buf.lsc,
        sizeof(g_interp_global_buf.lsc)
    );
    if(interp_ret == ISP_INTERP_OK)
    {
        libisp_set(g_isp_handle, LIB_ALGSET_LSC_ATTR, &g_interp_global_buf.lsc);
    }

    // 2. DRC插值下发
    interp_ret = isp_get_unified_interp_param(
        LIB_ALGSET_DRC_ATTR,
        cur_total_gain,
        &g_interp_global_buf.drc,
        sizeof(g_interp_global_buf.drc)
    );
    if(interp_ret == ISP_INTERP_OK)
    {
        libisp_set(g_isp_handle, LIB_ALGSET_DRC_ATTR, &g_interp_global_buf.drc);
    }

    // 3. YUV Gamma LUT插值下发
    interp_ret = isp_get_unified_interp_param(
        LIB_ALGSET_YUVGAMMALUT_ATTR,
        cur_total_gain,
        &g_interp_global_buf.yuv_gamma_lut,
        sizeof(g_interp_global_buf.yuv_gamma_lut)
    );
    if(interp_ret == ISP_INTERP_OK)
    {
        libisp_set(g_isp_handle, LIB_ALGSET_YUVGAMMALUT_ATTR, &g_interp_global_buf.yuv_gamma_lut);
    }
}
  1. 标定数据初始化示例(工程启动时填充)
cpp 复制代码
#include "isp_interp.h"

// 示例:LSC标定增益数组与参数池(工程从Flash/ini加载)
static uint32_t lsc_cal_gain_list[] = {100, 400, 800, 1600, 3200};
static ISP_LSC_ATTR lsc_cal_param_pool[sizeof(lsc_cal_gain_list)/sizeof(uint32_t)];

ISP_CAL_HEAD_T g_lsc_cal_head = {
    .cal_cnt = sizeof(lsc_cal_gain_list)/sizeof(uint32_t),
    .p_gain_arr = lsc_cal_gain_list,
    .p_param_pool = (uint8_t*)lsc_cal_param_pool,
    .param_single_size = sizeof(ISP_LSC_ATTR),
    .enable_interp = 1,
    .gain_sorted_ok = 1
};

// DPC关闭插值示例
static uint32_t dpc_cal_gain_list[] = {100, 3200};
static ISP_DPC_ATTR dpc_cal_param_pool[2];
ISP_CAL_HEAD_T g_dpc_cal_head = {
    .cal_cnt = 2,
    .p_gain_arr = dpc_cal_gain_list,
    .p_param_pool = (uint8_t*)dpc_cal_param_pool,
    .param_single_size = sizeof(ISP_DPC_ATTR),
    .enable_interp = 0, // 静态坏点不插值
    .gain_sorted_ok = 1
};

整体代码分为 4 大模块:头文件类型定义层、插值算法实现层、原有 libisp_set 寄存器下发层、标定数据初始化层,完全贴合你君正 ISP 工程分层架构,实现「统一插值接口 + 二分增益查找 + 多 ISP 模块差异化插值」。

二、文件结构总览

文件 核心职责
isp_interp.h 公共宏、错误码、数据结构、统一接口声明,对外暴露 API
isp_interp.c 底层定点工具、二分查找、统一插值主入口、各 ISP 模块插值回调、全局注册表
libisp 业务文件 保留你原图原始libisp_set,只新增 AE 线程插值调用逻辑,原有寄存器下发零修改
标定初始化文件 加载 Flash / 配置标定 ISO 增益、多档位参数,填充ISP_CAL_HEAD_T标定表头

三、isp_interp.h 头文件逐段解析

1. 定点运算可配置宏

cpp 复制代码
#define ISP_Q_SHIFT         12
#define ISP_Q_ONE           (1U << ISP_Q_SHIFT)  // Q12定点1.0 = 4096
#define ISP_Q_HALF          (ISP_Q_ONE >> 1)     // 0.5 = 2048
  • 全代码统一定点格式,如需切换 Q8/Q10 仅修改ISP_Q_SHIFT,所有插值逻辑自动适配;
  • ISP_Q_HALF用于开关类参数判定阈值,插值系数大于 0.5 取高增益档位枚举值。

2. 分层错误码(故障精准定位)

cpp 复制代码
typedef enum {
    ISP_INTERP_OK                = 0,
    ISP_INTERP_ERR_NULL_OUT     = -1,  // 输出缓存空指针
    ISP_INTERP_ERR_INVALID_CMD  = -2,  // cmd超出数组范围/未注册插值回调
    ISP_INTERP_ERR_NO_CAL_DATA  = -3,  // 标定增益数组/参数池为空
    ISP_INTERP_ERR_CAL_CNT_LT2  = -4,  // 标定档位<2档,无法插值
    ISP_INTERP_ERR_CB_FAIL      = -5,  // 模块私有插值回调执行失败
    ISP_INTERP_ERR_GAIN_ORDER   = -6,  // 标定增益未升序,二分查找失效
} ISP_INTERP_ERR_E;
  • 上层调用可通过返回值精准区分故障点,替代无区分度的TRUE/FALSE,便于调试打印日志。

3. 插值状态枚举(二分后 4 种分支逻辑)

cpp 复制代码
typedef enum {
    ISP_INTERP_CLAMP_MIN = 0,  // 当前增益 < 最小标定ISO,取最低档参数
    ISP_INTERP_CLAMP_MAX,      // 当前增益 > 最大标定ISO,取最高档参数
    ISP_INTERP_EXACT_MATCH,    // 增益精准匹配某一档标定,无需插值
    ISP_INTERP_LINEAR_CALC     // 增益落在两档中间,执行定点线性插值
} ISP_INTERP_ST_E;

二分查找后只会进入 4 种状态,逻辑分支清晰,无冗余判断。

4. 日志宏(量产 / 调试一键切换)

cpp 复制代码
#define ISP_INTERP_LOG_EN    1
#if ISP_INTERP_LOG_EN
#define ISP_LOG(fmt, ...)    printf("[ISP_INTERP]%s:%d " fmt "\r\n", __func__, __LINE__, ##__VA_ARGS__)
#else
#define ISP_LOG(fmt, ...)    do{}while(0)
#endif
  • 调试时打开,打印增益区间、插值系数、错误行号;
  • 量产直接注释#define ISP_INTERP_LOG_EN 1,消除 printf 耗时。

5. 标定通用结构体 ISP_CAL_HEAD_T(所有 ISP 模块共用)

cpp 复制代码
typedef struct {
    int32_t  cal_cnt;               // 标定档位总数
    uint32_t* p_gain_arr;           // 升序ISO增益数组 [100,400,800...]
    uint8_t*  p_param_pool;         // 连续内存存储cal_cnt组完整结构体参数
    uint32_t  param_single_size;    // 单组参数字节长度 sizeof(AK_ISP_LSC_ATTR)
    uint8_t   enable_interp;        // 插值总开关:1=插值,0=直接拷贝标定参数(DPC静态坏点使用)
    uint8_t   gain_sorted_ok;       // 启动时校验标记,增益升序置1,二分依赖
} ISP_CAL_HEAD_T;

核心设计:所有模块标定数据统一格式,新增模块无需重写标定管理逻辑,仅填充该结构体。

  • p_param_pool:连续内存存储多组参数,减少 Cache Miss,避免分散内存;
  • enable_interp:静态参数模块(DPC 固定坏点表)关闭插值,跳过定点计算节省算力。

6. 统一插值回调原型(实现 "一套接口兼容所有模块" 核心)

cpp 复制代码
typedef ISP_INTERP_ERR_E (*ISP_INTERP_CB)(
    void*        p_out,        // 输出插值后的参数
    const void*  p_cal_low,    // 左边界低增益标定参数
    const void*  p_cal_high,   // 右边界高增益标定参数
    uint32_t     alpha_q       // Q12定点插值系数 [0,4096]
);

关键优势

  1. 入参完全统一,上层主插值函数完全不感知每个 ISP 模块结构体差异;
  2. 模块内部通过(ISP_XXX_ATTR*)强转 void*,单独处理自身标量、数组、开关参数;
  3. 统一返回错误码,模块内部异常可向上抛出给主函数打印日志。

7. cmd 注册表结构(O (1) 极速查表优化)

复制代码
#define LIB_ALG_CMD_MAX    32
typedef struct {
    ISP_CAL_HEAD_T* p_cal_head; // 对应模块标定数据
    ISP_INTERP_CB   interp_cb;  // 对应模块私有插值回调
} ISP_CMD_INTERP_ITEM_T;
extern const ISP_CMD_INTERP_ITEM_T g_isp_interp_reg[LIB_ALG_CMD_MAX];
  • 数组下标 = 你原有libisp_setLIB_ALGSET_XXX指令码;
  • 完全删除原始方案for循环遍历查表,单次下标访问 O (1) 性能,适配 3A 高频帧循环调用。

8. 全局插值缓存联合体(内存优化,无动态 malloc)

cpp 复制代码
typedef union {
    ISP_LSC_ATTR        lsc;
    ISP_DRC_REG         drc;
    ISP_RGB_GAMMA       rgb_gamma;
    ISP_YUV_GAMMA_LUT   yuv_gamma_lut;
    ISP_DPC_ATTR        dpc;
} ISP_INTERP_UNION_BUF_T;
extern ISP_INTERP_UNION_BUF_T g_interp_global_buf;
  • 全局静态单块内存,程序启动一次性分配,无malloc/free,杜绝内存碎片、栈溢出;
  • 联合体自动对齐最大结构体内存,覆盖全部 ISP 模块参数类型。

9. 对外唯一统一入口(上层只调用这一个函数)

cpp 复制代码
ISP_INTERP_ERR_E isp_get_unified_interp_param(
    int32_t cmd,
    uint32_t cur_gain,
    void*    p_out_buf,
    uint32_t out_buf_max_size
);

上层 AE 线程无需区分 LSC/DRC/Gamma,仅传入 cmd、当前增益、缓存地址即可生成插值参数,完美解耦。

四、isp_interp.c 实现层逐段解析

1. 全局注册表初始化(数组下标绑定 cmd)

cpp 复制代码
const ISP_CMD_INTERP_ITEM_T g_isp_interp_reg[LIB_ALG_CMD_MAX] = {
    [LIB_ALGSET_LSC_ATTR] = {
        .p_cal_head = &g_lsc_cal_head,
        .interp_cb  = interp_lsc_cb
    },
    // DRC、RGBGamma、YUVGammaLUT、DPC以此类推
};

C99 指定下标初始化,新增 ISP 模块仅需增加一行赋值,不改动主插值逻辑,可维护性极强。

2. 底层 inline 工具函数

2.1 定点标量线性插值
cpp 复制代码
static inline int32_t isp_scalar_linear_interp(int32_t val0, int32_t val1, uint32_t alpha_q)
{
    ISP_LONG v0 = (ISP_LONG)val0 * (ISP_Q_ONE - alpha_q);
    ISP_LONG v1 = (ISP_LONG)val1 * alpha_q;
    ISP_LONG res = (v0 + v1) >> ISP_Q_SHIFT;
    // 溢出裁剪保护,防止参数超出寄存器范围导致画面花屏
    if(res > INT32_MAX) return INT32_MAX;
    if(res < INT32_MIN) return INT32_MIN;
    return (int32_t)res;
}

插值公式:

  • 使用 64 位长整型中间计算,避免乘法溢出;
  • 结果溢出强制裁剪,解决高增益插值参数越界问题。
2.2 插值系数 α 计算
cpp 复制代码
static inline uint32_t isp_calc_alpha_q(uint32_t g0, uint32_t g1, uint32_t cur)
{
    ISP_ULONG delta_cur = cur - g0;
    ISP_ULONG delta_total = g1 - g0;
    return (uint32_t)((delta_cur * ISP_Q_ONE) / delta_total);
}

纯定点无浮点除法,适配嵌入式 MCU 无 FPU 场景。

2.3 安全内存拷贝(越界防护)
cpp 复制代码
static void isp_safe_param_copy(void* dst, const void* src, uint32_t copy_len, uint32_t buf_max_len)
{
    uint32_t real_len = (copy_len > buf_max_len) ? buf_max_len : copy_len;
    memcpy(dst, src, real_len);
}

上层传入缓存最大长度,防止标定结构体大于输出缓存导致 memcpy 越界崩溃。

cpp 复制代码
static ISP_INTERP_ST_E isp_gain_binary_search(const ISP_CAL_HEAD_T* p_cal, uint32_t cur_gain, int32_t* p_out_left_idx)

执行流程

  1. 前置判断:增益小于最小标定 → 截断取最低档;增益大于最大标定 → 截断取最高档;
  2. 标准二分循环:精准匹配标定档位直接返回EXACT_MATCH
  3. 循环结束后right为左边界下标,插值区间[right, right+1]
  4. 打印日志输出当前增益区间,方便标定档位调试。

性能优势

标定档位 N=10 档仅最多 4 次循环,远优于顺序遍历,适配每帧高频调用。

4. 核心统一插值主入口 isp_get_unified_interp_param(全流程调度中枢)

完整执行链路拆解

  1. 入参安全拦截
    • 判断输出缓存空指针、cmd 超出数组范围,直接返回对应错误码;
  2. O (1) 查表 通过 cmd 下标读取模块标定头、插值回调;
  3. 标定合法性全校验 增益数组 / 参数池判空、档位≥2 档、增益升序标记校验,提前拦截非法标定数据;
  4. 二分查找增益区间 调用二分函数获取插值状态与左边界下标;
  5. 4 分支状态处理
    • 截断 / 精准匹配:调用安全拷贝直接复制标定参数;
    • 线性插值分支:

    ① 判断模块插值总开关enable_interp,关闭则直接拷贝;

    ② 计算 Q12 插值系数 α;

    ③ 调用模块专属插值回调完成结构体插值;

    ④ 校验回调返回值,异常打印日志并返回错误码;

  6. 插值完成打印日志,返回ISP_INTERP_OK

设计亮点

所有模块共用一套调度逻辑,新增模块完全不需要修改主入口函数,仅新增回调 + 注册表一行配置,低侵入、高内聚。

5. 各 ISP 模块插值回调实现(统一回调接口,差异化内部逻辑)

所有回调函数入参完全一致,仅内部针对自身结构体字段做区分处理,分为 3 类参数逻辑:

类型 1:开关 / 枚举参数(不线性插值,阈值切换)
cpp 复制代码
p_o->lsc_en = (alpha_q > ISP_Q_HALF) ? p1->lsc_en : p0->lsc_en;

插值系数 > 0.5 取高增益档位的枚举值,避免开关插值出现 0.5 中间非法状态。

类型 2:标量数值参数(定点线性插值)
cpp 复制代码
p_o->global_gain = isp_scalar_linear_interp(p0->global_gain, p1->global_gain, alpha_q);

DRC 强度、Gamma 系数、LSC 全局增益等连续数值直接调用通用定点插值工具。

类型 3:LUT 网格数组(循环逐点插值)
cpp 复制代码
for(int32_t i = 0; i < LSC_GRID_TOTAL; i++)
{
    p_o->r_grid[i] = isp_scalar_linear_interp(p0->r_grid[i], p1->r_grid[i], alpha_q);
}

LSC 二维网格、256 点 YUV Gamma 表逐点遍历插值,适配大尺寸查表参数。

类型 4:静态固定参数(关闭插值,直接拷贝)

DPC 坏点回调直接 memcpy,配合标定头enable_interp=0,跳过所有插值计算,节省算力。

五、业务层解析

1. libisp_set函数

cpp 复制代码
S32 libisp_set(VOID *obj, S32 cmd, VOID *arg)
{
    struct libisp_obj *p_libisp_obj = (struct libisp_obj *)obj;
    if((NULL == p_libisp_obj) || (NULL == p_libisp_obj->isp_cacheaddr))
        return AK_FALSE;
    ispReg = p_libisp_obj->isp_cacheaddr;
    switch (cmd)
    {
        case LIB_ALGSET_LSC_ATTR:
            ret = _isp_algset_lsc_attr(ispReg, (ISP_LSC_ATTR*)arg);
            break;
        // DRC/RGBGAMMA/GammaLUT/DPC分支和原图完全一致
    }
    return ret;
}
  • 职责不变:接收参数结构体,写入 ISP 寄存器缓存;
  • 插值逻辑作为前置分层新增,不改动原有寄存器下发、缓存机制,工程回退无风险。

2. AE 线程调用示例(插值 + 下发完整业务链路)

cpp 复制代码
void ae_task_loop_run(void)
{
    uint32_t cur_total_gain = ae_get_sensor_total_gain();
    // 1. 调用统一插值接口生成当前增益参数
    interp_ret = isp_get_unified_interp_param(LIB_ALGSET_LSC_ATTR, cur_total_gain, &g_interp_global_buf.lsc, sizeof(g_interp_global_buf.lsc));
    // 2. 插值成功调用原始libisp_set下发寄存器
    if(interp_ret == ISP_INTERP_OK)
        libisp_set(g_isp_handle, LIB_ALGSET_LSC_ATTR, &g_interp_global_buf.lsc);
}

完整业务链路: AE获取实时ISO增益 → 统一插值接口二分+插值生成参数 → libisp_set写入ISP缓存 → ISP硬件流水线生效

六、标定初始化代码解析

cpp 复制代码
static uint32_t lsc_cal_gain_list[] = {100, 400, 800, 1600, 3200}; // 升序标定增益
static ISP_LSC_ATTR lsc_cal_param_pool[5]; // 5组LSC标定参数
ISP_CAL_HEAD_T g_lsc_cal_head = {
    .cal_cnt = 5,
    .p_gain_arr = lsc_cal_gain_list,
    .p_param_pool = (uint8_t*)lsc_cal_param_pool,
    .param_single_size = sizeof(ISP_LSC_ATTR),
    .enable_interp = 1, // LSC随增益插值
    .gain_sorted_ok = 1 // 增益数组升序校验通过
};
  • 工程启动时从 Flash / 配置文件填充lsc_cal_param_pool每组 ISO 对应的完整 LSC 参数;
  • DPC 模块配置enable_interp=0,静态坏点不随增益插值。

七、整体架构分层总结

上层AE应用层

↓ 唯一统一插值API:isp_get_unified_interp_param()

┌─────────────────────────────────────────────┐

│ 插值算法分层(新增代码,无侵入原有逻辑) │

│ 1. O(1) cmd注册表查表 │

│ 2. 二分查找ISO增益区间 │

│ 3. Q12定点线性插值工具 │

│ 4. 模块回调差异化插值(LSC/DRC/Gamma/DPC) │

└─────────────────────────────────────────────┘

↓ 输出插值完成的参数结构体

↓ 你原有libisp_set(零修改,寄存器下发层)

┌─────────────────────────────────────────────┐

│ ISP寄存器缓存写入层(原图原始业务代码) │

│ switch(cmd) 调用底层_isp_algset_xxx写入缓存 │

└─────────────────────────────────────────────┘

↓ ISP硬件图像流水线生效

八、核心优化点复盘

  1. 性能优化
    • 删除 for 循环查表,cmd 数组下标 O (1) 访问;
    • inline 工具函数减少函数调用栈开销;
    • 插值开关提前拦截,无插值场景跳过定点运算。
  2. 安全健壮优化
    • 分层错误码精准定位故障;
    • 空指针、数组越界、定点数值溢出多层防护;
    • 标定增益升序校验,防止二分逻辑错乱;
    • 安全 memcpy 限制拷贝长度,杜绝内存越界崩溃。
  3. 工程可维护优化
    • 新增 ISP 模块仅新增回调 + 注册表一行赋值,不修改主插值函数;
    • 全量魔法数字统一宏定义,Q 格式、日志、最大 cmd 可一键修改;
    • 回调接口统一,模块逻辑完全隔离。
  4. 内存优化
    • 全局联合体静态缓存,无动态 malloc,无内存碎片;
    • 标定参数连续内存池排布,提升 Cache 命中率;
    • 无局部大数组栈分配,规避栈溢出。
  5. 业务兼容优化
    • 原图libisp_set寄存器下发代码零改动,可随时关闭插值回退原始逻辑;
    • 全定点无浮点运算,无 FPU 嵌入式 MCU。
相关推荐
程序员老邢1 天前
《技术底稿 46》AI 解构成果→知识库自动化同步管道 设计与落地总结
架构设计·异步任务·数据同步·后端开发·幂等性·技术底稿
码农飞哥1 天前
Spring Boot 多角色权限隔离实战:接口层+路由层+UI层三层防御,杜绝生产数据泄露
spring boot·状态模式·架构设计·系统设计·权限控制
brycegao3213 天前
Android MVI进阶:纯原生实现Slot化可插拔架构
android·kotlin·架构设计·mvi·viewmodel
心之伊始5 天前
Java 后端 AI 应用网关实战:多模型路由、Fallback、超时和可观测性设计
java·spring boot·大模型·架构设计·ai网关
handler018 天前
【C++11 】Lambda 表达式、std::function 与 std::bind 解析
c++·c·c++11·bind·解耦·function·lamda
我心飞翔@坚持不懈12 天前
高并发高可用电商平台交易架构实战(避坑)指南
高并发·架构设计·高可用·分布式系统·电商架构·架构避坑
SL-staff15 天前
规则引擎技术选型指南:从开源Drools到企业级方案,架构演进与私有化实践
架构·开源·私有化部署·架构设计·规则引擎·drools·jvs-rules
这是谁的博客?16 天前
LangChain 框架深度解析:从 LCEL 到 Agent 架构的核心原理
ai·架构·langchain·llm·agent·架构设计