Unity Shader - 邊緣檢測

邊緣檢測(英語:Edge detection)是圖像處理和計算機視覺中的基本問題,邊緣檢測的目的是標識數字圖像中亮度變化明顯的點。圖像屬性中的顯著變化通常反映了屬性的重要事件和變化。這些包括(i)深度上的不連續、(ii)表面方向不連續、(iii)物質屬性變化和(iv)場景照明變化。 邊緣檢測是圖像處理和計算機視覺中,尤其是特徵檢測中的一個研究領域。

原理

邊緣檢測實質上就是通過對圖像的卷積的結果,卷積是數學分析中一種重要的運算,主要區別在於卷積核的選擇,通過卷積操作我們不僅可以做到邊緣檢測,還可以做到模糊圖像銳化圖像等功能。

“模糊圖像” 可以參考我的這篇文章

另外的描邊 Shader可以參考這篇

邊緣檢測的卷積核 也叫做 邊緣檢測算子
如:Roberts Cross算子, Prewitt算子, Sobel算子, Canny算子,羅盤算子

這裏以Sobel算子爲例子:

Gx=[+101+202+101]AGy=[+12+1000121]AG_x = \begin{bmatrix}+1&0& -1\\\\+2&0& -2\\\\+1&0& -1\end{bmatrix} *A \qquad G_y = \begin{bmatrix}+1&2&+1\\\\0&0& 0\\\\-1&-2& -1\end{bmatrix}*A

A{A}代表原始圖像,GxGyG_x和 G_y分別代表經橫向及縱向邊緣檢測的圖像,

通過以上公式就可以分別計算出橫向縱向梯度值,即GxGyG_x和 G_y
梯度值越大,邊緣就越明顯。

注意:

  • 計算梯度值前需要先把圖像置灰,方便我們計算梯度值
  • 橫向的梯度值檢測出來的是縱向的邊緣線,縱向的梯度值檢測出來的是橫向的邊緣線。

以上是理論,可能有些小夥伴看得很懵逼,下面我們舉一個例子來說明一下:
在這裏插入圖片描述   在這裏插入圖片描述   在這裏插入圖片描述
假如上圖是我們在圖像上隨機選取的兩塊3*3的像素塊,以及橫向的Sobel算子

像素塊一的梯度值:Gx1=1861+1862+1861233123322321=187G_{x1}=186*1 + 186*2 + 186*1 - 233*1- 233*2- 232*1=-187
像素塊二的梯度值:Gx2=1861+1862+1861185118621861=1G_{x2}=186*1 + 186*2 + 186*1 - 185*1- 186*2- 186*1=1

最後我們取他們的絕對值, 分別 187和1, 這裏我們就很明顯的看到了他們的區別,梯度值越大,說明該像素越有可能是邊緣!

Shader 代碼實現:

首先我們需要把圖像置灰,置灰非常簡單,通過灰度心理學公式 Gray = R*0.299 + G*0.587 + B*0.114 轉換即可!

這裏我們分別對橫向和縱向做邊緣檢測,以便於我們做對比,理解。

創建一個C#腳本拖拽到攝像機上:
PS: PostEffectsBase 基類可以在我的這篇文章查看
或者到我的GitHub上獲取

using UnityEngine;
using System.Collections;

//-----------------------------【邊緣檢測】-----------------------------
public class EdgeDetection : PostEffectsBase {

	public Shader edgeDetectShader;
	private Material edgeDetectMaterial = null;
	public Material material {  
		get {
			edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
			return edgeDetectMaterial;
		}  
	}
	[Range(0.0f, 1.0f)]
	public float edgesOnly = 0.0f;
	public Color edgeColor = Color.black;
	
	public Color backgroundColor = Color.white;

	void OnRenderImage (RenderTexture src, RenderTexture dest) {
		if (material != null) {
			material.SetFloat("_EdgeOnly", edgesOnly);
			material.SetColor("_EdgeColor", edgeColor);
			material.SetColor("_BackgroundColor", backgroundColor);
			Graphics.Blit(src, dest, material);
		} else {
			Graphics.Blit(src, dest);
		}
	}
}

shader:(橫向檢測)

//-----------------------------【邊緣檢測】-----------------------------
Shader "lcl/learnShader3/002 Chapter 12/Edge Detection Test" {
	//-----------------------------【屬性】-----------------------------
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	//-----------------------------【子着色器】-----------------------------
	SubShader {
		Pass {  
			ZTest Always Cull Off ZWrite Off
			
			CGPROGRAM
			
			#include "UnityCG.cginc"
			
			#pragma vertex vert  
			#pragma fragment frag
			
			sampler2D _MainTex;  
			uniform half4 _MainTex_TexelSize;
			
			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;
				//計算周圍像素的紋理座標位置,其中4爲原始點,
				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.299 * color.r + 0.587 * color.g + 0.114 * 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 it = 0; it < 9; it++) {
					// 轉換爲灰度值
					texColor = luminance(tex2D(_MainTex, i.uv[it]));

					edgeX += texColor * Gx[it];
					// edgeY += texColor * Gy[it];
				}
				
				// half edge = 1 - abs(edgeX) - abs(edgeY);
				half edge = abs(edgeX);
				return edge;
			}
			//-----------------------------【片元着色器】-----------------------------
			fixed4 frag(v2f i) : SV_Target {
				half edge = Sobel(i);
				return edge;

 			}
			
			ENDCG
		} 
	}
	FallBack Off
}

最終結果對比
在這裏插入圖片描述
最後我們合併一下橫向和縱向的邊緣及原圖,最終邊緣檢測結果爲:

在這裏插入圖片描述
最終Shader代碼:

//-----------------------------【邊緣檢測】-----------------------------
Shader "lcl/learnShader3/002 Chapter 12/Edge Detection Test" {
	//-----------------------------【屬性】-----------------------------
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		// 描邊程度
		_EdgeOnly ("Edge Only", Float) = 1.0
		// 邊緣顏色
		_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
	}
	//-----------------------------【子着色器】-----------------------------
	SubShader {
		Pass {  
			ZTest Always Cull Off ZWrite Off
			
			CGPROGRAM
			
			#include "UnityCG.cginc"
			
			#pragma vertex vert  
			#pragma fragment frag
			
			sampler2D _MainTex;  
			uniform half4 _MainTex_TexelSize;
			fixed _EdgeOnly;
			fixed4 _EdgeColor;

			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;
				//計算周圍像素的紋理座標位置,其中4爲原始點,
				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.299 * color.r + 0.587 * color.g + 0.114 * 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 it = 0; it < 9; it++) {
					// 轉換爲灰度值
					texColor = luminance(tex2D(_MainTex, i.uv[it]));

					edgeX += texColor * Gx[it];
					edgeY += texColor * Gy[it];
				}
				// 合併橫向和縱向
				half edge = 1 - (abs(edgeX) + abs(edgeY));
				return edge;
			}
			//-----------------------------【片元着色器】-----------------------------
			fixed4 frag(v2f i) : SV_Target {
				half edge = Sobel(i);
				fixed4 edgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
				edgeColor = lerp(tex2D(_MainTex, i.uv[4]),edgeColor, _EdgeOnly);
				return edgeColor;
 			}
			
			ENDCG
		} 
	}
	FallBack Off
}

最後

有興趣的小夥伴可以來我的GitHub上逛逛,裏面有我學習Shader過程中的一些記錄,實現的一些特效,喜歡的可以點個Star,嘿嘿,謝謝啦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章