-1

I have been trying to use 2D Convolution for a project about image processing. Since i only need to convolve pixel by pixel i decided to use the following code (I know it is ugly and not optimized.) using the mathematical formula from Wikipedia:

output[1][1]  =  b[0][0]*mask_0[2][2]  +  b[0][1]*mask_0[2][1]  +  b[0][2]*mask_0[2][0]
              +  b[1][0]*mask_0[1][2]  +  b[1][1]*mask_0[1][1]  +  b[1][2]*mask_0[1][0]
              +  b[2][0]*mask_0[0][2]  +  b[2][1]*mask_0[0][1]  +  b[2][2]*mask_0[0][0]

I am using Kirsch Edge Detection. Unfortunately after the convolution with just one mask the resulting image is:

After Convolution:

After Convolution

Before Convolution:

Before Convolution

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • The right side of the assignment doesn't depend on `i` and `z`, so that would seem to put the same value at every output pixel. – interjay Mar 02 '17 at 00:33
  • Oh sorry. The output array is looped and the blockbuffer part changes. I didn't put them in the code part for the sake of brevity. Should i post the whole code? – Psofio Panda Mar 02 '17 at 00:39
  • What mask values are you using? Something like the ones mentioned in [the Wikipedia article](https://en.wikipedia.org/wiki/Multidimensional_discrete_convolution#Motivation_.26_Applications)? – Groo Mar 02 '17 at 01:15
  • I am using these 7 masks http://www.tutorialspoint.com/dip/Krisch_Compass_Mask.htm – Psofio Panda Mar 02 '17 at 11:29
  • 1
    It seems the error is not here, please provide a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) – B. Decoster Mar 02 '17 at 13:49

2 Answers2

2

I made a minimal complete example for convolution with which I got the algorithm running you described.

I did it the straight forward way. This is rather suitable for learning but not for serial usage (lacking any optimization for keeping the code clear and readable).

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef unsigned char uint8;
typedef unsigned int uint;

typedef struct {
  uint w, h;
  uint8 *data;
} Image;

uint newImage(Image *pImg, uint w, uint h)
{
  uint size = w * h * 3;
  if (!pImg) return 0;
  assert(!pImg->data);
  pImg->data = malloc(size);
  pImg->w = pImg->data ? w : 0; pImg->h = pImg->data ? h : 0;
  if (!pImg->data) {
    fprintf(stderr,
      "Allocation of %u bytes for image data failed!\n", size);
    return 0;
  }
  return size;
}

void fillImage(Image *pImg, uint8 r, uint8 g, uint8 b)
{
  if (!pImg || !pImg->data) return;
  { uint size = pImg->w * pImg->h * 3, i;
    for (i = 0; i < size; i += 3) {
      pImg->data[i] = r; pImg->data[i + 1] = g; pImg->data[i + 2] = b;
    }
  }
}

void freeImage(Image *pImg)
{
  if (!pImg) return;
  free(pImg->data);
  pImg->data = 0;
}

int readPPM(FILE *f, Image *pImg)
{
  char buffer[32] = ""; uint w = 0, h = 0, t = 0, size = 0, i = 0;
  if (!pImg) return 0;
  assert(!pImg->data);
  /* parse header */
  if ((i = 1, !fgets(buffer, sizeof buffer, f))
    || (i = 2, strcmp(buffer, "P6\n") != 0)
    || (i = 3, fscanf(f, "%u %u %u", &w, &h, &t) != 3)
    || (i = 4, t != 255)) {
    fprintf(stderr, "Not a PPM image! (%u)\n", i);
    return -1;
  }
  /* allocate appropriate memory */
  if (!(size = newImage(pImg, w, h))) return -1;
  /* read data */
  if (fread(pImg->data, 1, size, f) != size) {
    fprintf(stderr, "Not enough data in PPM image!\n");
    return -1;
  }
  /* done */
  return 0;
}

void writePPM(FILE *f, Image *pImg)
{
  if (!pImg || !pImg->data) return;
  fprintf(f, "P6\n%u %u 255\n", pImg->w, pImg->h);
  { uint size = pImg->w * pImg->h * 3, i;
    for (i = 0; i < size; i += 3) {
      fprintf(f, "%c%c%c",
        pImg->data[i], pImg->data[i + 1], pImg->data[i + 2]);
    }
  }
}

#define GET_PIXEL(P_IMG, ROW, COL, C) \
  ((P_IMG)->data[((ROW) * (P_IMG)->w + (COL)) * 3 + (C)])

void convolute(
  Image *pImg, uint dim, int *mat,
  Image *pImgOut)
{
  if (!pImg || !pImg->data) return;
  assert(dim & 1); /* dim Mat must be odd */
  { int offs = -(dim / 2);
    unsigned i, j;
    for (i = 0; i < pImg->h; ++i) {
      for (j = 0; j < pImg->w; ++j) {
        unsigned iM, jM;
        uint8 *pixelOut = pImgOut->data + (i * pImg->w + j) * 3;
        int r = 0, g = 0, b = 0;
        for (iM = 0; iM < dim; ++iM) {
          for (jM = 0; jM < dim; ++jM) {
            int mIJ = mat[iM * dim + jM];
            r += mIJ
              * (int)GET_PIXEL(pImg,
                (pImg->h + i + offs + iM) % pImg->h,
                (pImg->w + j + offs + jM) % pImg->w,
                0);
            g += mIJ
              * (int)GET_PIXEL(pImg,
               (pImg->h + i + offs + iM) % pImg->h,
               (pImg->w + j + offs + jM) % pImg->w,
               1);
            b += mIJ
              * (int)GET_PIXEL(pImg,
               (pImg->h + i + offs + iM) % pImg->h,
               (pImg->w + j + offs + jM) % pImg->w,
               2);
          }
        }
#if 1 /* colored output */
        pixelOut[0] = (uint8)abs(r);
        pixelOut[1] = (uint8)abs(g);
        pixelOut[2] = (uint8)abs(b);
#else /* gray level output */
        pixelOut[0] = pixelOut[1] = pixelOut[2]
          = abs(r) + abs(g) + abs(b) / 3;
#endif /* 1 */
      }
    }
  }
}

int main(int argc, char **argv)
{
  enum { Dim = 3 };
#if 0
  int mat[Dim * Dim] = {
     0, -1, 0,
    -1,  4, -1,
     0, -1, 0
  };
#endif
  int mat[Dim * Dim] = {
    -1, -1, -1,
    -1,  8, -1,
    -1, -1, -1
  };

  FILE *f = 0;
  const char *file, *outFile;
  /* read command line arguments */
  if (argc <= 2) {
    fprintf(stderr, "Missing command line arguments!\n");
    printf("Usage:\n"
      "  $ %s <IN_FILE> <OUT_FILE>\n",
      argv[0]);
    return -1;
  }
  file = argv[1]; outFile = argv[2];
  /* read PPM image */
  if (!(f = fopen(file, "rb"))) {
    fprintf(stderr, "Cannot open input file '%s'!\n", file);
    return -1;
  }
  Image img = { 0, 0, NULL };
  if (readPPM(f, &img)) return -1;
  fclose(f); f = 0;
  /* make output image */
  Image imgOut = { 0, 0, NULL };
  newImage(&imgOut, img.w, img.h);
  /* convolute image */
  convolute(&img, Dim, mat, &imgOut);
  /* write PPM image */
  if (!(f = fopen(outFile, "wb"))) {
    fprintf(stderr, "Cannot create output file '%s'!\n", outFile);
    return -1;
  }
  writePPM(f, &imgOut);
  fclose(f);
  /* done */
  return 0;
}

I compiled and tested it with VS2013 on Windows 10 as well as gcc in cygwin:

$ gcc -o edge-detect edge-detect.c 

$ ./edge-detect.exe fluffyCat.64x64.ppm edge-detect-out.ppm

$ 

fluffyCat.64x64.ppm looks like this: fluffyCat.64x64.png

edge-detect-out.ppm looks like this: edge-detect-out.png

Some notes:

I used the ancient X11 PPM format because

  1. It can be read and written with minimal code and is, thus, best fitting for such samples.
  2. It is supported in GIMP. Thus, creating and viewing is easy.

The code is inspired on Creating, Compiling, and Viewing ppm Images and, probably, cannot handle any flavor of PPM.

Attention! When GIMP saves PPM it includes a comment which the reader in the sample code cannot read. I simply removed this comment with a text editor. GIMP settings for saving: Raw data.

A common danger in such image procesing algorithms is the handling of border pixels (where matrix may be applied to neighbour non existing pixels outside the image). I simply solved it by wrapping the image around (using the resp. index modulo width/height of image).

In the convolution, I used abs() to keep output in positive range. Unfortunately, I cannot say whether this is fully correct. (It's 22 years ago since I heart about image processing in the University.)

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
0

You made a typo at the end of the second line :

output[1][1]  =  b[0][0]*mask_0[2][2]  +  b[0][1]*mask_0[2][1]  +  b[0][2]*mask_0[2][0]
              +  b[1][0]*mask_0[1][2]  +  b[1][1]*mask_0[1][1]  +  b[1][2]*mask_0[2][1] // Here, it should be [1][0]
              +  b[2][0]*mask_0[0][2]  +  b[2][1]*mask_0[0][1]  +  b[2][2]*mask_0[0][0]

Also, you don't need to worry about the mathematical formulation where indexes "go in reverse order". For the sake of simplicity, just do

output[1][1]  =  b[0][0]*mask_0[0][0]  +  b[0][1]*mask_0[0][1]  +  b[0][2]*mask_0[0][2]
              +  b[1][0]*mask_0[1][0]  +  ...

Of course, update your mask accordingly, but if it is symmetric, you don't even have to update it.

B. Decoster
  • 7,456
  • 1
  • 30
  • 52
  • Ooops didn't see that. Unfortunately in my code i had it right. I also had tried your solution due to the the fact that i use 7 masks that are symmetric between themselves but the result still stays the same. – Psofio Panda Mar 02 '17 at 11:37
  • Well, now you know why people ask for a [MVCE](http://stackoverflow.com/help/mcve), it prevents these issues =) – B. Decoster Mar 02 '17 at 13:48
  • Sorry first post. I will check it out. – Psofio Panda Mar 02 '17 at 14:25