Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
191 views
in Technique[技术] by (71.8m points)

image processing - Convolution for Edge Detection in C

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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!
", 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
") != 0)
    || (i = 3, fscanf(f, "%u %u %u", &w, &h, &t) != 3)
    || (i = 4, t != 255)) {
    fprintf(stderr, "Not a PPM image! (%u)
", 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!
");
    return -1;
  }
  /* done */
  return 0;
}

void writePPM(FILE *f, Image *pImg)
{
  if (!pImg || !pImg->data) return;
  fprintf(f, "P6
%u %u 255
", 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!
");
    printf("Usage:
"
      "  $ %s <IN_FILE> <OUT_FILE>
",
      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'!
", 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'!
", 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.)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...