【问题标题】:Is there a chance to make the bilinear interpolation faster?有没有机会使双线性插值更快?
【发布时间】:2018-05-05 02:31:52
【问题描述】:

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

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

更新 带有 MVP 的 github 存储库位于问题的底部。

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

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

为了实现这一点,我使用了writeablebitmapex repository 中的调整大小功能并添加了合并/混合。

一切正常,但我想减少执行时间。

这是当前的调试时间:

// 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 是像素图像的大小
  • width和height是输出图像的目标尺寸
  • 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

【问题讨论】:

  • C 还是 C++?您正在编写 C++,但乍一看,代码很像 C。决定使用一种语言。
  • 您通过标记 C 和C++ 来混淆事物。它们是不同的语言。微软当然只提供“Visual C++”,它恰好也有一个(可怜且有些隐藏的)C 编译器,从而增加了这种混乱,因此人们经常最终编写“C++ 中的 C 代码”......
  • @Grantly:编译器肯定会折叠这样的常量表达式。
  • @datoml:在这种情况下,您可能不需要担心矢量化(手动或自动) - 只需尝试使用启用优化的发布版本运行 - 您应该会看到显着的速度提升。
  • @PaulR 哦,快...我讨厌微软。我遇到了一个帖子,说托管 c++ 的编译器在优化等方面完全不好。所以我将此函数转移到我的本机 c++ 库中,并将托管部分仅用作我的 c# 代码的包装器。现在整个过程在 1ms 内完成:O。太棒了。

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


【解决方案1】:

可以加快代码速度的一个措施是避免从整数到浮点数的类型转换,反之亦然。这可以通过在合适的范围内使用 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 ;

等等。有点棘手但可行

【讨论】:

  • 感谢您的提示。我将尝试重构我的代码以避免这种类型的转换。我会回来报告的。
  • 这行l0 = (fracx * c1a + (width - fracx) * c2a) / width ; 是否会导致警告warning C4244: '=': conversion from 'int' to 'float', possible loss of data
  • 我试图用你的代码替换我的代码部分,但是输出图像被破坏了。缩放后的图片颜色不对,有点变形。
  • @datoml:意图是l0 也是int 类型。
  • @marom:好主意,但这不是通常应用整数算术优化的方式,因为您总是希望摆脱整数除法,这是一条非常慢的指令。因此,您的版本比使用浮点运算的版本更慢也就不足为奇了。还缺少舍入。
【解决方案2】:

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

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

编辑:

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

【讨论】:

    【解决方案3】:

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

    1. 利用x0fracx 独立于行以及y0fracy 独立于列的事实。即使您没有从 x 循环中提取 y0fracy 的计算,编译器优化也应该注意这一点。但是,对于x0fracx,需要预先计算所有列的值并将它们存储在一个数组中。计算 x0fracx 的复杂度变为 O(width) 与 O(width*height) 相比,无需预先计算。

    2. 用整数进行整个处理,用整数运算代替浮点运算,从而使用移位运算而不是整数除法。

    为了更好的可读性,我没有在下面的代码中实现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));
    

    【讨论】:

    • 感谢您的回答。这段代码是优化代码的骨架吗?我需要在特殊处理块中添加颜色吗?
    • 它是任何类型的 8 位图像/通道(灰度、RGB 之一等)的双线性插值代码。添加更多频道是直截了当的。当然sy,y0,fracysx,x0,fracx对所有频道都有效。
    • 更准确地说,它是使用双线性插值调整大小的代码,并使用整数算法进行了优化。
    • 好的。那么当我将此代码与 int* 目标一起使用时,int* source 目标图像应该是具有新大小的源图像?
    • 哦,好的。我的输出和输入图像是 32BppRGBA。所以我需要扩展代码来支持这种格式?
    猜你喜欢
    • 2018-02-09
    • 1970-01-01
    • 1970-01-01
    • 2015-11-04
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    • 1970-01-01
    • 2016-01-30
    相关资源
    最近更新 更多