美文网首页
边缘检测之模板算子

边缘检测之模板算子

作者: 飞羽田海 | 来源:发表于2020-09-21 20:02 被阅读0次

    对应GPUImage中的GPUImageSobelEdgeDetectionFilterGPUImagePrewittEdgeDetectionFilter两个类。

    模板算子

    边缘是图像中像素灰度值突变的结果,也就是不连续的像素,对于这些像素突变的地方,它的微积分运算中,一阶导数表现为极值点,二阶导数表现为过零点。因此,我们可以用微分算子来计算图像的边缘像素点,而这些微分算子,通常可以通过小区域的模板卷积来近似计算,这种小区域模板就是边缘检测的模板算子,这种模板卷积计算边缘像素的方法就叫做模板算子法。

    1-1.png
    Prewitt算子

    Prewitt算子又叫交叉微分算子,通过计算中心像素周围邻域的差分来定位边缘像素。它的水平和垂直方向模板如下:

    H=\begin{Bmatrix} -1 & +0 & +1\\ -1 & 0 & 1 \\ -1 & 0 & 1 \\ \end{Bmatrix} \\ V=\begin{Bmatrix} -1 & -1 & -1\\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{Bmatrix}
    对于图1-1中的像素点 P_5{(i,j)},Prewitt算子在水平和垂直方向上的表示为:

    D_x = P_3 + P_6 + P_9 - P_1 - P_4 - P_7 \\ D_y = P_7 + P_8 + P_9 - P_1 - P_2 - P_3

    写成公式则为:

    D_x = P_{(i + 1,j-1)} + P_{(i+1,j)} + P_{(i+1,j+1)} - P_{(i-1,j-1)} - P_{(i-1,j)} - P_{(i-1,j+1)} \\ D_y = P_{(i-1,j+1)} + P_{(i,j+1)} + P_{(i+1,j+1)} - P_{(i-1,j-1)} - P_{(i,j-1)} - P_{(i+1,j-1)} \\ D_{(i,j)} = \sqrt{D_x^2 + D_y^2}

    D_{(i,j)} 即为点P_5的梯度,表示图像在点{(i,j)}处的最大变化率,亦是该点的边缘像素值。

    Sobel算子

    Sobel算子常用的是3 * 3大小的模板算子,它在prewitt算子的基础上,考虑了中心像素与周围像素距离的关系,周围邻域像素距离中心像素越近,影响越大,因此,权重越大。Sobel算子常用水平和垂直方向模板分别如下:

    H=\begin{Bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & +1 \\ \end{Bmatrix} \\ V=\begin{Bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & +0 & +1 \end{Bmatrix}

    对于图1-1中的像素点 P_5{(i,j)},Sobel算子在水平和垂直方向上的表示为:

    D_x = P_1 + 2 * P_2 + P_3 - P_7 - 2 * P_8 - P_9 \\ D_y = P_3 + 2 * P_6 + P_9 - P_1 - 2 * P_4 - P_7
    写成公式:

    D_x = P_{(i - 1,j-1)} + 2 * P_{(i,j - 1)} + P_{(i+1,j-1)} - P_{(i-1,j+1)} - 2*P_{(i,j + 1)} - P_{(i+1,j+1)} \\ D_y = P_{(i + 1,j-1)} + 2 * P_{(i+1,j)} + P_{(i+1,j+1)} - P_{(i-1,j-1)} - 2*P_{(i-1,j)} - P_{(i-1,j+1)} \\ D_{(i,j)} = \sqrt{D_x^2 + D_y^2}



    有了公式我们就可以使用代码来实现了。下面是C代码实现的Sobel算子算法:
        // stride = 4 * width 
         for (int j = 0; j < height; j ++) {
              for (int k = 0; k < width; k ++) {
                   if (j < 1 || k < 1 || k > width -2 || j > height -2) {
                        ptr[0] = ptr[1] = ptr[2];
                    }else{
                        //H  -p1 - 2 * p2 - p3 + p7 + 2 * p8 + p9
                         //V  -p7 - 2 * p4 - p1 + p9 + 2 * p6 + p3
                        int pos = k * 4 + j * stride;
                         // tempData 是图片数据
                        int dx = tempData[pos - 4 + stride] + 2 * tempData[pos + stride] + tempData[pos + 4 + stride] - tempData[pos - 4 - stride] - 2 * tempData[pos - stride] - tempData[pos + 4 - stride];
                        int dy = tempData[pos + 4 - stride] + 2 * tempData[pos + 4] + tempData[pos + 4 + stride] - tempData[pos - 4 + stride] - 2 * tempData[pos - 4] - tempData[pos - 4 - stride];
                        ptr[0] = _CLIP3(sqrt(dx * dx + dy * dy), 0, 255);
                        pos++;
                        dx = tempData[pos - 4 + stride] + 2 * tempData[pos + stride] + tempData[pos + 4 + stride] - tempData[pos - 4 - stride] - 2 * tempData[pos - stride] - tempData[pos + 4 - stride];
                        dy = tempData[pos + 4 - stride] + 2 * tempData[pos + 4] + tempData[pos + 4 + stride] - tempData[pos - 4 + stride] - 2 * tempData[pos - 4] - tempData[pos - 4 - stride];
                        ptr[1] = _CLIP3(sqrt(dx * dx + dy * dy), 0, 255);
                        pos++;
                        dx = tempData[pos - 4 + stride] + 2 * tempData[pos + stride] + tempData[pos + 4 + stride] - tempData[pos - 4 - stride] - 2 * tempData[pos - stride] - tempData[pos + 4 - stride];
                        dy = tempData[pos + 4 - stride] + 2 * tempData[pos + 4] + tempData[pos + 4 + stride] - tempData[pos - 4 + stride] - 2 * tempData[pos - 4] - tempData[pos - 4 - stride];
                        ptr[2] = _CLIP3(sqrt(dx * dx + dy * dy), 0, 255);
                    }
                    ptr += 4;
                }
            }
    
    static inline unsigned char _CLIP3(unsigned char min,unsigned char max,unsigned char v){
        
        char ret = v;
        if (ret < min) { ret = min; }
        if (ret > max) { ret = max; }
        return ret;
    }
    
    Shader

    下面来看看在GPUImage中是如何使用Shader来实现算子算法的。其实用Shader来实现性能更好一些,因为顶点着色器和片元着色器本质就是一个循环结构,不需要显式的循环遍历。下面是GPUImage中使用Shader实现的Prewitt算子:

         // 为什么下面都在获取纹理转像素后的红色通道的值?因为Prewitt或Sobel算子都是作用于灰度图的,
         // 这里不光可以使用红色通道,使用绿色通道或是蓝色通道都可以。只要是单一的通道即可。
    
         // 左下点 相当于P7
         float bottomLeftIntensity = texture2D(inputImageTexture, bottomLeftTextureCoordinate).r;
          // 右上点 相当于P3
         float topRightIntensity = texture2D(inputImageTexture, topRightTextureCoordinate).r;
          // 左上点 相当于P1
         float topLeftIntensity = texture2D(inputImageTexture, topLeftTextureCoordinate).r;
          // 右下点 相当于P9
         float bottomRightIntensity = texture2D(inputImageTexture, bottomRightTextureCoordinate).r;
         // 左点  相当于P4
         float leftIntensity = texture2D(inputImageTexture, leftTextureCoordinate).r;
         // 右点 相当于P6
         float rightIntensity = texture2D(inputImageTexture, rightTextureCoordinate).r;
         // 下点 相当于P8
         float bottomIntensity = texture2D(inputImageTexture, bottomTextureCoordinate).r;
         // 上点 相当于P2
         float topIntensity = texture2D(inputImageTexture, topTextureCoordinate).r;
         // 水平方向 h = p7 + p8 + p9 - p1 - p2 - p3
         float h = -topLeftIntensity - topIntensity - topRightIntensity + bottomLeftIntensity + bottomIntensity + bottomRightIntensity;
         // 垂直方向 v = p3 + p6 + p9 - p1 - p4 - p7
         float v = -bottomLeftIntensity - leftIntensity - topLeftIntensity + bottomRightIntensity + rightIntensity + topRightIntensity;
          // 梯度
         float mag = length(vec2(h, v)) * edgeStrength;
         gl_FragColor = vec4(vec3(mag), 1.0);
    

    要注意的是,在Shader中因为纹理坐标的范围为0.0 ~ 1.0,需要对纹理的宽度和高度进行归一化操作。

      glUniform1f(texelWidthUniform, 1.0/(float)width);
      glUniform1f(texelHeightUniform, 1.0/(float)height);
    

    Sobel模板算子的Shader实现也大同小异。
    效果如下:

    原图.jpg prewitt模板.png Sobel模板.png

    如果想进一步了解Shader与图像的关系,可以看下《Graphics Shaders: Theory and Practice》这本书(GPUImage里面的有些filter也是参考的此书)。此外这本书的中文版我也上传到了网盘,需要的可以下载:传送门,提取码:u8tl。

    相关文章

      网友评论

          本文标题:边缘检测之模板算子

          本文链接:https://www.haomeiwen.com/subject/akecyktx.html