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]
为了进行比较,我们可以展示上述代码等效于使用scipy
的convolve2D
函数。以下是可复现的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.filter2D
和scipy.signal.convolve2d
来提取卷积结果的有效部分。就精度而言,C
是cv2.filter2D
的输出,而C2
是convolve2d
的输出,两者都是等效的。请特别注意两个输出数组的实际内容和形状。
但是,如果你希望保持原始图像的大小,并用过滤后的结果替换受影响的像素,只需复制原始图像并使用与剪切无效信息时相同的索引逻辑,在复制中用卷积结果替换那些像素:
C_copy = A.copy()
C_copy[H[0]:-H[0],H[1]:-H[1]] = C
(100,100)
、类型为np.float64
的数组返回了不同的值。断言(arr==arr2).all()
失败了,而且arr-arr2
并不全为零。 - mLstudent33np.allclose
。不过,看一下下面的答案也可能会对你有所帮助。 - rayryeng我会根据查看scipy源代码的结果修改已接受的答案:
它计算有效输出大小为
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