周期関数は三角関数の無限級数和で展開できることが知られており、フーリエ級数展開とよばれます。更に、連続した非周期関数にも適用できるようにフーリエ級数展開の考え方を拡張することができ、フーリエ変換とよばれます。これを離散化した離散フーリエ変換 (DFT: Discrete Fourier Transform) を用いると、デジタル信号の周波数解析を行うことができます。画像データをデジタル信号であると考えると、離散フーリエ変換を適用できます。
画像 $f(i,j)$ を離散フーリエ変換すると以下のようになります。
$$F(k,l) = \sum_{m=0}^{N-1} \sum_{n=0}^{N-1} f(m,n)\,\mathrm{e}^{-\frac{2\pi i}{N} (k m + l n)} $$
離散フーリエ変換には逆変換が存在しており、もとの画像データは、変換結果 $F(k,l)$ から構成できます。離散フーリエ変換には、計算を高速に行うためのアルゴリズムが存在しており、高速フーリエ変換 (FFT: Fast Fourier Transform) とよばれます。関連ページ
離散フーリエ変換の応用の一つに画像圧縮が存在します。人間の目は高周波の情報を認識しづらい傾向にあることが知られており、高周波に相当するデータを除いて画像データを圧縮することができます。この考え方は JPEG 圧縮で利用されています。$F(k,l)$ で逆フーリエ変換を行う際に、高周波に相当する $k,l$ の値を 0 にすることで、低周波の情報のみを用いてもと画像を構成することができます。
フィルタなし
ローパスフィルタ (k > 100 かつ l > 100 の周波数を除去)
ハイパスフィルタ (k < 100 かつ l < 100 の周波数を除去)
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// グレースケールで画像を読み込みます。
cv::Mat I = cv::imread("aaa.png", cv::IMREAD_GRAYSCALE);
// FFT アルゴリズムの計算に適したサイズに変換します。増加したサイズ部分は 0 で埋めます。
cv::Mat padded;
int m = cv::getOptimalDFTSize( I.rows );
int n = cv::getOptimalDFTSize( I.cols );
cv::copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
// DFT 変換結果は非常に大きな値になり得るため、浮動小数点型に変換しておきます。
// DFT 変換結果は複素数です。虚部を格納するために、画像データを 2 チャンネルの変数に格納しておきます。
cv::Mat planes[] = {cv::Mat_<float>(padded), cv::Mat::zeros(padded.size(), CV_32F)};
cv::Mat complexI;
cv::merge(planes, 2, complexI);
// 離散フーリエ変換を実行します。
cv::dft(complexI, complexI);
std::cout << complexI.size() << std::endl; //=> [200 x 200]
// フィルタをかけてみます。
for(int i = 0; i < 200; ++i) {
for(int j = 0; j < 200; ++j) {
// if(i > 100 && j > 100) {
if(i < 100 && j < 100) {
complexI.at<cv::Vec2f>(i, j)[0] = 0;
complexI.at<cv::Vec2f>(i, j)[1] = 0;
}
}
}
// 逆離散フーリエ変換
cv::Mat I2;
cv::dft(complexI, I2, cv::DFT_INVERSE | cv::DFT_REAL_OUTPUT);
std::cout << I2.size() << std::endl; //=> [200 x 200]
std::cout << I2.channels() << std::endl; //=> 1
// 描画するために [0, 1] の範囲に変換します。
cv::normalize(I2, I2, 0, 1, CV_MINMAX);
cv::imshow("Original", I);
cv::imshow("DFT/IDFT", I2);
cv::waitKey(0);
return 0;
}