使用opencv Filter2D函数时如何避免包含零?

2024-06-07 11:59:27 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在使用opencv-python中的Filter2D函数,该图像的边缘有几个黑色填充值(零)。这里你可以找到我所说的一个例子:https://landsat.gsfc.nasa.gov/wp-content/uploads/2013/06/truecolor.jpg

当我在该图像上使用Filter2D时,来自黑色填充区域的像素被视为有效值,从而产生边缘伪影。我怎么能不在计算中包括零呢?例如,在IDL中,我可以使用“missing”和“invalid”字段,如下所示:

output = CONVOL(input, kernel, /EDGE_TRUNCATE, MISSING=0.0, INVALID=0.0, /NAN, /NORMALIZE)

避免了边缘问题,但我在opencv中找不到类似的功能。如何解决这个问题?在


Tags: 函数https图像contentopencvnasa边缘例子
3条回答

OpenCV函数中没有掩码或忽略参数。然而,唯一的伪影是图像的外部,即黑色区域。每当过滤器的锚定点(默认情况下,中间像素)位于边缘但位于黑色像素上时,它会将过滤结果添加到该像素。但是当定位点位于图像顶部时,黑色值不会向过滤器添加任何内容。所以,一个简单的解决方案是创建一个具有黑色值的遮罩,并从过滤后的图像中删除这些值。在

编辑:好,那么从IDL convol docs开始:

^{bq}$

因此,从这里我们可以看到,通过将无效像素处理为0,然后通过除以不同于内核大小的像素数(即有效像素数)来偏移和。在

这在OpenCV中是不可能的,至少在内置的过滤方法中是不可能的,因为OpenCV不会规范化过滤结果。请看in the docs for ^{}这个方程只是简单的相关性,没有除法。在

现在,您所能做的就是手动规范化。这并不难。如果您创建了一个掩码,其中值在正常图像内为1,在图像外部为0,那么具有与您的filter2D()相同内核大小的^{}将在每个位置生成内核窗口内的像素计数。这是一种正常化图像的方法。然后您可以简单地屏蔽这个boxFilter()结果,这样图像边界之外的值就会被忽略,最后,将您的filter2D()结果除以屏蔽的boxFilter()结果(忽略boxFilter()结果是0的地方,这样就不会被零除)。那应该正是你想要的。在

编辑2:这是一个具体的例子。首先,我将定义一个简单的图像(7x7,5x5内正方形为1s):

import cv2
import numpy as np

img = np.array([
    [0, 0, 0, 0, 0, 0, 0],
    [0, 1, 1, 1, 1, 1, 0],
    [0, 1, 1, 1, 1, 1, 0],
    [0, 1, 1, 1, 1, 1, 0],
    [0, 1, 1, 1, 1, 1, 0],
    [0, 1, 1, 1, 1, 1, 0],
    [0, 0, 0, 0, 0, 0, 0]], dtype=np.float32)

我们将继续使用一个简单的高斯核滤波示例:

^{pr2}$

现在首先,过滤图像。。。在

filtered = cv2.filter2D(img, -1, gauss_kernel)
print(filtered)

[[ 0.25    0.375   0.5     0.5     0.5     0.375   0.25  ]
 [ 0.375   0.5625  0.75    0.75    0.75    0.5625  0.375 ]
 [ 0.5     0.75    1.      1.      1.      0.75    0.5   ]
 [ 0.5     0.75    1.      1.      1.      0.75    0.5   ]
 [ 0.5     0.75    1.      1.      1.      0.75    0.5   ]
 [ 0.375   0.5625  0.75    0.75    0.75    0.5625  0.375 ]
 [ 0.25    0.375   0.5     0.5     0.5     0.375   0.25  ]]

这就是我们所期望的…一束1的高斯模糊度应该是,1s,然后我们在边缘有一些衰减,包括图像内部和外部的零区域。因此,我们将创建一个遮罩;在本例中,它与图像相同。然后在遮罩上做一个长方体过滤器,以获得正确的缩放值:

mask = img.copy()  # in this case, the mask is identical
scaling_vals = cv2.boxFilter(mask, -1, gauss_kernel.shape, borderType=cv2.BORDER_CONSTANT)
print(scaling_vals)

[[ 0.111  0.222  0.333  0.333  0.333  0.222   0.111]
 [ 0.222  0.444  0.666  0.666  0.666  0.444   0.222]
 [ 0.333  0.666  1.     1.     1.     0.666   0.333]
 [ 0.333  0.666  1.     1.     1.     0.666   0.333]
 [ 0.333  0.666  1.     1.     1.     0.666   0.333]
 [ 0.222  0.444  0.666  0.666  0.666  0.444   0.222]
 [ 0.111  0.222  0.333  0.333  0.333  0.222   0.111]]

注意,如果您将其乘以9(内核中VAL的数量),那么我们将得到精确的“像素位置周围的非零像素数”。这是我们的标准化比例因子。现在要做的就是…规范化并移除图像边界之外的内容。在

mask = mask.astype(bool)  # turn mask bool for indexing
normalized_filter = filtered.copy()
normalized_filter[mask] /= scaling_vals[mask]
normalized_filter[~mask] = 0
print(normalized_filter)

[[ 0.  0.     0.     0.     0.     0.     0. ]
 [ 0.  1.265  1.125  1.125  1.125  1.265  0. ]
 [ 0.  1.125  1.     1.     1.     1.125  0. ]
 [ 0.  1.125  1.     1.     1.     1.125  0. ]
 [ 0.  1.125  1.     1.     1.     1.125  0. ]
 [ 0.  1.265  1.125  1.125  1.125  1.265  0. ]
 [ 0.  0.     0.     0.     0.     0.     0. ]]

现在这些值并不完美,但也不是有偏和。即使在IDL文档中也要注意:

you should use caution when analyzing these values, as the result may be biased by having fewer points within the kernel.

所以你不能通过这样的缩放来获得完美的结果。但是,我们可以做得更好!我们使用的比例因子只使用点数,而不是每个点的实际权重。为此,我们可以用相关的权重过滤掩码。换句话说,只需在蒙版上运行filter2D(),而不是图像。显然,将图像除以此值只会将所有值转换为1;然后进行遮罩。我们完了!不要被这个例子搞糊涂了,因为蒙版和图像是一样的——在这种情况下,将过滤后的图像除以过滤后的遮罩得到1,但总的来说,这只是一个比框式过滤器更好的缩放比例。在

mask = img.copy()
scaling_vals = cv2.filter2D(mask, -1, gauss_kernel)

mask = mask.astype(bool)  # turn mask bool for indexing
normalized_filter = filtered.copy()
normalized_filter[mask] /= scaling_vals[mask]
normalized_filter[~mask] = 0
print(normalized_filter)

[[ 0.  0.  0.  0.  0.  0.  0.]
 [ 0.  1.  1.  1.  1.  1.  0.]
 [ 0.  1.  1.  1.  1.  1.  0.]
 [ 0.  1.  1.  1.  1.  1.  0.]
 [ 0.  1.  1.  1.  1.  1.  0.]
 [ 0.  1.  1.  1.  1.  1.  0.]
 [ 0.  0.  0.  0.  0.  0.  0.]]

这是一个进行均值滤波的示例,但可能适合您的问题:

im[maskInvalid] = 0
maskValid = np.logical_not(maskInvalid)

# mean filter, but without dividing by the number of elements
imFilt = cv2.boxFilter(im,-1,(51,51),normalize=False)
# count the number of elements that you need to devide by
maskValidTmp = cv2.boxFilter(maskValid.astype('int'),-1,(51,51),normalize=False)
imFilt[maskValidTmp!=0] /= maskValidTmp[maskValidTmp!=0]
imFilt[maskValidTmp==0] = 0

亚历山大

首先,非常感谢您的详细解释和测试代码。我相信我理解您所使用的基本原理,并且我得出的代码似乎正是我所需要的:

import numpy as np
import cv2


a = np.array([[0.0,0.0,0.0,0.0,0.0],
              [0.0,0.0,0.0,0.0,0.0],
              [10.0,0.0,0.0,0.0,0.0],
              [20.0,20.0,20.0,0.0,0.0],
              [30.0,30.0,30.0,30.0,30.0]], dtype=np.float32)

kernel = np.ones((3,3), dtype=np.float32)

filtered_a = cv2.filter2D(a, -1, kernel)
mask = (a > 0)
if np.any(~mask):
    scaling_vals = cv2.filter2D(mask.astype(np.float32), -1, kernel)
    filtered_a[mask] /= scaling_vals[mask]
    filtered_a[~mask] = 0
    scaling_vals = None
    mask = None

print a
print filtered_a

产生:

^{pr2}$

相关问题 更多 >

    热门问题