- 第一次傅立葉就上手
- 一行心得
第一次傅立葉就上手,實作流程詳述!
以下為助教範例提供的ex2.bmp,解析度64 x 64:
===這是原圖,雖然沒特色,但方便Debug===
第一步,前餐,在傅立葉之前
//前置準備工作, 讀取圖片 與 定義參數, 以下數值針對範例舉例 Mat I = imread(filename, CV_LOAD_IMAGE_GRAYSCALE); // 讀取灰階圖 print( I.type() ); // 確認型態為 CV_8UC1, uchar 8 / channel 1 int M = I.cols; // M(width), 對傅立葉loop重要, 值為64 int N = I.rows; // N(height), 值為64 ... // zero-padding, I -> padded, 變兩倍大小, 為了不混疊 Mat padded; copyMakeBorder(I, padded, 0, N, 0, M, BORDER_CONSTANT, Scalar::all(0)); Mat plane = Mat_(padded); // 為了運算方便, 轉成double, 配合之後的CV_64F型態的Mat int P = plane.cols; // P = M / 2, 對傅立葉loop重要, 值為128, int Q = plane.rows; // Q = N / 2, 值為128, // 奇數座標, 值乘以-1, 將頻域shift to center // 參考 : Spatial domain*-1^(x+y) <=> Frequency domain F(u-P/2, v-Q/2) for (int y = 0; y < N; ++y) for (int x = 0; x < M; ++x) if( (x+y)&1 ) plane.ptr (y)[x] = plane.ptr (y)[x] * -1.0;
第二步,魔王的 離散傅立葉 D F T
// 參數快取, 加速計算
double PI_2 = 2.0 * CV_PI;
double angleTerm = 0.0;
double invP = 1.0 / P; // 注意
double invQ = 1.0 / Q; // 注意
// 實數. 虛數. Spectrum, Phase一次搞定, CV_64F型態
Mat Real = Mat::zeros(plane.size(), CV_64F); // 注意, size = P*Q
Mat Imag = Mat::zeros(plane.size(), CV_64F); // P*Q
Mat Spectrum = Mat::zeros(plane.size(), CV_64F);
Mat Phase = Mat::zeros(plane.size(), CV_64F);
// 傅立葉, 外圈循環計算F(u,v), 內圈循環f(x,y), 注意loop範圍, P.Q.M.N看清楚
for (int v = 0; v < Q; ++v)
{
double v_invQ = (double)(v)*invQ; // 加速計算
for (int u = 0; u < P; ++u)
{
double u_invP = (double)(u)*invP;
double rd = 0.0; // 實部加總
double id = 0.0; // 虛部加總
for (int y = 0; y < N; ++y)
{
double vy_invQ = (double)(y)*v_invQ;
for (int x = 0; x < M; ++x)
{
// 參照傅立葉. 尤拉公式, 參數聯想Cos與Sin函數的震幅.頻率.位移.週期
// e^(j*angleTerm) = cos(angleTerm) + j*sin(angleTerm)
angleTerm = PI_2*( (double)(x)*u_invP + vy_invQ );
double target = plane.ptr(y)[x]; // f(x,y)
rd += (double)(target*cos(angleTerm));
id -= (double)(target*sin(angleTerm));
}
}
Real.ptr(v)[u] = rd;
Imag.ptr(v)[u] = id;
// 參見公式
Spectrum.ptr(v)[u] = (double)(sqrt(rd*rd + id*id));
Phase.ptr(v)[u] = (double)(atan2(id, rd));
}
}
DFT卡關很久,原因在於公式上的M. N 與 P. Q搞混,反覆嘗試後,才得出正確結果
轉換到,順便得出兩個目標,Spectrum與Phase angle,顯示出來!
//imshow可接受範圍為[0-255]或[0-1], 兩個Mat型態都是double, 選擇正規化[0,1]後顯示
normalize(Spectrum, Spectrum, 0, 1, CV_MINMAX);
normalize(Phase, Phase, 0, 1, CV_MINMAX);
// viewable image form (float between values 0 and 1).
// Show the result
imshow("Input Image", I); // Show the result
imshow("Spectrum Image", Spectrum); // Show the result
imshow("Phase angle Image", Phase);
===是狙擊手,快趴下! Spectrum (沒做Log)===
===質 Phase angle 感===
第2.5步,如果顯示結果非預期...
提供點Debug技巧,拆解傅立葉公式,從觀念思考起:
===傅立葉公式,關鍵的angleTerm===
===尤拉公式,將 e 變成我們熟悉的cos與sin===
===根據cos與sin函數 的 特質,推出可能的問題點,嘗試看看===
第三步,EX關卡 反離散傅立葉I D F T
想說解決傅立葉,剩下都小咖了,沒想到又被 IDFT 的小細節卡到,挑出細節講述 //double invP = 1.0 / P; // 注意, 仍是P與Q
//double invQ = 1.0 / Q; // 注意
double invPQ = invP * invQ;
double cosineA, sineA;
Mat Recover = Mat::zeros(I.size(), CV_64F); // Size=M*N
for (int y = 0; y < N; ++y)
{
double y_invQ = (double)(y)*invQ;
for (int x = 0; x < M; ++x)
{
double x_invP = (double)(x)*invP;
double inv_rd = 0.0; // 加總實部
double inv_id = 0.0; // 加總虛部
for (int v = 0; v < Q; ++v)
{
double vy_invQ = (double)(v)*y_invQ;
for (int u = 0; u < P; ++u)
{
angleTerm = PI_2*( (double)(u)*x_invP + vy_invQ );
cosineA = cos(angleTerm);
sineA = sin(angleTerm);
double rd = Real.ptr(v)[u]; // Re(F(u,v))
double id = Imag.ptr(v)[u]; // Im(F(u,v))
// 實部係數為( real * cos x - imag * sin x )
// 虛數係數為( real * sin x + imag * cos x )
inv_rd += (rd*cosineA - id*sineA); // 實*實 - 虛*虛(變號)
inv_id += (rd*sineA + id*cosineA); // 實*虛 + 虛*實
}
}
inv_rd *= invPQ; // 對應公式上的 1/MN
inv_id *= invPQ;
//double odd_mul = ( (x+y)&1 ) ? -1 : 1; // 不知為何不用乘-1, 待釐清
// 實部平方 + 虛部平方, 再開根號
Recover.ptr(y)[x] = (double)(sqrt(inv_rd*inv_rd + inv_id*inv_id));
}
}
// 第二個顯示技巧, 用convertTo函數, 將CV_64F轉成CV_8U
Mat rec_out;
Recover.convertTo(rec_out, CV_8U);
imshow("Recover", rec_out);
// 小技巧, Image Compare, 確認產生結果與原圖一致, errorL2與similarity越小越相似
double errorL2 = norm( I, rec_out, CV_L2 );
double similarity = errorL2 / (double)( I.rows * I.cols );
===IDFT還原,一模一樣就對了===
第四步,Gaussian Filter
著重介紹頻域的,空間域的 則找維基,公式帶入
// Gaussian Frequency Filter, 此段放入DFT後(轉完頻域), IDFT前 double pow_D02 = 2.0*D0*D0; // D0 可以給使用者輸入 for (int v = 0; v < Q; ++v) { for (int u = 0; u <; P; ++u) { // M = P/2, N = Q/2, 公式一樣, 自行代換 double Duv = (double)(sqrt( (u-M)*(u-M)+(v-N)*(v-N) )); double Huv = exp(-(Duv*Duv/pow_D02)); // 注意, 不是尤拉, 而是exp Real.ptr(v)[u] *= Huv; Imag.ptr (v)[u] *= Huv; } } ===Gaussian Frequency Filter,D0=10.0===
===Gaussian Spatial Filter 3x3===
===Gaussian Spatial Filter 5x5===
一行心得
資質不好,耗費三天,寫得好崩潰XD,收穫挺多的,有種把上課學到的,應用起來的FU!









沒有留言:
張貼留言