CPU与GLSL实现相同Sobel算法结果不同,如何让GLSL匹配CPU效果?
Sobel滤波器CPU与GLSL实现结果不一致问题排查
一、CPU端Qt实现代码及结果
实现代码
QImage gradient_image(image_ptr->width(),image_ptr->height(),QImage::Format_ARGB32 ); //gradient_image.fill(Qt::red); //sobel algorythm double S_x [3] [3] = {{-1,0,1},{-2,0,2},{-1,0,1}}; double S_y [3] [3] = {{-1,-2,-1},{0,0,0},{1,2,1}}; QPoint source_point(0,0); double G_x = 0; double G_y = 0; int G = 0; QColor source_color; QRgb gradient_color; for(int x= 0;x < image_ptr->width()-2;x++) { for (int y = 0; y < image_ptr->height() - 2; y++) { source_point.setX(x); source_point.setY(y); source_color = image_ptr->pixelColor(x,y); G_x = (S_x[0] [0] * image_ptr->pixelColor(x,y).red()) + (S_x [0] [1] * image_ptr->pixelColor(x+1,y).red()) + (S_x [0] [2] * image_ptr->pixelColor(x+2,y).red())+ (S_x [1] [0] * image_ptr->pixelColor(x,y+1).red()) + (S_x [1] [1] * image_ptr->pixelColor(x+1,y+1).red()) + (S_x [1] [2] * image_ptr->pixelColor(x+2,y+1).red()) + (S_x [2] [0] * image_ptr->pixelColor(x,y+2).red()) + (S_x [2] [1] * image_ptr->pixelColor(x+1,y+2).red()) + (S_x [2] [2] * image_ptr->pixelColor(x+2,y+2).red()); G_y = (S_y[0] [0] * image_ptr->pixelColor(x,y).red()) + (S_y [0] [1] * image_ptr->pixelColor(x+1,y).red()) + (S_y [0] [2] * image_ptr->pixelColor(x+2,y).red())+ (S_y [1] [0] * image_ptr->pixelColor(x,y+1).red()) + (S_y [1] [1] * image_ptr->pixelColor(x+1,y+1).red()) + (S_y [1] [2] * image_ptr->pixelColor(x+2,y+1).red()) + (S_y [2] [0] * image_ptr->pixelColor(x,y+2).red()) + (S_y [2] [1] * image_ptr->pixelColor(x+1,y+2).red()) + (S_y [2] [2] * image_ptr->pixelColor(x+2,y+2).red()); G = (int) qSqrt((G_x * G_x) + (G_y * G_y)); //std::cout << "G is: " << G; gradient_color = qRgb(G,G,G); gradient_image.setPixel(x,y,gradient_color); } }
结果图

二、GLSL端实现代码及结果
实现代码
uniform sampler2D texture; uniform float width; uniform float height; uniform float threshold; varying vec2 vTexCoord; void main(void) { mat3 Sx; mat3 Sy; Sx[0].xyz = vec3(1,0,-1); Sx[1].xyz = vec3(2,0,-2); Sx[2].xyz = vec3(1,0,-1); Sy[0].xyz = vec3(1,2,1); Sy[1].xyz = vec3(0,0,0); Sy[2].xyz =vec3(-1,-2,-1); float w = 1.0 / width; float h = 1.0 / height; vec4 pos[9]; pos[0] = texture2D(texture, vTexCoord);//pixel pos[1] = texture2D(texture, vTexCoord + vec2(w,0));//x+1 y pos[2] = texture2D(texture, vTexCoord + vec2(2*w,0));//x+2 y pos[3] = texture2D(texture, vTexCoord + vec2(0,h));//y+1 pos[4] = texture2D(texture, vTexCoord + vec2(w,h));//x+1 und y+1 pos[5] = texture2D(texture, vTexCoord + vec2(2*w,h));//x+2 y+1 pos[6] = texture2D(texture, vTexCoord + vec2(0,2*h));//x y+2 pos[7] = texture2D(texture, vTexCoord + vec2(w,2*h));//x+1 y+2 pos[8] = texture2D(texture, vTexCoord + vec2(2*w,2*h));//x+2 y+2 float Gx; float Gy; Gx = (Sx[0].x * pos[0].r) + (Sx[0].y * pos[1].r) + (Sx[0].z * pos[2].r) + (Sx[1].x * pos[3].r ) + (Sx[1].y * pos[4].r )+ (Sx[1].z * pos[5].r )+ (Sx[2].x * pos[6].r ) + (Sx[2].y * pos[7].r ) + (Sx[2].z * pos[8].r ) ; Gy = (Sy[0].x * pos[0].r) + (Sy[0].y * pos[1].r) + (Sy[0].z * pos[2].r) + (Sy[1].x * pos[3].r ) + (Sy[1].y * pos[4].r )+ (Sy[1].z * pos[5].r )+ (Sy[2].x * pos[6].r ) + (Sy[2].y * pos[7].r ) + (Sy[2].z * pos[8].r ) ; float sobel = sqrt((Gx*Gx) + (Gy * Gy)); gl_FragColor = vec4( sobel,sobel,sobel,1.0 ); }
结果图

三、差异原因及修正方案
问题1:卷积核符号完全反转
CPU端的S_x、S_y卷积核与GLSL端的Sx、Sy符号完全相反:
- CPU的S_x:
{{-1,0,1},{-2,0,2},{-1,0,1}},GLSL的Sx:{{1,0,-1},{2,0,-2},{1,0,-1}} - CPU的S_y:
{{-1,-2,-1},{0,0,0},{1,2,1}},GLSL的Sy:{{1,2,1},{0,0,0},{-1,-2,-1}}
这会导致梯度计算方向完全相反,直接反转边缘的明暗表现。
问题2:纹理Y轴采样方向不匹配
OpenGL纹理坐标的Y轴是从下到上,而Qt的QImage坐标系Y轴是从上到下。GLSL中vTexCoord + vec2(0,h)实际采样的是图像下方的像素,对应CPU端的y-1位置,而非y+1,导致卷积核的行顺序反转,进一步放大结果差异。
问题3:数值范围未对齐
- CPU端输入的红色通道是0-255的整数,卷积后Gx/Gy的范围是
-4*255到4*255,开根号后G最大约1442,最终通过qRgb截断到0-255。 - GLSL端采样得到的红色通道是0.0-1.0的浮点数,卷积后Gx/Gy范围是-4到4,开根号后最大值约5.656,直接赋值给
gl_FragColor会被自动 clamp到0.0-1.0,导致亮度范围与CPU端完全不匹配。
修正后的GLSL代码
uniform sampler2D texture; uniform float width; uniform float height; varying vec2 vTexCoord; void main(void) { // 修正卷积核,与CPU端完全一致 mat3 Sx; mat3 Sy; Sx[0].xyz = vec3(-1, 0, 1); Sx[1].xyz = vec3(-2, 0, 2); Sx[2].xyz = vec3(-1, 0, 1); Sy[0].xyz = vec3(-1, -2, -1); Sy[1].xyz = vec3(0, 0, 0); Sy[2].xyz = vec3(1, 2, 1); float w = 1.0 / width; float h = 1.0 / height; // 修正Y轴采样方向,匹配Qt的从上到下坐标系 vec2 flipped_texcoord = vec2(vTexCoord.x, 1.0 - vTexCoord.y); vec4 pos[9]; pos[0] = texture2D(texture, flipped_texcoord); pos[1] = texture2D(texture, flipped_texcoord + vec2(w, 0)); pos[2] = texture2D(texture, flipped_texcoord + vec2(2*w, 0)); pos[3] = texture2D(texture, flipped_texcoord + vec2(0, h)); pos[4] = texture2D(texture, flipped_texcoord + vec2(w, h)); pos[5] = texture2D(texture, flipped_texcoord + vec2(2*w, h)); pos[6] = texture2D(texture, flipped_texcoord + vec2(0, 2*h)); pos[7] = texture2D(texture, flipped_texcoord + vec2(w, 2*h)); pos[8] = texture2D(texture, flipped_texcoord + vec2(2*w, 2*h)); // 将采样值转换为0-255范围,匹配CPU端计算逻辑 float r0 = pos[0].r * 255.0; float r1 = pos[1].r * 255.0; float r2 = pos[2].r * 255.0; float r3 = pos[3].r * 255.0; float r4 = pos[4].r * 255.0; float r5 = pos[5].r * 255.0; float r6 = pos[6].r * 255.0; float r7 = pos[7].r * 255.0; float r8 = pos[8].r * 255.0; float Gx = Sx[0].x*r0 + Sx[0].y*r1 + Sx[0].z*r2 + Sx[1].x*r3 + Sx[1].y*r4 + Sx[1].z*r5 + Sx[2].x*r6 + Sx[2].y*r7 + Sx[2].z*r8; float Gy = Sy[0].x*r0 + Sy[0].y*r1 + Sy[0].z*r2 + Sy[1].x*r3 + Sy[1].y*r4 + Sy[1].z*r5 + Sy[2].x*r6 + Sy[2].y*r7 + Sy[2].z*r8; float sobel = sqrt(Gx*Gx + Gy*Gy); // 截断到0-255范围,再转换为0-1.0的浮点数 sobel = clamp(sobel / 255.0, 0.0, 1.0); gl_FragColor = vec4(sobel, sobel, sobel, 1.0); }
内容的提问来源于stack exchange,提问作者david2
相关产品推荐
相关产品推荐

