Search code examples
image-processingprocessingconvolution

Convolution of Image Processing in Processing language


Since the Corona situation characterizes my studies as self-study, as a Processing-Language newbie I don't have an easy time getting into the subject of image processing , more specifically convolution. Therefore I hope that you can help me.

My lecturer, who unfortunately is nearly never reachable, left me the following conv code. The theory behind convolution is clear to me, but I have many gaps in understanding related to the code. Could someone leave a line comment so that I can get into the code a bit more fluently?

The Code is following

color convolution (int x, int y, float[][] matrix, int matrix_size, PImage img){
  float rtotal = 0.0;
  float gtotal = 0.0;
  float btotal = 0.0;
  int offset = matrix_size / 2;
  for (int i = 0; i < matrix_size; i++){
    for (int j= 0; j < matrix_size; j++){
      int xloc = x+i-offset;
      int yloc = y+j-offset;
      int loc = xloc + img.width*yloc;
  
      rtotal += (red(img.pixels[loc]) * matrix[i][j]);
      gtotal += (green(img.pixels[loc]) * matrix[i][j]);
      btotal += (blue(img.pixels[loc]) * matrix[i][j]);
  }
}
  rtotal = constrain(rtotal, 0, 255);
  gtotal = constrain(gtotal, 0, 255);
  btotal = constrain(btotal, 0, 255); 

  return color(rtotal, gtotal, btotal);

}


Solution

  • I have to do a bit of guesswork since I'm not positive about all of the functions you're using and I'm not familiar with the Processing 3+ library, but here's my best shot at it.

    color convolution (int x, int y, float[][] matrix, int matrix_size, PImage img){
        // Note: the 'matrix' parameter here will also frequently be referred to as
        // a 'window' or 'kernel' in research
        // I'm not certain what your PImage class is from, but I'll assume
        // you're using the Processing 3+ library and work off of that assumption
    
        // how much of each color we see within the kernel (matrix) space
        float rtotal = 0.0;
        float gtotal = 0.0;
        float btotal = 0.0;
    
        // this offset is to zero-center our kernel
        // the fact that we use matrix_size / 2 sort of implicitly
        // alludes to the fact that our matrix_size should be an odd-number
        // so that we can have a middle-pixel
        int offset = matrix_size / 2;
    
        // looping through the kernel. the fact that we use 'matrix_size'
        // as our end-condition for both dimensions means that our 'matrix' kernel
        // must always be a square
        for (int i = 0; i < matrix_size; i++){
            for (int j= 0; j < matrix_size; j++){
                // calculating the index conversion from 2D to the 1D format that PImage uses
                // refer to: https://processing.org/tutorials/pixels/
                // for a better understanding of PImage indexing (about 1/3 of the way down the page)
                // WARNING: by subtracting the offset it is possible to hit negative
                // x,y values here if you pick an x or y position less than matrix_size / 2. 
                // the same index-out-of-bounds can occur on the high end. 
                // When you convolve using a kernel of N x N size (N here would be matrix_size)
                // you can only convolve from [N / 2, Width - (N / 2)] for x and y
                int xloc = x+i-offset; 
                int yloc = y+j-offset;
                // this is the final 1D PImage index that corresponds to [xloc, yloc] in our 2D image
                // really go back up and take a look at the link if this doesn't make sense, it's pretty good
                int loc = xloc + img.width*yloc; 
    
                // I have to do some speculation again since I'm not certain what red(img.pixels[loc]) does
                // I'll assume it returns the red red channel of the pixel
                // this section just adds up all of the pixel colors multiplied by the value in the kernel
                rtotal += (red(img.pixels[loc]) * matrix[i][j]);
                gtotal += (green(img.pixels[loc]) * matrix[i][j]);
                btotal += (blue(img.pixels[loc]) * matrix[i][j]);
            }
        }
    
        // the fact that no further division or averaging happens after the for-loops implies
        // that the kernel you feed in should have balanced values for your kernel size
        // for example, a kernel that's designed to average out the color over the 3 x 3 area
        // it covers (this would be like blurring the image) would be filled with 1/9
        // in general: the kernel you're using should have a sum of 1 for all of the numbers inside
        // this is just 'in general' you can play around with not doing that, but you'll probably notice a
        // darkening effect for when the sum is less than 1, and a brightening effect if it's greater than 1
        // for more info on kernels, read this: https://en.wikipedia.org/wiki/Kernel_(image_processing)
    
        // I don't have the code for this constrain function,
        // but it's almost certainly just your typical clamp (constrains the values to [0, 255])
        // Note: this means that your values saturate at 0 and 255
        // if you see a lot of black or white then that means your kernel
        // probably isn't balanced as mentioned above
        rtotal = constrain(rtotal, 0, 255);
        gtotal = constrain(gtotal, 0, 255);
        btotal = constrain(btotal, 0, 255); 
    
        // Finished!
        return color(rtotal, gtotal, btotal);
    }