二維傅立葉變換與逆變換基於Unity的實現

2021-02-21 Unity3D遊戲開發精華教程乾貨

在網上看到很多關於傅立葉變換的內容, 但是沒找到具體工程上完整的一個例子

例如把一個紋理轉化為頻譜圖和相位 然後利用頻譜和相位在轉化回來

於是就自己做一個好了

如果有不對之處請使勁噴

然後如果你比較熟悉只想看工程部分的內容, 可以酌情跳過

先看一眼結果:

放出Git 地址:

https://github.com/lingzerg/LingzergDemo

在看一眼流程:

然後我們慢慢解釋

什麼是信號?

先把思路捋清楚

講傅立葉變換的定義有很多, 推薦大家各種搜一下

比如3B1B的傅立葉變換:

https://www.bilibili.com/video/BV1pW411J7s8

比如這篇文章:

https://zhuanlan.zhihu.com/p/19763358

我簡單描述下, 正常情況下 我們看到的信號是這樣的:

這種信號你可以想像是聲音, 在時間軸上連續不斷變換,自然界中的信號我們認為他是連續的, 比如這樣:

橫軸t就是時間, 所以這個叫做時域

這就是一個信號在時間軸上不斷變化, 所以這種信號我們叫一個信號的時域表示

然後為了讓計算機可以記錄描述這種信號,我們會這樣:

對信號進行離散採樣

以一個固定間隔對信號進行採樣, 得到的數據包含這個信號在當前採樣點的振幅和頻率

公式描述是:

f是頻率(frequency), A是振幅(Amplitude), phase是相位,x是時間, 得到的是振幅

所以我們只要是討論計算機領域的信號, 就一定是離散的

推薦大家去坐標系中手動拉一拉這些變量感受下這些變量的作用:

Desmos | Graphing Calculatorwww.desmos.com

什麼是傅立葉變換?

我們拿到一個信號,有時候想過濾掉高頻部分,例如通話降噪,還有一些不想要的低頻部分

這時我們可以用複數坐標系去描述一個信號, 讓信號的另外一個特徵暴露出來,以便於我們處理

複數坐標系:

使用複數平面描述信號有什麼好處呢?

在複數和實數構建的坐標系中,信號的時間周期就變成了一個環, 信號採樣就是不斷圍繞這個圈採樣

模長就是振幅,

那問題就在於,我們如何把一個我們抽樣得到的信號樣本轉換成複數形式?

先把歐拉公式扔這裡:

然後歐拉公式實際上是逆時針旋轉的,而正變換是順時針的

所以我們需要改成

而逆變換就要換回來

接著把頻率 和時間 帶入歐拉公式另一側, 除以 是因為N次採樣

在乘以2π 轉成弧度:

就如同公式裡的描述:

而這個又可以展開寫一篇了, 所以詳細描述我推薦諸位看我上面發的哪兩個連結

最後我們反正知道把一個信號的採樣結果帶入離散傅立葉變換的公式就可以得到一組複數的描述

在這個公式裡 實際上 x 就是時間點, u就是頻率,

就是每個單位時間的採樣點採樣得到的振幅,

最後會得到一個複數, 而逆變換就是反過來:

就是傅立葉變換得到的複數

二維傅立葉變換?

一維傅立葉變換和二維的差別,就在於二維還要在套一層,你需要遍歷整個二維空間

二維的離散逆傅立葉變換:

而在圖像處理中, 二維傅立葉變換上任意一個像素可以認為是一整個平面波:

還可以把圖片上的每個像素看成一個在x軸上方的信號,

比如這樣:

一個sin(x)+1的信號

接著將每個採樣點帶入公式就可以得到每個像素對應的複數

我開始做傅立葉變換的時候非常奇怪

我感覺傅立葉變換和逆變換的公式之間並沒有頻域和相位啊

那這個流程到底是什麼?

我先上一張圖然後慢慢解釋

這個基本上就是傅立葉變換的完整流程了

也就是說, 傅立葉變換原始公式裡是只有複數的,但是通過複數我們可以得到每個像素點的膜長, 還有弧度:

你不做這一步, 直接把複數的實部和虛部分別保存在紋理的r和g通道也是完全沒問題的

但是這就失去了對二維圖片進行傅立葉變換的意義

我們只有獲取了頻域和時域,才能根據頻域和時域處理圖片

我看過很多demo, 都是直接保存複數,而沒有對頻域和相位進行獨立的轉換

那我們怎麼在unity裡實現呢?

首先我選擇用compute shader來實現這個

因為感覺CS比較帥把

在Cshader中我們創建一個複數的結構體,並寫好常用方法:

struct Complex{    float real;    float imaginary;};
Complex getComplex(float num1, float num2){ Complex c; c.real = num1; c.imaginary = num2; return c;}
Complex getAdd(Complex c, Complex o){ c.real = c.real + o.real; c.imaginary = c.imaginary + o.imaginary; return c;}
Complex getSubtract(Complex c,Complex o){ c.real = c.real - o.real; c.imaginary = c.imaginary - o.imaginary; return c;} Complex getMultiply(Complex c,Complex o){ float tmp = c.real * o.real - c.imaginary * o.imaginary; c.imaginary = c.real * o.imaginary + c.imaginary * o.real; c.real = tmp; return c;}
Complex getMultiplyFloat(Complex c,float x){ c.real = c.real *x; c.imaginary = c.imaginary * x; return c;}
Complex getEuler(float x) { Complex c; c.real = cos(x); c.imaginary = sin(x); return c;}
int2 getCoordinate(int x, int y) { x = x - 31; y = y - 31; return int2(x,y);}

然後我們修改一下CS的核心, 按照公式做積分,詳細描述在注釋中:

[numthreads(16,16,1)]void Fourier (uint3 id : SV_DispatchThreadID){            float flag = -1 * 2 * 3.14 / TextureSize;     int2 coord = id.xy;        Complex sumRow;    sumRow.real = 0;    sumRow.imaginary = 0;
for(int i = 0; i< TextureSize; i++) { Complex complexU = getEuler(flag*i*coord.x); Complex sumColumn; sumColumn.real = 0; sumColumn.imaginary = 0; for(int j = 0; j < TextureSize; j++) { Complex complexV = getEuler(flag*j*coord.y); complexV = getMultiplyFloat(complexV, originalImg[int2(i,j)].r); sumColumn = getAdd(sumColumn,complexV); }
sumRow = getAdd(sumRow,getMultiply(sumColumn, complexU)); } float amplitude = sqrt(sumRow.real * sumRow.real + sumRow.imaginary * sumRow.imaginary); float phase = atan2(sumRow.imaginary , sumRow.real);
rtFourierSpectrum[id.xy] = amplitude/(TextureSize*TextureSize); rtFourierPhase[id.xy] = phase;
rtReal[id.xy] = amplitude * cos(phase); rtImaginary[id.xy] = amplitude * sin(phase); }

這時候我們就得到了頻譜圖和相位圖,你可以在這裡對頻譜或者相位進行一些操作

例如我注釋掉那個 //clamp(spectrum/TextureSize,0,0.5); 可以過濾一些高頻信息

或者用各種濾波器在這裡處理頻譜圖, 頻譜圖代表圖片的灰度信息,可以理解為亮度, 而相位代表位置信息.

然後用逆變換還原回去 我們就直接還原回去:

void FourierInverse(uint3 id : SV_DispatchThreadID){    float flag = 2 * 3.14 / TextureSize;     int2 coord = id.xy;        Complex sumRow;    sumRow.real = 0;    sumRow.imaginary = 0;
for(int i = 0; i< TextureSize; i++) { Complex complexU = getEuler(flag*i*coord.x); Complex sumColumn; sumColumn.real = 0; sumColumn.imaginary = 0; for(int j = 0; j < TextureSize; j++) { Complex complexV = getEuler(flag*j*coord.y); Complex sampleComplex; float amplitude = rtFourierSpectrum[int2(i,j)].r; float phase = rtFourierPhase[int2(i,j)].r;
sampleComplex.real = amplitude * cos(phase); sampleComplex.imaginary = amplitude * sin(phase);; complexV = getMultiply(complexV,sampleComplex); sumColumn = getAdd(sumColumn,complexV); } sumRow = getAdd(sumRow,getMultiply(sumColumn, complexU)); } float value = sqrt(sumRow.real * sumRow.real + sumRow.imaginary * sumRow.imaginary); rtFourierInverse[id.xy] = value; }

最後給一點tips:


聲明:發布此文是出於傳遞更多知識以供交流學習之目的。若有來源標註錯誤或侵犯了您的合法權益,請作者持權屬證明與我們聯繫,我們將及時更正、刪除,謝謝。

作者:lingzerg

原文:https://zhuanlan.zhihu.com/p/192659303

More:【微信公眾號】 u3dnotes

相關焦點

  • Unity中BVH骨骼動畫驅動的可視化理論與實現
    中驅動角色,而 unity 中的角色大部分是基於 Tpose 的,因而可以利用 Tpose 作為中間轉換姿態,將 BVH 中的所有動畫幀全部遷移到 unity 中。中都涉及到一個變換矩陣,如果內置姿態本來就是Tpose,那麼變換矩陣就是單位陣。
  • 論文精選 | 基於unity3D AR體感遊戲的設計與實現
    kinect2.0體感設備和 unity3D 2017.4.3開發AR體感遊戲。來  源《兵工自動化》2019年第9期《基於unity3D AR體感遊戲的設計與實現》作者:王濤單位:西安航空學院網絡信息中心,西安,710077引用格式:王濤。基於unity3D AR體感遊戲的設計與實現[J].兵工自動化,2019, 38(9):16-22.歡迎引用,謝謝!
  • 在unity中使用三種簡單的方式實現實時時鐘動畫
    中使用三種簡單的方式實現實時時鐘動畫目標這非常容易實現。你需要寫幾行代碼就可以實現了。在這篇文章中,我們將實現兩種動畫方式。效果圖在開始編碼之前,我想要告訴你一些下面會用到的關於 unity 的知識。什麼是材質在 unity 中,材質是用來給一個對象的細節,所以我們可以決定它會看起來像什麼。
  • 全橋逆變電路圖
    打開APP 全橋逆變電路圖 佚名 發表於 2019-11-04 10:00:06   全橋逆變電路圖
  • 一種基於FPGA實現SRRC濾波及多速率變換模塊的方法介紹
    一種基於FPGA實現SRRC濾波及多速率變換模塊的方法介紹 楊陽 閆崢 劉民偉 發表於 2020-01-22 16:22:00 0 引言 衛星通信系統中
  • 為什麼要進行傅立葉變換?
    讓我們先看看為什麼會有傅立葉變換?傅立葉是一位法國數學家和物理學家的名字,英語原名是Jean Baptiste Joseph Fourier(1768-1830),Fourier對熱傳遞很感興趣,於1807年在法國科學學會上發表了一篇論文,運用正弦曲線來描述溫度分布,論文裡有個在當時具有爭議性的決斷:任何連續周期信號可以由一組適當的正弦曲線組合而成。
  • 為什麼要進行傅立葉變換?(必看)
    讓我們先看看為什麼會有傅立葉變換?傅立葉是一位法國數學家和物理學家的名字,英語原名是Jean Baptiste Joseph Fourier(1768-1830),Fourier對熱傳遞很感興趣,於1807年在法國科學學會上發表了一篇論文,運用正弦曲線來描述溫度分布,論文裡有個在當時具有爭議性的決斷:任何連續周期信號可以由一組適當的正弦曲線組合而成。
  • unity 實現物體破碎效果
    現在很多遊戲的效果做的很逼真,那麼如何使用unity實現物體破碎的效果呢?相信很多做遊戲開發的想知道,為此下面就給大家介紹下unity 實現物體破碎效果的方法,一起來看看吧。    使用maya等3D工具製作碎塊組成的物體,遊戲中在物體被打碎的時候,首先銷毀原先的物體,然後再用碎片組成的pretab替換,形成的效果則是每個碎塊的加力方式不同,需要通過調整來實現。
  • AR實踐|基於Unity的傳送門——穿越如此簡單
    實現起來非常簡單,只要與有一個內面顯示,外面隱藏的場景模型就可以了,當然,模型最好有一個門,這樣才有穿門而入的感覺,也會有虛實兩屆穿越的感覺。作者實現了一個傳送門,靈感來源於哆啦A夢任意門。疫情之下,人們減少外出,也取消了許多旅行計劃,為彌補無法旅遊的遺憾,作者原本想利用傳送門實現虛擬旅遊概念。
  • Unity技術開放日 | 絕對乾貨 - 基於Unity SRP的Render Graph
    在 Unity 技術開放日-上海站活動中,我們邀請到紫龍遊戲 blackjack studio 的技術總監李知洋帶來精彩分享,《基於 Unity
  • 2021遊戲大廠Unity面試題
    皮膚蒙皮附著在骨骼上,決定了角色的外觀,每一個頂點數據都會隨著多個骨骼影響而改變,從而實現動畫效果。創建animator將各個動畫拖入到動畫狀態機當中,設置參數,連接各個動畫狀態,在通過腳本控制來實現動畫控制。關節動畫:了解不多,是骨骼動畫的前身,模型分成N個部分網格,分成部分動畫,組成一個整體動畫。
  • Unity免費的優質場景資源
    區域體積:基於體積的系統驅動的大氣散射效果、陽光和風力屬性。自定義大氣散射效果。為程序化風動畫製作的自定義頂點著色器,應用在所有的植被上。項目特有的光照著色器自定義內容。針對本項目的光照、陰影、遮蔽輸入和計算所做的自定義特殊處理。遮蔽探頭:應用於在樹葉上製作高效的天空遮蔽效果的烘焙解決方案。
  • Unity Shader技巧七則!
    Unity Shader 基於視差映射的雲海效果Unity Shader 實現雨天的水面漣漪效果1. Unity Shader 點陣像素剔除半透(Stipple Transparency )最近一段時間一直不停的刷只狼(打鐵真好玩..),無意中發現主角被場景物體遮擋的時候,半透效果比較有意思,是像馬賽克一樣被剔除掉了。
  • 霍夫變換及代碼實現
    霍夫變換簡述霍夫變換原理直線檢測原理在圖像空間中極坐標下霍夫變換示意圖兩種不同的霍夫變換原理代碼實現OUTPUT結論霍夫變換簡述霍夫變換是一種特徵檢測(feature extraction),被廣泛應用在圖像分析(image analysis)、計算機視覺(computer vision)以及數位影像處理(digital image processing
  • 騰訊雲發布人像變換新品,可實現細粒度年齡變化
    該產品基於騰訊優圖領先的人臉識別算法。它可以編輯面部特徵,以實現面部性別切換和年齡變化等效果。它用於社交娛樂,諸如廣告營銷和交互式交流之類的場景帶來了新的遊戲方式。肖像變換是今年流行的視覺特效。通過在相機應用中上傳照片,讓我們「穿越時間和空間」並一鍵跨越性別。
  • 基於數字移相器的逆變器系統相位跟蹤控制
    摘要:針對光伏分布式電源併網系統中的相位跟蹤控制問題,分析了數字鎖相環控制技術存在的缺點,提出一種基於數字移相器的相位跟蹤控制方法。實驗利用MSP430產生SPWM信號合成正弦波模擬光伏逆變電源系統。
  • Unity Shader實現《死亡擱淺》掃描效果!
    下面來按照這個步驟來逐步實現一下,另外說明的是我是在URP下實現的首先做一下這個掃描的效果,死亡擱淺中的掃描是扇形推進的,我這裡為了通用性 ,做成了圓形。這裡主要用到的技術是深度圖還原世界坐標。這裡推薦這一篇文章的下半部分《Unity Shader 深度值重建世界坐標》在本文底部擴展閱讀處。
  • 優化unity地形的曲面細分
    UnityEdgeLengthBasedTess 曲面細分問題修改思路經過觀察2個算法問題,那麼思路就比較明確了,就是要實現一版支持距離剔除的更近,對比以下的3個函數曲線圖可以看到,f2在黃箭頭處確實實現了更快下降
  • Unity3D 淺談美術那些事 - PBR技術
    從 Unity3D 5.x 開始有兩個標準著色器,一個名為Standard,我們稱它為標準著色器的標準版,另一個名為Standard(Specular Setup),我們稱它為標準著色器的高光版,它們共同組成了一個完整的PBS光照明模型,且非常易於使用。其實這兩個 Shader 基本差不多,只是有細微的屬性參數上的區別。
  • 通過 Unity 實踐渲染的基礎知識 #1 利用矩陣來進行空間變換
    當然,我們可以依賴於 Unity 自帶的 Transform 組件及 Shader 去處理這些事情,但是,如果你想要對物體的變換擁有絕對的控制權,那麼理解它們背後的實現原理就是十分重要的!為了完全理解全部過程,我們最好來創造一次自己的實現方式。位移,旋轉及縮放 Mesh 通過控制頂點的位置來實現,我們稱其為空間變換。我們最好將整個空間可視化,以了解我們究竟在做些什麼。