【问题标题】:How to make the following code of bilinear interpolation more efficient?如何让下面的双线性插值代码更有效率?
【发布时间】:2014-01-01 16:15:15
【问题描述】:

以下代码是使用双线性插值放大图片。

slow_rescale函数中哪里可以修改,提高效率?

我希望从计算机组织原理的角度对其进行修改。

期待您的回答!

谢谢!

unsigned char *slow_rescale(unsigned char *src, int src_x, int src_y, int dest_x, int dest_y)
{
 double step_x,step_y;          // Step increase as per instructions above
 unsigned char R1,R2,R3,R4;     // Colours at the four neighbours
 unsigned char G1,G2,G3,G4;
 unsigned char B1,B2,B3,B4;
 double RT1, GT1, BT1;          // Interpolated colours at T1 and T2
 double RT2, GT2, BT2;
 unsigned char R,G,B;           // Final colour at a destination pixel
 unsigned char *dst;            // Destination image - must be allocated here! 
 int x,y;               // Coordinates on destination image
 double fx,fy;              // Corresponding coordinates on source image
 double dx,dy;              // Fractional component of source image    coordinates

 dst=(unsigned char *)calloc(dest_x*dest_y*3,sizeof(unsigned char));   // Allocate and clear   destination image
 if (!dst) return(NULL);                           // Unable to allocate image

 step_x=(double)(src_x-1)/(double)(dest_x-1);
 step_y=(double)(src_y-1)/(double)(dest_y-1);

 for (x=0;x<dest_x;x++)         // Loop over destination image
  for (y=0;y<dest_y;y++)
  {
    fx=x*step_x;
    fy=y*step_y;
    dx=fx-(int)fx;
    dy=fy-(int)fy;   
    getPixel(src,floor(fx),floor(fy),src_x,&R1,&G1,&B1);    // get N1 colours
    getPixel(src,ceil(fx),floor(fy),src_x,&R2,&G2,&B2); // get N2 colours
    getPixel(src,floor(fx),ceil(fy),src_x,&R3,&G3,&B3); // get N3 colours
    getPixel(src,ceil(fx),ceil(fy),src_x,&R4,&G4,&B4);  // get N4 colours
   // Interpolate to get T1 and T2 colours
   RT1=(dx*R2)+(1-dx)*R1;
   GT1=(dx*G2)+(1-dx)*G1;
   BT1=(dx*B2)+(1-dx)*B1;
   RT2=(dx*R4)+(1-dx)*R3;
   GT2=(dx*G4)+(1-dx)*G3;
   BT2=(dx*B4)+(1-dx)*B3;
   // Obtain final colour by interpolating between T1 and T2
   R=(unsigned char)((dy*RT2)+((1-dy)*RT1));
   G=(unsigned char)((dy*GT2)+((1-dy)*GT1));
   B=(unsigned char)((dy*BT2)+((1-dy)*BT1));
  // Store the final colour
  setPixel(dst,x,y,dest_x,R,G,B);
 }
  return(dst);
}
void getPixel(unsigned char *image, int x, int y, int sx, unsigned char *R, unsigned char *G, unsigned char *B)
{
 // Get the colour at pixel x,y in the image and return it using the provided RGB pointers
 // Requires the image size along the x direction!
 *(R)=*(image+((x+(y*sx))*3)+0);
 *(G)=*(image+((x+(y*sx))*3)+1);
 *(B)=*(image+((x+(y*sx))*3)+2);
}

void setPixel(unsigned char *image, int x, int y, int sx, unsigned char R, unsigned char G, unsigned char B)
{
 // Set the colour of the pixel at x,y in the image to the specified R,G,B
 // Requires the image size along the x direction!
 *(image+((x+(y*sx))*3)+0)=R;
 *(image+((x+(y*sx))*3)+1)=G;
 *(image+((x+(y*sx))*3)+2)=B;
}

【问题讨论】:

  • 你能显示getPixel()和setPixel()吗?
  • 我刚刚编辑了一下,添加了getPixel()和setPixel()的函数。@self.
  • 除了完全改变算法之外,您还可以删除一些多余的乘法。在 getPixel:int x, int y, int sx((x+(y*sx))*3)+0 传递给函数。
  • 您可能想在CodeReview 上问这个问题,那里会更合适。
  • -O3?也许分析?然后,尽管由于编译器优化,其中一些可能是浪费/无效的:内联 get/setPixel;更改它们,以便完成一次 32 位从/到 mem 的读/写(注意字节顺序),可能是 4/8 字节对齐(你可以用“R G B 0 R G B 0”而不是“R G B R G B”吗?);一些更多的临时变量(例如对于 floor(fx))......

标签: c interpolation performance


【解决方案1】:

我一直担心图像处理性能。以下是一些需要牢记的明显注意事项:

数值精度:

从您的代码中跳出来的第一件事是使用双精度来表示步长、颜色值和坐标。你真的需要这些数量的精确度吗?如果没有,您可能会在使用定点或浮点数时进行一些分析以检查代码的性能。

请记住,这是一个与硬件相关的问题,性能可能会或可能不会成为问题,具体取决于您的硬件是实现双精度、仅浮点还是都不实现(然后两者都在软件中实现)。这方面的讨论还包括内存对齐、合并内存访问等。当然这些话题涉及到“计算机组织原理”,还有更多discussion on this topic is here

循环展开:

您是否也考虑过手动loop unrolling?这可能有帮助,也可能没有帮助,因为您的编译器可能已经尝试利用此类优化,但至少值得考虑,因为您对可能较大的数组大小进行了双循环。

数字冗余:

在您的 getPixel() 函数中,您还为每个 RGB 分量计算 image+((x+(y*sx))*3,这似乎没有改变,为什么不在函数开始时计算一次这个数量?

矢量处理:

如果不首先考虑是否可以利用矢量处理,就很难考虑优化这样的代码。您是否可以访问向量化指令集,例如 SSE?

并行处理:

大多数系统都安装了 OpenMP。如果是这样,您可能会考虑重组代码以利用处理器的多核功能。使用 pragma 实现这一点非常简单,当然值得一试。

编译器标志:

另外,虽然您没有直接提及,但编译标志会影响 C 代码的性能。例如,如果使用 gcc,您可以使用以下方法比较性能差异:

gcc -std=c99 -o main main.c

对比

gcc -std=c99 -O3 -o main main.c 

【讨论】:

    【解决方案2】:

    这里有一些想法:

    1. 使用fixed-point arithmetic 而不是浮点数。这将使floorceil(可能还有乘法,虽然我不确定)这样的计算更快。
    2. ceil(x) 替换为floor(x)+1
    3. 使用strength reductionfx=x*step_x 中的乘法替换为加法
    4. 如果您知道内存中像素的布局,请将getPixel 替换为更有效的方式
    5. 使用以下代码转换将两个乘法减少为一个:(dx*R2)+(1-dx)*R1 ==> R1+dx*(R2-R1)
    6. Unroll the inner loop
    7. (最后,但可能最具潜力)使用矢量化编译器或手动编辑代码以使用 SSE 或其他 SIMD 技术(如果在您的平台上可用)

    【讨论】:

    • ceil(x) 不等于 floor(x) + 1 如果 x 已经是一个整数。
    • 啊,我明白了 - OP 自己错误地使用了ceil(x)。我以为您是在暗示两者是等价的,而不是纠正 OP 对前者的滥用。
    • @Alnitak 我认为 OP 对 ceil 的使用没有任何问题。另外,如果ceil 的参数是整数,那么dx=0,计算结果乘以0,所以它不正确是可以的(但是,可能需要避免从错误地址读取,这可能会导致在页面错误和其他方面)。
    • 是的,碰巧它会退化为正确答案,但我认为这更有可能是运气而不是判断。
    【解决方案3】:

    在这段代码中可以大大减少乘法运算。

    dx 可以在外循环中计算,我们可以在那里为RT1=(dx*R2)+(1-dx)*R1 等进一步的操作准备乘法表,因为乘法(R2,R1 等)是 1 字节大小。

    以下代码的运行速度比我机器上的原始代码快约 10 倍(Mac OS,带有 -O3 的 Mac C++ 编译器):

    #include <stdio.h>
    #include <math.h>
    #include <stdlib.h>
    
    inline void fast_getPixel(unsigned char *image, int x, int y, int sx, unsigned char *R, unsigned char *G, unsigned char *B)
    {
        // Get the colour at pixel x,y in the image and return it using the provided RGB pointers
        // Requires the image size along the x direction!
        unsigned char *ptr = image+((x+(y*sx))*3);
        *R=ptr[0];
        *G=ptr[1];
        *B=ptr[2];
    }
    
    inline void fast_setPixel(unsigned char *image, int x, int y, int sx, unsigned char R, unsigned char G, unsigned char B)
    {
        // Set the colour of the pixel at x,y in the image to the specified R,G,B
        // Requires the image size along the x direction!
        unsigned char *ptr = image+((x+(y*sx))*3);
        ptr[0]=R;
        ptr[1]=G;
        ptr[2]=B;
    }
    
    void build_dx_table(double* table,double dx)
    {
        unsigned len = 0xff;
        table[0] = 0;
        for (unsigned i=1;i<len;i++)
        {
            table[i] = table[i-1]+dx;
        }
    }
    
    unsigned char *fast_rescale(unsigned char *src, int src_x, int src_y, int dest_x, int dest_y)
    {
        double step_x,step_y;          // Step increase as per instructions above
        unsigned char R1,R2,R3,R4;     // Colours at the four neighbours
        unsigned char G1,G2,G3,G4;
        unsigned char B1,B2,B3,B4;
        double RT1, GT1, BT1;          // Interpolated colours at T1 and T2
        double RT2, GT2, BT2;
        unsigned char R,G,B;           // Final colour at a destination pixel
        unsigned char *dst;            // Destination image - must be allocated here!
        int x,y;               // Coordinates on destination image
        double fx,fy;              // Corresponding coordinates on source image
        double dx,dy;              // Fractional component of source image    coordinates
        double dxtable[0xff];
    
        dst=(unsigned char *)calloc(dest_x*dest_y*3,sizeof(unsigned char));   // Allocate and clear   destination image
        if (!dst) return(NULL);                           // Unable to allocate image
    
        step_x=(double)(src_x-1)/(double)(dest_x-1);
        step_y=(double)(src_y-1)/(double)(dest_y-1);
    
        for (x=0,fx=0;x<dest_x;x++,fx+=step_x)         // Loop over destination image
            dx=fx-(int)fx;
            build_dx_table(dxtable,dx);
            for (y=0,fy=0;y<dest_y;y++,fy+=step_y)
            {
                dy=fy-(int)fy;
                fast_getPixel(src,floor(fx),floor(fy),src_x,&R1,&G1,&B1);    // get N1 colours
                fast_getPixel(src,ceil(fx),floor(fy),src_x,&R2,&G2,&B2); // get N2 colours
                fast_getPixel(src,floor(fx),ceil(fy),src_x,&R3,&G3,&B3); // get N3 colours
                fast_getPixel(src,ceil(fx),ceil(fy),src_x,&R4,&G4,&B4);  // get N4 colours
                // Interpolate to get T1 and T2 colours
                RT1=dxtable[R2-R1]+R1;
                GT1=dxtable[G2-G1]+G1;
                BT1=dxtable[B2-B1]+B1;
                RT2=dxtable[R4-R3]+R3;
                GT2=dxtable[G4-G3]+G3;
                BT2=dxtable[B4-B3]+B3;
                // Obtain final colour by interpolating between T1 and T2
                R=(unsigned char)(dy*(RT2-RT1)+RT1);
                G=(unsigned char)(dy*(GT2-GT1)+GT1);
                B=(unsigned char)(dy*(BT2-BT1)+BT1);
                // Store the final colour
                fast_setPixel(dst,x,y,dest_x,R,G,B);
            }
        return(dst);
    }
    

    【讨论】:

    • 当我在我的机器上运行你的代码时,它给出了一个调试错误消息“DAMAGE:after normal block(#161) at 0x0092B040”。我认为问题与数组 dxtable[] 有关,但我不知道为什么。你能帮我解决这个问题吗? @LiMar
    【解决方案4】:

    GPU 具有为您执行双线性插值的硬件。在 CPU 上执行此操作就像在软件中执行浮点运算而不使用浮点硬件(例如 x87 或 SSE/AVX)。我最好的建议是考虑优化算法,例如 bicubic interpolation 或一般图像过滤器,它们可能会提供更好的视觉效果,但大多数 GPU 不支持这些算法。 Graphic Gems III,尽管它很古老,但在“通用过滤图像重新缩放”方面有一个很好的部分,用于放大和缩小。

    但是,如果您仍想在 CPU 上进行双线性插值,则应考虑 CPU 上的硬件加速。在那种情况下,我会考虑使用 SIMD。请参阅此链接bilinear-pixel-interpolation-using-sse,它显示了如何使用 SSE 进行双线性插值。我已经测试了这段代码,SSE 代码要快得多。您可以将其与 OpenMP 结合使用,以在大图像上使用多个线程。

    我还测试了定点代码,发现它比使用 MSVC2010 的非 SSE 代码提供更好的结果,但在 MSVC2012 中则不然。我希望对于大多数现代编译器来说,定点代码不会更好,除非它在没有浮点硬件的嵌入式系统上运行。

    【讨论】:

      猜你喜欢
      • 2013-06-13
      • 1970-01-01
      • 1970-01-01
      • 2017-06-08
      • 2020-08-22
      • 1970-01-01
      • 2014-12-07
      • 1970-01-01
      • 2019-12-11
      相关资源
      最近更新 更多