欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测

程序员文章站 2022-07-14 11:25:06
...

通常使用Roberts、Prewitt、Sobel算子计算梯度,在图像处理时,梯度值比较常见的应用是用来计算边缘检测。

其原理是通过算子,计算出图像每一个像素的梯度值,再通过判断梯度值的大小,来判定该像素是不是边缘像素。判定方式有很多种,可以直接用梯度作为权,用来在正常颜色和边缘颜色之间插值。也可以设定一个阙值,梯度值大于该阙值时,认为是边缘像素。

关于算子的计算原理,其实就是一个小型的矩阵,有3 x 3的,也有 9 x 9的等等,在遍历图像上的每一个像素时,按照该矩阵,选取出该像素周围的像素点,用来计算该像素与周围像素之间的关系。

对于梯度,每个算子都有两个分量,分别用来计算X方向和Y方向的梯度。对于X方向,一般都是从左到右,所以变化不大。但是对于Y方向,有些平台是从下往上(坐标原点在左下角),有些平台是从上往下(坐标原点在左上角)。我们需要根据实际情况翻转Y方向的梯度算子。
这里看一下百度百科中对 Sobel算子 的描述:
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测

可以看到同样的Y方向梯度算子,前后描述并不一致,上下颠倒。这是因为在上面的描述中,遍历算子中像素的方式和下面不一样。当我们需要从左往右,从上往下遍历算子时,就应采用第一个公式。
我们在遍历算子时使用从左往右,从下往上的顺序,应采用第二个公式。
下面两个图片表示了3x3算子的两种不同的遍历顺序:
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
在本例中,我们采用的是,红色示例表示的遍历的顺序。
接下来给出本例中使用的算子:
Roberts算子
Gx
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
Gy
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测

Prewitt算子
Gx
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
Gy
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测

Sobel
Gx
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
Gy
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测

接下来给出代码:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;

[ExecuteInEditMode]
public class Gradient : MonoBehaviour {

    public Color edgeColor = Color.black;
    public Color backColor = Color.white;

    //边缘颜色比重
    [Range(0f,1f)]
    public float edgeWeight = 0.3f;
    //梯度算子类型
    public GradientType gradType;
    //算子权值
    private int[] weighArrX;
    private int[] weighArrY;
    //Roberts遍历方向
    private int[][] dir1 = {
        new int[2] { 0, 0 },
        new int[2] { 1, 0 },
        new int[2] { 0, 1 },
        new int[2] { 1, 1 },
    };
    //Prewitt、Sobel遍历方向
    private int[][] dir2 = {
        new int[2] { -1, -1 },
        new int[2] { 0, -1 },
        new int[2] { 1, -1},
        new int[2] { -1, 0 },
        new int[2] { 0, 0},
        new int[2] { 1, 0},
        new int[2] { -1, 1},
        new int[2] { 0, 1 },
        new int[2] { 1, 1 },
    };
    //遍历方向
    private int[][] dir;

    public void GetAllGradient(Texture2D tex)
    {
        if (!tex)
        {
            Debug.LogError("tex is null !!!");
            return;
        }
        Color[] colors = new Color[tex.width * tex.height];
        //初始化数据
        InitData();
        int cnt = 0;
        //遍历图片,计算各个像素的梯度值
        for (int i = 0; i < tex.height; i++)
        {
            for (int j = 0; j < tex.width; j++)
            {
                //遍历算子
                float Lumin = 0f;
                float gradX = 0f;
                float gradY = 0f;
                for (int k = 0; k < dir.Length; k++)
                {
                    int idx1 = i + dir[k][0];
                    int idx2 = j + dir[k][1];
                   // if ((idx1 >= 0 && idx1 <= tex.width) && (idx2 >= 0 && idx2 <= tex.height))
                    {
                        Lumin = GetLuminance(tex.GetPixel(idx1, idx2));
                        gradX += Lumin * weighArrX[k];
                        gradY += Lumin * weighArrY[k];
                    }
                }
                //梯度值
                float grad = Mathf.Abs(gradX) + Mathf.Abs(gradY);
                Color pixelColor = GetPixelColor(grad, tex.GetPixel(i, j));
                colors[cnt] = pixelColor;
                cnt++;
            }
        }
        tex.SetPixels(colors);
    }
    //初始化数据
    void InitData()
    {
        switch (gradType)
        {
            case GradientType.Roberts:
                weighArrX = new int[4] { -1, 0, 0, 1 };
                weighArrY = new int[4] { 0, -1, 1, 0 };
                dir = dir1;
                break;
            case GradientType.Prewitt:
                weighArrX = new int[9] { -1, 0, 1, -1, 0, 1, -1, 0, 1 };
                weighArrY = new int[9] { -1, -1, -1, 0, 0, 0, 1, 1, 1 };
                dir = dir2;
                break;
            case GradientType.Sobel:
                weighArrX = new int[9] { -1, 0, 1, -2, 0, 2, -1, 0, 1 };
                weighArrY = new int[9] { -1, -2, -1 , 0, 0, 0, 1, 2, 1 };
                dir = dir2;
                break;
            default:
                break;
        }
    }
    //获得亮度值
    private float GetLuminance(Color _color) {
        return 0.2125f * _color.r + 0.7154f * _color.g + 0.0721f * _color.b;
    }
    private Color GetPixelColor(float grad, Color _pixelColor) {
        Color c = new Color();
        Color _edgeColor = Color.Lerp(_pixelColor, edgeColor, grad);
        Color _backColor = Color.Lerp(backColor, edgeColor, grad);
        c = Color.Lerp(_edgeColor, _backColor, edgeWeight);
        return c;
    }

    [ContextMenu("RenderTexture")]
    public void DoRenderTexture() {
        DrawTex();
    }
    void DrawTex() {
        //获得相机
        Camera catchCamera = GetComponent<Camera>();
        //暂存当前渲染图像
        RenderTexture rendText = RenderTexture.active;
        //改为相机图像
        RenderTexture.active = catchCamera.targetTexture;
        catchCamera.Render();
        Texture2D camTex = new Texture2D(catchCamera.targetTexture.width, catchCamera.targetTexture.height, TextureFormat.ARGB32, false);
        camTex.ReadPixels(new Rect(0, 0, catchCamera.targetTexture.width, catchCamera.targetTexture.height), 0, 0);
        camTex.Apply();
        RenderTexture.active = rendText;
        //进行边缘检测
        GetAllGradient(camTex);
        //保存图片
        byte[] bytes = camTex.EncodeToPNG();
        File.WriteAllBytes(Application.dataPath+ "/TextureOutput/" + System.DateTime.Now.ToFileTime() + ".png", bytes);
    }
}
public enum GradientType
{
    Roberts,
    Prewitt,
    Sobel,
}

使用步骤:

将该脚本挂在场景中的摄像机上,并在场景中设置一张图片,使相机可以拍摄到该图片。
并且需要新建一张RanderTexture挂在相机的TargetTexture 上面。
GradType 参数可以选择不同的算子。
调整相应的参数后,在编辑器模式下右键点击该组件,执行RanderTexture方法,就可以在asset/TextureOutput目录下生成一张处理后的图片。
这里特别邀请皮卡叔做一下模特:
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测

放几张张最终的结果对比图(EdgeColor黑色,BackColor白色,EdgeWight = 1)。

Sobel算子。
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
Prewitt算子:
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测

Roberts算子:
Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测