22、OpenCV用卷積Filter2D進行濾波器

迄今為止,看到的函式中,卷積的操作發生在OpenCV函式的內部。理論上,影象卷積就是將核心與影象覆蓋區域對應位置相乘之後求和。從呼叫函式上來看,它需要一個數組引數來描述核心。在實踐層面,有一個重要的微妙因素會對結果產生重大影響。微妙之處在於一些核心是可分離的,而另一些則不是。

22、OpenCV用卷積Filter2D進行濾波器

圖1

圖1(A)是可分離的; 它可以表示為兩個一維卷積(B和C);D是一個不可分割核心的例子。可分離的核心是可以被認為是兩個一維核心的核心,首先與x核心進行卷積然後與y核心進行卷積來應用。這種分解的好處是核心卷積的計算成本大約是影象面積乘以核心區域。這意味著用n×n核心卷積區域A的影象需要時間與An2成正比,同時n×1核心與影象卷積一次,然後與1×n核心卷積佔用與An + An = 2An成比例。即使n小於3也有好處,隨著n的增長,優勢更為突出。

1、 利用filter2D()濾波

鑑於影象卷積所需的操作次數,是影象中畫素的數量乘以核心中的畫素數,這可能需要很多計算,因此,在這種情況下,最好讓OpenCV來幫你完成並利用內部最佳化。OpenCV完成這些操作的函式是filter2D():

cv::filter2D(

cv::InputArray src,

cv::OutputArray dst,

int ddepth,

cv::InputArray kernel,

cv::Point anchor = cv::Point(-1,-1),

double delta = 0,

int borderType = cv::BORDER_DEFAULT

);

建立一個適當大小的陣列,並用濾波器的係數填充它,然後將它與源影象和目標影象一起傳遞到filter2D()中。可以使用ddepth指定結果影象的深度,使用錨點指定濾波的錨點,使用borderType指定邊界型別。如果定義了錨點,則核心可以是偶數大小;否則,它應該是奇數大小。如果要在應用濾波器後將總體偏移應用於結果,則可以使用引數delta。

2、 利用sepFilter2D分離濾波器

在核心可分離的情況下,透過以分離的形式表示並將這些一維核心傳遞給OpenCV,將從OpenCV獲得最佳效能。sepFilter2D()與filter2D()類似,除了它期望這兩個一維核心而不是一個二維核心。

cv::sepFilter2D(

cv::InputArray src,

cv::OutputArray dst,

int ddepth,

cv::InputArray rowKernel,

cv::InputArray columnKernel,

cv::Point anchor = cv::Point(-1,-1),

double delta = 0,

int borderType = cv::BORDER_DEFAULT

);

sepFilter2D()的所有引數都與cv :: filter2D()的引數相同,但使用rowKernel和columnKernel引數替換核心引數除外。後者為n1×1和1×n2陣列,n1不一定等於n2。

3、構建核心

getDerivKernel()構造Sobel和Scharr核心,getGaussianKernel()構造高斯核心。

void cv::getDerivKernels(

cv::OutputArray kx,

cv::OutputArray ky,

int dx,

int dy,

int ksize,

bool normalize = true,

int ktype = CV_32F

);

getDerivKernel()的結果放置在kx和ky引數中。核心(Sobel和Scharr)是可分離的核心,將返回兩個陣列,一個是1×ksize(行係數,kx),另一個是ksize×1(列係數,ky)。這些是從x和y導數階dx和dy計算而來的。衍生核心總是方形的,因此大小引數ksize是一個整數。ksize可以是1,3,5,7或cv ::SCHARR中的任何一個。normalize引數告訴getDerivKernels()是否應該對核心元素進行歸一化。對於在浮點影象上操作的情況,將normalize設定為true,但是當正在操作整數陣列時,通常更為明智的做法是,在一些陣列之前不對陣列進行歸一化,這樣就不會丟掉以後需要的精度。最後一個引數ktype表示濾波器係數的型別。 ktype的值可以是CV_32F或CV_64F。

高斯濾波器的實際核心陣列由getGaussianKernel()生成。

cv::Mat cv::getGaussianKernel(

int ksize, // Kernel size

double sigma, // Gaussian half-width

int ktype = CV_32F // Type for filter coefficients

);

與派生核心一樣,高斯核心是可分離的。因此,getGaussianKernel()只計算一個ksize×1的係數陣列。ksize的值可以是任何奇數正數。引數sigma設定近似高斯分佈的標準偏差。根據以下函式從sigma計算係數:

22、OpenCV用卷積Filter2D進行濾波器

也就是說,計算係數α使得濾波器整體被歸一化。可以將其設定為-1,在這種情況下,將根據ksize大小自動計算σ值。在這種情況下,

22、OpenCV用卷積Filter2D進行濾波器

例1是具體的使用方法,從中能看出構造的每個卷積核的用途嗎?

例1:filter2D、sepFilter2D、getDerivKernels、getGaussianKernel功能演示。

#include #include using namespace std;using namespace cv;int main(int argc, char** argv){    Mat src = imread(“E:/ img。bmp”, 1);    namedWindow(“原始噪聲影象”, 0);    imshow(“原始噪聲影象”, src);    //構造濾波器    Mat kernal = Mat::ones(3, 3, CV_32FC1);    kernal/= 9;    Mat filterDst;    filter2D(src, filterDst, src。depth(), kernal);    namedWindow(“filter2D結果1”, 0);    imshow(“filter2D結果1”, filterDst);    Mat kernal2 = (Mat_(3, 3) << 0, -1, 0,         -1, 5, -1,         0, -1, 0);    Mat dst1;    filter2D(filterDst, dst1, src。depth(), kernal2);    namedWindow(“filter2D結果2”, 0);    imshow(“filter2D結果2”, dst1);    Mat kx = (Mat_(1, 3) << 0, -1, 0);    Mat ky = (Mat_(1, 3) << -1, 0, -1);    sepFilter2D(filterDst, dst1, src。depth(), kx, ky);// , Point(-1, -1), 0, BORDER_DEFAULT);    namedWindow(“sepFilter2D結果”, 0);    imshow(“sepFilter2D結果”, dst1);    getDerivKernels(kx, ky, 1, 1, 3, true);    cout << kx << endl;    cout << ky << endl;    Mat dst2;    sepFilter2D(filterDst, dst2, src。depth(), kx, ky);// , Point(-1, -1), 0, BORDER_DEFAULT);    cv::normalize(dst2, dst2, 0, 255, NORM_MINMAX, CV_8UC1);    namedWindow(“sepFilter2D結果2”, 0);    imshow(“sepFilter2D結果2”, dst2);    Mat gaussKernal;    gaussKernal = getGaussianKernel(7, -1);    filter2D(src, filterDst, src。depth(), gaussKernal);    namedWindow(“Filter2D結果3”, 0);    imshow(“Filter2D結果3”, filterDst);    waitKey(0);    return 0;}

22、OpenCV用卷積Filter2D進行濾波器

圖2 操作結果示意圖