加快对图像的方形样本求和

Speeding up square sample summing over image

我正在尝试从另一个数组创建一个二维数组,方法是对图像上某个点周围的 NxN 像素求和,并将结果保存在新图像中的相同坐标处:

def sum_black(image: np.ndarray, size=11) -> np.ndarray:
    assert(size % 2)
    pad = (size - 1) // 2
    iH, iW = image.shape[:2]
    image = ((255 - image) / 255).astype(np.float32)
    image = cv.copyMakeBorder(image, pad, pad, pad, pad, cv.BORDER_CONSTANT, None, 0)
    output = np.zeros((iH, iW), dtype="float32")

    for y in range(iH):
        for x in range(iW):
            output[y, x] = image[y-pad:y+pad, x-pad:x+pad].sum()

    output -= np.min(output)

    return 255 - (output / np.max(output)) * 255

但是,循环遍历输入图像并围绕一个点求和在较大的图像上似乎非常慢。有什么方便的方法可以加快速度,还是我必须用线程实现并发?

您要获取的是滑动 window 的总和。您可以通过使用 1s 的 NxN 矩阵对二维数组进行卷积来使用 numpy 执行此操作,但是由于您使用的是 opencv,我将分享 opencv 方法,因为它更快。

cv2.boxFilter(image, -1, (size,size), normalize=False, borderType=cv2.BORDER_CONSTANT)

boxfilter 将图像与 sizeXsize 的矩阵进行卷积,通常通过除以 size*size 来归一化,得到平均值。我们想要总和,所以我们设置 normalize=False。 borderType 常量告诉 opencv 用常量值填充图像的边缘(在本例中为 0)

是的,您可以使用内核和 filter2D。 filter2D 做了什么它需要你的内核(一些 N x N 大小的二维矩阵)并且它在每个位置逐元素相乘,然后总结答案。要获得区域中所有像素的总和,您可以使用填充了 1 的内核。

注意:我使用了 borderType = BORDER_ISOLATED 这意味着图像外的像素被假定为零。

import cv2
import numpy as np

# create image
img = np.zeros((5, 5), np.float32);
img[:] = 10;
print("Image: ");
print(img);
print("-------------------");

# filter
size = 3; # use an odd number
kernel = np.ones((size, size), np.float32);

# sum
convolved = cv2.filter2D(img, -1, kernel, borderType = cv2.BORDER_ISOLATED);
print("Convolved: ");
print(convolved);

打印结果如下:

Image:
[[10. 10. 10. 10. 10.]
 [10. 10. 10. 10. 10.]
 [10. 10. 10. 10. 10.]
 [10. 10. 10. 10. 10.]
 [10. 10. 10. 10. 10.]]
-------------------
Convolved:
[[40. 60. 60. 60. 40.]
 [60. 90. 90. 90. 60.]
 [60. 90. 90. 90. 60.]
 [60. 90. 90. 90. 60.]
 [40. 60. 60. 60. 40.]]

编辑:我测试了 filter2D 和 boxFilter 的速度,看看它是否真的像 O(N^2) 与 O(1) 一样大。根据图像大小或迭代次数测量时,增长因子没有差异,但是 Yves 在内核大小方面是正确的。

这是我用来测试的代码:

import cv2
import numpy as np
import time

# tweakables
kernel_size = 300;
img_size = 1000;
iters = 500;

# create image
img = np.zeros((img_size, img_size), np.float32);
img[:] = 10;

# create kernel
kernel = np.ones((kernel_size, kernel_size), np.float32);

# convolve with filter2D
start = time.time();
for a in range(iters):
    convolved = cv2.filter2D(img, -1, kernel, borderType = cv2.BORDER_ISOLATED);
end = time.time();
print("Filter2D: " + str(end - start));

# convolve with box filter
dst = np.copy(img);
start = time.time();
for a in range(iters):
    cv2.boxFilter(img, 0, (kernel_size,kernel_size), dst, (-1,-1), False, cv2.BORDER_ISOLATED);
end = time.time();
print("BoxFilter: " + str(end - start));

# kernel_size = 3, img_size = 1000, iters = 500:
# 1.10 seconds with filter2D
# 1.18 seconds with boxFilter

# kernel_size = 3, img_size = 2000, iters = 500: 
# 4.80 seconds with filter2D
# 5.07 seconds with boxFilter

# kernel_size = 3, img_size = 1000, iters = 5000:
# 11.13 seconds with filter2D
# 11.41 seconds with boxFilter

# kernel_size = 30, img_size = 1000, iters = 500:
# 10.52 seconds with filter2D
# 1.63 seconds with boxFilter

# kernel_size = 60, img_size = 1000, iters = 500:
# 13.09 seconds with filter2D
# 1.64 seconds with boxFilter

# kernel_size = 300, img_size = 1000, iters = 500:
# 28.01 seconds with filter2D
# 2.43 seconds with boxFilter