我做了一个最小的完整的例子卷积与我跑,你描述的算法。
我这样做是直接的方式。这非常适合学习,但不适合串行使用(缺少任何优化以保持代码清晰可读)。
#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 -o edge-detect edge-detect.c
$ ./edge-detect.exe fluffyCat.64x64.ppm edge-detect-out.ppm
$
fluffyCat.64x64.ppm看起来是这样的: ![fluffyCat.64x64.png](https://i.stack.imgur.com/E3zuX.png)
边缘detect- out.ppm看起来是这样的: ![edge-detect-out.png](https://i.stack.imgur.com/xvNMo.png)
一些注意事项:
我用古老的X11 PPM格式,因为
- 它可以读取和用最少的代码编写,并,因此,对于这些样本最佳拟合。
- 它在GIMP中受支持。因此,创建和查看很容易。
该代码的灵感来源于Creating, Compiling, and Viewing ppm Images,可能无法处理任何PPM风味。
注意!当GIMP保存PPM时,它包含示例代码中的阅读器无法阅读的注释。我只是用文本编辑器删除了这个评论。 保存GIMP设置:原始数据。
这种图像处理算法常见的危险是处理边界像素(其中矩阵可能应用于图像外部的相邻非现有像素)。我简单地通过环绕图像来解决它(使用resp。index模宽/图像高度)。
在卷积中,我用abs()
来保持输出的正范围。不幸的是,我不能说这是否完全正确。 (这是22年前我关心大学图像处理的心得。)
赋值的右侧不依赖于'i'和'z',所以似乎会在每个输出像素处放置相同的值。 – interjay
对不起。输出数组被循环并且缓冲区部分改变。为了简洁起见,我没有把它们放在代码部分。我应该发布整个代码吗? –
你使用了什么掩码值?就像[维基百科文章]中提到的那样(https://en.wikipedia.org/wiki/Multidimensional_discrete_convolution#Motivation_.26_Applications)? – Groo