読者です 読者をやめる 読者になる 読者になる

EigenでFFTする

はじめに

Eigenという行列演算ライブラリーにFFTが入っているので試してみました。

FFT

EigenのFFTはkissfft、FFTW、Intel Math Kernel Libraryをバックエンドとしていて、デフォルトだとLGPLのkissfftがバックエンドになるみたいです。インターフェースがEigenになるので元のライブラリーより使いやすくなります。

実装

実装といってもすごく簡単でこれだけです。

#include <Eigen/Core>
#include <unsupported/Eigen/FFT>

void dofft(const std::vector<float>& time, 
               std::vector<std::complex<float>>& freq)
{
	Eigen::FFT<float> fft;

	fft.fwd(freq, time);
}

void doinv(const std::vector<std::complex<float>>& freq, 
                std::vector<float>& time)
{
	Eigen::FFT<float> fft;

	fft.inv(time, freq);
}

以前C#のMath.NETを使ってFFTをしましたので比べてみます。C++/CLIでdofwd()を呼ぶコードを書きます。

List<float>^ FFT::ProcessList(List<float>^ data)
{
	std::vector<float> time;
	for each (float x in data)
	{
		time.push_back(x);
	}

	std::vector<std::complex<float>> freq;
	dofwd(time, freq);

	List<float>^ processed_data = gcnew List<float>();
	for (auto x : freq)
	{
		processed_data->Add(sqrt(x.real()*x.real()+x.imag()*x.imag()));
	}

	return processed_data;
}

f:id:wildpie:20141021220706p:plain
同じ値が得られました(*゚ー゚)

2次元の場合

せっかく行列演算できるEigenを使っているので、2次元のデータをFFTしてみます。
一発でできる関数はないみたいなので、
(c++ - How to use Eigen FFT with MatrixXf? - Stack Overflow)
1行ごとにFFTしていきます。
ソースはこんな感じです。

void dofwd2d(MatrixXf& image, MatrixXcf& freq)
{
	FFT<float> fft;

	MatrixXcf temp(image.rows(), image.cols());
	for (int i = 0; i < image.cols(); i++)
	{
		temp.col(i) = fft.fwd(image.col(i));
	}

	temp.transposeInPlace();
	for (int i = 0; i < temp.cols(); i++)
	{
		freq.col(i) = fft.fwd(temp.col(i));
	}
}

// 表示用に結果をずらす
void shift(MatrixXcf& freq)
{
	MatrixXcf temp(freq.rows(), freq.cols());

	int cx = freq.cols() / 2;
	int cy = freq.rows() / 2;// 追記:rowとcol逆
	temp.block(0, 0, cx, cy) = freq.block(cx, cy, cx, cy);
	temp.block(cx, cy, cx, cy) = freq.block(0, 0, cx, cy);
	temp.block(cx, 0, cx, cy) = freq.block(0, cy, cx, cy);
	temp.block(0, cy, cx, cy) = freq.block(cx, 0, cx, cy);

	freq = temp;
}

col方向に演算した後でrow方向に演算するんですが、うまくいかなかったのでtransposeしています。
(追記:freq.row(i) =fft.fwd(temp.row(i)).transpose()みたいにすれば良かった)
適当な画像をFFTしてみました。Eigenの形式だと触りやすいので、いろいろ便利そうです。

cv::Mat_<uchar> image = cv::imread("image_Lena512.png", 0);

MatrixXf mat_image;
cv::cv2eigen(image, mat_image);

MatrixXcf freq(mat_image.rows(), mat_image.cols());
dofwd2d(mat_image, freq);
shift(freq);

追記:画像を逆変換で元に戻す

フーリエ変換してから逆変換すると画像が元に戻る確認です。

#include <Eigen/Dense>
#include <unsupported/Eigen/FFT>
using namespace Eigen;

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/eigen.hpp>

MatrixXcf fft2(MatrixXf& in)
{
  FFT<float> fft;

  MatrixXcf temp(in.rows(), in.cols());
  for (int i = 0; i < in.cols(); i++)
  {
    temp.col(i).noalias() = fft.fwd(in.col(i));
  }

  MatrixXcf freq(in.rows(), in.cols());
  for (int i = 0; i < temp.rows(); i++)
  {
    freq.row(i).noalias() = fft.fwd(temp.row(i)).transpose();
  }

  return freq;
}

MatrixXf ifft2(MatrixXcf& in)
{
  FFT<float> fft;

  MatrixXcf temp(in.rows(), in.cols());
  for (int i = 0; i < in.cols(); i++)
  {
    temp.col(i).noalias() = fft.inv(in.col(i));
  }

  MatrixXf time(in.rows(), in.cols());
  for (int i = 0; i < temp.rows(); i++)
  {
    time.row(i).noalias() = fft.inv(temp.row(i)).transpose().real();
  }

  return time;
}

int main()
{
  cv::Mat1b image = cv::imread("a.jpg", 0);

  MatrixXf t;
  cv::cv2eigen(image, t);

  cv::Mat resultimage;
  cv::eigen2cv(ifft2(fft2(t)), resultimage);

  cv::imshow("Original", image);
  cv::imshow("Result", resultimage/255);
  cv::waitKey(0);
}