Ask Your Question

Negative values in filter2D convolution

asked 2020-10-12 05:15:17 -0600

wvdk gravatar image

updated 2020-10-12 08:09:25 -0600

I am playing around with an edge detection algorithm on a .JPG image using OpenCV in Python. For comparison I also created a simple convolution function that slides a kernel over every pixel in the image. However, the results between this 'manual' convolution and cv2::filter2D are quite different as can be seen in the attached picture. The filter2D function (middle row) is very fast compared to my implementation (bottom row) but the results miss some edges. In the attached example, a simple 3x3 horizontal ([121][000][-1-2-1]) kernel is used and we can already see notable differences.

This seems to be caused by the filter2D function surpressing negative values. When I flip the kernel, the missing edges are displayed, so the image is equal to the difference between the images in row 2 (cv2) and row 3 (my implementation). Is there an option that allows filter2D to also record negative (or absolute magnitude) values? A simple workaround would be to do convolutions for the kernel and the 180 degrees flipped version and sum the two results, but this will unnecessarily complicate the code.

Since I have little ambition to rewrite the filter2D function to suit my needs (since my simple implementation is too slow), I wondered whether there is an official workaround? Regardless of a solution, I think it would be good to update the documentation.

A minimal example using a .JPG image img with kernel as described above would be:

imgYUV = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
clr1, clr2, clr3 = cv2.split(imgYUV)
paddedImg = cv2.copyMakeBorder(clr1, pad,pad,pad,pad,cv2.BORDER_REPLICATE)
(iH, iW) = paddedImg.shape[:2]
resultsMyImplementation = np.zeros((iH,iW,1), dtype='int32')
for y in np.arange(pad, iH+pad):
    for x in np.arange(pad, iW+pad):
        roi = paddedImg[y-pad,y+pad+1, x-pad:x+pad+1]
        k = (roi*kernel).sum()
        resultsMyImplementation[y-pad,x-pad] = k
resultsCV2 = cv2.filter2D(clr1, -1, kernel)

image description

edit retag flag offensive close merge delete


show code, please.

berak gravatar imageberak ( 2020-10-12 06:07:50 -0600 )edit

Added code for clarity.

wvdk gravatar imagewvdk ( 2020-10-12 06:45:28 -0600 )edit

have a look at the docs again, your implementation differs

what is the type of img ?

berak gravatar imageberak ( 2020-10-12 07:18:16 -0600 )edit

The image is a JPG, updated code to reflect conversion to np.ndarray() containing only 1 color channel. Results for all 3 channels can be seen in the image.

wvdk gravatar imagewvdk ( 2020-10-12 07:55:39 -0600 )edit

1 answer

Sort by » oldest newest most voted

answered 2020-10-12 08:22:06 -0600

berak gravatar image

to achieve more similar / better results, you need to:

1: flip the kernel

instead of:

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


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

2: choose a different output depth

assuming your input img is uchar, and the result is the same (-1), negative values will get saturated to 0.

(your numpy code silently converts to float64)

all in all that would make it:

filtered = cv2.filter2D(img, cv2.CV_64F, flipped_kernel)

p.s: like this, the anchor point is still at the kernel center, while your code has it top-left, but imho this is the correct version

edit flag offensive delete link more



Indeed the output depth was what made the negative values saturate to zero. Using the provided line solved the problem. Since I was interested in the absolute value, the flipped kernel does not have an effect in my application but indeed would be needed for similar results using the given code snippet. Thanks for the quick solution!

wvdk gravatar imagewvdk ( 2020-10-12 09:20:30 -0600 )edit

Question Tools

1 follower


Asked: 2020-10-12 05:15:17 -0600

Seen: 2,054 times

Last updated: Oct 12 '20