1.图像频域处理的意义
在图像处理和分析中,经常会将图像从图像空间转换到其他空间中,并利用这些空间的特点进行对转换后图像进行分析处理,然后再将处理后的图像转换到图像空间中,这称之为图像变换。在一些图像处理和分析中通过空间变换往往会取得更有效的结果。图像频域处理是指将图像从图像空间转换到频域空间进行处理的过程。最常用的频域转换是傅里叶变换。傅里叶变换的计算量较大,人们为了提高速度,提出了快速傅里叶变换,并得到了广泛的应用。本篇博客主要 来学习VTK中的快速傅里叶变换。
2.快速傅里叶变换及其逆变换
快速傅里叶变换(Fast Fourier Transform),简称做FFT。它是根据离散傅氏变换的奇、偶、虚、实等特性,对离散傅立叶变换的算法进行改进获得的。傅里叶变换是可逆的,其逆变换为RFFT。FFT在数字图像处理中有着广泛的应用,例如数字图像频域滤波,去噪,增强等等。 目前VTK中两变换都已经实现,对应的类分别为vtkImageFFT和vtkImageRFFT。
vtkImageFFT和vtkImageRFFT的输入为实数或者复数数据,输出都为复数数据。因此,vtkImageFFT与vtkImageRFFT的输出结果不能直接显示,因为VTK会将其当做彩色图像显示,需要通过vtkImageExtractComponents提取某一组分图像显示。
VTK频率域的图像处理步骤如下:
下面代码演示了怎样对图像进行傅里叶变换,以及将傅里叶变换结果进行逆变换:#include <vtkAutoInit.h> VTK_MODULE_INIT(vtkRenderingOpenGL); #include <vtkSmartPointer.h> #include <vtkJPEGReader.h> #include <vtkImageFFT.h> #include <vtkImageExtractComponents.h> #include <vtkImageData.h> #include <vtkImageShiftScale.h> #include <vtkImageRFFT.h> #include <vtkImageActor.h> #include <vtkrenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkInteractorStyleImage.h> int main() { vtkSmartPointer<vtkJPEGReader> reader = vtkSmartPointer<vtkJPEGReader>::New(); reader->SetFileName("lena.jpg"); reader->Update(); //FFT// vtkSmartPointer<vtkImageFFT> imgFFT = vtkSmartPointer<vtkImageFFT>::New(); imgFFT->SetInputConnection(reader->GetOutputPort()); imgFFT->SetDimensionality(2); imgFFT->Update(); // the resualts are complex, we shoule extract one component to display vtkSmartPointer<vtkImageExtractComponents> imgComponent = vtkSmartPointer<vtkImageExtractComponents>::New(); imgComponent->SetInputConnection(imgFFT->GetOutputPort()); imgComponent->SetComponents(0); // norm image range double Range[2]; imgComponent->GetOutput()->GetScalarRange(Range); vtkSmartPointer<vtkImageShiftScale> imgfftShiftScale = vtkSmartPointer<vtkImageShiftScale>::New(); imgfftShiftScale->SetOutputScalarTypeToUnsignedChar(); imgfftShiftScale->SetScale(255.0 / (Range[1] - Range[0])); imgfftShiftScale->SetShift(-Range[0]); imgfftShiftScale->SetInputConnection(imgComponent->GetOutputPort()); imgfftShiftScale->Update(); //RFFT/ vtkSmartPointer<vtkImageRFFT> imgRFFT = vtkSmartPointer<vtkImageRFFT>::New(); imgRFFT->SetInputConnection(imgFFT->GetOutputPort()); imgRFFT->SetDimensionality(2); imgRFFT->Update(); vtkSmartPointer<vtkImageExtractComponents> imgComponetNew = vtkSmartPointer<vtkImageExtractComponents>::New(); imgComponetNew->SetInputConnection(imgRFFT->GetOutputPort()); imgComponetNew->SetComponents(0); double RangeNew[2]; imgComponetNew->GetOutput()->GetScalarRange(RangeNew); vtkSmartPointer<vtkImageShiftScale> imgrfftShiftScale = vtkSmartPointer<vtkImageShiftScale>::New(); imgrfftShiftScale->SetOutputScalarTypeToUnsignedChar(); imgrfftShiftScale->SetScale(255.0 / (RangeNew[1] - RangeNew[0])); imgrfftShiftScale->SetInputConnection(imgComponetNew->GetOutputPort()); imgrfftShiftScale->Update(); /// //谨记vtkImageActor仅能够显示UnsignedChar类型数据 vtkSmartPointer<vtkImageActor> origActor = vtkSmartPointer<vtkImageActor>::New(); origActor->SetInputData(reader->GetOutput()); vtkSmartPointer<vtkImageActor> fftActor = vtkSmartPointer<vtkImageActor>::New(); fftActor->SetInputData(imgFFT->GetOutput()); vtkSmartPointer<vtkImageActor> rfftActor = vtkSmartPointer<vtkImageActor>::New(); rfftActor->SetInputData(imgrfftShiftScale->GetOutput()); /// double origView[4] = { 0, 0, 0.33, 1 }; double fftView[4] = { 0.33, 0, 0.66, 1 }; double rfftView[4] = { 0.66, 0, 1, 1 }; vtkSmartPointer<vtkRenderer> origRender = vtkSmartPointer<vtkRenderer>::New(); origRender->SetViewport(origView); origRender->AddActor(origActor); origRender->ResetCamera(); origRender->SetBackground(1, 0, 0); vtkSmartPointer<vtkRenderer> fftRender = vtkSmartPointer<vtkRenderer>::New(); fftRender->SetViewport(fftView); fftRender->AddActor(fftActor); fftRender->ResetCamera(); fftRender->SetBackground(0, 1, 0); vtkSmartPointer<vtkRenderer> rfftRender = vtkSmartPointer<vtkRenderer>::New(); rfftRender->SetViewport(rfftView); rfftRender->AddActor(rfftActor); rfftRender->ResetCamera(); rfftRender->SetBackground(0, 0, 1); / vtkSmartPointer<vtkRenderWindow> rw = vtkSmartPointer<vtkRenderWindow>::New(); rw->AddRenderer(origRender); rw->AddRenderer(fftRender); rw->AddRenderer(rfftRender); rw->SetWindowName("Frequncy_FFT_RFFT"); rw->SetSize(960, 320); vtkSmartPointer<vtkRenderWindowInteractor> rwi = vtkSmartPointer<vtkRenderWindowInteractor>::New(); vtkSmartPointer<vtkInteractorStyleImage> style = vtkSmartPointer<vtkInteractorStyleImage>::New(); rwi->SetInteractorStyle(style); rwi->SetRenderWindow(rw); rwi->Initialize(); rwi->Start(); return 0; }
首先建立一个JPEG图像reader来读取一副灰度图像。然后定义一个vtkImageFFT指针,直接接收reader的输出即原图像数据作为输入进行二维快速傅里叶变换。其输出为一个像素类型为复数的vtkImageData数据,即每个像素值为两个组分(Component):复数实部和虚部。因此如果直接显示这个vtkImageData,会发现是一个彩色图像。如果需要显示频域图像,需要通过vtkImageExtractComponents类来提取某一个组分图像来显示。上例中通过定义vtkImageExtractComponents类指针,利用函数SetComponents(0)指定提取实部图像显示;由于vtkImageActor类仅支持unsigned char数据类型的图像,利用vtkImageCast类的SetOutputScalarTypeToUnsignedChar()指定输出类型为unsigned char,将FFT结果图像转换为需要的类型。
对于逆变换的过程也是类似。首先定义vtkImageRFFT指针,并接收输入为vtkImageFFT指针的输出,调用Update执行函数完成快速傅里叶逆变换。vtkImageRFFT的输出同样为一副复数图像,通常不能直接显示或者进行其他操作。对于傅里叶逆变换的图像中虚数部分值为0,实数部分图像即为重建的原始图像。因此再次利用vtkImageExtractComponents提取实数部分图像,并通过SetOutputScalarTypeToUnsignedChar()将图像转换为unsigned char类型进行显示。下图为计算结果:
3.参看资料
1.《C++ primer》
2.《The VTK User’s Guide – 11thEdition》
3. 张晓东, 罗火灵. VTK图形图像开发进阶[M]. 机械工业出版社, 2015.