调整存储为 '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.w
和im.h
是原始图像的宽度和高度。 w
和 h
是目标宽度和高度。
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;
}
有没有办法使这个功能更快:例如,通过创建一种更优化的方式来访问像素而不是这种跨步读取?另外,我在这里注意到,在我的情况下:
源图像和调整大小的图像的尺寸将是固定的,因此我的 'custom' 调整大小功能不必与大小无关。我将从 640x360 尺寸变为 626x352 尺寸。
目标平台是带有 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;
}
我有一段 C 代码是 public 存储库 (Darknet) 的一部分,它应该使用双线性插值调整图像大小。由于其余代码处理图像的方式,图像存储为一维数组,其中从原始 3 通道图像中读取像素值。像素(x, y, k)
(x:列,y:行,k:通道)对应的值因此存储在一维数组中的位置x + w.h + w.h.c
。
实际上是 Darknet 一部分的调整大小功能在预处理阶段花费了大量时间,可能是因为它嵌套的 for 循环遍历行和列并尝试访问相应的值,如以及可能的类型转换:因此我正在尝试创建一个更优化的版本。调整大小的原始代码如下。 im
是原始图像,因此im.w
和im.h
是原始图像的宽度和高度。 w
和 h
是目标宽度和高度。
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;
}
有没有办法使这个功能更快:例如,通过创建一种更优化的方式来访问像素而不是这种跨步读取?另外,我在这里注意到,在我的情况下:
源图像和调整大小的图像的尺寸将是固定的,因此我的 'custom' 调整大小功能不必与大小无关。我将从 640x360 尺寸变为 626x352 尺寸。
目标平台是带有 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;
}