numpy python中带回调函数的图像卷积

exdqitrt  于 2022-11-24  发布在  Python
关注(0)|答案(1)|浏览(139)

我想在python中循环二值图像的像素,并根据周围像素的邻域设置像素值。类似于卷积,但我想创建一个方法,使用自定义函数设置中心像素的值,而不是将中心像素设置为邻域的算术平均值的普通卷积。
实际上,我想创建一个函数,它执行以下操作:

def convolve(img, conv_function = lambda subImg: np.mean(subImg)):
  newImage = emptyImage
  for nxn_window in img:
    newImage[center_pixel] = conv_function(nxn_window)
  return newImage

此刻我有一个办法但却是十分缓慢:

#B is the structuing array or convolution window/kernel
def convolve(func):
  def wrapper(img, B):
    #get dimensions of img
    length, width = len(img), len(img[0])

    #half width and length of dimensions
    hw = (int)((len(B) - 1) / 2)
    hh = (int)((len(B[0]) - 1) / 2)

    #convert to npArray for fast operations
    B = np.array(B)

    #initialize empty return image
    retVal = np.zeros([length, width])

    #start loop over the values where the convolution window has a neighborhood 
    for row in range(hh, length - hh):
        for pixel in range(hw, width - hw):
            #window as subarray of pixels
            window = [arr[pixel-hh:pixel+hh+1]
                           for arr in img[row-hw:row+hw+1]]
            retVal[row][pixel] = func(window, B)

    return retVal
  return wrapper

使用这个函数作为装饰器

# dilation
@convolve
def __add__(img, B):
    return np.mean(np.logical_and(img, B)) > 0

# erosion
@convolve
def __sub__(img, B):
    return np.mean(np.logical_and(img, B)) == 1

有没有提供这种类型函数的库,或者有没有更好的方法可以循环图像?

fnvucqvd

fnvucqvd1#

这里有一个想法:给每个像素分配一个数组,然后简单地把你的自定义函数应用到扩展的图像上。如果你的B形是(3,3),那么你需要9倍的内存)。

import numpy as np

def convolve2(func):
    def conv(image, kernel):
        """ Apply given filter on an image """
        k = kernel.shape[0]  # which is assumed equal to kernel.shape[1]
        width = k//2  # note that width == 1 for k == 3 but also width == 1 for k == 2
        a = framed(image, width)  # create a frame around an image to compensate for kernel overlap when shifting
        b = np.empty(image.shape + kernel.shape)  # add two more dimensions for each pixel's neighbourhood
        di, dj = image.shape[:2]  # will be used as delta for slicing
        # add the neighbourhood ('kernel size') to each pixel in preparation for the final step 
        # in other words: slide the image along the kernel rather than sliding the kernel along the image
        for i in range(k):
            for j in range(k):
                b[..., i, j] = a[i:i+di, j:j+dj]
        # apply the desired function
        return func(b, kernel)
    return conv
    

def framed(image, width):
    a = np.zeros(np.array(image.shape) + [2 * width, 2 * width])  # only add the frame to the first two dimensions
    a[width:-width, width:-width] = image  # place the image centered inside the frame
    return a

我已经使用了灰度图像512x512像素和过滤器3x3进行测试:

embossing_kernel = np.array([
    [-2, -1, 0],
    [-1, 1, 1],
    [0, 1, 2]
])

@convolve2
def filter2(img, B):
    return np.sum(img * B, axis=(2,3))

@convolve2
def __add2__(img, B):
    return np.mean(np.logical_and(img, B), axis=(2,3)) > 0

# image_gray is a 2D grayscale image (not color/RGB)
b = filter2(image_gray, embossing_kernel)

为了与我使用的卷积进行比较:

@convolve
def filter(img, B):
    return np.sum(img * B)

@convolve
def __add__(img, B):
    return np.mean(np.logical_and(img, B)) > 0

b = filter2(image_gray, embossing_kernel)

在我的机器上,convolve的时间是4.3秒,convolve2的时间是0.05秒。
在我的例子中,自定义函数需要指定操作的轴,即保存邻域数据的附加维度。也许轴也可以避免,但我没有尝试过。
注意:这适用于2D图像(灰度)(如您所问的二进制图像),但也可以很容易地扩展到3D(彩色)图像。在您的情况下,您可能会摆脱框架(或填充0或1,例如,在重复应用该功能的情况下)。
如果内存是一个问题,你可能想适应一个快速实现的卷积我已经张贴在这里:https://stackoverflow.com/a/74288118/20188124

相关问题