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

CS50 Pset4 Blur函数输出图像过暗问题求助

CS50 Pset4 Blur函数颜色偏暗问题排查

问题描述

实现CS50 Pset4的Blur函数时,运行后输出图像颜色异常偏暗。核心思路是遍历每个像素的3x3邻域,计算RGB通道平均值实现模糊,代码逻辑与参考代码类似但结果不符合预期。同时疑惑参考代码中直接更新原图像像素会导致后续计算使用新值的问题,且类似问题的解决方案不适用于当前场景。

复现方法

使用任意24位无压缩BMP图像,运行命令:

./filter -b INFILE.bmp OUTFILE.bmp

完整代码

helpers.c

void blur(int height, int width, RGBTRIPLE image[height][width])
{
    RGBTRIPLE blurred[height][width];

    for (int i = 0; i < height; i++){
        for (int j = 0; j < width; j++){
            float colorCount = 0.0;
            blurred[i][j].rgbtBlue = 0;
            blurred[i][j].rgbtGreen = 0;
            blurred[i][j].rgbtRed = 0;

            for (int count = -1; count < 2; count++){
                if (((count+i) >= 0) && ((count+i) < height)){
                    for (int count2 = -1; count2 < 2; count2++){
                        if (((count2+j) >= 0) && ((count2+j) < width)){
                            blurred[i][j].rgbtBlue += image[i+count][j+count2].rgbtBlue;
                            blurred[i][j].rgbtGreen += image[i+count][j+count2].rgbtGreen;
                            blurred[i][j].rgbtRed += image[i+count][j+count2].rgbtRed;
                            colorCount += 1.0;
                        }
                    }
                }
            }
            blurred[i][j].rgbtBlue = round(blurred[i][j].rgbtBlue/colorCount);
            blurred[i][j].rgbtGreen = round(blurred[i][j].rgbtGreen/colorCount);
            blurred[i][j].rgbtRed = round(blurred[i][j].rgbtRed/colorCount);

        }
    }

    for (int i = 0; i < height; i++){
        for (int j = 0; j < width; j++){
            image[i][j].rgbtBlue = blurred[i][j].rgbtBlue;
            image[i][j].rgbtGreen = blurred[i][j].rgbtGreen;
            image[i][j].rgbtRed = blurred[i][j].rgbtRed;
        }
    }
    return;
}

helpers.h

// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width]);

// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width]);

// Detect edges
void edges(int height, int width, RGBTRIPLE image[height][width]);

// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width]);

bmp.h

#include <stdint.h>

/**
 * Common Data Types
 *
 * The data types in this section are essentially aliases for C/C++
 * primitive data types.
 */
typedef uint8_t  BYTE;
typedef uint32_t DWORD;
typedef int32_t  LONG;
typedef uint16_t WORD;

/**
 * BITMAPFILEHEADER
 *
 * The BITMAPFILEHEADER structure contains information about the type, size,
 * and layout of a file that contains a DIB [device-independent bitmap].
 */
typedef struct
{
    WORD   bfType;
    DWORD  bfSize;
    WORD   bfReserved1;
    WORD   bfReserved2;
    DWORD  bfOffBits;
} __attribute__((__packed__))
BITMAPFILEHEADER;

/**
 * BITMAPINFOHEADER
 *
 * The BITMAPINFOHEADER structure contains information about the
 * dimensions and color format of a DIB [device-independent bitmap].
 */
typedef struct
{
    DWORD  biSize;
    LONG   biWidth;
    LONG   biHeight;
    WORD   biPlanes;
    WORD   biBitCount;
    DWORD  biCompression;
    DWORD  biSizeImage;
    LONG   biXPelsPerMeter;
    LONG   biYPelsPerMeter;
    DWORD  biClrUsed;
    DWORD  biClrImportant;
} __attribute__((__packed__))
BITMAPINFOHEADER;

/**
 * RGBTRIPLE
 *
 * This structure describes a color consisting of relative intensities of
 * red, green, and blue.
 */
typedef struct
{
    BYTE  rgbtBlue;
    BYTE  rgbtGreen;
    BYTE  rgbtRed;
} __attribute__((__packed__))
RGBTRIPLE;

filter.c

#include <stdio.h>
#include <stdlib.h>

#include "helpers.h"

int main(int argc, char *argv[])
{
    // Define allowable filters
    char *filters = "begr";

    // Get filter flag and check validity
    char filter = getopt(argc, argv, filters);
    if (filter == '?')
    {
        printf("Invalid filter.\n");
        return 1;
    }

    // Ensure only one filter
    if (getopt(argc, argv, filters) != -1)
    {
        printf("Only one filter allowed.\n");
        return 2;
    }

    // Ensure proper usage
    if (argc != optind + 2)
    {
        printf("Usage: ./filter [flag] infile outfile\n");
        return 3;
    }

    // Remember filenames
    char *infile = argv[optind];
    char *outfile = argv[optind + 1];

    // Open input file
    FILE *inptr = fopen(infile, "r");
    if (inptr == NULL)
    {
        printf("Could not open %s.\n", infile);
        return 4;
    }

    // Open output file
    FILE *outptr = fopen(outfile, "w");
    if (outptr == NULL)
    {
        fclose(inptr);
        printf("Could not create %s.\n", outfile);
        return 5;
    }

    // Read infile's BITMAPFILEHEADER
    BITMAPFILEHEADER bf;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);

    // Read infile's BITMAPINFOHEADER
    BITMAPINFOHEADER bi;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);

    // Ensure infile is (likely) a 24-bit uncompressed BMP 4.0
    if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 ||
        bi.biBitCount != 24 || bi.biCompression != 0)
    {
        fclose(outptr);
        fclose(inptr);
        printf("Unsupported file format.\n");
        return 6;
    }

    // Get image's dimensions
    int height = abs(bi.biHeight);
    int width = bi.biWidth;

    // Allocate memory for image
    RGBTRIPLE(*image)[width] = calloc(height, width * sizeof(RGBTRIPLE));
    if (image == NULL)
    {
        printf("Not enough memory to store image.\n");
        fclose(outptr);
        fclose(inptr);
        return 7;
    }

    // Determine padding for scanlines
    int padding = (4 - (width * sizeof(RGBTRIPLE)) % 4) % 4;

    // Iterate over infile's scanlines
    for (int i = 0; i < height; i++)
    {
        // Read row into pixel array
        fread(image[i], sizeof(RGBTRIPLE), width, inptr);

        // Skip over padding
        fseek(inptr, padding, SEEK_CUR);
    }

    // Filter image
    switch (filter)
    {
        // Blur
        case 'b':
            blur(height, width, image);
            break;

        // Edges
        case 'e':
            edges(height, width, image);
            break;

        // Grayscale
        case 'g':
            grayscale(height, width, image);
            break;

        // Reflect
        case 'r':
            reflect(height, width, image);
            break;
    }

    // Write outfile's BITMAPFILEHEADER
    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);

    // Write outfile's BITMAPINFOHEADER
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);

    // Write new pixels to outfile
    for (int i = 0; i < height; i++)
    {
        // Write row to outfile
        fwrite(image[i], sizeof(RGBTRIPLE), width, outptr);

        // Write padding at end of row
        for (int k = 0; k < padding; k++)
        {
            fputc(0x00, outptr);
        }
    }

    // Free memory for image
    free(image);

    // Close files
    fclose(inptr);
    fclose(outptr);
    return 0;
}

Makefile

filter:
    clang -ggdb3 -gdwarf-4 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-gnu-folding-constant -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -lm -o filter filter.c helpers.c

问题分析与解决

核心错误:BYTE类型累加溢出

RGBTRIPLE中的rgbtBlue、rgbtGreen、rgbtRed都是BYTE类型(即uint8_t,取值范围0-255)。代码中直接用blurred[i][j].rgbtBlue += ...进行累加,当累加值超过255时会发生整数溢出,导致总和被截断为0-255范围内的数值,最终计算出的平均值远小于实际值,图像因此偏暗。

修复方案

改用int类型的临时变量存储每个通道的累加和,避免溢出:

void blur(int height, int width, RGBTRIPLE image[height][width])
{
    RGBTRIPLE blurred[height][width];

    for (int i = 0; i < height; i++){
        for (int j = 0; j < width; j++){
            float colorCount = 0.0;
            // 用int类型存储累加和,避免溢出
            int sumBlue = 0, sumGreen = 0, sumRed = 0;

            for (int count = -1; count < 2; count++){
                int row = i + count;
                if (row >= 0 && row < height){
                    for (int count2 = -1; count2 < 2; count2++){
                        int col = j + count2;
                        if (col >= 0 && col < width){
                            sumBlue += image[row][col].rgbtBlue;
                            sumGreen += image[row][col].rgbtGreen;
                            sumRed += image[row][col].rgbtRed;
                            colorCount += 1.0;
                        }
                    }
                }
            }
            // 计算平均值后转成BYTE类型
            blurred[i][j].rgbtBlue = round(sumBlue / colorCount);
            blurred[i][j].rgbtGreen = round(sumGreen / colorCount);
            blurred[i][j].rgbtRed = round(sumRed / colorCount);
        }
    }

    // 将模糊结果复制回原图像
    for (int i = 0; i < height; i++){
        for (int j = 0; j < width; j++){
            image[i][j] = blurred[i][j];
        }
    }
    return;
}

关于原图像直接更新的疑惑

你担心参考代码直接更新原图像会导致后续计算使用新值,这个顾虑是正确的。你的代码中先创建blurred临时数组存储所有模糊结果,最后再复制回原图像的做法是正确且标准的,避免了计算过程中数据被污染的问题,无需修改这部分逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 15:45:54