1. 程式人生 > >5.5 影象濾波(均值、高斯、中值、各項異性濾波)

5.5 影象濾波(均值、高斯、中值、各項異性濾波)

5.5.1 均值濾波

均值濾波是一種經常用到的平滑方法,其對應的模板各個畫素的值為1。在VTK中沒有直接實現均值濾波的類,但是我們可以通過影象卷積運算來實現。卷積運算通過vtkImageConvolve類實現。通過vtkImageConvolve類,只需要設定相應的卷積模板,便可以實現多種空域影象濾波。

下面程式碼說明了怎樣使用vtkImageConvolve類來實現影象的均值濾波:


//中值濾波
#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h> //影象資料型別轉換為計算型別
#include <vtkImageData.h>
#include <vtkImageConvolve.h>  //影象卷積執行
#include <vtkImageShiftScale.h> //設定畫素值範圍
//#include <vtkImageMandelbrotSource.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkImageMandelbrotSource.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>

int main()
{
	vtkSmartPointer<vtkJPEGReader> reader =
		vtkSmartPointer<vtkJPEGReader>::New();
	reader->SetFileName("data\\lena-noise.jpg");
	reader->Update();

	vtkSmartPointer<vtkImageCast> originalCastFilter =
		vtkSmartPointer<vtkImageCast>::New();
	originalCastFilter->SetInputConnection(reader->GetOutputPort()); //建管線
	originalCastFilter->SetOutputScalarTypeToFloat(); //設定屬性
	originalCastFilter->Update();

	vtkSmartPointer<vtkImageConvolve> convolveFilter =
		vtkSmartPointer<vtkImageConvolve>::New();
	convolveFilter->SetInputConnection(originalCastFilter->GetOutputPort()); //建管線

	double kernel[25] = {
		0.04, 0.04, 0.04, 0.04, 0.04,
		0.04, 0.04, 0.04, 0.04, 0.04,
		0.04, 0.04, 0.04, 0.04, 0.04,
		0.04, 0.04, 0.04, 0.04, 0.04,
		0.04, 0.04, 0.04, 0.04, 0.04
	};
	convolveFilter->SetKernel5x5(kernel);
	convolveFilter->Update();

	vtkSmartPointer<vtkImageCast> convCastFilter =
		vtkSmartPointer<vtkImageCast>::New();
	convCastFilter->SetInputData(convolveFilter->GetOutput());
	convCastFilter->SetOutputScalarTypeToUnsignedChar(); //轉換為影象資料
	convCastFilter->Update();
	///////////////////////////////////////////////////
	vtkSmartPointer<vtkImageActor> originalActor =
		vtkSmartPointer<vtkImageActor>::New();
	originalActor->SetInputData(reader->GetOutput());

	vtkSmartPointer<vtkImageActor> convolvedActor =
		vtkSmartPointer<vtkImageActor>::New();
	convolvedActor->SetInputData(convCastFilter->GetOutput());
	////////////////////////
	double leftViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
	double rightViewport[4] = { 0.5, 0.0, 1.0, 1.0 };

	vtkSmartPointer<vtkRenderer> originalRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	originalRenderer->SetViewport(leftViewport);
	originalRenderer->AddActor(originalActor);
	originalRenderer->SetBackground(1.0, 1.0, 1.0);
	originalRenderer->ResetCamera();

	vtkSmartPointer<vtkRenderer> convolvedRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	convolvedRenderer->SetViewport(rightViewport);
	convolvedRenderer->AddActor(convolvedActor);
	convolvedRenderer->SetBackground(1.0, 1.0, 1.0);
	convolvedRenderer->ResetCamera();
	////////////////////////
	vtkSmartPointer<vtkRenderWindow> rw =
		vtkSmartPointer<vtkRenderWindow>::New();;
	rw->AddRenderer(originalRenderer);
	rw->AddRenderer(convolvedRenderer);
	rw->SetSize(640, 320);
	rw->Render();
	rw->SetWindowName("Smooth by MeanFilter");

	vtkSmartPointer<vtkRenderWindowInteractor> rwi =
		vtkSmartPointer<vtkRenderWindowInteractor>::New();
	vtkSmartPointer<vtkInteractorStyleImage> style =
		vtkSmartPointer<vtkInteractorStyleImage>::New();
	rwi->SetInteractorStyle(style);
	rwi->SetRenderWindow(rw);
	rwi->Initialize();
	rwi->Start();

	return 0;
}

執行結果如下:

        首先vtkJPEGReader物件讀取一幅影象。考慮到進行卷積運算時資料範圍的變化和精度要求,需要先將影象畫素資料型別由unsigned char轉換到float型別,該變換通過vtkImageCast實現,對應的設定函式SetOutputScalarTypeToFloat()。接下來需要定義卷積運算元和卷積模板。vtkImageConvolve類實現影象的卷積運算,它需要兩個輸入。一個是需要進行卷積的影象,這裡為vtkJPEGReader讀取的影象資料,第二個是卷積模板陣列。SetKernel5x5()函式接收一個5x5的卷積模板陣列,即本例上定義的kernel陣列。執行Update()後即可完成卷積運算。需要注意的是,卷積模板對應的係數之和應該為1,否則需要對計算結果進行歸一化處理。另外該類中還定義了3x3和7x7的卷積模板設定函式,使用過程是一樣的。卷積完成以後,再次通過vtkImageCast將float資料型別轉換為unsigned char進行影象顯示。

5.5.2高斯濾波

高斯平滑的原理類似於均值濾波。均值濾波模板的係數都是一樣的,而高斯平滑則是需要根據畫素與模板中心的距離來定義權重。權重的計算方法是採用高斯分佈,離中心越遠,權重越小。

#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>//影象資料型別轉換
#include <vtkImageData.h>
#include <vtkImageGaussianSmooth.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("data\\lena-noise.jpg");
	reader->Update();

	vtkSmartPointer<vtkImageGaussianSmooth> gaussianSmoothFilter =
		vtkSmartPointer<vtkImageGaussianSmooth>::New();
	gaussianSmoothFilter->SetInputConnection(reader->GetOutputPort());
	gaussianSmoothFilter->SetDimensionality(2);
	gaussianSmoothFilter->SetRadiusFactor(5); //設定模板範圍
	gaussianSmoothFilter->SetStandardDeviation(3);//正態分佈/高斯分佈標準差
	gaussianSmoothFilter->Update();

	vtkSmartPointer<vtkImageActor> originalActor =
		vtkSmartPointer<vtkImageActor>::New();
	originalActor->SetInputData(reader->GetOutput());

	vtkSmartPointer<vtkImageActor> smoothedActor =
		vtkSmartPointer<vtkImageActor>::New();
	smoothedActor->SetInputData(gaussianSmoothFilter->GetOutput());

	double originalViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
	double smoothedViewport[4] = { 0.5, 0.0, 1.0, 1.0 };

	vtkSmartPointer<vtkRenderer> originalRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	originalRenderer->SetViewport(originalViewport);
	originalRenderer->AddActor(originalActor);
	originalRenderer->ResetCamera();
	originalRenderer->SetBackground(1.0, 0, 0);

	vtkSmartPointer<vtkRenderer> gradientMagnitudeRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	gradientMagnitudeRenderer->SetViewport(smoothedViewport);
	gradientMagnitudeRenderer->AddActor(smoothedActor);
	gradientMagnitudeRenderer->ResetCamera();
	gradientMagnitudeRenderer->SetBackground(1.0, 1.0, 1.0);

	vtkSmartPointer<vtkRenderWindow> rw =
		vtkSmartPointer<vtkRenderWindow>::New();
	rw->AddRenderer(originalRenderer);
	rw->AddRenderer(gradientMagnitudeRenderer);
	rw->SetSize(640, 320);
	rw->SetWindowName("Smooth by Gaussian");

	vtkSmartPointer<vtkRenderWindowInteractor> rwi =
		vtkSmartPointer<vtkRenderWindowInteractor>::New();
	vtkSmartPointer<vtkInteractorStyleImage> style =
		vtkSmartPointer<vtkInteractorStyleImage>::New();
	rwi->SetInteractorStyle(style);
	rwi->SetRenderWindow(rw);
	rwi->Initialize();
	rwi->Start();

	return 0;
}

執行結果如下所示:

vtkImageGaussianSmooth類預設是執行三維高斯濾波;
SetDimensionality()根據需要設定相應的維數;
SetRadiusFactor()用於設定高斯模板的大小,當超出該模板的範圍時,係數取0;
SetStandardDeviation()用於設定高斯分佈函式的標準差。
 

5.5.3 中值濾波

vtkImageHybridMedian2D實現了對二維影象的中值濾波。其實現原理是,採用一個5x5的模板,逐次將模板中心對應於影象的每個畫素上,將模板影象覆蓋的畫素的中值作為當前畫素的輸出值。

#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageData.h>
#include <vtkImageCast.h>
#include <vtkImageHybridMedian2D.h>
#include <vtkImageActor.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>

int main(int argc, char* argv[])
{
	vtkSmartPointer<vtkJPEGReader> reader =
		vtkSmartPointer<vtkJPEGReader>::New();
	reader->SetFileName("data\\lena-noise.jpg");
	reader->Update();

	vtkSmartPointer<vtkImageHybridMedian2D> hybridMedian =
		vtkSmartPointer<vtkImageHybridMedian2D>::New();
	hybridMedian->SetInputData(reader->GetOutput());
	hybridMedian->Update();
	///////////////////////////////////////////////////////
	vtkSmartPointer<vtkImageActor> originalActor =
		vtkSmartPointer<vtkImageActor>::New();
	originalActor->SetInputData(reader->GetOutput());

	vtkSmartPointer<vtkImageActor> hybridMedianActor =
		vtkSmartPointer<vtkImageActor>::New();
	hybridMedianActor->SetInputData(hybridMedian->GetOutput());
	/////////////////////
	double originalViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
	double hybridMedianViewport[4] = { 0.5, 0.0, 1.0, 1.0 };

	vtkSmartPointer<vtkRenderer> originalRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	originalRenderer->SetViewport(originalViewport);
	originalRenderer->AddActor(originalActor);
	originalRenderer->ResetCamera();
	originalRenderer->SetBackground(1.0, 0, 0);

	vtkSmartPointer<vtkRenderer> hybridMedianRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	hybridMedianRenderer->SetViewport(hybridMedianViewport);
	hybridMedianRenderer->AddActor(hybridMedianActor);
	hybridMedianRenderer->ResetCamera();
	hybridMedianRenderer->SetBackground(1.0, 1.0, 1.0);
	//////////////////////////
	vtkSmartPointer<vtkRenderWindow> rw =
		vtkSmartPointer<vtkRenderWindow>::New();
	rw->AddRenderer(originalRenderer);
	rw->AddRenderer(hybridMedianRenderer);
	rw->SetSize(640, 320);
	rw->Render();
	rw->SetWindowName("MedianFilterExample");

	vtkSmartPointer<vtkRenderWindowInteractor> rwi =
		vtkSmartPointer<vtkRenderWindowInteractor>::New();
	vtkSmartPointer<vtkInteractorStyleImage> style =
		vtkSmartPointer<vtkInteractorStyleImage>::New();
	rwi->SetInteractorStyle(style);
	rwi->SetRenderWindow(rw);
	rwi->Initialize();
	rwi->Start();

	return 0;
}

執行結果如下:

該類使用非常簡單,不需要使用者設定任何引數。該方法能夠有效的保持影象邊緣,並對於椒鹽噪聲有較好的抑制作用。對於三維影象,則使用vtkImageHybridMedian3D類。

5.5.4 各項異性濾波

高斯平滑方法在平滑噪聲的同時,模糊了影象的重要邊緣影象。

各向異性濾波是一種基於偏微分方程的濾波技術,建立於熱量的各向異性擴散理論。

各向異性濾波在影象的平坦區域選擇大尺度平滑,而邊緣區域則選擇小尺度的平滑,在抑制噪聲的同時保持了影象的邊緣資訊。

vtkImageAnisotropicDiffusion2D(vtkImageAnisotropicDiffusion3D)實現影象各向異性擴散濾波,程式碼如下:

#include <vtkSmartPointer.h>
#include <vtkJPEGReader.h>
#include <vtkImageCast.h>
#include <vtkImageAnisotropicDiffusion2D.h>
#include <vtkImageActor.h>
#include <vtkCamera.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>

int main()
{
	vtkSmartPointer<vtkJPEGReader> reader =
		vtkSmartPointer<vtkJPEGReader>::New();
	reader->SetFileName("data\\lena-noise.jpg");

	vtkSmartPointer<vtkImageAnisotropicDiffusion2D> diffusion =
		vtkSmartPointer<vtkImageAnisotropicDiffusion2D>::New();
	diffusion->SetInputConnection(reader->GetOutputPort());
	diffusion->SetNumberOfIterations(200); //用於設定迭代次數
	diffusion->SetDiffusionThreshold(5); //小於該閾值擴散
	diffusion->Update();
	/////////////////////////////////////////////////////////////////
	vtkSmartPointer<vtkImageActor> originalActor =
		vtkSmartPointer<vtkImageActor>::New();
	originalActor->SetInputData(reader->GetOutput());

	vtkSmartPointer<vtkImageActor> diffusionActor =
		vtkSmartPointer<vtkImageActor>::New();
	diffusionActor->SetInputData(diffusion->GetOutput());
	////////////////////
	double leftViewport[4] = { 0.0, 0.0, 0.5, 1.0 };
	double rightViewport[4] = { 0.5, 0.0, 1.0, 1.0 };

	vtkSmartPointer<vtkCamera> camera =
		vtkSmartPointer<vtkCamera>::New();
	vtkSmartPointer<vtkRenderer> leftRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	leftRenderer->SetViewport(leftViewport);
	leftRenderer->AddActor(originalActor);
	leftRenderer->SetBackground(1.0, 1.0, 1.0);
	leftRenderer->SetActiveCamera(camera);
	leftRenderer->ResetCamera();

	vtkSmartPointer<vtkRenderer> rightRenderer =
		vtkSmartPointer<vtkRenderer>::New();
	rightRenderer->SetViewport(rightViewport);
	rightRenderer->SetBackground(1.0, 1.0, 1.0);
	rightRenderer->AddActor(diffusionActor);
	rightRenderer->SetActiveCamera(camera);
	/////////////////////
	vtkSmartPointer<vtkRenderWindow> rw =
		vtkSmartPointer<vtkRenderWindow>::New();
	rw->AddRenderer(leftRenderer);
	rw->AddRenderer(rightRenderer);
	rw->SetSize(540, 320);
	rw->SetWindowName("Smooth by AnistropicFilter");

	vtkSmartPointer<vtkRenderWindowInteractor> rwi =
		vtkSmartPointer<vtkRenderWindowInteractor>::New();
	vtkSmartPointer<vtkInteractorStyleImage> style =
		vtkSmartPointer<vtkInteractorStyleImage>::New();
	rwi->SetInteractorStyle(style);
	rwi->SetRenderWindow(rw);
	rwi->Initialize();
	rwi->Start();

	return 0;
}

執行結果如下:

vtkImageAnisotropicDiffusion2D類通過迭代方法實現。
其中SetNumberOfIterations()用於設定迭代的次數;
各向異性擴散濾波原理是在梯度較小的畫素處進行較大幅度擴散,而在大梯度處則只進行細微的擴散。因此需要設定一個擴算的閾值DiffusionThreshold,這個閾值與影象的梯度有關。SetDiffusionThreshold()即是用來設定擴散閾值。該類中還有一個梯度標誌GradientMagnitudeThreshold,用來設定梯度運算元。當該標誌開時梯度通過中心差分方法計算;當標誌為關時,需要單獨處理每個相鄰畫素。當畫素與相鄰畫素梯度小於DiffusionThreshold時進行擴散處理。

參考資料:

1.《The Visualization Toolkit – AnObject-Oriented Approach To 3D Graphics (4th Edition)》
2. 張曉東, 羅火靈. VTK圖形影象開發進階[M]. 機械工業出版社, 2015.

所用軟體:vtk7.0+visual studio 2013


注:此文知識學習筆記,僅記錄完整程式和實現結果,具體原理參見:

https://blog.csdn.net/www_doling_net/article/details/8541534

https://blog.csdn.net/shenziheng1/article/category/6114053/4