Edge Detection

edge-detection.pde
/**
 * Edge Detection.
 *
 * This program analyzes every pixel in an image and compares it with the
 * neighboring pixels to identify edges.
 *
 * This is an example of an "image convolution" using a kernel (small matrix)
 * to analyze and transform a pixel based on the values of its neighbors.
 *
 * This kernel describes a "Laplacian Edge Detector".  It is effective,
 * but sensitive to noise.  One common enhancement is to add a Gaussian
 * blur to the source image first, as in
 *   grayImg->filter(BLUR);
 * to reduce impact of noise on the output.  The combination is often called
 * "Laplace of Gaussian", or "LoG" for short.
 *
 * For weaker detection effect, try this kernel:     [  0  -1   0 ]
 *                                                   [ -1   4  -1 ]
 *                                                   [  0  -1   0 ]
 */

float kernel[3][3] = {{ -1, -1, -1},
                       { -1,  8, -1},
                       { -1, -1, -1}};

PImage* img;

void setup() {
  size(640, 360);
  img = loadImage("moon.jpg"); // Load the original image
  noLoop();
}

void draw() {
  image(img, 0, 0); // Displays the image from point (0,0)
  img->loadPixels();

  // Edge detection should be done on a grayscale image.
  //  Create a copy of the source image, and convert to gray.
  // PImage's copy CONSTRUCTOR is deleted (see E0002 in Processing.h --
  // it owns a GPU texture, so value-style copying isn't supported), but
  // PImage::copy() is a real method that duplicates the whole image into
  // an independent PImage. It returns by value, so wrap it in `new` to
  // fit this codebase's PImage* convention -- the move constructor
  // handles the transfer, no pixel data is copied twice.
  PImage* grayImg = new PImage(img->copy());
  grayImg->filter(GRAY);
  // grayImg->filter(BLUR);

  // Create an opaque image of the same size as the original
  PImage* edgeImg = createImage(grayImg->width, grayImg->height, RGB);

  // Loop through every pixel in the image
  for (int y = 1; y < grayImg->height - 1; y++) {   // Skip top and bottom edges
    for (int x = 1; x < grayImg->width - 1; x++) {  // Skip left and right edges
      // Output of this filter is shown as offset from 50% gray.
      // This preserves transitions from low (dark) to high (light) value.
      // Starting from zero will show only high edges on black instead.
      float sum = 128;
      for (int ky = -1; ky <= 1; ky++) {
        for (int kx = -1; kx <= 1; kx++) {
          // Calculate the adjacent pixel for this kernel point
          int pos = (y + ky) * grayImg->width + (x + kx);

          // Image is grayscale, red/green/blue are identical
          float val = blue(grayImg->pixels[pos]);
          // Multiply adjacent pixels based on the kernel values
          sum += kernel[ky + 1][kx + 1] * val;
        }
      }
      // For this pixel in the new image, set the output value
      // based on the sum from the kernel
      edgeImg->pixels[y * edgeImg->width + x] = color(sum);
    }
  }
  // State that there are changes to edgeImg->pixels[]
  edgeImg->updatePixels();

  image(edgeImg, width / 2, 0); // Draw the new image
}