边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化,这些包括深度上的不连续、表面方向不连续、物质属性变化和场景照明变化, 边缘检测大幅度地减少了数据量,并且剔除了可以认为不相关的信息,保留了图像重要的结构属性。常用的描边也是先进行边缘然后再进行边缘处理。

一、卷积

  卷积(Convolution)本质上来讲就是一种数学运算,跟减加乘除没有区别。在图像处理中用一个模板(这个模板就是卷积核(kernel))和一幅图像进行卷积,对于图像上的一个点,让模板的原点和该点重合,然后模板上的点和图像上对应的点相乘,最后将各点的积相加,就得到该点的卷积值。然后移动模板对正下一个点,对图像上的每个点都这样处理。卷积是一种积分运算,可以看作加权求和,可以用来消除噪声、特征增强, 把一个点的像素值用它周围的点的像素值的加权平均代替。
  卷积核通常是一个四方形风格结构(如2x2、3x3),该网格区域内的每一个方格都有一个权重值。当对图像中的某个像素进行卷积时,我们会把卷积核的中心放置于该像素上,如下图所示,翻转核之后再依次计算核中每个元素和其覆盖的图像像素值的乘积,最后将各乘积累加,得到的结果就是该像素的新像素值。然后移动卷积核到下一个像素,进行同样的处理,至到所有像素都处理完。

  卷积听起来很难,在图形处理中其实就这么简单,但卷积可以实现很多常见的图像处理效果,例如图像模糊、边缘检测等等。

二、sobel算子

  卷积的神奇之处在于选择的卷积核,用于边缘检测的卷积核也叫边缘检测算子,先后有好几种边缘检测算子被提出来。

  • Roberts算子
  Roberts算子采用对角线方向相邻两像素之差近似梯度幅值检测边缘。检测水平和垂直边缘的效果好于斜向边缘,定位精度高,但对噪声敏感。
  • Prewitt算子

  Prewitt算子利用像素点上下、左右邻点灰度差,在边缘处达到极值检测边缘。对噪声具有平滑作用,但是定位精度不够高。

  • Sobel算子

  Sobel 算子主要用作边缘检测,它是一个离散的一阶差分算子,用来计算图像亮度函数的一阶梯度之近似值。在图像的任何一点使用此算子,将会产生该点对应的梯度矢量或是其法矢量。与Prewitt算子相比,Sobel算子对于像素的位置的影响做了加权,可以降低边缘模糊程度,因此效果更好。

  该算子包含两组3x3的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。如果以A代表原始图像,Gx及Gy分别代表经横向及纵向边缘检测的图像灰度值,其公式如下:

  具体计算如下:

Gx = (-1)*f(x-1, y-1) + 0*f(x,y-1) + 1*f(x+1,y-1)

      +(-2)*f(x-1,y) + 0*f(x,y)+2*f(x+1,y)

      +(-1)*f(x-1,y+1) + 0*f(x,y+1) + 1*f(x+1,y+1)

     = [f(x+1,y-1)+2*f(x+1,y)+f(x+1,y+1)]-[f(x-1,y-1)+2*f(x-1,y)+f(x-1,y+1)]



Gy =1* f(x-1, y-1) + 2*f(x,y-1)+ 1*f(x+1,y-1)

      +0*f(x-1,y) 0*f(x,y) + 0*f(x+1,y)

      +(-1)*f(x-1,y+1) + (-2)*f(x,y+1) + (-1)*f(x+1, y+1)

     = [f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1)]-[f(x-1, y+1) + 2*f(x,y+1)+f(x+1,y+1)]

  其中f(a,b), 表示图像(a,b)点的灰度值;
  图像的每一个像素的横向及纵向灰度值通过以下公式结合,来计算该点灰度的大小:

  G=Gx2+Gy2

  通常,为了提高效率 使用不开平方的近似值:

  G=Gx+Gy

  如果梯度G大于某一阀值则认为该点(x,y)为边缘点。Sobel算子根据像素点上下、左右邻点灰度加权差,在边缘处达到极值这一现象检测边缘。对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高。当对精度要求不是很高时,是一种较为常用的边缘检测方法。

  Sobel算子的计算速度比Roberts算子慢,但其较大的卷积核在很大程度上平滑了输入图像,使算子对噪声的敏感性降低。与Roberts算子相比,通常也会为相似的边缘产生更高的输出值。与Roberts算子一样,操作时输出值很容易溢出仅支持小整数像素值(例如8位整数图像)的图像类型的最大允许像素值。当发生这种情况时,标准做法是简单地将溢出的输出像素设置为最大允许值。通过使用支持范围更大的像素值的图像类型,可以避免此问题。

三、ARCore计算机视觉示例

  打开ARCore SDK自带的Computer vision示例。

  在Hierarchy窗口中选中ComputerVisionController,在Inspector窗口中双击编辑ComputerVisionController.cs脚本。先不管其他辅助操作的功能如获取摄像头图像、显示信息等,我们直接查看EdgeDetector.Detect()方法,这里采用的边缘检测就是使用的Sobel算子卷积。
       private static void Sobel(byte[] outputImage, IntPtr inputImage, int width, int height, int rowStride)
        {
            // Adjust buffer size if necessary.
            int bufferSize = rowStride * height;
            if (bufferSize != s_ImageBufferSize || s_ImageBuffer.Length == 0)
            {
                s_ImageBufferSize = bufferSize;
                s_ImageBuffer = new byte[bufferSize];
            }

            // Move raw data into managed buffer.
            System.Runtime.InteropServices.Marshal.Copy(inputImage, s_ImageBuffer, 0, bufferSize);

            // 边缘检测的阈值
            int threshold = 128 * 128;

            for (int j = 1; j < height - 1; j++)
            {
                for (int i = 1; i < width - 1; i++)
                {
                    // Offset of the pixel at [i, j] of the input image.
                    int offset = (j * rowStride) + i;

                    // Neighbour pixels around the pixel at [i, j].
                    int a00 = s_ImageBuffer[offset - rowStride - 1];
                    int a01 = s_ImageBuffer[offset - rowStride];
                    int a02 = s_ImageBuffer[offset - rowStride + 1];
                    int a10 = s_ImageBuffer[offset - 1];
                    int a12 = s_ImageBuffer[offset + 1];
                    int a20 = s_ImageBuffer[offset + rowStride - 1];
                    int a21 = s_ImageBuffer[offset + rowStride];
                    int a22 = s_ImageBuffer[offset + rowStride + 1];

                    int xSum = -a00 - (2 * a10) - a20 + a02 + (2 * a12) + a22;
                    int ySum = a00 + (2 * a01) + a02 - a20 - (2 * a21) - a22;

                    if ((xSum * xSum) + (ySum * ySum) > threshold)
                    {
                        outputImage[(j * width) + i] = 0xFF;  //是边缘则输出纯白
                    }
                    else
                    {
                        outputImage[(j * width) + i] = 0x1F;  //不是边缘则输出黑色
                    }
                }
            }
        }

  通过之前的讲解,我们应该很容易理解这段代码,使用sobel算子做边缘检测的效果如下图所示:

12-09 22:43