【Unity Shader】屏幕后处理2.0:实现Sobel边缘检测
边缘检测是描边效果的一种实现方法,关于描边效果其实还有更好的基于深度+法线纹理实现的方法,这里就先以边缘检测为主进行学习。
1 理解卷积
参考:深入理解卷积(卷积核到底要不要翻卷)
[深度学习]深度学习中卷积操作和数学中卷积操作的异同
哪位高手能解释一下卷积神经网络的卷积核? - 知乎 (zhihu.com)
卷积(Convolution),是图像处理中很常见的方法,平常也能在课程学习中(例如我《机器学习》这门课)看到它的身影(CNN,Convolutional neural network,即卷积神经网络)。但要注意,数学中的卷积和卷积神经网络中的卷积严格意义上是两种不同的运算方式。
1.1 数学中的卷积
数字图像处理中的卷积
数学中的卷积分为连续、离散两种卷积操作,但由于一般CNN都是离散卷积,所以数学卷积就拿二维离散卷积处理为例,我们可以拿数字图像处理为例,图像处理(Image Processing)是通过计算机技术将图像信号转化为数字信号,并利用数字信号对该图像进行处理的过程。
而图像处理中的卷积操作实际上是指使用一个卷积核对图像中的每个像素进行处理。卷积核(kernel)就是一个四方形网格结构(2X2、3X3的矩阵),网格的每个方格都有一个权重值,是一组权重的集合。
如何进行——当我们开始对一张图像的某个像素进行操作时,会把卷积核的中心放在该像素上,先翻转,再依次计算卷积核中每个权重值与覆盖像素值的乘积并求和,得到最终的新像素值。如果要对图像上多个像素处理,卷积核就先翻转,之后在进行平移、计算。
1.2 CNN中的卷积
卷积神经网络中的卷积,本质上是是信号处理中的互相关函数(Correlation)计算,或者说是相当于图像处理中的spatial filter。
跟数学上的卷积(Convolution)不同,CNN中的卷积更多的是为了提取图像的特征,仅仅借鉴了一个”加权求和“的概念。而且CNN本身就是一个寻求卷积核的过程,这个卷积核压根不像接下来的XX算子一样是给定的,而是未知、需要训练得出的,因此无需翻转。
2 边缘检测概述
边缘检测是图像处理和计算机视觉中一个常见的问题,目的是标出数字图像中亮度变化明显的点。参考高通滤波法、微分算子法、神经网络方法实现图像边缘检测,我们能够发现实际上实现边缘检测,有很多方法:高通滤波、微分算子法、神经网络方法。
其中,
- 高通滤波——检测图像某个区域,根据像素与周围像素的亮度插值来提升该像素亮度的滤波器
- 微分算子法——就是用一些特定的边缘检测算子(核)对图像进行卷积操作,我感觉就是高通滤波的一种具体的实现方法。对于边缘检测来说,这些算子(卷积核)在原始图像上移动,当图像上最亮的点经过核中央像素时,生成的新像素会比周围的更加突出,边缘这样被检测了出来,我们可以在后面的Sobel算子的代码去理解这个检测过程
(上述叙述参考了 计算机视觉(二):边缘检测)
2.1 边缘检测算子
首先,理解梯度
前面介绍微分算子法时就提到了图像的边缘是如何被检测出来的,换个思路还可以反过来想边缘是如何形成的?——边缘(edge)是指一个图像上局部特性的不连续性,图像上如果相邻像素之间存在显著的颜色、亮度、纹理等等属性的差别(突变),我们就会认为这里存在一条边界。从卷积角度理解就是:核经过亮像素点时,中间像素值和周围像素值会有一个突变,我们就用梯度(gradient)去描述这一个数值的突变。
不错!也就是说,边缘处的梯度总会是比较大的,那么现在目光聚焦到了:
获取梯度——算子
于是,为了获得这样一个梯度,几种不同的边缘检测算子(卷积核)相继被提出。详细一点可以直接参考这篇文章:常见边缘检测对比(Roberts算子、Prewitt算子、Sobel算子、Laplacian算子、Canny算子),这篇文章对几种常见的算子进行了公式上和优缺点的对比,同时给出了每种算子常用的场景,值得一看。
2.2 Sobel算子简介
参考了边缘检测算子(Roberts算子、Prewitt算子、Sobel算子 和 Laplacian算子)
由于后面实现边缘检测用到了Sobel算子,这之前必须要做一个简单的了解。
Sobel算子结合了高斯平滑和微分求导,充分考虑了位置对边缘影响——相邻点的距离远近对当前像素点的影响程度,距离越近的影响程度越大,从而可以实现图像锐化并突出边缘轮廓的效果。Sobel算子对噪声较多的图片进行边缘检测的效果更好!对灰度变化不敏感。
为什么算子的权重和都为0?
学着学着我发现用于边缘检测的卷积核,无论是简单的Roberts、Prewitt还是Sobel算子,核里的权重值和都为0,而且这几个卷积核是180°对称的(上下翻转是对称的),这是为啥?
找到了有人跟我有一样的疑问:为什么卷积核的系数和为零,卷积后的图像像素。也为零?有什么好处?
从数学角度,个人认为解释的较为贴切的回答:
如果理解的更浅显、通俗一点,还是拿Sobel算子为例,可以假设中间像素周围的像素值都十分相近(该像素并不在边缘位置),那么得到的梯度值一定是趋近于0的。
3 Unity中实现Sobel边缘检测
好了,上面一直介绍卷积、边缘检测,这里终于可以进行具体的实现了。与之前实现画面亮度、饱和度和对比度调整的过程类似,实现边缘检测也需要给Main Camera挂上一个特定的脚本,并给它相应的Material和Unity Shader。
3.1 摄像机挂上C#脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;[ExecuteInEditMode]
public class edgeDetect : MonoBehaviour
{public Material edgeDetectMaterial;public Shader edgeDetectShader;[Range(0.0f, 1.0f)]public float edgesOnly = 0.0f;public Color edgeColor = Color.black;public Color backgroundColor = Color.white;private void OnRenderImage(RenderTexture source, RenderTexture destination){if(edgeDetectMaterial!=null){edgeDetectMaterial.SetFloat("_EdgeOnly", edgesOnly);edgeDetectMaterial.SetColor("_EdgeColor", edgeColor);edgeDetectMaterial.SetColor("_BackgroundColor", backgroundColor);Graphics.Blit(source, destination, edgeDetectMaterial);}else{Debug.LogWarning("Please input your Material");Graphics.Blit(source, destination);}}
}
3.2 Shader代码
先放上完整代码:
Shader "Unity Shaders Book/Chapter 12/edgeDetectShader"
{Properties{_MainTex ("Texture", 2D) = "white" {}_EdgeOnly ("EdgeOnly", float) = 1.0_EdgeColor ("EdgeColor", Color) = (0, 0, 0, 1)_BackgroudColor ("BackgroundColor", Color) = (1, 1, 1, 1)}SubShader{Pass{ZTest Always Cull Off ZWrite OffCGPROGRAM#pragma vertex vert#pragma fragment fragSobel#include "UnityCG.cginc"//propertiessampler2D _MainTex;half4 _MainTex_TexelSize;fixed _EdgeOnly;fixed4 _EdgeColor;fixed4 _BackgroudColor;struct v2f {float4 pos : SV_POSITION;half2 uv[9] : TEXCOORD0; };v2f vert(appdata_img v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);half2 uv = v.texcoord;o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0);o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1);o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1);return o;}fixed luminance(fixed4 color) {return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;}//自定义一个Sobel算子half Sobel(v2f i) {//定义卷积核:const half Gx[9] = {-1, 0, 1,-2, 0, 2,-1, 0, 1};const half Gy[9] ={-1, -2, -1,0, 0, 0, 1, 2, 1};half texColor;half edgeX = 0;half edgeY = 0;for(int j=0;j<9;j++) {texColor = luminance(tex2D(_MainTex, i.uv[j])); //依次对9个像素采样,计算明度值edgeX += texColor * Gx[j];edgeY += texColor * Gy[j];}half edge = 1 - abs(edgeX) - abs(edgeY); //绝对值代替开根号求模,节省开销//half edge = 1 - pow(edgeX*edgeX + edgeY*edgeY, 0.5);return edge;}fixed4 fragSobel(v2f i) : SV_Target {half edge = Sobel(i);fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge); //4是原始像素位置fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroudColor, edge);return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);}ENDCG}}FallBack Off
}
接下来挑我认为的重点细说一下。
计算梯度值
代码中依次计算9个方格覆盖的像素(包括当前中心像素)的明度值,分别与自己x和y方向的权重值相乘,叠加后,得到最终中心像素Gx和Gy的梯度值,接着计算edge值(绝对值代替了开根号,为了节省开销),edge越小(梯度越大),越有可能是边缘点。
为什么要给一个edge参数?
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge); //4是原始像素位置
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroudColor, edge);
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
我认为给一个edge值是为了实现用户通过调整_EdgeOnly的值控制边缘呈现样式的目的,我们知道:
- 当edge趋向于1时意味着当前像素是边缘点的几率小
- edge趋向于0时像素是边缘点的几率大;
从结果的lerp角度说:
- 当_EdgeOnly=0,非边缘点的颜色将会是像素本身的颜色,而边缘点的颜色将会是用户设定的边缘颜色(默认是黑色)
- 当_EdgeOnly=1,非边缘点的颜色将会是背景色(默认白色),而边缘点的颜色将会是用户设定的边缘颜色(默认是黑色)
3.3 最终效果
_EdgeOnly=0
_EdgeOnly=1
4 一些后话
通过结果图我们可以发现,通过Sobel算子进行的边缘检测操作,实现的描边效果其实不是很好,因为他把一些纹理、阴影等边界也给包括进去了。为了实现更好的描边效果,后面的博客将会介绍进阶版的结合深度和法线纹理的边缘检测实现描边效果的方案。
【Unity Shader】屏幕后处理2.0:实现Sobel边缘检测相关推荐
- Unity Shader 屏幕后处理之血屏
欢迎来到我的第一篇博客 看了冯乐乐老师的<Unity Shader入门精要>在感叹Shader牛逼的同时也对图形学产生了莫大的兴趣,推荐各位在学Unity已及对Shader有兴趣的小伙伴一 ...
- Unity Shader·屏幕抖音效果
Unity Shader·屏幕抖音效果 前言 最近在做一个新的MMD(用Unity来实现),其中用到了一些好看的渲染技术在这里分享一下. 视频链接 https://www.bilibili.com/v ...
- Unity Shader·屏幕破碎效果
Unity Shader·屏幕破碎效果 前言 最近在做一个新的MMD(用Unity来实现),其中用到了一些好看的渲染技术在这里分享一下. 视频链接 https://www.bilibili.com/v ...
- 【Unity Shader】屏幕后处理3.0:均值模糊和高斯模糊
发现之前学习记录的太过详细,导致整理的过程占用太长的时间了,这篇之后博客重要的是掌握实现过程,关于基础的理论会更多的放上别人写得更好的文章. 参考:[Unity Shader编程]之十五 屏幕高斯模糊 ...
- Unity Shader屏幕特效基础OnRenderImage()函数
前言: unity shader中的pass是会顺序执行的,但是由于在图像处理中我们常常需要使用一个pass的处理结果作为另一个pass的输入,这个时候就需要用到OnRenderImage()函数了 ...
- Unity Shader 屏幕后效果——高斯模糊
高斯模糊是图像模糊处理中非常经典和常见的一种算法,也是Bloom屏幕效果的基础. 实现高斯模糊同样用到了卷积的概念,关于卷积的概念和原理详见我的另一篇博客: https://www.cnblogs.c ...
- Unity Shader 屏幕后效果——Bloom外发光
Bloom的原理很简单,主要是提取渲染图像中的亮部区域,并对亮部区域进行模糊处理,再与原始图像混合而成. 一般对亮部进行模糊处理的部分采用高斯模糊,关于高斯模糊,详见之前的另一篇博客: https:/ ...
- Unity Shader 屏幕抓取,屏幕坐标
GrabPass("Name") 抓取屏幕,抓取后名字为Name 屏幕坐标 获取屏幕坐标有3种方法: #####SV_POSITION语义的xy 使用SV_POSITION语义,在 ...
- Unity shader学习之屏幕后期处理效果之高斯模糊
高斯模糊,见 百度百科. 也使用卷积来实现,每个卷积元素的公式为: 其中б是标准方差,一般取值为1. x和y分别对应当前位置到卷积中心的整数距离. 由于需要对高斯核中的权重进行归一化,即使所有权重相加 ...
最新文章
- Python:模拟登录、点击和执行 JavaScript 语句案例
- 2018-3-26论文(GWO和WOA)中Table1--Table3中的benchmark函数F1-F23图形
- 一个管理者的反思(太深刻了!)
- 当React Native 遇到了Google reCAPTCHA
- wifi协议_冷知识科普 手机上的Wifi/WLAN究竟有何区别
- 给大家推荐一位我非常佩服的Python工程师 人生赢家
- 横竖屏切换时候Activity的生命周期的总结
- 当前完整路径_详解关键路径法,这可能是你找得到最详细的了
- WebService之初体验
- tomcat报错“The specified JRE installation does not exist”
- 838计算机专业课包含什么,华南农业大学
- 【光学】基于matlab多缝夫琅禾费衍射【含Matlab源码 061期】
- 软件项目版本管理规范总结
- NetWare 客户服务禁用了欢迎屏幕和快速切换恢复方法
- 外卖CPS小程序部署指南,个人获取美团外卖小程序跳转链接
- shell命令 ffmpeg 批量提取视频的音频文件
- 几种优秀的屏幕录像软件用法介绍(图)
- 方法重载和重写的区别,以及如何体现了多态性
- 塔望食业洞察|中国乳制品市场分析、竞争格局、发展趋势及思考
- 计算机等级考试office2007,计算机等级考试:WPS巧借Office2007制作描红字帖
热门文章
- 信号相角位移量的计算与信号位移计算-附Matlab代码
- 【Linux驱动】I2C子系统与触摸屏驱动
- 前端无法反序列化START\u数组标记-JSONCannot deserialize instance of `java.lang.String` out of START_ARRAY toke
- 对称加密及AES加密算法
- 先验、后验概率,似然,EM算法,ELBO(Evidence Lower Bound),多变量条件概率公式(多变量贝叶斯公式)
- Retrofit请求时动态切换IP
- 几款简约而且华丽丽的桌面应用
- Revit如何给模型绑定动画的教程
- MS Sql Server查询数据库文件大小和剩余空间,数据库日志压缩
- 信号处理与数据分析——Z变换