目录
[二、方案1---- DPCM 差分 + 5bit 固定残](#二、方案1---- DPCM 差分 + 5bit 固定残)
ISP参数中,一般都需要根据不同的ISO调整不同的图像参数,然后再根据线性插值计算对应ISO的对应模块图像参数。各个ISO直接的参数一般也会遵循单调性,尽可能使得不同ISO直接参数平滑过渡。
此外,某些模块保存固定阶数的查找表,然后底层再利用线性插值生需要的参数。查找表在调试工具上一般以曲线的形式表现。由于不同ISO都需要调试相应的参数,但是相邻ISO直接的ISP参数存在单调渐变的趋势。我们是否可以利用这些特性对ISP参数做压缩处理,减少ISP参数暂用内存大小呢?
实际上是可行的。可以主要针对多字节的参数进行压缩。单字节的参数可能在压缩前后占用的内存并没有太大变化。
假设我们有128阶每阶10bit的三通道Gamma需要保存。
一、定义Gamma曲线
- 输入 x:0 ~ 127(8bit,128 个点)
- 输出 y:0 ~ 1023(10bit)
- 原始存储:128 × 2字节 = 256字节
- 压缩目标:极小存储 + 最大误差 ≤ 1 LSB(准无损)
二、方案1---- DPCM 差分 + 5bit 固定残
2.1、压缩规则
- 第 0 点 :存储 原始 10bit(基准值)
- 第 1 ~ 127 点 :只存储 与前一点的差值(残差)
- 差值 = 当前值 − 前一个值
- 每个差值用 5bit 存储
2.2、压缩后大小
- 基准点:10 bit
- 127 个残差:127 × 5 = 635 bit
- 总 bit = 10 + 635 = 645 bit
- 换算字节:645 / 8 ≈ 81 字节
📊 压缩效果
- 原始:160 字节
- 压缩后:81 字节
- 压缩比:1.97×(接近 2:1)
- 完全无损、无精度丢失
示例代码:
cpp
#include <stdint.h>
// ==================== 修复版:8点 Gamma 解压 ====================
// 功能:8点 Gamma LUT 解压(10bit基准 + 5bit残差,无损)
// 输入:compressed -> 压缩后数据
// 输出:gamma_table[8] -> 解压后的10bit Gamma表
void gamma_decode_8points(const uint8_t *compressed, uint16_t gamma_table[8])
{
// 1. 正确读取第0点 10bit原始值
gamma_table[0] = ((uint16_t)compressed[0] << 2) | ((compressed[1] >> 6) & 0x03);
int bit_offset = 10; // 已读取10位,从第10位开始
// 2. 依次解压7个5bit残差
for(int i=1; i<8; i++){
// ===================== 修复:正确提取5bit =====================
int byte_idx = bit_offset / 8;
int bit_in_byte = bit_offset % 8;
int diff = 0;
// 读取5bit(跨字节安全处理)
if (bit_in_byte <= 3) {
// 5bit在同一个字节内
diff = (compressed[byte_idx] >> (3 - bit_in_byte)) & 0x1F;
} else {
// 跨两个字节
int bits_first = 8 - bit_in_byte;
int bits_second = 5 - bits_first;
diff = ((compressed[byte_idx] & ((1 << bits_first) - 1)) << bits_second)
| (compressed[byte_idx + 1] >> (8 - bits_second));
}
bit_offset += 5;
// ===================== 修复:正确5bit有符号扩展 =====================
if (diff & 0x10) { // 第4位是符号位
diff -= 32; // 5bit有符号数:-16 ~ +15
}
// ===================== 修复:防止溢出 + 10bit限幅 =====================
int32_t val = (int32_t)gamma_table[i-1] + diff;
if (val < 0) val = 0;
if (val > 1023) val = 1023;
gamma_table[i] = (uint16_t)val;
}
}
三、方案2----分段三次样条拟合
为什么用三次样条?
- Gamma 曲线平滑、单调、无突变 → 完美适配样条拟合
- 只存少量节点 坐标,不用存 128 个点
- 精度可控(可做到 10bit 内无损 / 几乎无损)
- 硬件 / 软件计算都极快
- 压缩比远超 DPCM
四、最优量产配置
节点数量:16 个节点(完美平衡)
- 均匀分布在 0~127
- 只存储 16 个 10bit 节点值
- 大小:
横坐标为16*8bit = 16字节 纵坐标16 × 16bit =32字节 总共48字节 - 压缩比:256→ 40 字节 = 6.4×
- 误差:≤ 0.5 LSB(10bit 准无损)
极致压缩(8 个节点)
- 存储:
横坐标为8*8bit = 8字节 纵坐标8 × 16bit =16字节 总共24字节 - 压缩比:256→ 24字节 = 10.6×
- 误差:≤ 1 LSB
三次样条插值示例代码:
python
#include <stdint.h>
// ===================== 8节点 Gamma 样条参数 =====================
#define NODES_CNT 8
const uint8_t x_nodes[NODES_CNT] = {0, 18, 36, 54, 72, 90, 108, 127};
const uint16_t y_nodes[NODES_CNT] = {0, 24, 92, 210, 381, 611, 901, 1023};
// 预计算 8 个节点的斜率(定点 Q15)
const int16_t m_slope[NODES_CNT] = {
2048, 2912, 4352, 5504, 6016, 6528, 5888, 3072
};
// ===================== 正确三次分段插值(Hermite 平滑) =====================
uint16_t gamma_calc(uint8_t x)
{
// 1. 定位区间 k
int k = 0;
for (int i = 1; i < NODES_CNT; i++) {
if (x >= x_nodes[i]) k = i - 1;
else break;
}
// 2. 区间端点
uint8_t x0 = x_nodes[k];
uint8_t x1 = x_nodes[k+1];
uint16_t y0 = y_nodes[k];
uint16_t y1 = y_nodes[k+1];
int16_t m0 = m_slope[k];
int16_t m1 = m_slope[k+1];
uint8_t dx = x1 - x0;
// 3. 计算 t = (x - x0)/dx 定点 Q15
int32_t t = ((int32_t)(x - x0) << 15) / dx;
// 4. Hermite 三次插值系数
int32_t t2 = (t * t) >> 15;
int32_t t3 = (t2 * t) >> 15;
int32_t h00 = ( 2*t3 - 3*t2 + 32768) >> 15;
int32_t h10 = ( t3 - 2*t2 + t ) >> 15;
int32_t h01 = (-2*t3 + 3*t2 ) >> 15;
int32_t h11 = ( t3 - t2 ) >> 15;
// 5. 插值计算
int32_t y = 0;
y += h00 * y0;
y += h10 * m0 * dx;
y += h01 * y1;
y += h11 * m1 * dx;
y >>= 15;
// 6. 10bit 限幅
if(y < 0) y = 0;
if(y > 1023) y = 1023;
return (uint16_t)y;
}
四、总结:
类似的在ISP 图像参数中凡是用曲线表现形式的参数都可以使用第二种方法进行压缩处理。当曲线较多且ISO也较多时,这样压缩会节省较大内存空间。