Search code examples
pythonopencvsobel

Interpreting Sobel from cv2


I'm trying to understand the Sobel convolution from cv2 in Python.

According to documentation the Sobel kernel is

-1 0 1
-2 0 2
-1 0 1

So, I tried to apply it to the following img (a binary 3x3 array):

0 1 0
1 0 1
0 1 0

Now, I have a problem to interpret the output. I computed by hand and got different result. As fas as I know, I have to center the kernel at each pixel (i,j) and multiply element wise and sum.

So, the first entry in output should be 2. The program returns 0.

Am I wrong? I hope so.

Code

import cv2
import numpy as np

img = np.array([[0,1,0],[1,0,1],[0,1,0]]).astype(float)

# Output dtype = cv2.CV_8U
sobelx8u = cv2.Sobel(img,cv2.CV_8U,1,0,ksize=3)

# Output dtype = cv2.CV_64F. Then take its absolute and convert to cv2.CV_8U
sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)

abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)

print 'img'
print img

print 'sobelx8u'
print sobelx8u

print 'sobelx64f'
print sobelx64f

print 'abs_sobel64f'
print abs_sobel64f

print 'sobel_8u'
print sobel_8u

Output

img
[[ 0.  1.  0.]
 [ 1.  0.  1.]
 [ 0.  1.  0.]]
sobelx8u
[[0 0 0]
 [0 0 0]
 [0 0 0]]
sobelx64f
[[ 0.  0.  0.]
 [ 0.  0.  0.]
 [ 0.  0.  0.]]
abs_sobel64f
[[ 0.  0.  0.]
 [ 0.  0.  0.]
 [ 0.  0.  0.]]
sobel_8u
[[0 0 0]
 [0 0 0]
 [0 0 0]]

Solution

  • Read the second paragraph of your documentation page:

    Another common feature of the functions and classes described in this section is that, unlike simple arithmetic functions, they need to extrapolate values of some non-existing pixels. For example, if you want to smooth an image using a Gaussian 3x3 filter, then, when processing the left-most pixels in each row, you need pixels to the left of them, that is, outside of the image. You can let these pixels be the same as the left-most image pixels (“replicated border” extrapolation method), or assume that all the non-existing pixels are zeros (“constant border” extrapolation method), and so on. OpenCV enables you to specify the extrapolation method. For details, see the function borderInterpolate() and discussion of the borderType parameter in the section and various functions below.

    Make it work as you expected

    For it to work as you expected it to work, you had to explicitly specify that you want to interpolate your border with zero values. Like this:

    import cv2
    import numpy as np
    
    img = np.array([[0,1,0],[1,0,1],[0,1,0]]).astype(float)
    
    border = cv2.borderInterpolate(0, 1, cv2.BORDER_CONSTANT)
    sobelx64f = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3, borderType=border)
    
    print 'img'
    print img
    
    print 'sobelx64f'
    print sobelx64f
    

    Output:

    img
    [[ 0.  1.  0.]
     [ 1.  0.  1.]
     [ 0.  1.  0.]]
    sobelx64f
    [[ 2.  0. -2.]
     [ 2.  0. -2.]
     [ 2.  0. -2.]]
    

    Default border type

    The default value of borderType is BORDER_DEFAULT which on my machine is the same as BORDER_REFLECT_101. You can run this script to confirm it on your machine:

    import cv2
    
    for var in dir(cv2):
        if not var.startswith('BORDER_'): continue
        if cv2.__dict__[var] == cv2.BORDER_DEFAULT:
            print 'BORDER_DEFAULT ==', var
    

    Output:

    BORDER_DEFAULT == BORDER_DEFAULT
    BORDER_DEFAULT == BORDER_REFLECT101
    BORDER_DEFAULT == BORDER_REFLECT_101
    

    And BORDER_REFLECT_101 works exactly the way that is consistent with your results. Here is explanation of different border types:

    BORDER_REPLICATE:     aaaaaa|abcdefgh|hhhhhhh
    BORDER_REFLECT:       fedcba|abcdefgh|hgfedcb
    BORDER_REFLECT_101:   gfedcb|abcdefgh|gfedcba
    BORDER_WRAP:          cdefgh|abcdefgh|abcdefg
    BORDER_CONSTANT:      iiiiii|abcdefgh|iiiiiii  with some specified 'i'
    

    Explanation of what you get

    So the default border interpolation type (which is BORDER_REFLECT_101) makes your array to look like this prior to computation:

    0 1 0 1 0
    1 0 1 0 1
    0 1 0 1 0
    1 0 1 0 1
    0 1 0 1 0
    

    By simple arithmetics you can confirm that the correct values after applying Sobel kernel to inner 3x3 pixels are all zeroes – that's what you got by running your script.