欢迎光临
我们一直在努力

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.c 实现层逐段解析

五、业务层解析

六、标定初始化代码解析

七、整体架构分层总结

八、核心优化点复盘


一、示例代码实现

1. isp_interp.h 头文件

#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

2. isp_interp.c 完整实现代码

#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;
}

3. 插值调用业务示例

#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);
}
}

4. 标定数据初始化示例(工程启动时填充)

#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. 定点运算可配置宏

#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. 分层错误码(故障精准定位)

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 种分支逻辑)

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. 日志宏(量产 / 调试一键切换)

#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 模块共用)

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. 统一插值回调原型(实现 “一套接口兼容所有模块” 核心)

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]
);

关键优势

  • 入参完全统一,上层主插值函数完全不感知每个 ISP 模块结构体差异;
  • 模块内部通过(ISP_XXX_ATTR*)强转 void*,单独处理自身标量、数组、开关参数;
  • 统一返回错误码,模块内部异常可向上抛出给主函数打印日志。
  • 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_set的LIB_ALGSET_XXX指令码;
    • 完全删除原始方案for循环遍历查表,单次下标访问 O (1) 性能,适配 3A 高频帧循环调用。

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

    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. 对外唯一统一入口(上层只调用这一个函数)

    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)

    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 定点标量线性插值

    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 插值系数 α 计算

    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 安全内存拷贝(越界防护)

    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 越界崩溃。

    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)

    执行流程

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

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

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

    完整执行链路拆解

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

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

    ② 计算 Q12 插值系数 α;

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

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

  • 插值完成打印日志,返回ISP_INTERP_OK。
  • 设计亮点

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

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

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

    类型 1:开关 / 枚举参数(不线性插值,阈值切换)

    p_o->lsc_en = (alpha_q > ISP_Q_HALF) ? p1->lsc_en : p0->lsc_en;

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

    类型 2:标量数值参数(定点线性插值)

    p_o->global_gain = isp_scalar_linear_interp(p0->global_gain, p1->global_gain, alpha_q);

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

    类型 3:LUT 网格数组(循环逐点插值)

    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函数

    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 线程调用示例(插值 + 下发完整业务链路)

    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硬件流水线生效

    六、标定初始化代码解析

    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硬件图像流水线生效

    八、核心优化点复盘

  • 性能优化
    • 删除 for 循环查表,cmd 数组下标 O (1) 访问;
    • inline 工具函数减少函数调用栈开销;
    • 插值开关提前拦截,无插值场景跳过定点运算。
  • 安全健壮优化
    • 分层错误码精准定位故障;
    • 空指针、数组越界、定点数值溢出多层防护;
    • 标定增益升序校验,防止二分逻辑错乱;
    • 安全 memcpy 限制拷贝长度,杜绝内存越界崩溃。
  • 工程可维护优化
    • 新增 ISP 模块仅新增回调 + 注册表一行赋值,不修改主插值函数;
    • 全量魔法数字统一宏定义,Q 格式、日志、最大 cmd 可一键修改;
    • 回调接口统一,模块逻辑完全隔离。
  • 内存优化
    • 全局联合体静态缓存,无动态 malloc,无内存碎片;
    • 标定参数连续内存池排布,提升 Cache 命中率;
    • 无局部大数组栈分配,规避栈溢出。
  • 业务兼容优化
    • 原图libisp_set寄存器下发代码零改动,可随时关闭插值回退原始逻辑;
    • 全定点无浮点运算,无 FPU 嵌入式 MCU。
  • 赞(0)
    未经允许不得转载:171主机测评 » ISP模块参数统一对外接口插值逻辑
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址