CS50 PSET4 filter-more edges() Sobel边缘检测结果与标准答案不匹配
问题定位
你的代码有3个核心错误,是导致结果和标准答案不匹配的直接原因:
- Gx/Gy存储类型错误,数值溢出
你用RGBTRIPLE类型数组存储Gx、Gy计算结果,但RGBTRIPLE的颜色分量是BYTE(无符号8位整数,取值范围仅0255)。Sobel算子计算出的单通道Gx、Gy值范围是-10201020,远超过0~255的存储范围,赋值给BYTE类型时会发生截断溢出,数值完全失真。
另外你在Gx、Gy函数内提前对计算结果取绝对值,虽然平方运算本身不区分正负,但结合溢出问题会进一步放大计算误差。 - Gy算子权重逻辑完全错误
标准Sobel Gy卷积核应为:
[ [-1, -2, -1], [ 0, 0, 0], [ 1, 2, 1] ]
你的Gy实现存在两处关键权重错误:
- 漏了
i=-1,j=0(当前像素正上方)位置的权重计算,该位置正确权重为-2,你的代码里这个位置没有匹配任何计算分支,相当于按权重0计算; - 错误给
i=0,j=-1(当前像素正左方)位置设置了-2的权重,该位置在Gy核中权重应为0,属于Gx核的权重位置,属于算子逻辑写串; - 分支判断存在重复条件,虽然按else if顺序不会重复执行,但属于逻辑冗余,很容易引入计算错误。
- 不影响结果的规范问题
内存申请失败时打印的提示文字是blur滤镜的提示,和edges功能不匹配。
修正方案
- 更换Gx、Gy的存储类型,不要用
RGBTRIPLE存中间计算值,单独定义支持带符号大数值的浮点结构体存储中间结果,不要提前取绝对值,保留Gx、Gy的原始浮点计算值,等最后算平方和的时候再参与运算。
可参考的结构体定义:
typedef struct { float rgbtBlue; float rgbtGreen; float rgbtRed; } SOBEL_TRIPLE;
将gx、gy的类型改为SOBEL_TRIPLE (*gx)[width],对应calloc申请大小改为width * sizeof(SOBEL_TRIPLE),Gx、Gy函数的new_matrix形参也同步替换为该类型,去掉存值时的fabsf调用,直接存储原始浮点计算结果即可。
2. 抛弃硬写else if匹配权重的实现方式,直接把Gx、Gy卷积核定义为二维常量数组,遍历邻域时直接按索引取权重相乘,从根源上避免权重位置写错的问题:
const int Gx_kernel[3][3] = {{-1,0,1}, {-2,0,2}, {-1,0,1}}; const int Gy_kernel[3][3] = {{-1,-2,-1}, {0,0,0}, {1,2,1}};
遍历3x3邻域时直接用Gx_kernel[di+1][dj+1]、Gy_kernel[di+1][dj+1]取对应位置权重即可,逻辑更简洁不容易出错。
3. 把内存申请失败的提示文字改成对应edges的内容即可。
简化后的核心实现参考
typedef struct { float b, g, r; } SOBEL_TRIPLE; const int gx_kernel[3][3] = {{-1,0,1},{-2,0,2},{-1,0,1}}; const int gy_kernel[3][3] = {{-1,-2,-1},{0,0,0},{1,2,1}}; // Detect edges void edges(int height, int width, RGBTRIPLE image[height][width]) { SOBEL_TRIPLE (*res)[width] = calloc(height, width * sizeof(SOBEL_TRIPLE)); if (res == NULL) { printf("Not enough memory to store edge pixels.\n"); return; } for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { float gx_b = 0, gx_g = 0, gx_r = 0; float gy_b = 0, gy_g = 0, gy_r = 0; // 遍历3x3邻域 for (int di = -1; di <= 1; di++) { for (int dj = -1; dj <= 1; dj++) { int ni = i + di; int nj = j + dj; // 越界像素按0处理,直接跳过 if (ni < 0 || ni >= height || nj <0 || nj >= width) continue; int kx = gx_kernel[di+1][dj+1]; int ky = gy_kernel[di+1][dj+1]; RGBTRIPLE p = image[ni][nj]; gx_b += p.rgbtBlue * kx; gx_g += p.rgbtGreen * kx; gx_r += p.rgbtRed * kx; gy_b += p.rgbtBlue * ky; gy_g += p.rgbtGreen * ky; gy_r += p.rgbtRed * ky; } } // 计算最终通道值,超过255截断 int b = round(sqrt(gx_b*gx_b + gy_b*gy_b)); int g = round(sqrt(gx_g*gx_g + gy_g*gy_g)); int r = round(sqrt(gx_r*gx_r + gy_r*gy_r)); res[i][j].b = b > 255 ? 255 : b; res[i][j].g = g > 255 ? 255 : g; res[i][j].r = r > 255 ? 255 : r; } } // 结果写回原图像 for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { image[i][j].rgbtBlue = res[i][j].b; image[i][j].rgbtGreen = res[i][j].g; image[i][j].rgbtRed = res[i][j].r; } } free(res); return; }
你原来写的
r_sqrt_sum函数逻辑本身是正确的,只要保证传入的a、b参数是未被截断的原始Gx、Gy浮点值,就能得到正确结果。
内容的提问来源于stack exchange,提问作者nishkant
相关产品推荐
相关产品推荐

