卷积操作不使用任何填充-OpenCV Python

5
在Opencv-python中是否有任何函数可以对图像进行卷积,而不需要填充?基本上,我希望在内核和图像部分完全重叠的区域中进行卷积,从而得到一幅只在这些区域内进行卷积的图像。

1
使用Scipy的2D卷积函数,并选择“valid”选项,仅更新有效的元素。 - Divakar
2个回答

10

OpenCV只支持对图像进行卷积,其中返回的输出与输入图像大小相同。因此,您仍然可以使用OpenCV的滤波函数,但只需忽略那些卷积核没有完全包含在图像内部的边缘像素即可。假设您的图像卷积核是奇数,则可以将每个维度除以二,取下限(或向下舍入),并使用它们来削减不合法的信息并返回剩余内容。正如Divakar所提到的,这与使用scipy的2D卷积方法'valid'选项相同。

因此,假设您的图像存储在A中,卷积核存储在B中,您只需执行以下操作即可获得完全包含在图像中的卷积核的过滤图像。请注意,我们将假设卷积核为奇数,输出结果存储在C中。

import cv2
import numpy as np

A = cv2.imread('...') # Load in image here
B = (1.0/25.0)*np.ones((5,5)) # Specify kernel here
C = cv2.filter2D(A, -1, B) # Convolve

H = np.floor(np.array(B.shape)/2).astype(np.int) # Find half dims of kernel
C = C[H[0]:-H[0],H[1]:-H[1]] # Cut away unwanted information

请注意,cv2.filter2D执行的是相关操作,而不是卷积操作。但是,如果卷积核是对称的(即如果你对它进行转置后等于自身),那么相关和卷积是等价的。如果不是这种情况,您需要在使用cv2.filter2D之前将卷积核旋转180度。您可以通过简单地执行以下操作来实现:

B = B[::-1,::-1]

为了进行比较,我们可以展示上述代码等效于使用scipyconvolve2D函数。以下是可复现的IPython会话,展示了这一点:

In [41]: import cv2

In [42]: import numpy as np

In [43]: from scipy.signal import convolve2d

In [44]: A = np.reshape(np.arange(49), (7,7)).astype(np.float32)

In [45]: A
Out[45]:
array([[  0.,   1.,   2.,   3.,   4.,   5.,   6.],
       [  7.,   8.,   9.,  10.,  11.,  12.,  13.],
       [ 14.,  15.,  16.,  17.,  18.,  19.,  20.],
       [ 21.,  22.,  23.,  24.,  25.,  26.,  27.],
       [ 28.,  29.,  30.,  31.,  32.,  33.,  34.],
       [ 35.,  36.,  37.,  38.,  39.,  40.,  41.],
       [ 42.,  43.,  44.,  45.,  46.,  47.,  48.]], dtype=float32)

In [46]: B = (1.0/25.0)*np.ones((5,5), dtype=np.float32)

In [47]: B
Out[47]:
array([[ 0.04,  0.04,  0.04,  0.04,  0.04],
       [ 0.04,  0.04,  0.04,  0.04,  0.04],
       [ 0.04,  0.04,  0.04,  0.04,  0.04],
       [ 0.04,  0.04,  0.04,  0.04,  0.04],
       [ 0.04,  0.04,  0.04,  0.04,  0.04]], dtype=float32)

In [48]: C = cv2.filter2D(A, -1, B)

In [49]: H = np.floor(np.array(B.shape)/2).astype(np.int)

In [50]: C = C[H[0]:-H[0],H[1]:-H[1]]

In [51]: C
Out[51]:
array([[ 15.99999809,  16.99999809,  18.        ],
       [ 22.99999809,  24.        ,  24.99999809],
       [ 29.99999809,  30.99999809,  31.99999809]], dtype=float32)

In [52]: C2 = convolve2d(A, B, mode='valid')

In [53]: C2
Out[53]:
array([[ 15.99999905,  17.00000191,  18.00000191],
       [ 22.99999809,  23.99999809,  24.99999809],
       [ 29.99999809,  30.99999809,  31.99999809]], dtype=float32)

这个例子相当简单易懂。我声明了一个7x7的虚拟矩阵,其中数值从0到48逐行增加。我还声明了一个5x5的核心元素为(1/25)的矩阵,这将实现一个5x5的平均滤波器。因此我们使用cv2.filter2Dscipy.signal.convolve2d来提取卷积结果的有效部分。就精度而言,Ccv2.filter2D的输出,而C2convolve2d的输出,两者都是等效的。请特别注意两个输出数组的实际内容和形状。

但是,如果你希望保持原始图像的大小,并用过滤后的结果替换受影响的像素,只需复制原始图像并使用与剪切无效信息时相同的索引逻辑,在复制中用卷积结果替换那些像素:

C_copy = A.copy()
C_copy[H[0]:-H[0],H[1]:-H[1]] = C 

这个方法对于一个小的示例数组是有效的,但是对于一个形状为(100,100)、类型为np.float64的数组返回了不同的值。断言(arr==arr2).all()失败了,而且arr-arr2并不全为零。 - mLstudent33
1
@mLstudent33 浮点数比较是很危险的。你应该使用 np.allclose。不过,看一下下面的答案也可能会对你有所帮助。 - rayryeng

0

我会根据查看scipy源代码的结果修改已接受的答案:

(请参见https://github.com/scipy/scipy/blob/2526df72e5d4ca8bad6e2f4b3cbdfbc33e805865/scipy/signal/firfilter.c#L137)

它计算有效输出大小为

else if (outsize == VALID) {Os[0] = Ns[0]-Nwin[0]+1; Os[1] = Ns[1]-Nwin[1]+1;}

其中N对应A的维度,Nwin对应B的。

因此替换为:

H = np.floor(np.array(B.shape)/2).astype(np.int)
C = C[H[0]:-H[0],H[1]:-H[1]]

使用

H = np.floor(np.array(B.shape)/2).astype(np.int)
outdims = (np.array(A.shape) - np.array(B.shape)) + 1
C = C[H[0]:H[0]+outdims[0], H[1]:H[1]+outdims[1]]

运行以下代码片段可以说明两种方法之间输出大小的差异:
import cv2
import numpy as np
from scipy.signal import convolve2d

A = np.reshape(np.arange(49), (7,7)).astype(np.float32)
B = np.random.rand(2, 2)  # convolve with an even kernel
C = cv2.filter2D(A, -1, B)
C2 = convolve2d(A, B, mode='valid')
H = np.floor(np.array(B.shape)/2).astype(np.int)
C = C[H[0]:-H[0],H[1]:-H[1]]
shapes_match = C.shape == C2.shape
print shapes_match #  prints False

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接