Processing a neighborhood of pixels with convolution filters

For a convolution filter, the channel values at each output pixel are a weighted average of the corresponding channel values in a neighborhood of input pixels. We can put the weights in a matrix, called a convolution matrix or kernel . For example, consider the following kernel:

{{ 0, -1,  0},
 {-1,  4, -1},
 { 0, -1,  0}}

The central element is the weight for the source pixel that has the same indices as of the destination pixel. Other elements represent weights for the rest of the neighborhood of input pixels. Here, we are considering a 3 x 3 neighborhood. However, OpenCV supports kernels with any square and odd-numbered dimensions. This particular kernel is a type of edge-finding filter called a Laplacian filter. For a neighborhood of flat (same) color, it yields a black output pixel. For a neighborhood of high contrast, it yields a bright output pixel.

Let's consider another kernel where the central element is greater by 1:

{{ 0, -1,  0},
 {-1,  5, -1},
 { 0, -1,  0}}

This is equivalent to taking the result of a Laplacian filter and then adding it to the original image. Instead of edge-finding, we get edge-sharpening. That is, edge regions get brighter while the rest of the image remains unchanged.

OpenCV provides many static methods for convolution filters that use certain popular kernels. The following are some examples:

Moreover, OpenCV provides a static method, Imgproc.filter2D(Mat src, Mat dst, int ddepth, Mat kernel), which enables us to specify our own kernels. For learning purposes, we will take this approach. The ddepth argument determines the numeric type of the destination's data. This argument may be any of the following:

Let's use a convolution filter as part of a more complex filter that draws heavy, black lines atop edge regions in the image. To achieve this effect, we also rely on two more static methods from OpenCV:

The following is the implementation of the blackened edge effect in StrokeEdgesFilter:

public class StrokeEdgesFilter implements Filter {
  private final Mat mKernel = new MatOfInt(
    0, 0,   1, 0, 0,
    0, 1,   2, 1, 0,
    1, 2, -16, 2, 1,
    0, 1,   2, 1, 0,
    0, 0,   1, 0, 0
  );
  private final Mat mEdges = new Mat();
  @Override
  public void apply(final Mat src, final Mat dst) {
    Imgproc.filter2D(src, mEdges, -1, mKernel);
    Core.bitwise_not(mEdges, mEdges);
    Core.multiply(src, mEdges, dst, 1.0/255.0);
  }
}

We will look at some other complex uses of convolution filters in subsequent chapters.

Next, let's add a user interface for enabling and disabling all our filters.