2次元フーリエ変換,フーリエ逆変換

17 6月 2010 Under: opencv2.x-samples

YouTube で見た以下のビデオが面白かったので,似たようなものを OpenCV のサンプルとして作ってみました.cv::dft,cv::idft を利用しています.処理の詳細は,ビデオをご覧ください.

C++

#include <iostream>
#include <cv.h>
#include <highgui.h>

using namespace cv;
using namespace std;

// class declaration
class DftIdftApp {
public:
  DftIdftApp(const string filename);
  ~DftIdftApp(){};
  void calcMagImage();
  void showOrgImage(){imshow(org_win, src_img);}
  void showMagImage(){imshow(mag_win, mag_img);}
  void showIDFTImage(){imshow(idft_win, idft_img);}
  void shiftDFT(Mat &src, Mat &dst);
  void calcIDFT(bool all=false);
  void clear(){idft_img.setTo(0);}
  static void onMouse(int event, int x, int y, int flags, void* param);
private:
  Mat src_img, mag_img;
  Mat Re_img, Im_img, Complex_img;
  Mat zero, dft_src, dft_dst, dft_dst_p;
  Mat idft_img;
  vector<Mat> mv;
  string org_win, mag_win, idft_win;
  int src_cols, src_rows, dft_cols, dft_rows;
};

// constructor
DftIdftApp::DftIdftApp(const string filename)
  :org_win("Original"), mag_win("Magnitude"), idft_win("IDTF")
{
  src_img = imread(filename, 0);
  if(!src_img.data) return;

  Size s_size = src_img.size();
  src_cols = s_size.width;
  src_rows = s_size.height;
  Re_img = Mat(s_size, CV_64F);
  Im_img = Mat::zeros(s_size, CV_64F);
  Complex_img = Mat(s_size, CV_64FC2);
  
  src_img.convertTo(Re_img, CV_64F);
  mv.push_back(Re_img);
  mv.push_back(Im_img);
  merge(mv, Complex_img);
  idft_img = zero = Mat::zeros(s_size, CV_64F);

  namedWindow(org_win, CV_WINDOW_AUTOSIZE);
  namedWindow(mag_win, CV_WINDOW_AUTOSIZE);
  namedWindow(idft_win, CV_WINDOW_AUTOSIZE);
  cvSetMouseCallback(mag_win.c_str(), DftIdftApp::onMouse, this);
}

// calc magnitude image
void
DftIdftApp::calcMagImage()
{
  dft_rows = getOptimalDFTSize(src_rows);
  dft_cols = getOptimalDFTSize(src_cols);
  dft_src = Mat::zeros(dft_rows, dft_cols, CV_64FC2);
  
  Mat roi(dft_src, Rect(0, 0, src_cols, src_rows));
  Complex_img.copyTo(roi);

  dft(dft_src, dft_dst);
  dft_dst_p = Mat::zeros(dft_dst.size(), dft_dst.type());

  //split(dft_dst.mul(dft_dst), mv);
  //sqrt(mv[0]+mv[1], mv[0]);
  split(dft_dst, mv);
  magnitude(mv[0], mv[1], mv[0]);
  log(mv[0]+1, mv[0]); // for ver. 2.1 or later
   
  shiftDFT(mv[0], mv[0]);

  //double min, max;
  //minMaxLoc(mv[0], &min, &max);
  //mag_img = mv[0]*1.0/(max-min) - 1.0*min/(max-min);
  normalize(mv[0], mag_img, 0, 1, CV_MINMAX);
}

// swap 1,3 and 2,4 quadrants respectively
void
DftIdftApp::shiftDFT(Mat &src, Mat& dst)
{
  Mat tmp;
  int cx = src.cols/2;
  int cy = src.rows/2;

  for(int i=0; i<=cx; i+=cx) {
    Mat qs(src, Rect(i^cx,0,cx,cy));
    Mat qd(dst, Rect(i,cy,cx,cy));
    qs.copyTo(tmp);
    qd.copyTo(qs);
    tmp.copyTo(qd);
  }
}

// mouse event callback
void
DftIdftApp::onMouse(int event, int x, int y, int flags, void* param)
{
  DftIdftApp *app = static_cast<DftIdftApp*>(param);
  Mat &dft_dst = app->dft_dst;
  Mat &dft_dst_p = app->dft_dst_p;
  Mat &mag_img = app->mag_img;
  int cx = app->src_cols/2;
  int cy = app->src_rows/2;
  int mx=x, my=y;
  int w = app->dft_cols;
  int h = app->dft_rows;

  switch(event) {
  case CV_EVENT_MOUSEMOVE:
    if((flags&CV_EVENT_FLAG_LBUTTON)==0) return;
  case CV_EVENT_LBUTTONUP:
    if(flags&CV_EVENT_FLAG_CTRLKEY) { // LeftButton+CTRL
      int step = 5;
      for(int j=-step; j<=step; j++) {
        my = y+j + ((y+j)<cy ? cy:-cy);
        if(y+j<0 || y+j>=h) continue;
        double *from = dft_dst.ptr<double>(my);
        double *to = dft_dst_p.ptr<double>(my);
        double *mag = mag_img.ptr<double>(y+j);
        for(int i=-step; i<=step; i++) {
          mx = x+i + ((x+i)<cx ? cx:-cx);
          if(x+i<0 || x+i>=w) break;
          to[(mx)*2+0] = from[(mx)*2+0];
          to[(mx)*2+1] = from[(mx)*2+1];
          mag[x+i] = 0;
        }
      }
    } else {  // LeftButton
      mx += x<cx ? cx:-cx;
      my += y<cy ? cy:-cy;
      double *from = dft_dst.ptr<double>(my);
      double *to = dft_dst_p.ptr<double>(my);
      double *mag = mag_img.ptr<double>(y);
      to[(mx)*2+0] = from[(mx)*2+0];
      to[(mx)*2+1] = from[(mx)*2+1];
      mag[x] = 0;
    }
    break;
  default:
    return;
  }
  app->calcIDFT();  
}

// Inverse Discrete Fourier Transforma
void
DftIdftApp::calcIDFT(bool all)
{
  if(all) {
    dft_dst.copyTo(dft_dst_p);
    mag_img.setTo(0);
  }

  double min, max;
  idft(dft_dst_p, dft_src);
  split(dft_src, mv);
  minMaxLoc(mv[0], &min, &max);
  idft_img = Mat(mv[0]*1.0/max, Rect(0, 0, src_cols, src_rows));
}

int
main(int argc, char *argv[])
{
  const string filename = argc > 1 ? argv[1] : "../image/Fourier.png";
  DftIdftApp app(filename);

  cout << "Usage: click or drag on Magnitude Image.\n" <<
    "Hot keys: \n"
    "\tESC - quit the program\n"
    "\ta - select all pixels\n"
    "\tr - restore original images\n"
    "\tleft mouse button - select one pixel\n"
    "\tCTRL+left mouse button - select a pixel and neighbors\n";

  app.showOrgImage();
  app.calcMagImage();
  while(1) {
    int c = waitKey(10);
    switch((char)c) {
    case '\x1b': // exit
      return 0;
    case 'a': // IDFT all
      app.calcIDFT(true);
      break;
    case 'r': // reset
      app.clear();
      app.calcMagImage();
      break;
    }
    app.showIDFTImage();
    app.showMagImage();
  }

  return 0;
}

実行結果例

(1)動画(サンプル実行の例)

(2)静止画

(左)対数パワースペクトル, (右)IDFT結果 左画像の黒い領域は,選択された(IDFTの対象となる)周波数,方向の正弦波を表します.この領域は,対数パワースペクトル画像上をマウスでクリック,ドラッグなどすることで選択できます.多くの正弦波を重ね合わせることで,より元の画像に近づく様子が分かります.

⇒  

⇒  

⇒    

ジョゼフ・フーリエ (Joseph Fourier)

“2次元フーリエ変換,フーリエ逆変換” への8件のコメント

  1. akii より:

    77行目の【shiftDFT(mv[0], mv[0]);】では、何のためにこのような処理がされているのでしょうか。
    基本的なことだったらすいません。
    よろしくお願いします。

  2. Munya より:

    これはわかりやすいですね。
    フーリエ変換とOpenCVの使い方がよくわかりました。

    >idft_win(“IDTF”)

    IDTFはIDFTのミスですよね。
    勉強してて、ちょっと微笑みました。

    すごく勉強になりました。

  3. ftarrega より:

    お世話になります.
    僭越ながら質問させて頂きます.

    上記 DftIdftApp::calcMagImage() において,

    dft_rows = getOptimalDFTSize(src_rows-1);
    dft_cols = getOptimalDFTSize(src_cols-1);

    のコードがありますが, getOptimalDFTSize() 関数の引数がなぜ src_rows-1 あるいは src_cols-1
    とわざわざ”-1″のついた形になっているのでしょうか.

    getOptimalDFTSize() は必ず引数以上の値を返すので,上記の場合の戻り値が src_rows-1 および
    src_cols-1 であれば,元の画像のサイズが (src_rows * src_cols) であるのに対し,それより小さい
    サイズの ((src_rows-1) * (src_cols-1)) でDFTを行ってしまうのではないかと思います.

    この”-1″はReference ManualやOpenCV付属のサンプルソースでもそうなっているのですが(C/C++ともに),
    Reference Manualやインターネットの検索でも分かりませんでした.

    “-1″が必要である根拠を教えて下さればありがたく存じます.
    よろしくお願い致します.

    • idojun より:

      ご指摘ありがとうございます.
      OpenCVのソースコードを追いかけてみたのですが,これは単純にサンプルのバグのようです.ご迷惑をおかけしました.
      ちなみに,r4709以降のSVN版やOpenCV-2.2付属のサンプル(cpp)でも修正されていました(しかし,Python版はそのまま).
      上述のサンプルも修正いたしました.

      • ftarrega より:

        idojun様
        ご返信ありがとうございます.
        了解致しました.

  4. Yoshii より:

    返事が遅れてすみません。
    了解しました。ありがとうございました。

  5. Yoshii より:

    単純な質問ですが、「2次元フーリエ変換,フーリエ逆変換」のサンプルソースの74行目(log(mv[0]+1, mv[0]); )ですが、これで正しいのでしょうか?よろしくお願いします。

    • idojun より:

      2乗和の値が大きいため,その対数を取っています.その際に「0」の対数を取ることはできませんので,全体に+1をしてから処理を行っています.

コメントをどうぞ