【问题标题】:How do I use 1D gradients to compute a 2D Sobel in OpenCV with a different vector norm?如何使用 1D 梯度在 OpenCV 中计算具有不同向量范数的 2D Sobel?
【发布时间】:2023-01-31 01:14:36
【问题描述】:

OpenCV 使用 Sobel operator defined here (details here) 的实现。在这个实现中,先生成水平导数,然后生成垂直导数,然后计算梯度作为导数的 L2 范数。

假设我想改用 L1 范数。为了证明这一点,我拍摄了一张图像,并尝试从 OpenCV 的Sobel() 中获得与手动计算梯度的 L2 范数相同的结果:

import cv2


z_img = cv2.imread(".\\some_image.tif", cv2.IMREAD_UNCHANGED)

z_px_rows = z_img.shape[0]
z_px_cols = z_img.shape[1]

print(f'Center pixel intensity (original): {z_img[z_px_rows // 2, z_px_cols // 2]}')

gx = cv2.Sobel(z_img, cv2.CV_32F, 1, 0, ksize=13)
print(f'Center pixel intensity (gx): {gx[z_px_rows // 2, z_px_cols // 2]}')

gy = cv2.Sobel(z_img, cv2.CV_32F, 0, 1, ksize=13)
print(f'Center pixel intensity (gy): {gy[z_px_rows // 2, z_px_cols // 2]}')

mag, _ = cv2.cartToPolar(gx, gy)
print(f'Center pixel intensity (homebrew sobel): {mag[z_px_rows // 2, z_px_cols // 2]}')

native_sobel = cv2.Sobel(z_img, cv2.CV_32F, 1, 1, ksize=13)
print(f'Center pixel intensity (native sobel): {native_sobel[z_px_rows // 2, z_px_cols // 2]}')

这里我使用的是 32 位浮点图像,其中最小值为 0.0,最大值约为 600.0。其输出是:

Center pixel intensity (original): 537.156982421875
Center pixel intensity (gx): -220087.90625
Center pixel intensity (gy): 350005.25
Center pixel intensity (homebrew sobel): 413451.78125
Center pixel intensity (native sobel): 16357.7548828125

显然,事情有点不对劲。我希望最后两个值相同(不是确切地相同,但绝对接近)。我尝试将图像中的像素标准化到 [0, 1] 范围内,但这没有帮助。我尝试将图像转换为 8 位无符号格式,但也无济于事。我对导致这种差异的实施有什么误解?

【问题讨论】:

    标签: python opencv derivative sobel


    【解决方案1】:

    您正在将“苹果”与“橘子”进行比较。

    在 Python/OpenCV 中,cv2.Sobel() 计算 X 方向导数或 Y 方向导数或混合导数,如下所示:

    这与梯度的大小不同:

    其中 x(I) = X 来自 Sobel 的方向导数, y(I) = Y Sobel 的方向导数 I = src 和 magnitude = dst

    如果你想要 L1 范数,那么代替上面的平方根幅度,使用

    magnitude(I) = |x(I)| + |y(I)|
    

    其中 x(I) = X 方向导数和 y(I) = Y 方向导数(来自 Sobel)和 I = src 和 magnitude = dot

    【讨论】:

    • 我想我的问题更像是,如果我有 sobel(dx=1, dy=0) 和 sobel(dx=0, dy=1),这些图像/矩阵是否可以用于计算 sobel(dx=1) 的输出, dy=1)?如果是这样,如何?我从我发布的链接中对算法的理解是它应该只是幅度,但这显然是错误的。我只是不明白为什么这是错误的。
    • 不,不幸的是,正如我的回答,文档中的公式表明 sobel(dx=1, dy=1) 的结果不是幅度而是混合导数。如果你想要幅度(与其他工具进行比较),那么你需要自己从 X 和 Y 导数计算它,即 sobel(dx=1, dy=0) 和 sobel(dx=0, dy=1) .您不能直接从 sobel(dx=1, dy=1) 获得大小。
    • 根据en.wikipedia.org/wiki/Partial_derivative 的维基百科,混合导数是交叉偏导数。例如对于二阶,这将是 gradient_x of gradient_y of image 或 gradient_y of gradient_x of image。所以我相信这意味着:sobel(sobel(image, dx=1, dy=1), dx=0, dy=1)
    • 啊,谢谢,我至少明白了我误解的一部分。如果 sobel(dx=1, dy=1) 的结果是一个二维标量数组,您能帮我理解这怎么可能吗?如果它不是一个幅度,那是否意味着它必须是一个二维向量数组?我认为这就是为什么我假设 sobel() 会为每个像素返回一个幅度(这就是我解释文档的方式)。
    • 它是标量的混合导数。我不认为它是一个向量。如果它是一个向量,那么您将得到两个分量结果,一个用于 x,一个用于 y。但是 cv2.Sobel 只返回一个标量图像。它是图像的 y_gradient 的 x_gradient(或者等同于图像的 x_gradient 的 y_gradient)。
    【解决方案2】:

    我已经接受来自 @fmw42 的 the answer 是正确的,但我想添加更多上下文。

    我的问题的直接答案是我发现文档具有误导性。 this link 中的“公式”部分向我暗示,cv2.Sobel() 方法与 x 和 y 内核进行卷积,然后返回这些导数的大小(L2 范数)。

    @fmw42 向我指出的错误是 cv2.Sobel()才不是为你计算大小。它仅使用 Sobel 内核进行卷积。您可以通过查看此代码的输出来验证:

    import cv2
    import numpy as np
    
    
    z_img = cv2.imread(".\some_image.tif", cv2.IMREAD_UNCHANGED)
    
    z_px_rows = z_img.shape[0]
    z_px_cols = z_img.shape[1]
    
    kernel_x = np.array([[-1, 0, 1],
                         [-2, 0, 2],
                         [-1, 0, 1]])
    
    kernel_y = np.array([[-1, -2, -1],
                         [ 0,  0,  0],
                         [ 1,  2,  1]])
      
    gx = cv2.filter2D(src=z_img, ddepth=-1, kernel=kernel_x)
    print(f'Center pixel intensity (gx): {gx[z_px_rows // 2, z_px_cols // 2]}')
    
    native_sobel_x = cv2.Sobel(z_img, cv2.CV_32F, 1, 0, ksize=3)
    print(f'Center pixel intensity (native sobel x): {native_sobel_x[z_px_rows // 2, z_px_cols // 2]}')
    
    gy = cv2.filter2D(src=z_img, ddepth=-1, kernel=kernel_y)
    print(f'Center pixel intensity (gy): {gy[z_px_rows // 2, z_px_cols // 2]}')
    
    native_sobel_y = cv2.Sobel(z_img, cv2.CV_32F, 0, 1, ksize=3)
    print(f'Center pixel intensity (native sobel y): {native_sobel_y[z_px_rows // 2, z_px_cols // 2]}')
    
    # Now take gx and convolve using the y kernel
    mixed = cv2.filter2D(src=gx, ddepth=-1, kernel=kernel_y)
    print(f'Center pixel intensity (mixed): {mixed[z_px_rows // 2, z_px_cols // 2]}')
    
    # And compare to calling Sobel(dy=1) on the output of Sobel(dx=1)
    native_sobel_mixed = cv2.Sobel(native_sobel_x, cv2.CV_32F, 0, 1, ksize=3)
    print(f'Center pixel intensity (native sobel mixed): {native_sobel_mixed[z_px_rows // 2, z_px_cols // 2]}')
    
    native_sobel_both = cv2.Sobel(z_img, cv2.CV_32F, 1, 1, ksize=3)
    print(f'Center pixel intensity (native sobel): {native_sobel_both[z_px_rows // 2, z_px_cols // 2]}')
    

    这将产生中心像素的强度,如下所示:

    Description Intensity of center pixel
    Manual convolution in x -0.4061279296875
    cv2.Sobel(dx=1, dy=0) -0.4061279296875
    Manual convolution in y 0.41986083984375
    cv2.Sobel(dx=0, dy=1) 0.419921875
    Manual convolution in x, then y 1.994873046875
    cv2.Sobel() in x, then y 1.9949951171875
    cv2.Sobel(dx=1, dy=1) 0.2659912109375

    对我来说仍然是个谜的是,为什么调用 cv2.Sobel(dx=1, dy=1) 生成的结果与在原始图像上调用 cv2.Sobel(dx=1) 然后在第一次调用的结果(最后三行)上调用 cv2.Sobel(dy=1) 产生不同的结果。不过,这似乎是一个不同的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-18
      • 2020-08-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多