【问题标题】:How do I use only numpy to apply filters onto images?如何仅使用 numpy 对图像应用过滤器?
【发布时间】:2020-11-12 03:35:34
【问题描述】:

我想对图像应用过滤器/内核来改变它(例如,执行垂直边缘检测、对角线模糊等)。我发现这个wikipedia page 有一些有趣的内核示例。

当我在网上查看时,过滤器是使用 opencv 或默认的 matplotlib/Pillow 函数实现的。我希望能够仅使用 numpy 数组和矩阵乘法等函数来修改图像(似乎没有默认的 numpy 函数来执行卷积运算。)我已经非常努力地想出来但是我一直在犯错误,而且我对 numpy 也比较陌生。

我编写了这段代码来将图像转换为灰度:

import numpy as np
from PIL import Image

img = Image.open("my_path/my_image.jpeg")
img = np.array(img.resize((180, 320)))
grey = np.zeros((320, 180))

grey_avg_array = (np.sum(img,axis=-1,keepdims=False)/3)
grey_avg_array = grey_avg_array.astype(np.uint8)

grey_image = Image.fromarray(grey_avg_array)

我试图将我的图像乘以一个 numpy 数组 [[1, 0, -1], [1, 0, -1], [1, 0, -1]] 来实现边缘检测,但这给了我广播错误。一些示例代码/有用的函数可以做到这一点而不会出错?

另外:我整天都面临的一个小问题是 PIL 无法将 (x, x, 1) 形状的数组显示为图像。为什么是这样?我该如何解决这个问题? (np.squeeze 没用)

【问题讨论】:

  • 查看opencv它拥有各种图像处理能力
  • 为什么不想使用 PIL 或 OpenCV?仅使用 NumPy 实现它的目的是什么?您是否查找了卷积操作的描述,它通常是如何实现的?您是否尝试实施它?先这样做。如果遇到麻烦,请查看此处有关实现卷积的其他数百个问题。如果您仍然无法使其正常工作,那么您可以在此处发布问题。然后,您的问题将被充分了解并对其他人有用。这个问题既不是

标签: python numpy image-processing matrix edge-detection


【解决方案1】:

效率不高,但您可以通过以下方式扩展代码以检测边缘:

edge = np.zeros([322, 182])

for i in range(grey_avg_array.shape[0]-2):
    for j in range(grey_avg_array.shape[1]-2):
        edge[i+1, j+1] = np.sum(grey_avg_array[i:i+3, j:j+3]*[[1, 0, -1], [1, 0, -1], [1, 0, -1]])

edge = edge.astype(np.uint8)
edge_img = Image.fromarray(edge)
edge_img

要在(比如说)Jupyter Notebook 中显示图像,您只需键入变量名称(在您完成 Image.fromarray() 之后),正如我在上面最后一行中所写的那样。

【讨论】:

  • 这不是卷积。这实际上是没有意义的,因为写入输出数组的值会在以后的循环迭代中被覆盖。
【解决方案2】:

注意:我强烈建议您查看 OpenCV,它具有多种内置图像过滤器。

另外:我整天面临的一个小问题是 PIL 无法将 (x, x, 1) 形状的数组显示为图像。为什么是这样?我该如何解决这个问题? (np.squeeze 没用)

我认为这里的问题是处理灰度浮点数组。要解决此问题,您必须将浮点数组转换为 np.uint8 并在 PIL 中使用 'L' 模式。

img_arr = np.random.rand(100, 100) # Our float array in the range (0, 1)
uint8_img_arr = np.uint8(img_arr * 255) # Converted to the np.uint8 type

img = Image.fromarray(uint8_img_arr, 'L') # Create PIL Image from img_arr

至于卷积,SciPy 提供了functions 用于使用您可能会觉得有用的内核进行卷积。

但由于我们只使用 NumPy,让我们实现它!

注意:为了尽可能通用,我添加了一些对您可能重要或不重要的额外参数。

# Assuming the image has channels as the last dimension.
# filter.shape -> (kernel_size, kernel_size, channels)
# image.shape -> (width, height, channels)
def convolve(image, filter, padding = (1, 1)):
    # For this to work neatly, filter and image should have the same number of channels
    # Alternatively, filter could have just 1 channel or 2 dimensions
    
    if(image.ndim == 2):
        image = np.expand_dims(image, axis=-1) # Convert 2D grayscale images to 3D
    if(filter.ndim == 2):
        filter = np.repeat(np.expand_dims(filter, axis=-1), image.shape[-1], axis=-1) # Same with filters
    if(filter.shape[-1] == 1):
        filter = np.repeat(filter, image.shape[-1], axis=-1) # Give filter the same channel count as the image
    
    #print(filter.shape, image.shape)
    assert image.shape[-1] == filter.shape[-1]
    size_x, size_y = filter.shape[:2]
    width, height = image.shape[:2]
    
    output_array = np.zeros(((width - size_x + 2*padding[0]) + 1, 
                             (height - size_y + 2*padding[1]) + 1,
                             image.shape[-1])) # Convolution Output: [(W−K+2P)/S]+1
    
    padded_image = np.pad(image, [
        (padding[0], padding[0]),
        (padding[1], padding[1]),
        (0, 0)
    ])
    
    for x in range(padded_image.shape[0] - size_x + 1): # -size_x + 1 is to keep the window within the bounds of the image
        for y in range(padded_image.shape[1] - size_y + 1):

            # Creates the window with the same size as the filter
            window = padded_image[x:x + size_x, y:y + size_y]

            # Sums over the product of the filter and the window
            output_values = np.sum(filter * window, axis=(0, 1)) 

            # Places the calculated value into the output_array
            output_array[x, y] = output_values
            
    return output_array

下面是它的用法示例:

原图(另存为original.png):

filter = np.array([
    [1, 1, 1],
    [1, 1, 1],
    [1, 1, 1]
], dtype=np.float32)/9.0 # Box Filter

image = Image.open('original.png')
image_arr = np.array(image)/255.0

convolved_arr = convolve(image_arr, filter, padding=(1, 1))
convolved = Image.fromarray(np.uint8(255 * convolved_arr), 'RGB') # Convolved Image

卷积图像:

【讨论】:

  • 如何显示输出图像只是 convolved.show() ?我应用了相同的过滤器,输出就像彩虹一样显示
【解决方案3】:

一些事情:

  • OpenCVSciPyscikit-image 都使用 Numpy 数组作为存储和操作图像的标准方式,并且在很大程度上都可以与Numpy 和彼此

  • 关于用形状(x,y,1) 绘制im,您可以只取第零个平面并绘制它,即newim = im[...,0]


将 RGB 图像转换为灰度图像时,无需将所有 RGB 分量相加并除以 3,您只需计算平均值即可:

grey = np.mean(im, axis=2)

实际上,ITU-R 601-2 中推荐的权重是

L = 0.299 * Red + 0.587 * Green + 0.114 * Blue

所以,您可以使用np.dot() 来做到这一点:

grey = np.dot(RGBimg[...,:3], [0.299, 0.587,0.114]).astype(np.uint8)

至于寻找垂直边缘,您可以使用 Numpy 执行此操作,方法是从其最右边的一个像素中减去每个像素,即差分。这是一个小例子,我还用 Numpy 绘制了形状,所以你可以看到一种不使用 OpenCV 的方法,因为它似乎让你很不高兴;-)

#!/usr/bin/env python3

import numpy as np
# Create a test image with a white square on black
rect = np.zeros((200,200), dtype=np.uint8)
rect[40:-40,40:-40] = 255

# Create a test image with a white circle on black
xx, yy = np.mgrid[:200, :200]
circle = (xx - 100) ** 2 + (yy - 100) ** 2
circle = (circle<4096).astype(np.uint8)*255

# Concatenate side-by-side to make our test image
im = np.hstack((rect,circle))

现在看起来像这样:

# Calculate horizontal differences only finding increasing brightnesses
d = im[:,1:] - im[:,0:-1]

# Calculate horizontal differences finding increasing or decreasing brightnesses
d = np.abs(im[:,1:].astype(np.int16) - im[:,0:-1].astype(np.int16))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-22
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多