Unity之梯度应用实现Roberts、Prewitt、Sobel边缘检测
通常使用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(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算子:
上一篇: 查找/替换对话框的使用
下一篇: 推荐一个超好用的linux服务器监控插件