1

So I'm trying to perform 2D image convolution using a 3x3 horizontal kernel that looks like this,

horizontal = np.array([
                      [0, 0, 0], 
                      [-1, 0, 1], 
                      [0, 0, 0]]),

So I am using the below convolution function, where I'm looping through the image, starting by ignoring the first few pixels (size of the kernel) and multiplying and adding,

def perform_convolution(k_h, k_w, img_h, img_w, kernel, picture):
    # k_w = kernel width, k_h = kernel_height
    # img_h = image height, img_w = image width

    conv = np.zeros(picture.shape)

    for i in range(k_h, img_h - (k_h)):
        for j in range(k_w, img_w - (k_w)):
            tot = 0
            for m in range(k_h):
                for n in range(k_w):
                    tot = tot + kernel[m][n] * picture[i - (k_h) + m][j - (k_w) + n]
            conv[i][j] = tot
    return conv

But the output that I get is, is completely weird, as shown below

enter image description here

Alternatively, by using the kernel from PIL, I get a proper blurred image like this,

enter image description here

So can anyone help me figure out where I'm going wrong?

I have tried the same function with box kernels and it works just fine, but I'm not being able to figure out why the output of this is so weird.

I have also tried to separate the RGB bands and convolve them separately but with no result.

The origin image is this,

enter image description here

Cris Luengo
  • 55,762
  • 10
  • 62
  • 120
hrishikeshpaul
  • 459
  • 1
  • 11
  • 27
  • how is picture defined? – Walter Tross Feb 04 '20 at 17:12
  • im = Image.open('pp.jpg') picture = np.array(im) @WalterTross – hrishikeshpaul Feb 04 '20 at 17:13
  • 1
    This is a problem with overflow/underflow of integer values, and/or a problem with image display. The result of the convolution with your kernel has values outside of the [0,255] range, you need to figure out how you want to store those correctly and how to display them meaningfully. – Cris Luengo Feb 04 '20 at 17:15
  • Does this answer your question? [How do you handle negative pixel values after filtering?](https://stackoverflow.com/questions/53027923/how-do-you-handle-negative-pixel-values-after-filtering) – Cris Luengo Feb 04 '20 at 17:17

2 Answers2

0

you need the following change to your code, if you want to get an image equal to the blurred one in your question, except possibly for the edges and for a diagonal shift by 1 or 2 pixels:

horizontal = np.array([[0.0, 0.0, 0.0], 
                       [0.5, 0.0, 0.5], 
                       [0.0, 0.0, 0.0]])

Furthemore, not for this kernel, but if you want to see a more realistic output of your previous kernel, you also need this change:

conv[i][j] = np.clip(tot, 0, 255)

The kernel you were using was the difference between the left and the right pixel, which is approximately 0 for most of the image, and often underflows in one or more channels, causing those channels to get a value like 255.

np.clip() is needed exactly to avoid overflows and underflows (which don't happen with your desired kernel, since it is an averaging one)

UPDATE:

You may need this change too:

conv = np.zeros(picture.shape, dtype=np.uint8)
Walter Tross
  • 12,237
  • 2
  • 40
  • 64
  • Taking the difference between two pixels yields the approximation to a derivative. It's an important operation. Telling someone to not do that is a rather weird recommendation. Clipping the result of the difference operation throws away half of the result (all the light-dark transitions, keeping only the dark-light transitions). Again, a weird recommendation. – Cris Luengo Feb 04 '20 at 19:50
  • I tired using np.clip() but I'm still getting the same answer! – hrishikeshpaul Feb 04 '20 at 19:51
  • @CrisLuengo I can't help it if the OP comes up with a weird question. He says that “using the kernel from PIL” he gets a “proper blurred image”, and shows it to us. So I was supposing that this is the image he wanted to obtain, and provided the correct kernel for that. On the other hand, if he *really* wanted the “derivative” image, how could he display that? Not without further processing. E.g., by offsetting it. But, in all cases, unless he dampens it, he must clip it. But since there was no trace of further processing, and because of the blurred image, I went for the first hypothesis – Walter Tross Feb 04 '20 at 20:37
  • @hrishikeshpaul please read the above, and clarify your desired output. – Walter Tross Feb 04 '20 at 20:40
  • @CrisLuengo sorry, not dampen, damp. I mean, e.g., multiply by a factor < 1. Anyway, the clipping, which is necessary for so many kernels that I would add it to the OP's generic function anyway (possibly conditioned by an analysis of the kernel), would allow him to see the non-negative part of the image produced by his original kernel, which would give him a feeling of what he's dealing with. So I wouldn't call it a “weird recommendation”. – Walter Tross Feb 04 '20 at 23:05
  • @hrishikeshpaul sorry, I had incorrectly transcribed the clipping code, rotating parameters! Now I fixed that. The correct order of parameters is `np.clip(a, a_min, a_max)`. But keep in mind that just clipping your output is incorrect, because it would mean to only keep the positive part of the gradient in the left-to-right direction. If you really want to visualize the gradient, you could try to add a 127 grey image. If, on the other hand, you wanted to produce the same blurred image that you showed us, then you need the kernel I supplied. – Walter Tross Feb 04 '20 at 23:19
0

Firstly the filter you are using is a gradient filter, which in this case is giving values in the range of [-255,255]. Here the main point to consider is that the value you are getting is not just magnitude, you are also pocking the direction of the edge. So, to account for this you can store the phase information i.e 0 degrees for positive value, 180 degrees for a negative value in another image. Finally while visualizing the gradient image, you can simply have a look at the magnitude of the image and know that a phase image exists that determines the direction of the gradient.

Secondly, the slightly smoothened image you are showing in the question can not be created with this kernel.

Also as a rule of thumb, when you want to apply smooth filter always make sure to have the sum of all elements in the kernel as 1.

Raviteja Narra
  • 458
  • 3
  • 11