是否有机会更快地进行双线性插值?

时间:2017-11-21 15:06:11

标签: c++ c image performance image-scaling

首先,我想为您提供一些背景信息。

我需要合并两种图像。第一幅图像是背景图像,格式为8BppGrey,分辨率为320x240。第二个图像是格式为32BppRGBA且分辨率为64x48的forground图像。

更新 带有MVP的github repo位于问题的最底层。

为此,我使用双线性插值将第二个图像调整为与第一个图像相同的大小,然后使用混合将两者合并到一个图像。仅当第二个图像的alpha值大于0时才会发生混合。

我需要尽快完成,所以我的想法是结合调整大小和合并/混合过程。

为实现这一目标,我使用了writeablebitmapex repository中的resize函数并添加了合并/混合。

一切都按预期工作,但我想减少执行时间。

这是当前的调试时间:

// CPU: Intel(R) Core(TM) i7-4810MQ CPU @ 2.80GHz

MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 5 ms.
MediaServer: Execution time in c++ 4 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 3 ms
MediaServer: Resizing took 3 ms.
MediaServer: Execution time in c++ 4 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 6 ms
MediaServer: Resizing took 6 ms.
MediaServer: Execution time in c++ 3 ms
MediaServer: Resizing took 3 ms.

我是否有机会提高性能并降低调整大小/合并/混合过程的执行时间?

我是否可以并行化某些部分?

我是否有机会使用某些处理器功能?

一个巨大的性能影响是嵌套循环,但我不知道如何更好地编写它。

我希望整个过程达到1或2毫秒。这甚至可能吗?

这是我使用的修改后的visual c ++函数。

  • pd是我用来显示的可写位图的后备缓冲区 导致wpf。我使用的格式是默认的32BppRGBA。
  • pixels是64x48 32BppRGBA图像的int []数组
  • widthSource和heightSource是像素图像的大小
  • 宽度和高度是输出图像的目标大小
  • baseImage是320x240 8BppGray图像的int []数组

VC ++代码:

unsigned int Resize(int* pd, int* pixels, int widthSource, int heightSource, int width, int height, byte* baseImage)
{
    unsigned int start = clock();

    float xs = (float)widthSource / width;
    float ys = (float)heightSource / height;

    float fracx, fracy, ifracx, ifracy, sx, sy, l0, l1, rf, gf, bf;
    int c, x0, x1, y0, y1;
    byte c1a, c1r, c1g, c1b, c2a, c2r, c2g, c2b, c3a, c3r, c3g, c3b, c4a, c4r, c4g, c4b;
    byte a, r, g, b;

    // Bilinear
    int srcIdx = 0;

    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            sx = x * xs;
            sy = y * ys;
            x0 = (int)sx;
            y0 = (int)sy;

            // Calculate coordinates of the 4 interpolation points
            fracx = sx - x0;
            fracy = sy - y0;
            ifracx = 1.0f - fracx;
            ifracy = 1.0f - fracy;
            x1 = x0 + 1;
            if (x1 >= widthSource)
            {
                x1 = x0;
            }
            y1 = y0 + 1;
            if (y1 >= heightSource)
            {
                y1 = y0;
            }

            // Read source color
            c = pixels[y0 * widthSource + x0];
            c1a = (byte)(c >> 24);
            c1r = (byte)(c >> 16);
            c1g = (byte)(c >> 8);
            c1b = (byte)(c);

            c = pixels[y0 * widthSource + x1];
            c2a = (byte)(c >> 24);
            c2r = (byte)(c >> 16);
            c2g = (byte)(c >> 8);
            c2b = (byte)(c);

            c = pixels[y1 * widthSource + x0];
            c3a = (byte)(c >> 24);
            c3r = (byte)(c >> 16);
            c3g = (byte)(c >> 8);
            c3b = (byte)(c);

            c = pixels[y1 * widthSource + x1];
            c4a = (byte)(c >> 24);
            c4r = (byte)(c >> 16);
            c4g = (byte)(c >> 8);
            c4b = (byte)(c);

            // Calculate colors
            // Alpha
            l0 = ifracx * c1a + fracx * c2a;
            l1 = ifracx * c3a + fracx * c4a;
            a = (byte)(ifracy * l0 + fracy * l1);

            // Write destination
            if (a > 0)
            {
                // Red
                l0 = ifracx * c1r + fracx * c2r;
                l1 = ifracx * c3r + fracx * c4r;
                rf = ifracy * l0 + fracy * l1;

                // Green
                l0 = ifracx * c1g + fracx * c2g;
                l1 = ifracx * c3g + fracx * c4g;
                gf = ifracy * l0 + fracy * l1;

                // Blue
                l0 = ifracx * c1b + fracx * c2b;
                l1 = ifracx * c3b + fracx * c4b;
                bf = ifracy * l0 + fracy * l1;

                // Cast to byte
                float alpha = a / 255.0f;
                r = (byte)((rf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));
                g = (byte)((gf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));
                b = (byte)((bf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));

                pd[srcIdx++] = (255 << 24) | (r << 16) | (g << 8) | b;
            }
            else
            {
                // Alpha, Red, Green, Blue                          
                pd[srcIdx++] = (255 << 24) | (baseImage[srcIdx] << 16) | (baseImage[srcIdx] << 8) | baseImage[srcIdx];
            }
        }
    }

    unsigned int end = clock() - start;
    return end;
}

Github repo

3 个答案:

答案 0 :(得分:3)

可以加速代码的一个操作是避免从整数到浮点数的类型转换,反之亦然。这可以通过在适当的范围内使用int值而不是在范围0..1

上的浮点数来实现

这样的事情:

for (int y = 0; y < height; y++)
{
    for (int x = 0; x < width; x++)
    {
        int sx1 = x * widthSource ;
        int x0 = sx1 / width;
        int fracx = (sx1 % width) ; // range 0..width - 1

变成类似

的东西
        l0 = (fracx * c2a + (width - fracx) * c1a) / width ;

等等。有点棘手但可行

答案 1 :(得分:0)

感谢您的帮助,但问题是托管的c ++项目。我现在将函数转移到我的本机c ++库,并仅将托管c ++部分用作c#应用程序的包装器。

编译器优化后,该函数现在在1ms完成。

编辑:

我现在将自己的答案标记为解决方案,因为@marom的优化会导致图像损坏。

答案 2 :(得分:0)

使用双线性插值加速调整大小操作的常用方法是:

  1. 利用x0fracx独立于行且y0fracy独立于该行的事实。即使你没有从x循环中取出y0fracy的计算,编译器优化也应该解决这个问题。但是,对于x0fracx,需要预先计算所有列的值并将它们存储在数组中。在没有预先计算的情况下,计算x0fracx的复杂度与O(宽度*高度)相比变为O(宽度)。

  2. 通过整数算术替换浮点算术,用整数进行整个处理,从而使用移位操作代替整数除法。

  3. 为了更好的可读性,我没有在以下代码中实现x0fracx的预计算。无论如何,预先计算是直截了当的。

    注意FACTOR = 2048是你在这里使用32位有符号整数的最大值(2048 * 2048 * 255就好了)。为了获得更高的精度,您应该切换到int64_t,然后分别增加FACTOR和SHIFT。

    我将边框检查放入内部循环以提高可读性。对于优化的实现,应该在这种情况发生之前通过在两个循环中迭代来移除它,并为边框像素添加特殊处理。

    如果有人想知道+ (FACTOR * FACTOR / 2)的用途,那就是与后续的分组一起进行四舍五入。

    最后请注意,(FACTOR * FACTOR / 2)2 * SHIFT在编译时进行评估。

    #define FACTOR      2048
    #define SHIFT       11
    
    const int xs = (int) ((double) FACTOR * widthSource / width + 0.5);
    const int ys = (int) ((double) FACTOR * heightSource / height + 0.5);
    
    for (int y = 0; y < height; y++)
    {
        const int sy = y * ys;
        const int y0 = sy >> SHIFT;
        const int fracy = sy - (y0 << SHIFT);
    
        for (int x = 0; x < width; x++)
        {
            const int sx = x * xs;
            const int x0 = sx >> SHIFT;
            const int fracx = sx - (x0 << SHIFT);
    
            if (x0 >= widthSource - 1 || y0 >= heightSource - 1)
            {
                // insert special handling here
                continue;
            }
    
            const int offset = y0 * widthSource + x0;
    
            target[y * width + x] = (unsigned char)
                ((source[offset] * (FACTOR - fracx) * (FACTOR - fracy) +
                source[offset + 1] * fracx * (FACTOR - fracy) +
                source[offset + widthSource] * (FACTOR - fracx) * fracy +
                source[offset + widthSource + 1] * fracx * fracy +
                (FACTOR * FACTOR / 2)) >> (2 * SHIFT));
        }
    }
    

    为了澄清,要匹配OP使用的变量,例如,在alpha通道的情况下,它是:

    a = (unsigned char)
        ((c1a * (FACTOR - fracx) * (FACTOR - fracy) +
        c2a * fracx * (FACTOR - fracy) +
        c3a * (FACTOR - fracx) * fracy +
        c4a * fracx * fracy +
        (FACTOR * FACTOR / 2)) >> (2 * SHIFT));