# How do I use 1D gradients to compute a 2D Sobel in OpenCV with a different vector norm?

## Question:

OpenCV uses an implementation of a Sobel operator defined here (details here). In this implementation, the horizontal derivative is generated, then the vertical derivative is generated, then the gradient is computed as the L2 norm of the derivatives.

Let’s say I wanted to use the L1 norm instead. In order to prove this out, I take an image and try to get the same result from OpenCV’s `Sobel()` that I get from manually calculating the L2 norm of the gradients:

``````import cv2

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]}')
``````

Here I’m using a 32-bit float image where the minimum is 0.0 and the maximum is around 600.0. The output of this is:

``````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
``````

Obviously, something is way off. I would expect those last two values to be the same (not exactly the same, but definitely close). I tried normalizing the pixels in the image to the range [0, 1], which didn’t help. I tried converting the images to 8-bit unsigned, which also didn’t help. What have I misunderstood about the implementation that would account for this discrepancy?

You are comparing "apples" to "oranges".

In Python/OpenCV, cv2.Sobel() computes either the X directional derivative or the Y directional derivative or a mixed derivative as follows:

This is not the same as the magnitude of the gradient:

where `x(I) = X` directional derivative from the Sobel,
`y(I) = Y` directional derivative from the Sobel with
`I` = src and magnitude = dst

If you want the L1 norm, then in place of the square root magnitude above, use

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

where `x(I) = X` directional derivative and `y(I) = Y` directional derivative (from Sobel) and `I` = src and magnitude = dot

I’ve accepted the answer from @fmw42 as correct, but I want to add more context.

The direct answer to my question is that I found the documentation misleading. The "Formulation" section in this link implied to me that the `cv2.Sobel()` method does the convolutions with the x and y kernels, and then returns the magnitude (L2 norm) of those derivatives.

The mistake that @fmw42 pointed out to me, and which I then verified, was that `cv2.Sobel()` does not calculate the magnitude for you. It only does the convolution using the Sobel kernels. You can verify by looking at the output of this code:

``````import cv2
import numpy as np

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]}')
``````

This will produce intensities of the center pixel as follows:

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

What remains a mystery to me is why calling `cv2.Sobel(dx=1, dy=1)` generates a different result from calling `cv2.Sobel(dx=1)` on the original image and then calling `cv2.Sobel(dy=1)` on the result of the first call (the last three rows). That seems like a different question, though.

You question why the mixed filter2D gradient is not the same as the mixed sobel gradient with kernel dx=1 and dy=1. You have a couple of issues. 1) You did not ensure that your filter2D outputs were floats. 2) The answer is to do multiplication (element wise) between the two kernels first, then do the convolution with filter_2D (rather than successive convolutions). Note that it is element-wise multiplication rather than matrix multiplication. To show that, see the following script and results.

Input:

``````import cv2
import numpy as np

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]])
# matrix multiply
kernel_xy1 = np.matmul(kernel_x,kernel_y)
kernel_xy2 = np.matmul(kernel_x,kernel_y)
# element-wise multiply
kernel_xy3 = cv2.multiply(kernel_x,kernel_y)
kernel_xy4 = cv2.multiply(kernel_y,kernel_x)

print('')
print("kernel_xy1:n", kernel_xy1)
print('')
print("kernel_xy2:n", kernel_xy1)
print('')
print("kernel_xy3:n", kernel_xy1)
print('')
print("kernel_xy4:n", kernel_xy1)
print('')

gx = cv2.filter2D(src=z_img, ddepth=cv2.CV_32F, 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=cv2.CV_32F, 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]}')

mixed1 = cv2.filter2D(src=z_img, ddepth=cv2.CV_32F, kernel=kernel_xy1)
print(f'Center pixel intensity (mixed1 - matrix multiply): {mixed1[z_px_rows // 2, z_px_cols // 2]}')

mixed2 = cv2.filter2D(src=z_img, ddepth=cv2.CV_32F, kernel=kernel_xy2)
print(f'Center pixel intensity (mixed2 - matrix multiply): {mixed2[z_px_rows // 2, z_px_cols // 2]}')

mixed3 = cv2.filter2D(src=z_img, ddepth=cv2.CV_32F, kernel=kernel_xy3)
print(f'Center pixel intensity (mixed3 - element-wise multiply): {mixed3[z_px_rows // 2, z_px_cols // 2]}')

mixed4 = cv2.filter2D(src=z_img, ddepth=cv2.CV_32F, kernel=kernel_xy4)
print(f'Center pixel intensity (mixed4 - element-wise multiply): {mixed4[z_px_rows // 2, z_px_cols // 2]}')

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

Results:

``````kernel_xy1:
[[2 4 2]
[4 8 4]
[2 4 2]]

kernel_xy2:
[[2 4 2]
[4 8 4]
[2 4 2]]

kernel_xy3:
[[2 4 2]
[4 8 4]
[2 4 2]]

kernel_xy4:
[[2 4 2]
[4 8 4]
[2 4 2]]

Center pixel intensity (gx): [6. 6. 6.]
Center pixel intensity (native sobel x): [6. 6. 6.]
Center pixel intensity (gy): [-184. -184. -184.]
Center pixel intensity (native sobel y): [-184. -184. -184.]
Center pixel intensity (mixed1 - matrix multiply): [2580. 2580. 2580.]
Center pixel intensity (mixed2 - matrix multiply): [2580. 2580. 2580.]
Center pixel intensity (mixed3 - element-wise multiply): [-28. -28. -28.]
Center pixel intensity (mixed4 - element-wise multiply): [-28. -28. -28.]
Center pixel intensity (native sobel xy): [-28. -28. -28.]
``````
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.