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
相关产品推荐
相关产品推荐

