C语言中MLX90640红外图像放大的最优实现方案探讨
你已经实现了将MLX90640的32×24浮点红外图像放大至320×240像素的C函数,且通过预处理器宏完成了初步优化。以下是进一步的高效优化思路:
现有实现回顾
1. 温度值转RGB565颜色映射
#define TEMPERATURE_MAP_SIZE 21 static const USHORT GRADIENT_MAP[TEMPERATURE_MAP_SIZE+20] = //filler zeroes { 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0x001f, //Position 20 for 20degrees 0x00dc,0x0199,0x0276,0x0333,0x0410, 0x04cc,0x0589,0x0666,0x0723,0x07e0, 0x1f20,0x3660,0x4d80,0x64c0,0x8400, 0x9b20,0xb260,0xc980,0xe0c0,0xf800 //Position 40 for 40degrees }; static inline USHORT get_gradient(uint_fast16_t val) { if(val<20)return GRADIENT_MAP[20]; if(val>40)return GRADIENT_MAP[40]; return GRADIENT_MAP[val]; }
2. 像素化放大逻辑
#define RESIZE_FACTOR 10 #define RESIZE_FACTOR_SQUARED RESIZE_FACTOR*RESIZE_FACTOR #define RESIZE_FACTOR_BY_COLUMN_NUM MLX90640_COLUMN_NUM*RESIZE_FACTOR #define RESIZE_FACTOR_SQUARED_BY_COLUMN_NUM RESIZE_FACTOR_SQUARED*MLX90640_COLUMN_NUM static inline void resize_pixelmap() { USHORT *pixelmap_data_ptr,*pixelmap_resized_data_ptr; float *arr=get_ir_sensor_data_pointer(); pixelmap_data_ptr=ir_icon_pixelmap_data; pixelmap_resized_data_ptr=ir_icon_pixelmap_resized_data; for(int x=0;x<MLX90640_LINE_NUM;x++) { for(int y=0;y<MLX90640_COLUMN_NUM;y++) { pixelmap_data_ptr[MLX90640_COLUMN_NUM*x+y]=get_gradient((uint_fast16_t)roundf(arr[MLX90640_COLUMN_NUM*x+y])); for(int k=0;k<RESIZE_FACTOR;k++) { for(int j=0;j<RESIZE_FACTOR;j++) { pixelmap_resized_data_ptr[j+k*RESIZE_FACTOR_BY_COLUMN_NUM+y*RESIZE_FACTOR+x*RESIZE_FACTOR_SQUARED_BY_COLUMN_NUM]=pixelmap_data_ptr[MLX90640_COLUMN_NUM*x+y]; } } } } }
具体优化思路
一、温度映射与取整环节优化
替换
roundf为快速整数转换roundf是浮点库函数,调用开销较大。由于MLX90640输出的温度值均为正数,可直接用(uint_fast16_t)(arr[offset] + 0.5f)替代(uint_fast16_t)roundf(arr[offset]),完全等价且无需调用浮点函数,速度提升明显。简化梯度映射数组与索引逻辑
原数组前20个元素均为0,可裁剪数组只保留20~40度对应的21个值:static const USHORT GRADIENT_MAP[] = { 0x001f,0x00dc,0x0199,0x0276,0x0333,0x0410, 0x04cc,0x0589,0x0666,0x0723,0x07e0,0x1f20, 0x3660,0x4d80,0x64c0,0x8400,0x9b20,0xb260, 0xc980,0xe0c0,0xf800 };同时简化索引计算:先将温度值钳位在20~40之间,再直接取数组对应位置:
static inline USHORT get_gradient(uint_fast16_t val) { val = (val < 20) ? 20 : (val > 40) ? 40 : val; return GRADIENT_MAP[val - 20]; }此举减少数组内存占用,降低缓存命中率压力,同时简化分支判断。
手动内联映射逻辑
即使标记了static inline,部分编译器可能仍保留函数调用开销。可直接将温度映射逻辑写入主循环,彻底消除函数调用的额外开销。
二、放大循环的内存与计算优化
消除中间数组
pixelmap_data
当前实现先将颜色值写入中间数组再复制,多了一次内存读写。可直接计算颜色值后填充到目标放大数组,节省内存带宽:// 去掉pixelmap_data相关代码,直接计算颜色值并填充 USHORT color = get_gradient((uint_fast16_t)(arr[MLX90640_COLUMN_NUM*x+y] + 0.5f)); // 直接用color填充放大区域优化循环顺序与内存访问连续性
原循环内存访问是跳跃式的,可调整为先填充同一行的10个连续像素,再处理下一行,充分利用CPU缓存行:// 预计算当前原始像素对应的放大区域起始地址 USHORT *target_base = pixelmap_resized_data_ptr + y*RESIZE_FACTOR + x*RESIZE_FACTOR_SQUARED_BY_COLUMN_NUM; for(int k=0;k<RESIZE_FACTOR;k++) { USHORT *target_row = target_base + k*RESIZE_FACTOR_BY_COLUMN_NUM; // 连续填充10个像素 for(int j=0;j<RESIZE_FACTOR;j++) { target_row[j] = color; } }批量填充替代逐像素赋值
针对固定10倍放大,可展开循环或利用编译器内置批量复制函数,减少循环控制开销:// 展开j循环示例 target_row[0] = color; target_row[1] = color; target_row[2] = color; target_row[3] = color; target_row[4] = color; target_row[5] = color; target_row[6] = color; target_row[7] = color; target_row[8] = color; target_row[9] = color;若平台支持SIMD(如ARM NEON、x86 SSE),可利用SIMD指令一次性写入多个16位像素值,大幅提升填充速度。
预计算地址偏移量
将循环中重复计算的地址偏移量提前算出,避免每次循环的乘法运算:const size_t x_offset = x * RESIZE_FACTOR_SQUARED_BY_COLUMN_NUM; for(int y=0;y<MLX90640_COLUMN_NUM;y++) { const size_t y_offset = y * RESIZE_FACTOR; USHORT *target_base = pixelmap_resized_data_ptr + x_offset + y_offset; // 后续循环直接使用target_base }
三、硬件与编译优化
开启编译器优化选项
启用最高级别的优化(如GCC的-O3,ARM的-O3 -march=native -mfpu=neon),编译器会自动完成循环展开、向量化、常量折叠等优化,无需手动修改代码即可获得显著性能提升。内存对齐优化
将ir_icon_pixelmap_resized_data数组设置为缓存行对齐,避免跨缓存行的拆分访问:USHORT ir_icon_pixelmap_resized_data[320*240] __attribute__((aligned(32)));嵌入式平台的DMA利用
若运行在带DMA的嵌入式平台(如STM32),可在计算出颜色值后,启动DMA将该值批量复制到目标区域,让CPU同时处理下一个像素的温度转换,实现并行处理,降低CPU占用率。
内容的提问来源于stack exchange,提问作者Christoph B

