如何在C++中实现图像快速软件Binning(像素合并)?
图像快速Binning优化需求与方案
我正在尝试对以行主序存储在一维数组中的2D图像执行快速Binning(像素合并)操作。该图像为12位,因此使用uint16_t数据类型。
Binning定义:若执行2x2 Binning,则x和y方向各2个像素合并为一个超级像素,该超级像素的值为这4个像素的平均值。
我编写了两段代码:
- 一段为避免整数溢出,每次累加前将像素值除以
DIVISIONFACTOR,但因除法操作较多,耗时0.75ms; - 另一段先累加所有像素值(存在溢出风险,当总和超过65535时会溢出),最后仅执行一次除法,耗时仅0.13ms。
现寻求进一步优化方案,使用Intel C++编译器,欢迎提出Intel专属技术方案。
图像定义
WIDTH和HEIGHT为相机输出的实际图像尺寸,NX和NY为输出图像的最终尺寸。
#define NX 256 #define NY 256 #define WIDTH 768 #define HEIGHT 768 #define BINNINGFACTORWIDTH (WIDTH / NX) #define BINNINGFACTORHEIGHT (HEIGHT / NY) #define DIVISIONFACTOR (BINNINGFACTORWIDTH * BINNINGFACTORHEIGHT)
无溢出风险代码
int binning(uint16_t* image, uint16_t* binnedImage){ int i, j, k, l; for (j=0; j< NY; j++) { for (i=0;i < NX; i++) { binnedImage[j * NX + i] = 0; for (k=i * BINNINGFACTORWIDTH; k<i * BINNINGFACTORWIDTH + BINNINGFACTORWIDTH; k++) { for (l=j * BINNINGFACTORHEIGHT; l<j * BINNINGFACTORHEIGHT + BINNINGFACTORHEIGHT; l++) { binnedImage[j * NX + i] += (image[l * HEIGHT + k] / DIVISIONFACTOR); } } } } return 0; }
存在溢出风险代码
int binning(uint16_t* image, uint16_t* binnedImage){ int i, j, k, l; for (j=0; j< NY; j++) { for (i=0;i < NX; i++) { binnedImage[j * NX + i] = 0; for (k=i * BINNINGFACTORWIDTH; k<i * BINNINGFACTORWIDTH + BINNINGFACTORWIDTH; k++) { for (l=j * BINNINGFACTORHEIGHT; l<j * BINNINGFACTORHEIGHT + BINNINGFACTORHEIGHT; l++) { binnedImage[j * NX + i] += image[l * HEIGHT + k]; } } binnedImage[j * NX + i] /= DIVISIONFACTOR; } } return 0; }
完整可执行代码
#include <iostream> #include <chrono> #define NX 256 #define NY 256 #define WIDTH 768 #define HEIGHT 768 #define BINNINGFACTORWIDTH (WIDTH / NX) #define BINNINGFACTORHEIGHT (HEIGHT / NY) #define DIVISIONFACTOR (BINNINGFACTORWIDTH * BINNINGFACTORHEIGHT) using namespace std; int binning(uint16_t* image, uint16_t* binnedImage); int main() { chrono::high_resolution_clock::time_point t0, t1; chrono::duration<double> dt; double totalTime = 0; uint64_t count = 0; uint32_t i; uint16_t *image = (uint16_t*) malloc(sizeof(uint16_t) * WIDTH * HEIGHT); for (i=0; i < WIDTH * HEIGHT; i++) { image[i] = 4095; } uint16_t *binnedIimage = (uint16_t*) calloc(sizeof(uint16_t), NX * NY); while(1) { t0 = chrono::high_resolution_clock::now(); binning(image, binnedIimage); t1 = chrono::high_resolution_clock::now(); dt = chrono::duration_cast<chrono::duration<double>>(t1 - t0); totalTime += dt.count(); count += 1; if (count % 3000 == 0) { cout<<"Time (ms): "<< totalTime * 1000 / count<<"\r"; cout.flush(); } } return 0; } int binning(uint16_t* image, uint16_t* binnedImage){ int i, j, k, l; for (j=0; j< NY; j++) { for (i=0;i < NX; i++) { binnedImage[j * NX + i] = 0; for (k=i * BINNINGFACTORWIDTH; k<i * BINNINGFACTORWIDTH + BINNINGFACTORWIDTH; k++) { for (l=j * BINNINGFACTORHEIGHT; l<j * BINNINGFACTORHEIGHT + BINNINGFACTORHEIGHT; l++) { binnedImage[j * NX + i] += (image[l * HEIGHT + k]/ DIVISIONFACTOR); } } } } return 0; }
优化方案
1. 安全的累加-除法模式
当前场景是3x3 Binning(DIVISIONFACTOR=9),12位像素最大值为4095,总和为4095*9=36855,远小于uint16_t的最大值65535,完全没有溢出风险,可以直接使用累加后单次除法的版本。
如果未来需要更大的Binning因子(比如8x8),总和会超过65535,可改用uint32_t作为累加临时变量,既避免溢出又保留单次除法的高效性:
int binning(uint16_t* image, uint16_t* binnedImage){ int i, j, k, l; for (j=0; j< NY; j++) { for (i=0;i < NX; i++) { uint32_t sum = 0; // 32位无符号整数存储累加值 for (k=i * BINNINGFACTORWIDTH; k<i * BINNINGFACTORWIDTH + BINNINGFACTORWIDTH; k++) { for (l=j * BINNINGFACTORHEIGHT; l<j * BINNINGFACTORHEIGHT + BINNINGFACTORHEIGHT; l++) { sum += image[l * HEIGHT + k]; } } binnedImage[j * NX + i] = static_cast<uint16_t>(sum / DIVISIONFACTOR); } } return 0; }
2. 缓存友好的循环重排
原代码的图像访问是按列跳跃式读取,缓存命中率极低。调整循环顺序为行优先遍历,让图像访问连续,大幅提升缓存利用率:
int binning(uint16_t* image, uint16_t* binnedImage){ int i, j, k, l; memset(binnedImage, 0, NX * NY * sizeof(uint16_t)); for (j=0; j< NY; j++) { // 遍历当前Binning块的所有行 for (l=j * BINNINGFACTORHEIGHT; l<j * BINNINGFACTORHEIGHT + BINNINGFACTORHEIGHT; l++) { // 遍历每个输出像素的列块 for (i=0;i < NX; i++) { // 遍历列块内的连续像素 for (k=i * BINNINGFACTORWIDTH; k<i * BINNINGFACTORWIDTH + BINNINGFACTORWIDTH; k++) { binnedImage[j * NX + i] += image[l * HEIGHT + k]; } } } } // 统一执行除法 for (j=0; j< NY; j++) { for (i=0;i < NX; i++) { binnedImage[j * NX + i] /= DIVISIONFACTOR; } } return 0; }
3. Intel编译器专属优化
3.1 启用高级编译优化
编译时添加以下参数,让Intel编译器自动进行循环展开、向量优化等:
icpc -O3 -xAVX2 your_code.cpp -o binning
-O3:最高级优化,包含循环展开、指令调度等-xAVX2:针对支持AVX2的CPU生成向量指令(根据CPU型号可替换为-xAVX512等)
3.2 显式向量化提示
在循环前添加#pragma ivdep,提示编译器忽略循环依赖,允许自动向量化:
int binning(uint16_t* image, uint16_t* binnedImage){ int i, j, k, l; memset(binnedImage, 0, NX * NY * sizeof(uint16_t)); #pragma ivdep for (j=0; j< NY; j++) { for (l=j * BINNINGFACTORHEIGHT; l<j * BINNINGFACTORHEIGHT + BINNINGFACTORHEIGHT; l++) { #pragma ivdep for (i=0;i < NX; i++) { for (k=i * BINNINGFACTORWIDTH; k<i * BINNINGFACTORWIDTH + BINNINGFACTORWIDTH; k++) { binnedImage[j * NX + i] += image[l * HEIGHT + k]; } } } } #pragma ivdep for (j=0; j< NY; j++) { for (i=0;i < NX; i++) { binnedImage[j * NX + i] /= DIVISIONFACTOR; } } return 0; }
3.3 使用Intel IPP库
Intel集成性能基元(IPP)提供了高度优化的图像处理函数,直接调用即可实现高效Binning:
#include "ipp.h" int binning(uint16_t* image, uint16_t* binnedImage){ IppiSize srcSize = {WIDTH, HEIGHT}; IppiSize dstSize = {NX, NY}; IppiRect srcRoi = {0, 0, WIDTH, HEIGHT}; IppiRect dstRoi = {0, 0, NX, NY}; // 使用区域插值模式实现平均Binning ippiResize_16u_C1R(image, WIDTH*sizeof(uint16_t), srcSize, srcRoi, binnedImage, NX*sizeof(uint16_t), dstSize, dstRoi, IPPI_INTER_AREA); return 0; }
编译时需链接IPP库:
icpc -O3 -xAVX2 your_code.cpp -o binning -lippi -lippcore -lippvm
4. 预计算地址偏移
提前计算循环内的地址偏移量,减少重复算术运算:
int binning(uint16_t* image, uint16_t* binnedImage){ int i, j, k, l; memset(binnedImage, 0, NX * NY * sizeof(uint16_t)); for (j=0; j< NY; j++) { const int l_start = j * BINNINGFACTORHEIGHT; const int l_end = l_start + BINNINGFACTORHEIGHT; const int bin_j_offset = j * NX; for (l=l_start; l<l_end; l++) { const int image_l_offset = l * HEIGHT; for (i=0;i < NX; i++) { const int k_start = i * BINNINGFACTORWIDTH; const int k_end = k_start + BINNINGFACTORWIDTH; uint16_t* bin_ptr = &binnedImage[bin_j_offset + i]; for (k=k_start; k<k_end; k++) { *bin_ptr += image[image_l_offset + k]; } } } } for (j=0; j< NY; j++) { const int bin_j_offset = j * NX; for (i=0;i < NX; i++) { binnedImage[bin_j_offset + i] /= DIVISIONFACTOR; } } return 0; }
内容的提问来源于stack exchange,提问作者Harsh M
相关产品推荐
相关产品推荐

