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

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);
    }
}

结果图

CPU端Sobel结果

二、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 );
}

结果图

GLSL端Sobel结果

三、差异原因及修正方案

问题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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 08:15:36