圖像處理 傅里葉正逆變換與餘弦正逆變換 【附C++實現】

一點說明

1. 完成狀況

  • 傅里葉變換(DFT)與餘弦變換(DCT)
  • 快速傅里葉變換(FFT)與快速傅里葉逆變換(IFFT)
  • 快速餘弦變換(FCT)與快速餘弦逆變換(IFCT)

2. 項目結構

  • root/
    • bin/(存放可執行文件,雙擊直接運行
    • src/(存放項目源代碼,編譯須要先配置OpenCV)
    • Photos/(存放圖片樣例,包括原圖與變換後的圖片)
    • Data/(存放圖像變換生成的數據,如幅度值與相位值)
    • README.md(技術分析報告)

思路分析

1. 傅里葉變換

利用二維的傅里葉變換,咱們能夠將圖像信號從空間域(spatial domain)變換到其對應的頻域(frequency domain)中進行分析,這與咱們平常觀察世界的視角是大相徑庭的。dom

所謂傅里葉變換實際上是正交變換的一種,其原理是「週期與非週期信號均可用正弦函數的加權積分表示」。在圖像處理中,咱們通常用到的是二維離散傅里葉變換,具體公式以下。函數

img

在具體的編碼實現時,咱們經常利用歐拉變換將公式中的實部與虛部分離(以下圖)。優化

img

能夠發現, 經過上述變換,咱們將二維離散傅里葉變換的公式由\(F(u,v)=|F(u,v)|e^{jφ(u,v)}\)的形式轉化成了\(F(u,v)=R(u,v)+jI(u,v)\)的形式,因此有下式。編碼

\[φ(u,v)=arctan[\frac{I(u,v)}{R(u,v)}]\\ |F(u,v)|=[R^2(u,v)+I^2(u,v)]^{\frac{1}{2}} \]

上式中的 \(φ(u,v)\)爲圖像信號在\((u,v)\)點的幅度值,\(|F(u,v)|\)爲信號在\((u,v)\)點的相位值。經過上面的拆分,咱們能夠輕鬆地編寫程序進行計算,獲得所謂的幅度圖像和相位圖像。其中幅度圖像包含了咱們所須要的圖像幾何結構的全部信息,在圖像分析和處理中應用最爲普遍。spa

2. 餘弦變換

餘弦變換也是正交變換的一種。由前面提到的傅里葉變換公式能夠知道,偶函數的傅立葉變換的虛部爲零,於是不須要計算,只計算餘弦項變換,這就是餘弦變換 。顯然,餘弦變換的變換核爲實數的餘弦函數(公式以下),其計算速度相比傅里葉變換要快得多,因此餘弦變換被普遍應用於圖像有損壓縮和語音信號處理等衆多領域。.net

image-20200420210619302

3. 逆變換

能夠證實,傅里葉變換與餘弦變換都是可逆的,其逆變換公式能夠經過矩陣的逆運算性質求出,具體公式以下。code

image-20200420211414562

image-20200420211418834

4. 快速變換

不管是快速傅里葉變換仍是快速餘弦變換,其實都是利用了變換的可分離性,藉助動態規劃的思想,將二維離散變換的複雜度從 \(O(n^2)\) 優化到 \(O(nlogn)\) 。基本思路以下圖所示(圖源博客)。blog

img

關鍵代碼

此處僅貼出了快速傅里葉正逆變換(FFT&IFFT)與快速餘弦正逆變換(FCT&IFCT)等關鍵代碼,其餘代碼如簡單離散傅里葉變換(DFT)與餘弦變換(DCT)的代碼請見附件。圖片

1. 主函數與主要參數

constexpr auto AMAX = 100; 	//調整幅度值可視化參數
constexpr auto CMAX = 50;	//調整餘弦變換可視化參數

//可選的照片:"photo" "Gundam" "lena"
static String name = "Gundam";

//將圖像存儲容器中的值映射到[0,255]的灰度區間內
enum StandardType
{
    AMPLITUDE,	//幅度圖
    PHASE,		//相位圖
    COSINE,		//餘弦變換圖
    SIFFT,		//傅里葉逆變換
    SIFCT		//餘弦逆變換
};

int Shift(Mat& src, Mat& dst); //中心化
int Standard(Container src, Mat& dst, int type); //映射到[0,255]區間
int DFT(Mat src, Container& A, Container& φ); //二維離散傅里葉變換
int DCT(Mat src, Container& C); //二維離散餘弦變換
int FFT(Mat src, Container& R, Container& I, Container& A, Container& φ); //快速傅里葉變換
int FCT(Mat src, Container& C); //快速餘弦變換
int IFFT(Container R, Container I, Mat& dst); //快速傅里葉逆變換
int IFCT(Container& C, Mat& dst); //快速餘弦逆變換

2. 快速傅里葉變換

int FFT(Mat src, Container& R, Container& I, Container& A, Container& φ) {
    double M = src.rows; double N = src.cols;
    for (int v = 0; v < N; v++) {
        vector<double> listR, listI;
        for (int x = 0; x < M; x++) {
            double r = 0.0, i = 0.0;
            for (int y = 0; y < N; y++) {
                double t = -2 * PI * v * y / N;
                r += src.ptr<uchar>(x)[y] * cos(t);
                i += src.ptr<uchar>(x)[y] * sin(t);
            }
            listR.push_back(r);
            listI.push_back(i);
        }
        for (int u = 0; u < M; u++) {
            double r = 0.0, i = 0.0;
            for (int x = 0; x < M; x++) {
                double t = -2 * PI * u * x / M;
                r += listR[x] * cos(t) - listI[x] * sin(t);
                i += listR[x] * sin(t) + listI[x] * cos(t);
            }
            R[u].push_back(r); I[u].push_back(i);
            A[u].push_back(sqrt(r * r + i * i));
            φ[u].push_back(atan(i / r));
        }
    }
    return 0;
}

3. 快速餘弦變換

int FCT(Mat src, Container& C) {
    for (int v = 0; v < N; v++) {
        vector<double> list;
        for (int x = 0; x < M; x++) {
            double c = 0.0;
            for (int y = 0; y < N; y++) {
                double t = (2.0 * y + 1.0) * v * PI / (double)(2.0 * N);
                t = (double)src.ptr<uchar>(x)[y] * cos(t);
                if (v == 0) t /= sqrt(2);
                c += t;
            }
            list.push_back(c);
        }
        for (int u = 0; u < M; u++) {
            double c = 0.0;
            for (int x = 0; x < M; x++) {
                double t = (2.0 * x + 1.0) * u * PI / (double)(2.0 * M);
                t = list[x] * cos(t);
                if (u == 0) t /= sqrt(2);
                c += t;
            }
            C[u].push_back(c * 2 / sqrt(M * N));
        }
    }
    return 0;
}

4. 快速傅里葉逆變換

int IFFT(Container R, Container I, Mat& dst) {
    double M = dst.rows; double N = dst.cols; Container Cdst(M);
    for (int v = 0; v < N; v++) {
        vector<double> listR, listI;
        for (int x = 0; x < M; x++) {
            double r = 0.0, i = 0.0;
            for (int y = 0; y < N; y++) {
                double t = 2 * PI * v * y / N;
                r += R[x][y] * cos(t) - I[x][y] * sin(t);
                i += R[x][y] * sin(t) + I[x][y] * cos(t);
            }
            listR.push_back(r);
            listI.push_back(i);
        }
        for (int u = 0; u < M; u++) {
            double ifft = 0.0;
            for (int x = 0; x < M; x++) {
                double t = 2 * PI * u * x / M;
                ifft += listR[x] * cos(t) - listI[x] * sin(t);
            }
            Cdst[u].push_back(ifft / (M * N));
        }
    }
    Standard(Cdst, dst, SIFFT);
    return 0;
}

5. 快速餘弦逆變換

int IFCT(Container& C, Mat& dst) {
    double M = dst.rows; double N = dst.cols; Container Cdst(M);
    for (int v = 0; v < N; v++) {
        vector<double> list;
        for (int x = 0; x < M; x++) {
            double c = 0.0;
            for (int y = 0; y < N; y++) {
                double t = (2.0 * y + 1.0) * v * PI / (double)(2.0 * N);
                t = C[x][y] * cos(t);
                if (v == 0) t /= sqrt(2);
                c += t;
            }
            list.push_back(c);
        }
        for (int u = 0; u < M; u++) {
            double c = 0.0;
            for (int x = 0; x < M; x++) {
                double t = (2.0 * x + 1.0) * u * PI / (double)(2.0 * M);
                t = list[x] * cos(t);
                if (u == 0) t /= sqrt(2);
                c += t;
            }
            Cdst[u].push_back(c * 2.0 / sqrt(M * N));
        }
    }
    Standard(Cdst, dst, SIFCT);
    return 0;
}

結果展現

1. 基本展現

從左到右依次爲 原圖 - 幅度圖 - 餘弦變換圖 - 相位圖get

img
img
img

2. 傅里葉變換

爲了更好的展現兩種變換的特色,避免將數據映射到[0,255]灰度區間所帶來的數據丟失,我在三維空間座標系中從新繪製了幅度圖與相位圖,以下圖所示。

(1)幅度圖
img
(2)中心化之後的幅度圖
img
(3)相位圖
img

2. 餘弦變換

從下圖咱們能夠看出,餘弦變換後圖像信號的能量集中於一角,這是餘弦變換最顯著的特色。

img

做業小結

  • 本次做業使用C++語言進行編寫,在編寫過程當中,數字精度的控制給我形成了很大的困擾;
  • 映射到[0,255]灰度區間時不一樣映射函數的選擇也給我帶來了很大的麻煩,最後我爲不一樣的圖像設置了不一樣的映射函數,經過枚舉變量做爲參數進行選擇;
  • 一直沒有找到合適的可視化方法,直到 Origin 出如今個人面前;
  • 傅里葉變換與餘弦變換的原理掌握還不夠透徹,須要進一步理論的增強;
  • 本想要作濾波等傅里葉變換的實際應用,但終是沒有時間,下次必定,下次必定!
相關文章
相關標籤/搜索