C语言实现BMP 3x3平滑滤波 输出图像右移问题求助
BMP 3x3平滑滤波图像右移异常排查
项目实现逻辑
我目前正在开展BMP文件滤波相关项目开发,本次使用的平滑滤波采用固定尺寸的3x3模糊核(小型权重矩阵)。卷积运算过程中,该核在图像上逐像素滑动,将矩阵元素与其下方覆盖的像素值做点积运算,本质是遍历整幅图像,通过9个相邻像素加权计算生成单个输出像素。
为在水平、垂直方向均保持原始图像尺寸,我通过在图像边缘添加0值像素的方式对输入图像做零填充(padding)处理,相关操作参考示意图:
- 卷积操作示意图:

- 零填充操作示意图:

现有实现代码
#include <stdio.h> #include <stdlib.h> #include "header.h" typedef struct{ uint8_t red; uint8_t green; uint8_t blue; }rgb ; void smoothing_filter(int height, int width, rgb **image, rgb **dest); int main() { FILE *inputPtr; if ((inputPtr = fopen("starry_night.bmp", "rb+")) == NULL) { printf("The input file could not be opened\n"); } else { BITMAPFILEHEADER fileheader_input; fread(&fileheader_input, sizeof(BITMAPFILEHEADER), 1, inputPtr); BITMAPINFOHEADER infoheader_input; fread(&infoheader_input, sizeof(BITMAPINFOHEADER), 1, inputPtr); if (fileheader_input.bfType != 0x4d42 || fileheader_input.bfOffBits != 54 || infoheader_input.biSize != 40 || infoheader_input.biBitCount != 24 || infoheader_input.biCompression != 0) { fclose(inputPtr); printf("Unsupported input file.\n"); } else { int height = abs(infoheader_input.biHeight); int width = infoheader_input.biWidth; rgb **rgb_array; rgb_array = calloc(height, sizeof(rgb *)); for (int row = 0; row < height; ++row) { rgb_array[row] = calloc(width, sizeof(rgb)); } for (int row = 0; row < height; row++) { for (int col = 0; col < width; ++col) { fread(&rgb_array[row][col], sizeof(rgb), 1, inputPtr); } } infoheader_input.biWidth = width + 2; infoheader_input.biHeight = height + 2; fileheader_input.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + 3 * abs(infoheader_input.biWidth) * abs(infoheader_input.biHeight); rewind(inputPtr); fwrite(&(fileheader_input), sizeof(BITMAPFILEHEADER), 1, inputPtr); fwrite(&(infoheader_input), sizeof(BITMAPINFOHEADER), 1, inputPtr); rgb blanckRgb = {0, 0, 0}; for (int i = 0; i < (width + 2); ++i) { fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr); } for (int row = 0; row < height; ++row) { fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr); for (int col = 0; col < width; ++col) { fwrite(&rgb_array[row][col].red, 1, 1, inputPtr); fwrite(&rgb_array[row][col].green, 1, 1, inputPtr); fwrite(&rgb_array[row][col].blue, 1, 1, inputPtr); } fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr); } for (int i = 0; i < (width + 2); ++i) { fwrite(&blanckRgb, sizeof(rgb), 1, inputPtr); } FILE *outputPtr; if ((outputPtr = fopen("starry_night_filtered.bmp", "wb")) == NULL) { printf("The \"starry_night_filtered.bmp\" file could not be created\n"); }else{ BITMAPFILEHEADER fileheader_output = fileheader_input; BITMAPINFOHEADER infoheader_output = infoheader_input; infoheader_output.biWidth = width; infoheader_output.biHeight = height; fileheader_output.bfSize = sizeof(BITMAPINFOHEADER)+sizeof(BITMAPFILEHEADER)+3*abs(width*height); fwrite(&(fileheader_output), sizeof(BITMAPFILEHEADER), 1, outputPtr); fwrite(&(infoheader_output), sizeof(BITMAPINFOHEADER), 1, outputPtr); rgb **image; image = calloc(infoheader_input.biHeight, sizeof(rgb *)); for (int row = 0; row < infoheader_input.biHeight; ++row) { image[row] = calloc(infoheader_input.biWidth, sizeof(rgb)); } rewind(inputPtr); for (int row = 0; row < infoheader_input.biHeight; row++) { for (int col = 0; col < infoheader_input.biWidth; col++) { fread(&image[row][col], sizeof(rgb), 1, inputPtr); } } rgb **dest; dest = calloc(height, sizeof(rgb *)); for (int row = 0; row < height; ++row) { dest[row] = calloc(width, sizeof(rgb)); } smoothing_filter(infoheader_input.biHeight, infoheader_input.biWidth, image, dest); for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { fwrite(&dest[row][col], sizeof(rgb), 1, outputPtr); } } free(image); free(rgb_array); fclose(outputPtr); fclose(inputPtr); } } } } void smoothing_filter(int height, int width, rgb **image, rgb **dest) { double redVal, blueVal, greenVal; // Iterate through rows for (int row = 0; row < (height-2); row++) { // Iterate through columns for (int col = 0; col < (width-2); col++) { // Obtain RGB values of current image pixel redVal = (0.0625*image[row][col].red) + (0.125*image[row][col+1].red) + (0.0625*image[row][col+2].red) +(0.125*image[row+1][col].red) + (0.25*image[row+1][col+1].red) + (0.125*image[row+1][col+2].red) +(0.0625*image[row+2][col].red) + (0.125*image[row+2][col+1].red) + (0.0625*image[row+2][col+2].red); greenVal = (0.0625*image[row][col].green) + (0.125*image[row][col+1].green) + (0.0625*image[row][col+2].green) +(0.125*image[row+1][col].green) + (0.25*image[row+1][col+1].green) + (0.125*image[row+1][col+2].green) +(0.0625*image[row+2][col].green) + (0.125*image[row+2][col+1].green) + (0.0625*image[row+2][col+2].green); blueVal = (0.0625*image[row][col].blue) + (0.125*image[row][col+1].blue) + (0.0625*image[row][col+2].blue) +(0.125*image[row+1][col].blue) + (0.25*image[row+1][col+1].blue) + (0.125*image[row+1][col+2].blue) +(0.0625*image[row+2][col].blue) + (0.125*image[row+2][col+1].blue) + (0.0625*image[row+2][col+2].blue); dest[row][col].red = (int) redVal; dest[row][col].green = (int) greenVal; dest[row][col].blue = (int) blueVal; } } }
异常现象
代码可正常编译运行,但输出的滤波后图像存在小幅向右偏移的异常,暂未定位根因,运行效果参考:
- 原始输入图像:

- 异常输出图像:

问题根因
代码未处理24位BMP格式强制要求的行4字节对齐规则:
- BMP标准规定,图像每一行的像素数据字节长度必须向上对齐到4的整数倍,不足部分会在每行末尾填充1~3个值为0的冗余字节,这些字节不属于实际像素数据。
- 现有代码在读取原始图像、写入零填充图像、读取零填充图像做卷积、写入最终输出图像的全流程中,都没有计算和跳过这些行尾填充字节,逐像素读写时文件指针位置会逐行错位:上一行读取结束后直接读取下一行像素,会把行尾的填充字节误判为下一行开头的像素值,最终所有像素列位置整体偏移,表现为滤波后图像向右偏移。
- 额外隐患:代码直接以读写模式打开原始输入文件,运行过程中会修改原文件内容,容易造成原始素材损坏。
修复方案
- 定义统一的行字节数计算宏,所有文件读写环节都用该值计算每行实际占用的存储长度:
// 计算24位BMP每行实际存储字节数(4字节对齐) #define ROW_BYTES(width) ((((width) * 3 + 3) >> 2) << 2) - 所有逐行读写像素的逻辑,在每行width个像素读写完成后,用
fseek跳过对应数量的填充字节(填充字节数 =ROW_BYTES(当前图像宽度) - width*3),保证文件指针始终对准下一行第一个像素的起始位置。 - 计算BMP文件大小时,不要直接用
width*height*3计算像素区大小,替换为ROW_BYTES(width) * height,保证文件头记录的尺寸和实际写入内容一致。 - 不要直接修改原始输入文件,建议单独创建临时文件存储零填充后的中间图像,原始文件全程以只读模式打开。
内容的提问来源于stack exchange,提问作者hakanxdurak
相关产品推荐
相关产品推荐

