调整存储为 'strided' 数组的图像的大小:我可以使这种双线性插值更快吗?

Resizing an image stored as a 'strided' array: can I make this bilinear interpolation faster?

我有一段 C 代码是 public 存储库 (Darknet) 的一部分,它应该使用双线性插值调整图像大小。由于其余代码处理图像的方式,图像存储为一维数组,其中从原始 3 通道图像中读取像素值。像素(x, y, k)(x:列,y:行,k:通道)对应的值因此存储在一维数组中的位置x + w.h + w.h.c

实际上是 Darknet 一部分的调整大小功能在预处理阶段花费了大量时间,可能是因为它嵌套的 for 循环遍历行和列并尝试访问相应的值,如以及可能的类型转换:因此我正在尝试创建一个更优化的版本。调整大小的原始代码如下。 im是原始图像,因此im.wim.h是原始图像的宽度和高度。 wh 是目标宽度和高度。

image resize_image(image im, int w, int h)
{
    image resized = make_image(w, h, im.c);   
    image part = make_image(w, im.h, im.c);
    int r, c, k;
    float w_scale = (float)(im.w - 1) / (w - 1);
    float h_scale = (float)(im.h - 1) / (h - 1);
    for(k = 0; k < im.c; ++k){
        for(r = 0; r < im.h; ++r){
            for(c = 0; c < w; ++c){
                float val = 0;
                if(c == w-1 || im.w == 1){
                    val = get_pixel(im, im.w-1, r, k);
                } else {
                    float sx = c*w_scale;
                    int ix = (int) sx;
                    float dx = sx - ix;
                    val = (1 - dx) * get_pixel(im, ix, r, k) + dx * get_pixel(im, ix+1, r, k);
                }
                set_pixel(part, c, r, k, val);
            }
        }
    }
    for(k = 0; k < im.c; ++k){
        for(r = 0; r < h; ++r){
            float sy = r*h_scale;
            int iy = (int) sy;
            float dy = sy - iy;
            for(c = 0; c < w; ++c){
                float val = (1-dy) * get_pixel(part, c, iy, k);
                set_pixel(resized, c, r, k, val);
            }
            if(r == h-1 || im.h == 1) continue;
            for(c = 0; c < w; ++c){
                float val = dy * get_pixel(part, c, iy+1, k);
                add_pixel(resized, c, r, k, val);
            }
        }
    }
    free_image(part);
    return resized;
}

有没有办法使这个功能更快:例如,通过创建一种更优化的方式来访问像素而不是这种跨步读取?另外,我在这里注意到,在我的情况下:

  1. 源图像和调整大小的图像的尺寸将是固定的,因此我的 'custom' 调整大小功能不必与大小无关。我将从 640x360 尺寸变为 626x352 尺寸。

  2. 目标平台是带有 ARM CPU 的 NVIDIA Jetson,因此像 AVX2 这样的指令不适用于我的情况。但我确实可以访问 CUDA。

我必须在这里说明一下,由于我的项目的要求,这个调整大小函数实际上是从 Python 调用的库 (.so) 的一部分。所以我不能保留任何东西 "in memory" 本身,例如 CUDA 纹理对象等,所以一次又一次地创建它们实际上可能会在 CUDA 端产生更多开销。

任何改进此例程的建议都会非常有帮助。

[正如 Stargateur 提到的]get_pixel 等。都是浪费。大多数像素访问都可以用指针处理。在处理需要速度的图像时,这是一个非常标准的事情。

大多数访问沿着 x 维度蔓延,因此我们可以只增加指针。

来自 get_pixel,创建此函数:

static float *loc_pixel(image m, int x, int y, int c)
{
    return &m.data[(c * m.h * m.w) + (y * m.w) + x];
}

resize_image 中的 if 可以通过一些重组移出第一个内部 for 循环。

在所有 for 循环中,我们可以使用 loc_pixel 和指针从内部循环中删除所有 *_pixel 函数。

这是一个重构版本,它只使用应该更快的指针。请注意,我已经对此进行了编码,但既没有测试也没有编译它。我认为它非常接近,但你应该仔细检查以确定。

你可以补充的一件事我没有做的是 loc_pixel 指针 指向图像(即 image *m)而不是传递整个结构。

此外,您可以尝试将 src[0] 替换为 src[c],将 *dst 替换为 dst[c]。这将消除一些 ++src++dst 并且 可能 更快。它还可能允许编译器更好地理解循环,因此它可以使用任何 arm 向量指令,并可能使其更适合 CUDA。 YMMV.

image
resize_image(image im, int w, int h)
{
    image resized = make_image(w, h, im.c);
    image part = make_image(w, im.h, im.c);
    int r,
     c,
     k;
    float w_scale = (float) (im.w - 1) / (w - 1);
    float h_scale = (float) (im.h - 1) / (h - 1);
    int wm1 = w - 1;
    float val;
    float marg;
    float *src;
    float *dst;

    for (k = 0; k < im.c; ++k) {
        for (r = 0; r < im.h; ++r) {
            src = loc_pixel(im, 0, r, k);
            dst = loc_pixel(part, 0, r, k);
            marg = get_pixel(im, im.w - 1, r, k);

            if (im.w == 1) {
                for (c = 0; c < w; ++c, ++dst)
                    *dst = marg;
                continue;
            }

            for (c = 0; c < wm1; ++c, ++src, ++dst) {
                float sx = c * w_scale;
                int ix = (int) sx;
                float dx = sx - ix;
                val = (1 - dx) * src[0] + dx * src[1];
                *dst = val;
            }

            // handle c == w - 1 case
            *dst = marg;
        }
    }

    for (k = 0; k < im.c; ++k) {
        for (r = 0; r < h; ++r) {
            float sy = r * h_scale;
            int iy = (int) sy;
            float dy = sy - iy;

            src = loc_pixel(part, 0, iy, k);
            dst = loc_pixel(resized, 0, r, k);
            for (c = 0; c < w; ++c, ++src, ++dst) {
                val = (1 - dy) * src[0];
                *dst = val;
            }

            if (r == h - 1 || im.h == 1)
                continue;

            src = loc_pixel(part, 0, iy + 1, k);
            dst = loc_pixel(resized, 0, r, k, val);
            for (c = 0; c < w; ++c, ++src, ++dst) {
                val = dy * src[0];
                *dst += val;
            }
        }
    }

    free_image(part);
    return resized;
}