我在Filter2D
上使用opencv-python
功能在卫星图像上,边缘周围有几个黑色填充值(零)。在这里,您可以找到我所谈论的例子:https://landsat.gsfc.nasa.gov/wp-content/uploads/2013/06/truecolor.jpg
当我在该图像上使用Filter2D
时,黑色填充区域中的像素被视为有效值,从而产生边缘瑕疵。我怎么能不在计算中包含零?例如,在IDL中我可以使用"缺失"和"无效"像这样的字段:
output = CONVOL(input, kernel, /EDGE_TRUNCATE, MISSING=0.0, INVALID=0.0, /NAN, /NORMALIZE)
并避免边缘问题,但我在opencv中找不到类似的功能。如何解决这个问题?
答案 0 :(得分:2)
OpenCV函数中没有掩码或忽略参数。然而,唯一的伪像将是在图像之外,即在黑色区域中。每当滤镜的锚点(默认情况下,中间像素)位于边缘但位于黑色像素上时,它会将滤波后的结果添加到该像素。但是当锚点位于图像的顶部时,黑色值不会为您的过滤器添加任何内容。因此,一个简单的解决方案是创建一个带有黑色值的蒙版,并从过滤后的图像中删除它们。
编辑:好的IDL convol docs:
提示:使用
INVALID
关键字相当于在计算卷积和时将这些值视为0.0。您可以使用NORMALIZE
关键字完全排除这些点。提示:如果设置了
NORMALIZE
且您的输入数组缺少数据(设置了INVALID
或NAN
个关键字),那么对于每个结果值,将计算比例因子和偏差仅使用那些对该结果值有贡献的内核值。这可确保所有结果值的大小都相当,无论是否有任何丢失的数据。
所以从这里我们可以看到积分被排除在外"通过将无效像素视为0,然后通过除以与内核大小不同的像素数(即,有效像素数)来偏置总和。
这在OpenCV中是不可能的,至少不是内置的过滤方法,因为OpenCV没有规范化过滤结果。见in the docs for filter2D()
方程式只是简单的相关性,没有除法。
现在,您可以做的是手动规范化。这不是太难。如果您创建了一个掩码,其中值在正常图像中为1,而在图像外部为0,则与filter2D()
具有相同内核大小的boxFilter()
将生成内核中的像素计数每个位置的窗口。这将是标准化图像的一种方法。然后,您可以简单地屏蔽此boxFilter()
结果,以便忽略图像边界之外的值,然后最后将filter2D()
结果除以屏蔽的boxFilter()
结果(忽略{ {1}}结果为0,因此您不能除以零)。这应该完全符合你的要求。
Edit2:所以这是一个具体的例子。首先,我将定义一个简单的图像(7x7,内部正方形为5x5):
boxFilter()
我们将继续使用高斯内核进行简单的过滤示例:
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)
现在首先,过滤图像......
gauss_kernel = np.array([
[1/16, 1/8, 1/16],
[1/8, 1/4, 1/8],
[1/16, 1/8, 1/16]], dtype=np.float32)
这就是我们所期待的......一堆1的高斯模糊应该很好,1s。然后我们在边缘处有一些衰减,包括图像内部和零区域外部。所以我们要创造一个面具;在这种情况下,它与图像完全相同。然后我们在遮罩上做一个盒式滤镜,以获得正确的缩放值:
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 ]]
请注意,如果将此乘以9(内核中的val数),那么我们将获得围绕像素位置的确切"数量的非零像素"。所以这是我们的标准化比例因子。现在要做的就是...标准化并删除图像边框外的东西。
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]]
现在这些价值并不完美,但两者都不是有偏见的。请注意,即使在IDL文档中,他们声明:
分析这些值时应谨慎,因为内核中的点数较少会导致结果偏差。
所以你不会通过像这样的缩放来获得完美的结果。但是,我们可以做得更好!我们使用的比例因子仅使用点数,而不是与每个点相关的实际权重。为此,我们可以使用相关权重过滤蒙版。换句话说,只需在我们的蒙版上运行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. ]]
而不是图像。显然,通过这个划分我们的图像只会将所有值变为1;然后我们掩饰我们已经完成了!不要对这个例子感到困惑,因为掩码和图像是相同的 - 在这种情况下将过滤后的图像除以过滤后的图像给我们1,但一般来说,它只是比盒式过滤器。
filter2D()
答案 1 :(得分:1)
亚历山大,
首先,非常感谢您的详细解释和测试代码。我相信我理解你使用的基本原理,并且我已经得到了这个代码,它似乎完全符合我的需要:
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
产生:
[[ 0. 0. 0. 0. 0. ]
[ 17.5 0. 0. 0. 0. ]
[ 22.8571434 22.8571434 26. 0. 0. ]
[ 23.33333397 23.33333397 24.2857151 26. 30. ]]
答案 2 :(得分: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