You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 21:12:34