在圖像處理中經常會遇到各種濾波(平滑、銳化)的情況,基本方法都是將圖像與一個核進行卷積實現。而卷積定理指出,兩個函數的卷積的傅里葉變換等於各自的傅里葉變換的乘積,即:
那麼,兩個函數的卷積可以通過如下方式得到,對兩個函數傅里葉變換的乘積做傅里葉反變換,即:
在進行卷積運算時,一般是將核沿着圖像從左到右從上到下計算每一個像素處與核卷積後的值,這樣的計算量較大,採用傅里葉變換的方法可以提高運算效率。
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#dft[2]
void convolveDFT(Mat A, Mat B, Mat& C)
{
// reallocate the output array if needed
C.create(abs(A.rows - B.rows)+1, abs(A.cols - B.cols)+1, A.type());
Size dftSize;
// calculate the size of DFT transform
dftSize.width = getOptimalDFTSize(A.cols + B.cols - 1);
dftSize.height = getOptimalDFTSize(A.rows + B.rows - 1);
// allocate temporary buffers and initialize them with 0's
Mat tempA(dftSize, A.type(), Scalar::all(0));
Mat tempB(dftSize, B.type(), Scalar::all(0));
// copy A and B to the top-left corners of tempA and tempB, respectively
Mat roiA(tempA, Rect(0,0,A.cols,A.rows));
A.copyTo(roiA);
Mat roiB(tempB, Rect(0,0,B.cols,B.rows));
B.copyTo(roiB);
// now transform the padded A & B in-place;
// use "nonzeroRows" hint for faster processing
dft(tempA, tempA, 0, A.rows);
dft(tempB, tempB, 0, B.rows);
// multiply the spectrums;
// the function handles packed spectrum representations well
mulSpectrums(tempA, tempB, tempA, DFT_COMPLEX_OUTPUT);
//mulSpectrums(tempA, tempB, tempA, DFT_REAL_OUTPUT);
// transform the product back from the frequency domain.
// Even though all the result rows will be non-zero,
// you need only the first C.rows of them, and thus you
// pass nonzeroRows == C.rows
dft(tempA, tempA, DFT_INVERSE + DFT_SCALE, C.rows);
// now copy the result back to C.
tempA(Rect(0, 0, C.cols, C.rows)).copyTo(C);
// all the temporary buffers will be deallocated automatically
}
int main(int argc, char* argv[])
{
const char* filename = argc >=2 ? argv[1] : "Lenna.png";
Mat I = imread(filename, CV_LOAD_IMAGE_GRAYSCALE);
if( I.empty())
return -1;
Mat kernel = (Mat_<float>(3,3) << 1, 1, 1, 1, 1, 1, 1, 1, 1);
cout << kernel;
Mat floatI = Mat_<float>(I);// change image type into float
Mat filteredI;
convolveDFT(floatI, kernel, filteredI);
normalize(filteredI, filteredI, 0, 1, CV_MINMAX); // Transform the matrix with float values into a
// viewable image form (float between values 0 and 1).
imshow("image", I);
imshow("filtered", filteredI);
waitKey(0);
}
convolveDFT函數是從官方文檔中抄錄並做了修改,因爲原來的程序有問題。一是輸出Mat C應聲明爲引用;二是其中的mulSpectrums函數的第四個參數flag值沒有指定,應指定爲DFT_COMPLEX_OUTPUT或是DFT_REAL_OUTPUT.
main函數中首先按灰度圖讀入圖像,然後創造一個平滑核kernel,將輸入圖像轉換成float類型(注意這步是必須的,因爲dft只能處理浮點數),在調用convolveDFT求出卷積結果後,將卷積結果歸一化方便顯示觀看。
需要注意的是,一般求法中,利用核遊走整個圖像進行卷積運算,實際上進行的是相關運算,真正意義上的卷積,應該首先把核翻轉180度,再在整個圖像上進行遊走。OpenCV中的filter2D實際上做的也只是相關,而非卷積。"The function does actually compute correlation, not the convolution: ... That is, the kernel is not mirrored around the anchor point. If you need a real convolution, flip the kernel using flip() and set the new anchor to (kernel.cols - anchor.x - 1, kernel.rows - anchor.y - 1)"[3]
參考文獻:
[1] http://zh.wikipedia.org/wiki/%E5%8D%B7%E7%A7%AF%E5%AE%9A%E7%90%86
[2] http://docs.opencv.org/modules/core/doc/operations_on_arrays.html#dft
[3] http://docs.opencv.org/modules/imgproc/doc/filtering.html#filter2d