C 中的卷积边缘检测

标签 c image-processing

我一直在尝试使用 2D Convolution 来进行图像处理的项目。由于我只需要逐像素进行卷积,我决定使用以下代码(我知道它很难看并且未优化。)使用维基百科的数学公式:

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]

我正在使用 Kirsch 边缘检测。 不幸的是,仅使用一个掩模进行卷积后,所得图像是:

卷积后:

After Convolution

卷积前:

Before Convolution

最佳答案

我制作了一个最小的完整卷积示例,我用它运行了您描述的算法。

我用直接的方式做到了。这比较适合学习,但不适合串行使用(缺乏任何保持代码清晰可读的优化)。

#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;
}

我在Windows 10上使用VS2013以及cygwin中的gcc编译并测试了它:

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

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

$ 

fluffyCat.64x64.ppm 看起来像这样: fluffyCat.64x64.png

edge-detect-out.ppm 看起来像这样: edge-detect-out.png

一些注意事项:

我使用古老的 X11 PPM 格式是因为

  1. 它可以用最少的代码进行读取和写入,因此最适合此类示例。
  2. GIMP 支持它。因此,创建和查看都很容易。

该代码的灵感来自 Creating, Compiling, and Viewing ppm Images并且,可能无法处理 PPM 的任何风格。

注意!当 GIMP 保存 PPM 时,它包含示例代码中的读者无法读取的注释。我只是用文本编辑器删除了这条评论。 用于保存的 GIMP 设置:原始数据

这种图像处理算法中的一个常见危险是边界像素的处理(其中矩阵可能应用于图像外部的相邻不存在像素)。我只是通过环绕图像来解决这个问题(使用相应的索引模图像的宽度/高度)。

在卷积中,我使用 abs() 将输出保持在正范围内。不幸的是,我不能说这是否完全正确。 (自从我在大学听说图像处理已经是 22 年前了。)

关于C 中的卷积边缘检测,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42544773/

相关文章:

c# - 使用 MonoTouch 创建自定义图像过滤器

c++ - C++中圆形区域中的Opencv对象检测

c - 对 C 中的 char 数组进行排序

c - 是否可以在 C 中将 char[] 转换为 char*?

c - 列出可执行文件中的全局变量

c - 直接从指针使用结构体

opencv - 使用 OpenCV 从深度图像中去除白点

R 魔法 : Square crop and circular mask

opencv - 使用具有阈值/相似性分数的 OpenCV 特征匹配进行对象检测 - Java/C++

当其他 channel (同一定时器)具有 i2c 备用功能时,我可以使用不同的定时器 channel 吗