2

I am trying to implement a convolution method taking two vectors: an image; and a kernel. My problem is that i don't know how to calculate the index of the image neighbour element when I "slide" the kernel over the image vector. For example, with two identical vectors {0, 1, 2, 3, 4, 5, 6, 7, 8} I would like to achieve the following result:

enter image description here

My code so far is as follows:

public int[] convolve(int[] image, int[] kernel)
{       
  int imageValue; 
  int kernelValue;
  int outputValue;
  int[] outputImage = new int[image.length()];

  // loop through image
  for(int i = 0; i < image.length(); i++)
  {      
    outputValue = 0;

    // loop through kernel
    for(int j = 0; j < kernel.length(); j++)
    {
      neighbour = ?;

      // discard out of bound neighbours 
      if (neighbour >= 0 && neighbour < imageSize)
      {
        imageValue = image[neighbour];
        kernelValue = kernel[j];          
        outputValue += imageValue * kernelValue;
      }
    }

    output[i] = outputValue;
  }        

  return output;
}
5had3sofQu4rtz
  • 504
  • 5
  • 15

2 Answers2

2

As i + j - (kernel.length / 2) may be too short for an answer:

public class Convolution
{
    public static void main(String[] args)
    {
        int image[] = { 0,1,2,3,4,5,6,7,8 };
        int kernel[] = { 0,1,2,3,4,5,6,7,8 };

        int output[] = convolve(image, kernel);

        for (int i=0; i<image.length; i++)
        {
            System.out.printf(output[i]+" ");
        }
    }

    public static int[] convolve(int[] image, int[] kernel)
    {       
        int[] output = new int[image.length];

        // loop through image
        for(int i = 0; i < image.length; i++)
        {      
            System.out.println("Compute output["+i+"]");
            int outputValue = 0;

            // loop through kernel
            for(int j = 0; j < kernel.length; j++)
            {
                int neighbour = i + j - (kernel.length / 2);

                // discard out of bound neighbours 
                if (neighbour >= 0 && neighbour < image.length)
                {
                    int imageValue = image[neighbour];
                    int kernelValue = kernel[j];          
                    outputValue += imageValue * kernelValue;

                    System.out.println("image["+neighbour+"] and kernel["+j+"]");
                }
            }

            output[i] = outputValue;
        }        

        return output;
    }
}

Note that this only works properly when the kernel has an odd length. In fact, what you are doing there is to move the center of the kernel through the image space (this is where the kernel.length/2 comes from). For even length kernels, like 0 1 2 3, you would have to decide whether you wanted to include...

0 1 2 3 4 (image)
3                   <- This line and/or ...
2 3
1 2 3
0 1 2 3
  0 1 2 3
    0 1 2
      0 1 
        0           <- ... this line
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • Thank you, this was what i was looking for, unfortunately i've just realised that I have another issue that also concerns neighbourhood indexing. I have opened a seperate question here: https://stackoverflow.com/questions/31704468/convolution-mapping-a-matrix-neighbour-with-its-vectorised-neighbour – 5had3sofQu4rtz Jul 29 '15 at 15:16
  • How does this work when image and filter have different size? – Raffaele Jul 29 '15 at 16:38
  • @Raffaele It should work as it is - but I only quickly tested it with an image of size 9 and a filter of size 7, and it seemed right. Do you see any issues that I missed? – Marco13 Jul 29 '15 at 17:14
  • I'm answering the other question OP linked. And I was following this one and I wonder how one can compute neighbours without knowing width. I may be missing the point here (not an image processing specialist) but it seems to me that this program compute the same neighbours when the image is 100 or 10000 pixels wide because it doesn't take into account the image width. The input vector is actually a 2D image – Raffaele Jul 29 '15 at 17:19
  • @Raffaele Yes, I looked at the other question as well, and it seems (for me) that the approach is basically flawed. Or to put it that way: Yes, you have to know the image width when you want to access "neighbors" in a 2D image that is given as a 1D array. – Marco13 Jul 29 '15 at 17:23
1

Sounds to me like you want something like a slider:

static class Slider implements Iterable<List<Integer>> {

    final List<Integer> kernel;
    final int imageWidth;
    final int center;

    public Slider(int imageWidth, int kernelWidth) {
        // Build my kernel offsets list.
        this.kernel = new ArrayList<>(kernelWidth);
        for (int i = 0; i < kernelWidth; i++) {
            kernel.add(i, i);
        }
        // Which kernel cell is in the center.
        center = kernelWidth / 2;
        // Remember the image width.
        this.imageWidth = imageWidth;
    }

    @Override
    public Iterator<List<Integer>> iterator() {
        return new Iterator<List<Integer>>() {
            int x = 0;

            @Override
            public boolean hasNext() {
                return x < imageWidth;
            }

            @Override
            public List<Integer> next() {
                List<Integer> slice = kernel.subList(Math.max(0, center - x), Math.min(kernel.size(), center - x + kernel.size()));
                x += 1;
                return slice;
            }

        };
    }

}

public void test() {
    List<Integer> image = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    List<Integer> kernel = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8);
    // Keep track of image position.
    int x = 0;
    for (List<Integer> slice : new Slider(image.size(), kernel.size())) {
        System.out.println(slice);
        int outputValue = 0;
        int imageValue = image.get(x++);
        for (Integer o : slice) {
            int kernelValue = kernel.get(o);
            outputValue += imageValue * kernelValue;
        }
        System.out.println("outputValue=" + outputValue);
    }
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • 1
    Thank you, this was definitely an interesting approach, in the end i needed something a little less object oriented, but i tested this and certainly works. I now realise that I wasn't fully aware of my problem and have opened a separate question regarding this: https://stackoverflow.com/questions/31704468/convolution-mapping-a-matrix-neighbour-with-its-vectorised-neighbour – 5had3sofQu4rtz Jul 29 '15 at 15:25