您的位置:首页 > 移动开发 > Unity3D

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

2017-12-26 10:26 661 查看
通常使用Roberts、Prewitt、Sobel算子计算梯度,在图像处理时,梯度值比较常见的应用是用来计算边缘检测。

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

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

对于梯度,每个算子都有两个分量,分别用来计算X方向和Y方向的梯度。对于X方向,一般都是从左到右,所以变化不大。但是对于Y方向,有些平台是从下往上(坐标原点在左下角),有些平台是从上往下(坐标原点在左上角)。我们需要根据实际情况翻转Y方向的梯度算子。

这里看一下百度百科中对 Sobel算子 的描述:





可以看到同样的Y方向梯度算子,前后描述并不一致,上下颠倒。这是因为在上面的描述中,遍历算子中像素的方式和下面不一样。当我们需要从左往右,从上往下遍历算子时,就应采用第一个公式。

我们在遍历算子时使用从左往右,从下往上的顺序,应采用第二个公式。

下面两个图片表示了3x3算子的两种不同的遍历顺序:





在本例中,我们采用的是,红色示例表示的遍历的顺序。

接下来给出本例中使用的算子:

Roberts算子

Gx



Gy



Prewitt算子

Gx



Gy



Sobel

Gx



Gy



接下来给出代码:

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(<
4000
span class="hljs-number">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目录下生成一张处理后的图片。

这里特别邀请皮卡叔做一下模特:



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

Sobel算子。



Prewitt算子:



Roberts算子:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐