You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的冗余字节,这些字节不属于实际像素数据。
  • 现有代码在读取原始图像、写入零填充图像、读取零填充图像做卷积、写入最终输出图像的全流程中,都没有计算和跳过这些行尾填充字节,逐像素读写时文件指针位置会逐行错位:上一行读取结束后直接读取下一行像素,会把行尾的填充字节误判为下一行开头的像素值,最终所有像素列位置整体偏移,表现为滤波后图像向右偏移。
  • 额外隐患:代码直接以读写模式打开原始输入文件,运行过程中会修改原文件内容,容易造成原始素材损坏。

修复方案

  1. 定义统一的行字节数计算宏,所有文件读写环节都用该值计算每行实际占用的存储长度:
    // 计算24位BMP每行实际存储字节数(4字节对齐)
    #define ROW_BYTES(width) ((((width) * 3 + 3) >> 2) << 2)
    
  2. 所有逐行读写像素的逻辑,在每行width个像素读写完成后,用fseek跳过对应数量的填充字节(填充字节数 = ROW_BYTES(当前图像宽度) - width*3),保证文件指针始终对准下一行第一个像素的起始位置。
  3. 计算BMP文件大小时,不要直接用width*height*3计算像素区大小,替换为ROW_BYTES(width) * height,保证文件头记录的尺寸和实际写入内容一致。
  4. 不要直接修改原始输入文件,建议单独创建临时文件存储零填充后的中间图像,原始文件全程以只读模式打开。

内容的提问来源于stack exchange,提问作者hakanxdurak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:33:23