【问题标题】:Image convolution at specific points特定点的图像卷积
【发布时间】:2015-04-23 06:14:19
【问题描述】:

scipy(或其他类似库)中是否有一种方法可以仅在某些所需点处获得具有给定内核的图像的卷积?

我正在寻找类似的东西:

ndimage.convolve(image, kernel, mask=mask)

当需要应用内核时,mask 包含True(或1),否则为False(或0)。

编辑:执行我正在尝试做的事情的示例 python 代码(但不比使用 scipy 的整个图像卷积更快):

def kernel_responses(im, kernel, mask=None, flatten=True):
    if mask is None:
        mask = np.ones(im.shape[:2], dtype=np.bool)

    ks = kernel.shape[0]//2

    data = np.pad(im, ks, mode='reflect')
    y, x = np.where(mask)

    responses = np.empty(y.shape[0], float)

    for k, (i, j) in enumerate(zip(y, x)):
        responses[k] = (data[i:i+ks*2+1, j:j+ks*2+1] * kernel).sum()

    if flatten:
        return responses

    result = np.zeros(im.shape[:2], dtype=float)
    result[y, x] = responses
    return result

上面的代码在wrap 边界条件下完成了这项工作,但是内部循环是在python 中的,因此速度很慢。我想知道scipy/opencv/skimage中是否已经实现了更快的东西。

【问题讨论】:

  • @tom10 并不是要忽略评估中的要点。这个想法是在某些时候忽略评估。我不想对整个图像进行卷积,我只想要给定(比如 20)点的内核响应。我想通过“计算给定点的内核响应”来加速传统的“卷积图像并获取点”。

标签: python numpy scipy convolution scikit-image


【解决方案1】:

我知道我正在回复我自己的答案,我希望下面的代码能够进一步改进,或者它可能对其他用户有用。

下面的代码是一个 cython/python 实现:

Python:

def py_convolve(im, kernel, points):
    ks = kernel.shape[0]//2
    data = np.pad(im, ks, mode='constant', constant_values=0)
    return cy_convolve(data, kernel, points)

赛通:

import numpy as np
cimport cython

@cython.boundscheck(False)
def cy_convolve(unsigned char[:, ::1] im, double[:, ::1] kernel, Py_ssize_t[:, ::1] points):
    cdef Py_ssize_t i, j, y, x, n, ks = kernel.shape[0]
    cdef Py_ssize_t npoints = points.shape[0]
    cdef double[::1] responses = np.zeros(npoints, dtype='f8')

    for n in range(npoints):
        y = points[n, 0]
        x = points[n, 1]
        for i in range(ks):
            for j in range(ks):
                responses[n] += im[y+i, x+j] * kernel[i, j]

     return np.asarray(responses)

与其他方法的比较

下表显示了 4 种方法的评估:

  1. 问题中我的python方法
  2. @Vighnesh Birodkar 的方法
  3. 用 scipy 完成图像卷积
  4. 我在这篇文章中的 python/cython 实现

每一行依次对应于 3 个不同图像的这些方法(coinscameralena 分别来自 skimage.data)并且每一列对应于不同数量的点来计算内核响应(以百分比表示,意思是“计算图像点的x% 中的响应”)。

为了计算少于50% 点的内核响应,我的实现比卷积整个图像要快,但不是更快..

编辑:用于测试的内核窗口是 5x5 统一窗口 (np.ones((5,5)))。

['303x384']    1%     2%     5%    10%     20%     50%
1            4.97   9.58  24.32  48.28  100.39  245.77
2            7.60  15.09  37.42  75.17  150.09  375.60
3            3.05   2.99   3.04   2.88    2.96    2.98
4            0.17   0.22   0.38   0.60    1.10    2.49

['512x512']     1%     2%     5%     10%     20%     50%
1            10.68  21.87  55.47  109.16  223.58  543.73
2            17.90  34.59  86.02  171.20  345.46  858.24
3             6.52   6.53   6.74    6.63    6.43    6.60
4             0.31   0.43   0.78    1.34    2.73    6.82

['512x512']     1%     2%     5%     10%     20%     50%
1            13.21  21.45  54.98  110.80  217.11  554.96
2            19.55  34.78  87.09  172.33  344.58  893.02
3             6.87   6.82   7.00    6.60    6.64    7.71
4             0.35   0.47   0.87    1.57    2.47    6.07

注意:时间为ms

【讨论】:

  • 对于您的配置文件表,您应该添加内核形状(因为不同的内核大小显然会改变您的结果)。此外,还有一个错字建议,escribiste percentajes en espanol:“(在 percentajes 中表示...”
【解决方案2】:

我不知道有什么功能可以完全满足您的要求。如果您没有提供要卷积的点掩码,而是提供了点列表。 [(7, 7), (100, 100)] 那么它可能就像获取适当的图像补丁一样简单(比如与您提供的内核大小相同),将图像补丁和内核卷积,然后插入回原始图像。

这是一个编码示例,希望它足够接近您可以轻松修改:

[编辑:我注意到我在填充和补丁算术中遇到了几个错误。以前,你不能与边界上的一个点(比如 (0, 0))进行卷积,我将填充加倍,修复了一些算术,现在一切都很好。]

import cv2
import numpy as np
from scipy import ndimage
from matplotlib import pyplot as plt

def image_convolve_mask(image, list_points, kernel):
# list_points ex. [(7, 7), (100, 100)]
# assuming kernels of dims 2n+1 x 2n+1
rows, cols = image.shape
k_rows, k_cols = kernel.shape
r_pad = int(k_rows/2)
c_pad = int(k_cols/2)
# zero-pad the image in case desired point is close to border
padded_image = np.zeros((rows + 2*k_rows, cols + 2*k_cols))
# set the original image in the center
padded_image[k_rows: rows + k_rows, k_cols: cols + k_cols] = image
# should you prefer to use np.pad:
# padded_image = np.pad(image, (k_rows, k_cols), 'constant', constant_values=(0, 0))

for p in list_points:
    # extract pertinent patch from image
    # arbitrarily choosing the patch as same size as the kernel; change as needed
    patch = padded_image[p[0] + k_rows - r_pad: p[0] + 2*k_rows - r_pad, p[1] + k_cols - c_pad: p[1] + 2*k_cols - c_pad]

    # here use whatever function for convolution; I prefer cv2filter2D()
    # commented out is another option
    # conv = ndimage.convolve(patch, kernel, mode='constant', cval=0.0)
    conv = cv2.filter2D(patch, -1, kernel)
    # set the convolved patch back in to the image
    padded_image[p[0] + k_rows - r_pad: p[0] + 2*k_rows - r_pad, p[1] + k_cols - c_pad: p[1] + 2*k_cols - c_pad] = conv

return padded_image[k_rows: rows + k_rows, k_cols: cols + k_cols]

现在在图片上试用一下:

penguins = cv2.imread('penguins.png', 0)
kernel = np.ones((5,5),np.float32)/25
# kernel = np.array([[-1, -1, -1], [-1, 8, -1], [-1, -1, -1]], np.float32)
conv_image = image_convolve_mask(penguins, [(7, 7), (36, 192), (48, 207)], kernel)
plt.imshow(conv_image, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([])
plt.show()

我应用了一个 5x5 的平滑器,在像素 (7, 7) 周围看不到任何变化,但我选择了另外两个点作为最左边的两个企鹅喙的尖端。所以你可以看到平滑的补丁。

这是一个 lena512 图像,有 21 个卷积点(时间:0.006177 秒)。

[编辑 2:使用掩码生成行列表的示例,用于输入函数的集合元组。]

mask = np.eye(512)
k = np.ones((25, 25), np.float32)/625
list_mask = zip(np.where(mask==1)[0], np.where(mask==1)[1])
tic = time.time()
conv_image = image_convolve_mask(lena, list_mask, k)
print 'time: ', time.time()-tic # 0.08136 sec

【讨论】:

  • 谢谢!它与我正在寻找的东西足够接近(为此+1),但我仍然需要检查它的性能。 python 循环可能比仅使用 scipy 或 opencv 卷积整个图像要慢。
  • 不客气。如果你真的想使用数组掩码,显然你可以取出掩码,拉出 (row, col),然后将其转换为元组列表。至于性能,这一切都取决于你有多少分。我在 512x512 图像上运行了 20 个点,运行时间为 0.00286 秒。另外,如果您的点最终成为一个连续的补丁(例如一个大矩形),您可以修改上面的代码以仅对矩形进行卷积。
  • 再次非常感谢您。然而,该方法比用 scipy 卷积整个图像(我认为是 fortran/c++)要慢。我添加了一些分析的答案。
  • 由于没有其他实现,而你的帮助我制作了 cython 版本我刚刚给了你答案:P 谢谢你的帮助!
  • 还要注意np.pad函数
【解决方案3】:

你可以使用下面的代码sn-p。如果你的掩码足够密集,它可能不会那么低效。

def mask_conv(img, kernel, mask):
    out = filters.convolve(img, kernel)
    return np.where(mask, out, img)

一些示例代码

from skimage import data, draw, io, color
from scipy.ndimage import filters
import numpy as np

def mask_conv(img, kernel, mask):
    out = filters.convolve(img, kernel)
    return np.where(mask, out, img)

img = data.camera()
mask = np.zeros_like(img, dtype=np.bool)

kernel = np.ones((9,9))/100
circle = draw.circle(300, 350, 100)
mask[circle] = True

out = mask_conv(img, kernel, mask)

io.imshow(out)
io.show()

【讨论】:

  • 这需要对整个图像进行卷积,这是我试图避免的。我的mask 是我想要评估内核的图像周围的一些稀疏像素。
  • 用我正在寻找的示例 python 代码编辑了答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-17
  • 2018-08-10
  • 2019-09-29
相关资源
最近更新 更多