ISP Pipeline中Lv实现方式探究之六--lv值计算再优化

  • 目录

    一、优劣对比

    [1️⃣ 查表 + 线性插值](#1️⃣ 查表 + 线性插值)

    [✅ 优点](#✅ 优点)

    [❌ 缺点](#❌ 缺点)

    适用场景

    [2️⃣ 归一化 + 多项式逼近(如 2 次 / 3 次多项式)](#2️⃣ 归一化 + 多项式逼近(如 2 次 / 3 次多项式))

    [✅ 优点](#✅ 优点)

    [❌ 缺点](#❌ 缺点)

    适用场景

    二、代码实现

    三、总结


一、优劣对比

如上博文,都是讲解了Lv计算的基本原理。实际在ISP驱动中使用的是LV的定点数据进行相应的后续操作。比如根据Lv定点值进行线性插值,判断当前场景亮度等级等。博文ISP Pipeline中Lv实现方式探究之五--lv值计算框架优化使用的是多项式+归一化的方式进行Lv定点计算。为了提高速度,我们使用查找表+线性插值进行相应的优化。其两种方式的优劣为:

1️⃣ 查表 + 线性插值

✅ 优点

速度极快:只有移位、查表、乘法

  • 10~20 个时钟周期搞定
  • 无循环、无迭代

实现简单、不易出错

  • 逻辑固定,调试一次永久用

定点运算完美适配

  • Q10/Q15 随便用
  • 无溢出风险(控制好位数)

精度可控

  • 64 表:低精度
  • 128 表:中等精度(最常用)
  • 256 表:高精度

无除法、无复杂运算→ 单片机 / DSP/ASIC/FPGA 最爱

❌ 缺点

适用场景

90% 嵌入式项目、电机控制、仪表、电源、音频、通信


2️⃣ 归一化 + 多项式逼近(如 2 次 / 3 次多项式)

公式示例:

log2(1+x) ≈ a0 + a1*x + a2*x² + a3*x³

  • 占用 Flash
    • 256 表 ≈ 512 字节
    • 64 表 ≈ 128 字节
  • 精度不如高阶多项式

✅ 优点

几乎不占 Flash

  • 只有几个系数,20 字节以内

精度极高

  • 3 次多项式误差 < 0.001%

适合超小资源 MCU(如 8051、tiny 单片机)

❌ 缺点

适用场景

极小 Flash、极高精度、非常低速的系统

优劣总结:

  • 运算量大
    • 乘法多、平方、立方
    • 速度比 LUT 慢 3~10 倍
  • 定点容易溢出
    • 平方 / 立方会扩位,必须小心处理
  • 调试难、系数难拟合
    • 系数需要 MATLAB/Python 拟合
    • 改一次精度重算一次
指标 查表 + 线性插值 归一化 + 多项式
速度 ⭐⭐⭐⭐⭐ 最快 ⭐⭐ 慢
Flash 占用 ⭐⭐ 中等 ⭐⭐⭐⭐⭐ 极小
定点实现难度 ⭐⭐ 简单 ⭐⭐⭐⭐⭐ 难
精度 ⭐⭐⭐ 良好 ⭐⭐⭐⭐⭐ 极高
调试难度 ⭐ 极易 ⭐⭐⭐⭐⭐ 难
嵌入式推荐度 ⭐⭐⭐⭐⭐ 首选 ⭐⭐⭐ 特殊场景

二、代码实现

Q10定点实现代码:

cpp 复制代码
/////////////////////////Q10定点输入 
//=============================================================================
// 1. 64点 LUT 版本
//=============================================================================
#define LUT_SIZE_64   64
static const int16_t log2_table_q10_64[LUT_SIZE_64 + 1] = {
	0, 23, 45, 68, 90, 111, 132, 153,
	174, 194, 214, 234, 254, 273, 292, 311,
	330, 348, 366, 384, 402, 419, 436, 454,
	470, 487, 504, 520, 536, 552, 568, 584,
	599, 614, 629, 644, 659, 674, 689, 703,
	717, 731, 745, 759, 773, 787, 800, 813,
	827, 840, 853, 866, 879, 891, 904, 916,
	929, 941, 953, 965, 977, 989, 1001, 1012,
	1024,
};

int32_t q10_log2_64lut(uint32_t x)
{
	uint32_t val = x;
	int32_t  exp = 0;
	
	if (x <= 0) return 0;
	// 归一化到 [1024, 2047]
	while (val >= 2UL * Q10_SCALE) {
		val >>= 1; exp++;
	}
	while (val < Q10_SCALE)       {
		val <<= 1; exp--;
	}

	uint32_t delta = val - Q10_SCALE;
	uint8_t  idx = (uint8_t)(delta >> 4);   // 64点:右移4位
	uint8_t  frac = (uint8_t)(delta & 0x0F); // 插值 0~15

	int16_t y0 = log2_table_q10_64[idx];
	int16_t y1 = log2_table_q10_64[idx + 1];

	// 插值公式:y0 + (y1-y0)*frac/16
	int32_t interp = y0 + (((int32_t)(y1 - y0) * frac) >> 4);
	int32_t res = (exp * Q10_SCALE) + interp;

	return res;
}

//=============================================================================
// 2. 128点 LUT 版本
//=============================================================================
#define LUT_SIZE_128  128
static const int16_t log2_table_q10_128[LUT_SIZE_128 + 1] = {
	0, 11, 23, 34, 45, 57, 68, 79,
	90, 100, 111, 122, 132, 143, 153, 164,
	174, 184, 194, 204, 214, 224, 234, 244,
	254, 264, 273, 283, 292, 302, 311, 320,
	330, 339, 348, 357, 366, 375, 384, 393,
	402, 411, 419, 428, 436, 445, 454, 462,
	470, 479, 487, 495, 504, 512, 520, 528,
	536, 544, 552, 560, 568, 576, 584, 591,
	599, 607, 614, 622, 629, 637, 644, 652,
	659, 667, 674, 681, 689, 696, 703, 710,
	717, 724, 731, 738, 745, 752, 759, 766,
	773, 780, 787, 793, 800, 807, 813, 820,
	827, 833, 840, 846, 853, 859, 866, 872,
	879, 885, 891, 898, 904, 910, 916, 922,
	929, 935, 941, 947, 953, 959, 965, 971,
	977, 983, 989, 995, 1001, 1007, 1012, 1018,
	1024,
};

int32_t q10_log2_128lut(uint32_t x)
{
	
	uint32_t val = x;
	int32_t  exp = 0;

	if (x <= 0) return 0;
	while (val >= 2UL * Q10_SCALE) {
		val >>= 1; exp++;
	}
	while (val < Q10_SCALE)       {
		val <<= 1; exp--;
	}

	uint32_t delta = val - Q10_SCALE;
	uint8_t  idx = (uint8_t)(delta >> 3);   // 128点:右移3位
	uint8_t  frac = (uint8_t)(delta & 0x07); // 插值 0~7

	int16_t y0 = log2_table_q10_128[idx];
	int16_t y1 = log2_table_q10_128[idx + 1];

	// 插值公式:y0 + (y1-y0)*frac/8
	int32_t interp = y0 + (((int32_t)(y1 - y0) * frac) >> 3);
	int32_t res = (exp * Q10_SCALE) + interp;

	return res;
}




//256 LUT
// Q10 定点数定义:1位符号 + 5位整数 + 10位小数,缩放系数 2^10 = 1024
// 高精度 256 点 log2 真值表
// 对应区间 [1.0, 2.0),步长 1/256,存储 Q10 格式的 log2(m) 真值
static const int16_t log2_table_q10_256[LUT_SIZE_256 + 1] = {
	0, 6, 11, 17, 23, 29, 34, 40, 45, 51, 57, 62, 68, 73, 79, 84,
	90, 95, 100, 106, 111, 116, 122, 127, 132, 138, 143, 148, 153, 159, 164, 169,
	174, 179, 184, 189, 194, 199, 204, 209, 214, 219, 224, 229, 234, 239, 244, 249,
	254, 259, 264, 268, 273, 278, 283, 288, 292, 297, 302, 306, 311, 316, 320, 325,
	330, 334, 339, 343, 348, 353, 357, 362, 366, 371, 375, 380, 384, 388, 393, 397,
	402, 406, 411, 415, 419, 424, 428, 432, 436, 441, 445, 449, 454, 458, 462, 466,
	470, 475, 479, 483, 487, 491, 495, 500, 504, 508, 512, 516, 520, 524, 528, 532,
	536, 540, 544, 548, 552, 556, 560, 564, 568, 572, 576, 580, 584, 587, 591, 595,
	599, 603, 607, 610, 614, 618, 622, 626, 629, 633, 637, 641, 644, 648, 652, 656,
	659, 663, 667, 670, 674, 678, 681, 685, 689, 692, 696, 699, 703, 707, 710, 714,
	717, 721, 724, 728, 731, 735, 738, 742, 745, 749, 752, 756, 759, 763, 766, 770,
	773, 776, 780, 783, 787, 790, 793, 797, 800, 803, 807, 810, 813, 817, 820, 823,
	827, 830, 833, 837, 840, 843, 846, 850, 853, 856, 859, 863, 866, 869, 872, 875,
	879, 882, 885, 888, 891, 894, 898, 901, 904, 907, 910, 913, 916, 919, 922, 926,
	929, 932, 935, 938, 941, 944, 947, 950, 953, 956, 959, 962, 965, 968, 971, 974,
	977, 980, 983, 986, 989, 992, 995, 998, 1001, 1004, 1007, 1010, 1012, 1015, 1018, 1021,
	1024,
};

/**
* @brief Q10 定点数 log2 计算 (查表 + 线性插值,高精度无错误)
* @param x  输入:Q10 格式有符号定点数 (x > 0)
* @return   输出:Q10 格式 log2(x)
*/
int32_t q10_log2_256lut(int32_t x)
{
	// 1. 输入保护:log2 仅支持正数
	if (x <= 0) {
		return 0; // 非法输入返回0
	}

	int32_t val = x;
	int32_t exponent = 0;

	// 2. 核心:归一化,将 val 缩放到 [1024, 2047] (Q10 的 [1.0, 2.0))
	while (val >= 2 * Q10_SCALE) {
		val >>= 1;
		exponent++;
	}
	while (val < Q10_SCALE) {
		val <<= 1;
		exponent--;
	}

	// 3. 计算查表索引 + 插值小数位 (100% 正确)
	uint32_t delta = val - Q10_SCALE;  // 范围 [0, 1023]
	uint8_t idx = delta >> 2;          // 高8位:0~255 查表索引
	uint8_t frac = delta & 0x03;       // 低2位:0~3 插值小数

	// 4. 线性插值(防溢出 + 高精度)
	int16_t y0 = log2_table_q10_256[idx];
	int16_t y1 = (idx < 255) ? log2_table_q10_256[idx + 1] : y0;
	int32_t interp = y0 + ((int32_t)(y1 - y0) * frac) / 4;

	// 5. 合并结果:log2(x) = 指数 + 小数部分
	int32_t result = (exponent * Q10_SCALE) + interp;
	return result;
}
void test_q10_log2(void) {
	

	uint32_t i = 0;  // log2(10) ≈ 3.3219
	int32_t res64, res128, res256;
	FILE *fp = fopen("lv_fixed_q10_LUT-new.txt", "w");
	fprintf(fp, "ID  Q10-64 LUT	Q10 64 lut 浮点	Q10-128 LUT	Q10-128 LUT 浮点  Q10-256 LUT Q10-256 LUT 浮点	理论值\n");
	for (i = 1024; i <= 1024 * 512; i+=103)
	{
	
		res64 = q10_log2_64lut(i);
		res128 = q10_log2_128lut(i);
		res256 = q10_log2_256lut(i);
		fprintf(fp, "%.4f  %4d		%.6f		%4d		%.6f		%4d		%.6f		%.6f\n", (float)i / 1024, res64, (float)res64 / Q10_SCALE, res128, (float)res128 / Q10_SCALE, res256, (float)res256 / Q10_SCALE, log2f((float)i/1024.0));

		//fprintf(fp, "%.6f 	  %d		%.6f		%.6f\n", (float)i / 1024,  res256, (float)res256 / Q10_SCALE, log2f((float)i / 1024.0));
	}
}

int main() {
	test_q10_log2();
	return 0;
}

结果对比,64LUT/128LUT/256LUT计算的结果一致,和理论值差距比较小。后面三个为理论值和三个查找表计算你的值差值。对比如下:

Q8定点实现代码:

cpp 复制代码
static const int16_t log2_table_q8_64[LUT_SIZE_64 + 1] = {
	0, 6, 11, 17, 22, 28, 33, 38,
	44, 49, 54, 59, 63, 68, 73, 78,
	82, 87, 92, 96, 100, 105, 109, 113,
	118, 122, 126, 130, 134, 138, 142, 146,
	150, 154, 157, 161, 165, 169, 172, 176,
	179, 183, 186, 190, 193, 197, 200, 203,
	207, 210, 213, 216, 220, 223, 226, 229,
	232, 235, 238, 241, 244, 247, 250, 253,
	256,
};


static const int16_t log2_table_q8_128[LUT_SIZE_128 + 1] = {
	0, 3, 6, 9, 11, 14, 17, 20,
	22, 25, 28, 30, 33, 36, 38, 41,
	44, 46, 49, 51, 54, 56, 59, 61,
	63, 66, 68, 71, 73, 75, 78, 80,
	82, 85, 87, 89, 92, 94, 96, 98,
	100, 103, 105, 107, 109, 111, 113, 116,
	118, 120, 122, 124, 126, 128, 130, 132,
	134, 136, 138, 140, 142, 144, 146, 148,
	150, 152, 154, 155, 157, 159, 161, 163,
	165, 167, 169, 170, 172, 174, 176, 178,
	179, 181, 183, 185, 186, 188, 190, 192,
	193, 195, 197, 198, 200, 202, 203, 205,
	207, 208, 210, 212, 213, 215, 216, 218,
	220, 221, 223, 224, 226, 228, 229, 231,
	232, 234, 235, 237, 238, 240, 241, 243,
	244, 246, 247, 249, 250, 252, 253, 255,
	256,
};

static const int16_t log2_table_q8_256[LUT_SIZE_256 + 1] = {
	0, 1, 3, 4, 6, 7, 9, 10, 11, 13, 14, 16, 17, 18, 20, 21,
	22, 24, 25, 26, 28, 29, 30, 32, 33, 34, 36, 37, 38, 40, 41, 42,
	44, 45, 46, 47, 49, 50, 51, 52, 54, 55, 56, 57, 59, 60, 61, 62,
	63, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81,
	82, 84, 85, 86, 87, 88, 89, 90, 92, 93, 94, 95, 96, 97, 98, 99,
	100, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 116, 117,
	118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
	134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
	150, 151, 152, 153, 154, 155, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
	165, 166, 167, 168, 169, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 178,
	179, 180, 181, 182, 183, 184, 185, 185, 186, 187, 188, 189, 190, 191, 192, 192,
	193, 194, 195, 196, 197, 198, 198, 199, 200, 201, 202, 203, 203, 204, 205, 206,
	207, 208, 208, 209, 210, 211, 212, 212, 213, 214, 215, 216, 216, 217, 218, 219,
	220, 220, 221, 222, 223, 224, 224, 225, 226, 227, 228, 228, 229, 230, 231, 231,
	232, 233, 234, 234, 235, 236, 237, 238, 238, 239, 240, 241, 241, 242, 243, 244,
	244, 245, 246, 247, 247, 248, 249, 249, 250, 251, 252, 252, 253, 254, 255, 255,
	256,
};
//= == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
// Q8 格式 log2 计算(64/128/256 LUT)
// 关键:Q8 归一化到 [256,511] → delta 8bit
//=============================================================================
int32_t q8_log2_64lut(int32_t x)
{
	
	uint32_t val = (uint32_t)x;
	int32_t exp = 0;
	if (x <= 0) return 0;

	while (val >= 2UL * Q8_SCALE){
		val >>= 1; exp++;
	}
	while (val < Q8_SCALE)     {
		val <<= 1; exp--;
	}

	uint32_t d = val - Q8_SCALE;  // [0,255]
	uint8_t idx = d >> 2;           // 64点:右移2位
	uint8_t frac = d & 0x03;         // 插值 0~3

	int32_t y0 = log2_table_q8_64[idx];
	int32_t y1 = log2_table_q8_64[idx + 1];
	int32_t interp = y0 + ((y1 - y0)*frac) >> 2;
	return (exp * Q8_SCALE) + interp;  // 输出 Q10
}

int32_t q8_log2_128lut(int32_t x)
{
	
	uint32_t val = (uint32_t)x;
	int32_t exp = 0;
	if (x <= 0) return 0;

	while (val >= 2UL * Q8_SCALE){
		val >>= 1; exp++;
	}
	while (val < Q8_SCALE)     {
		val <<= 1; exp--;
	}

	uint32_t d = val - Q8_SCALE;
	uint8_t idx = d >> 1;
	uint8_t frac = d & 0x01;

	int32_t y0 = log2_table_q8_128[idx];
	int32_t y1 = log2_table_q8_128[idx + 1];
	int32_t interp = y0 + ((y1 - y0)*frac) >> 1;
	return (exp * Q8_SCALE) + interp;
}

int32_t q8_log2_256lut(int32_t x)
{
	
	uint32_t val = (uint32_t)x;
	int32_t exp = 0;
	if (x <= 0) return 0;

	while (val >= 2UL * Q8_SCALE){
		val >>= 1; exp++;
	}
	while (val < Q8_SCALE)     {
		val <<= 1; exp--;
	}

	uint8_t d = val - Q8_SCALE;
	uint8_t idx = d;
	uint8_t frac = 0;

	int32_t y0 = log2_table_q8_256[idx];
	return (exp * Q8_SCALE) + y0;
}



void test_q8_log2(void) {


	uint32_t i = 0;  // log2(10) ≈ 3.3219
	int32_t res64, res128, res256;
	FILE *fp = fopen("lv_fixed_q8_LUT-new.txt", "w");
	fprintf(fp, "ID  Q10-64 LUT	Q10 64 lut 浮点	Q10-128 LUT	Q10-128 LUT 浮点  Q10-256 LUT Q10-256 LUT 浮点	理论值\n");
	for (i = 256; i <= 256 * 512; i += 26)
	{

		res64 = q8_log2_64lut(i);
		res128 = q8_log2_128lut(i);
		res256 = q8_log2_256lut(i);
		fprintf(fp, "%.4f  %4d		%.6f		%4d		%.6f		%4d		%.6f		%.6f\n", (float)i / 256, res64, (float)res64 / Q8_SCALE, res128, (float)res128 / Q8_SCALE, res256, (float)res256 / Q8_SCALE, log2f((float)i / 256.0));

		//fprintf(fp, "%.6f 	  %d		%.6f		%.6f\n", (float)i / 1024,  res256, (float)res256 / Q10_SCALE, log2f((float)i / 1024.0));
	}
}

int main() {
	test_q8_log2();
	return 0;
}

结果对比,64LUT/128LUT/256LUT计算中,256LUT的结果精度比较高,和理论值差距比较小。后面三个为理论值和三个查找表计算的差值。对比如下:

三、总结

兼顾速度和内存等多方面因素,我们可以考虑是使用Q10 的64LUT计算log2定点数据。

相关推荐
奇妙之二进制几秒前
zmq源码分析之signaler_t
linux·服务器·网络
weixin_41306321几秒前
比较阅读理解opencv 和 LuminanceHDR中 色调映射Drago算法
opencv·算法·计算机视觉·hdr·色调映射
自我意识的多元宇宙几秒前
【数据结构】图----图的应用(拓扑排序)
数据结构·算法
聊点儿技术2 分钟前
海外玩家伪装来源? 怎么用IP归属地识别
网络·tcp/ip·游戏·游戏安全·ip地址查询·查ip归属地·海外玩家
汤愈韬2 分钟前
防火墙双机热备之VRRP
网络·网络协议·security
itzixiao5 分钟前
L1-055 谁是赢家(10 分)[java][python]
java·python·算法
阿Y加油吧6 分钟前
小林计算机网络・传输篇TCP/UDP|三次握手|四次挥手|可靠传输
网络
ghie90906 分钟前
运用强跟踪无迹卡尔曼滤波来实现捷联惯导的初始对准
算法
其实防守也摸鱼8 分钟前
面试常问问题总结--渗透测试工程师方向
网络·sql·面试·职场和发展·xss·工具·owasp
菜菜的顾清寒15 分钟前
力扣HOT100(21)相交链表
算法·leetcode·链表