OpenCV的滤波与卷积
目錄
預備知識
濾波、核和卷積
邊界外推和邊界處理
閾值化操作
Otsu算法
自適應閾值
平滑
簡單模糊和方框型濾波器
中值濾波器
高斯濾波器
雙邊濾波器
導數和梯度
索貝爾導數
Scharr濾波器
拉普拉斯變換
圖像形態學
膨脹和腐蝕
通用形態學函數
開操作和閉操作
形態學梯度
頂帽和黑帽
自定義核
用任意線性濾波器做卷積
用cv::filter2D()進行卷積
通過cv::sepFilter2D使用可分核
生成卷積核
預備知識
濾波、核和卷積
濾波器指的是一種由一幅圖像 I(x,y)根據像素點x,y附近的區域計算得到一幅新圖像 I'(x,y)的算法。其中,模板規定了濾波器的形狀以及這個區域內像素的值的組成規律,也稱“濾波器”或“核”。在下面的介紹中多采用的是線性核,即 I'(x,y)的像素的值由 I(x,y)及其周圍的像素的值的加權相加得來的。可由以下方程表示:
| (A)5×5盒狀核 | (B)規范化的5×5盒狀核 | (C)3×3的Sobel核 | (D)5×5規范化高斯核 |
注:“錨點”均用粗體表示
邊界外推和邊界處理
自定義邊框
在處理圖像時,只要告訴調用的函數添加虛擬像素的規則,庫函數就會自動創建虛擬像素。cv::copyMakeBorder()就是一個為圖像創建邊框的函數。
cv::copyMakeBorder(InputArray src, InputArray dst, int top, int bottom, int left, int right, int borderType, const cv::Scalar& value=cv::Scalar())
作用:通過指定兩幅圖像,同時指明填充方法,該函數就會將第一幅圖填補后的結果保存在第二幅圖像中。其中,src是原圖像,dst是填充后的圖像,top、bottom、left、right分別是四個方向上的尺寸,borderType是像素填充的方式,value是常量填充時的值。
| cv::BORDER_CONSTANT | 為每個邊框像素賦予一個相同的值。 |
| cv::BORDER_WRAP | 類似于平鋪擴充 |
| cv::BORDER_REPLICATE | 復制邊緣的像素擴充 |
| cv::BORDER_REFLECT | 通過鏡像復制擴充 |
| cv::BORDER_REFLECT_101 | 通過鏡像復制擴充,邊界像素除外 |
| cv::BORDER_DEFAULT | cv::BORDER_REFLECT_101 |
自定義外推
?int cv::borderInterpolate(int p, int len, int borderType)
作用:計算一個維度上的外推,p為原圖上一個坐標,len是p指維度上的大小,borderType是邊界類型。
例子:混合的邊界條件下計算一個特定像素的值,在一維中使用BORDER_REFLECT_101,在二維中使用BORDER_WRAP:
float val = img.at<float>(cv::borderInterpolate(100, img.rows, cv::BORDER_REFLECT_101),cv::borderInterpolate(-5, img.cols, cv::BORDER_WRAP) )閾值化操作
閾值化操作的原理是對于數組中每個值,根據其高于或低于某個閾值做出相對應的處理,OpenCV中提供了實現這種功能的方法cv::threshold()。
double?cv::threshold(InputArray src, cv::OutputArray dst, double thresh, double maxValue, int thresholdType)
| cv::THRESH_BINARY | |
| cv::THRESH_BINARY_INV | |
| cv::THRESH_TRUNC | |
| cv::THRESH_TOZERO | |
| cv::THRESH_TOZERO_INV |
?
例一:將一幅圖像的三個通道相加并將像素值限制在100以內
#include "stdafx.h" #include <opencv2/opencv.hpp> #include <iostream>using namespace std;void sum_rgb(const cv::Mat& src, cv::Mat& dst) {// 分通道vector<cv::Mat> planes;cv::split(src, planes);cv::Mat b = planes[0];cv::Mat g = planes[1];cv::Mat r = planes[2];cv::Mat s;// 加權融合,防止越界cv::addWeighted(r, 1. / 3, g, 1. / 3, 0.0, s);cv::addWeighted(s, 1., r, 1. / 3, 0.0, s);// 閾值化操作cv::threshold(s, dst, 100, 100, cv::THRESH_TRUNC); }void help() {cout << "Call: ./ch10_ex10_1 faceScene.jpg" << endl;cout << "Show use of alpha blending (addWeighted) and threshold" << endl; }int main() {help();cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");cv::Mat dst;if (src.empty()){cout << "can not load the image" << endl;return -1;}sum_rgb(src, dst);cv::imshow("img", dst);cv::waitKey(0);cv::destroyAllWindows();return 0; }運行結果:?
例二:對于浮點型圖像進行三個通道相加并將像素值限制在100以內
#include "stdafx.h" #include <opencv2/opencv.hpp> #include <iostream>using namespace std;void sum_rgb(const cv::Mat& src, cv::Mat& dst) {// 分通道vector<cv::Mat> planes;cv::split(src, planes);cv::Mat b = planes[0];cv::Mat g = planes[1];cv::Mat r = planes[2];// 全0初始化s矩陣cv::Mat s = cv::Mat::zeros(b.size(), CV_32F);// accumulate可將8位整型的圖像累加到一幅浮點型的圖像中cv::accumulate(b, s);cv::accumulate(g, s);cv::accumulate(r, s);// 閾值化操作cv::threshold(s, dst, 100, 100, cv::THRESH_TRUNC);s.convertTo(dst, b.type()); }void help() {cout << "Call: ./ch10_ex10_1 faceScene.jpg" << endl;cout << "Show use of alpha blending (addWeighted) and threshold" << endl; }int main() {help();cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");cv::Mat dst;if (src.empty()){cout << "can not load the image" << endl;return -1;}sum_rgb(src, dst);cv::imshow("img", dst);cv::waitKey(0);cv::destroyAllWindows();return 0; }運行結果:
Otsu算法
函數cv::threshold()也可以自動決定最優的閾值,只需將參數thresh傳遞值cv::THRESH_OTSU即可。
簡而言之,Otsu算法就是遍歷所有可能的閾值,然后對每個閾值結果的兩類像素(低于閾值和高于閾值兩類像素)計算方差,然后計算的值,取其最小的閾值。
式中,和是根據兩類像素的數量計算而來的權重。由于要遍歷所有可能的閾值,所以這并不是一個相對高效的過程。
自適應閾值
自適應閾值方法中閾值在整個過程中自動產生變化,這由OpenCV中的cv::adaptiveThreshold()實現。
void?cv::adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int threshType, int blockSize, double C)
該方法是逐個像素地計算自適應閾值T(x, y),具體計算過程是:計算每個像素位置周圍的blockSize×blockSize區域的加權平均值,然后減去常數C。求均值時所用權重和adaptiveMethod有關,若是cv::ADAPTIVE_THRESH_MEAN_C,則權重相等,若是cv::ADAPTIVE_THRESH_GAUSSIAN_C,則權重由高斯方差得到。
注:該方法只適應與單通道8位或浮點型圖像
#include "stdafx.h" #include <opencv2/opencv.hpp> #include <iostream>using namespace std;int main() {// 設置參數double fixed_threshold = 15;int threshold_type = 1 ? cv::THRESH_BINARY : cv::THRESH_BINARY_INV;int adaptive_method = 1 ? cv::ADAPTIVE_THRESH_MEAN_C : cv::ADAPTIVE_THRESH_GAUSSIAN_C;int block_size = 71;double offset = 15;// 以灰度圖形式加載圖片cv::Mat Igray = cv::imread("D:\\personal-data\\wallpapers\\test.png", cv::IMREAD_GRAYSCALE);// 判斷圖像是否加載成功if (Igray.empty()) { cout << "Can not load " << "D:\\personal-data\\wallpapers\\test.png" << endl; return -1; }// 聲明輸出矩陣cv::Mat It, Iat;// 閾值化操作cv::threshold(Igray,It,fixed_threshold,255,threshold_type);// 自適應閾值cv::adaptiveThreshold(Igray,Iat,255,adaptive_method,threshold_type,block_size,offset);// 展示結果圖像cv::imshow("Raw", Igray);cv::imshow("Threshold", It);cv::imshow("Adaptive Threshold", Iat);cv::waitKey(0);cv::destroyAllWindows();return 0; }運行結果:
當threshold_type=1且adaptive_method=1
| 原圖 | 閾值化操作 | 自適應閾值化 |
當threshold_type=0且adaptive_method=0
| 原圖 | 閾值化操作 | 自適應閾值化 |
平滑
平滑也稱“模糊”,是一種簡單而又常用的圖像處理操作。平滑圖像的目的有很多,但通常都是為了減少噪聲和偽影。在降低圖像分辨率的時候,平滑也十分重要,可以防止圖片出現鋸齒狀。
簡單模糊和方框型濾波器
void cv::blur(InputArray src,OutputArray dst,Size ksize,Point anchor=Point(-1,-1),int borderType=BORDER_DEFAULT)
作用:實現簡單的濾波,目標圖像中的每個值都是原圖像中相應位置一個窗口(核)中像素的平均值,窗口的尺寸由ksize聲明;anchaor指定計算時核與源圖像的對齊方式,默認情況下anchor為cv::Point(-1, -1),表示核相對于濾波器居中。
void cv::boxFilter(InputArray src, OutputArray dst, cv::Size ksize, cv::Point anchor=cv::Point(-1,-1), bool normalize=true;? int borderType=cv::BORDER_DEFAULT)
作用:方框濾波器是一種矩形的并且濾波器中所有值全部相等的濾波器。通常,所有的為1或者1/A,其中A是濾波器的面積。后一種濾波器稱為“歸一化方框型濾波器”,下面所示的是一個5×5的模糊濾波器,也稱“歸一化方框型濾波器”。
通過上述介紹,可以發現cv::boxFilter()是一種一般化的形式,而cv::blur()是一種特殊化的形式。但前者可以以非歸一化形式調用,并且輸出圖像深度可以控制,但后者智能以歸一化形式調用,且輸出圖像深度必須和原圖像保持一致。
中值濾波器
中值濾波器(Median Filter)將每個像素替換為圍繞這個像素的矩形領域內的中值或“中值”像素(相對于平均像素)。通過均值濾波器對噪聲圖像,尤其是有較大孤立的異常值非常敏感,少量具有較大偏差的點也會嚴重影響到均值濾波器。中值濾波器可以采用取其中間點的方式來消除異常值。
void cv::medianBlur(InputArray src,?OutputArray dst, Size ksize)
高斯濾波器
關于高斯濾波器,在之前的文章中已做了詳細介紹,可以參考OpenCV高斯濾波GaussianBlur
雙邊濾波器
雙邊濾波器是一種比較大的圖像分析算子,也就是邊緣保持平滑。高斯平滑的模糊過程是減緩像素在空間上的變化,因此與鄰近的關系緊密,而隨機噪聲在像素間的變化幅度又會非常的大(即噪聲不是空間相關的)。基于這種前提高斯平滑很好地減弱了噪聲并且保留了小信號,但是卻破壞了邊緣信息,邊緣也模糊了。
和高斯平滑類似,雙邊濾波對每個像素及其領域內的像素進行了加權平均。其權重由兩部分組成,第一部分同高斯平滑,第二部分也是高斯權重,但是它不是基于空間距離而是色彩強度差計算而來的,在多通道(色彩)圖像上強度差由各分量的加權累加代替。可將其當做高斯平滑,指示相似程度更高的像素權值更高,邊緣更加明顯,對比度更高。
cv::bilateralFilter(InputArray src,?OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType= cv::BORDER_DEFAULT)
參數:d是像素鄰近的最大距離,處理視頻時一般不大于5,非實時應用時可放大到9;sigmaColor是色彩空間濾波器的sigma值,該值越大,則色彩強度越大,不連續性越強;sigmaSpace是坐標空間濾波器的sigma值。
導數和梯度
卷積中最重要也是最基本的部分就是(近似)計算導數。
索貝爾導數
一般來說,用來表示微分的最常用的算子是索貝爾(Sobel)導數算子,可以實現任意階導數和混合偏導數。
void sv::Sobel(InputArray src, OutputArray dst, int ddepth, int xorder, int yorder, cv::Size ksize=3, double scale=1, double delta=0, int borderType=cv::BORDER_DEFAULT)
參數:ddepth指明目標圖像的深度或類型;xorder和yorder是求導順序,其取值可為0,、1或2,其中0代表在該方向不求導,kszie是一個奇數,表示濾波器核的大小, 目前最大到31;閾值和偏移量在結果存入dst前進行調用,公式如下:
Sobel算子的好處是可以將核定義為各種大小,并且可以快速迭代式地構造這些核。大的核可以更好地近似導數,因為可以消除噪聲影響。其缺點是如果導數在空間上變化劇烈,核太大會使結果發生偏差,并且核比較小時準確度不高。
實際上,由于Sobel算子定義在離散空間上,所以它并不是真正的導數,而是一個多項式,即在x方向上進行Sobel運算表示的并不是二階導數,而是對拋物線函數的局部擬合。
Scharr濾波器
為了將圖像內的信息聯系起來,可能需要測量一幅圖像:在處理過程中,通過在目標附近組織一幅梯度直方圖來收集其形狀信息,這些直方圖是許多形狀分類器訓練和使用的基礎。因此,梯度角的誤差會降低分類器識別的效果。
對于3×3的Sobel濾波器,梯度角距離水平或垂直方向越遠,誤差越明顯。在OpenCV中,調用cv::Sobel()時設置ksize為cv::SCHARR,即可消除3×3這樣小但是快的Sobel導數濾波器所帶來的誤差。Scharr濾波器核Sobel濾波器同樣很快,但是前者精度更高。因此選擇3×3的濾波器時,應當使用Scharr濾波器。
? ? ?或? ? ?
拉普拉斯變換
OpenCV中的函數Laplacian實現了對拉普拉斯算子的離散近似:
void sv::Laplacian(InputArray src, OutputArray dst, int ddepth, cv::Size ksize=3, double scale=1, double delta=0, int borderType=cv::BORDER_DEFAULT)
只要ksize不為1,Laplacian算子的實現就是直接計算Sobel算子響應之和。當ksize=1時的卷積核如下所示:
Laplacian算子可應用于各種場景處理,一種常見的應用就是匹配“斑點”。Laplacian算子就是圖像在x和y軸方向的導數之和,這意味著一個被較大值包圍的點或小斑點(比ksize小)處的值將會變得很大。相反,被較小值包圍的點或小斑點處的值將在負方向上變得很大。
Laplacian算子同樣可以用于邊緣檢測,函數一階導數在原函數變化大的地方,值會相應變大,同樣在圖像邊緣處也同樣變化,所有導數在這些地方將變得很大。因此可以在二階導數為0的地方搜尋這么一個極大值,原圖像中的邊緣通過Laplacian算子運算后會變成0。對于有些不是邊緣也變成0的問題,可以通過濾掉Sobel一階導數中較大值的點解決。
圖像形態學
OpenCV提供了一種高效且易用的圖像形態學變換接口。其中有很多形態學方法,但基本上所有的形態學操作都基于兩種原始操作——膨脹與腐蝕。
膨脹和腐蝕
膨脹和腐蝕是最基本的形態學變換,可用于消除噪聲、元素分割和連接等。基于這兩種操作,可以實現更復雜的形態學操作,用來定位強度峰值或孔洞、另一種形式的圖像梯度等。
膨脹是一種卷積操作,它將目標像素的值替換為卷積核覆蓋區域的局部最大值,擴張了明亮區域,填充凹面。此卷積核是一個非線性核,是一個四邊形或圓形的實心核,其錨點在中心。與膨脹對應,腐蝕是與之相反的操作,腐蝕操作計算的是核覆蓋范圍內的局部最小值縮減了明亮區,消除凸起。
| 原圖 | 膨脹 | 腐蝕 |
void cv::erode(InputArray src, OutputArray dst, InputArray element, cv::Point anchor=cv::Point(-1, -1), int iterations=1?int borderType=cv::BORDER_CONSTANT, const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue())
void cv::dilate(InputArray src, OutputArray dst, InputArray element, cv::Point anchor=cv::Point(-1, -1), int iterations=1?int borderType=cv::BORDER_CONSTANT, const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue())
以上兩個函數分別是膨脹和腐蝕對應的函數,第三個參數是核,可以傳遞一個未初始化的cv::Mat,會使用默認的錨點在中心的3×3的核。
腐蝕操作通常用于消除圖中斑點一樣的噪聲,原理是斑點經過腐蝕后會消失,而大的可見區域不會受影響。膨脹操作通常用于發生連通分支。
通用形態學函數
當處理的對象是二值圖像時,像素只能是開(>0)或關(=0)的圖像掩膜時,基本的腐蝕和膨脹操作就夠用了。需要對灰度圖或者彩色圖進行處理時,一些其他操作就非常有用了,這些操作可以通過cv::morphologyEx()實現。
void cv::morphologyEx(InputArray src, OutputArray dst, int op, InputArray element, cv::Point anchor=cv::Point(-1, -1), int iterations=1, int?borderType=cv::BORDER_CONSTANT, const cv::Scalar& borderValue = cv::morphologyDefaultBorderValue())
| cv::MORPH_OPEN | 開操作 | 否 |
| cv::MORPH_CLOSE | 閉操作 | 否 |
| cv::MORPH_GRADIENT | 形態學梯度 | 總是需要 |
| cv::MORPH_TOPHAT | 頂帽操作 | 就地調用需要(src = dst) |
| cv::MORPH_BLACKHAT | 地貌操作 | 就地調用需要(src = dst) |
開操作和閉操作
開操作先將圖像進行腐蝕,然后對腐蝕的結果進行膨脹。開操作常用語對二值圖像中的區域進行計算。
閉操作想將圖像進行膨脹,然后對膨脹的結果進行腐蝕。閉操作用于復雜連通分支算法中減少無用或噪聲驅動的片段。
對于連通分支,通常先進行腐蝕或閉操作消除噪聲,然后通過開操作連接相互靠近的大型區域。
對于一幅非布爾型圖像進行形態學操作時,閉操作最明顯的效果是消除值小于鄰域內的點的孤立異常,而開操作消除的是大于鄰域內點的孤立異常值。
形態學梯度
梯度操作的結果(擴張亮域)減腐蝕操作的結果(縮減亮域)產生了原圖像中的目標邊緣。對于灰度圖像,其結果就是計算明暗變換的趨勢。形態學梯度通常用于顯示明亮區域的邊界,然后便可以將他們看作目標或者目標的部分。用擴張的圖像減去了收縮的圖像便得到完整的邊界。與計算梯度不同,它并不會關注某個物體的周圍。
頂帽和黑帽
頂帽用于顯示與其鄰近相比更亮的部分;黑帽用于顯示與其鄰近相比更暗的部分。
#include "stdafx.h" #include <opencv2/opencv.hpp>int main() {cv::namedWindow("image", cv::WINDOW_NORMAL);cv::namedWindow("erosion", cv::WINDOW_NORMAL);cv::namedWindow("dilation", cv::WINDOW_NORMAL);cv::namedWindow("opening", cv::WINDOW_NORMAL);cv::namedWindow("closing", cv::WINDOW_NORMAL);cv::namedWindow("gradient", cv::WINDOW_NORMAL);cv::namedWindow("topHat", cv::WINDOW_NORMAL);cv::namedWindow("blackHat", cv::WINDOW_NORMAL);cv::Mat img = cv::imread("D:\\personal-data\\wallpapers\\test.png");cv::Mat erosion, dilation, opening, closing, gradient, topHat, blackHat;cv::erode(img, erosion, cv::Mat());cv::dilate(img, dilation, cv::Mat());cv::morphologyEx(img, opening, cv::MORPH_OPEN, cv::Mat());cv::morphologyEx(img, closing, cv::MORPH_CLOSE, cv::Mat());cv::morphologyEx(img, gradient, cv::MORPH_GRADIENT, cv::Mat());cv::morphologyEx(img, topHat, cv::MORPH_TOPHAT, cv::Mat());cv::morphologyEx(img, blackHat, cv::MORPH_BLACKHAT, cv::Mat());cv::imshow("image", img);cv::imshow("erosion", erosion);cv::imshow("dilation", dilation);cv::imshow("opening", opening);cv::imshow("closing", closing);cv::imshow("gradient", gradient);cv::imshow("topHat", topHat);cv::imshow("blackHat", blackHat);cv::waitKey(0);cv::destroyAllWindows();return 0; }自定義核
在形態學上,核常常稱為“構造元素”,OpenCV提供了創建自定義形態學核的函數cv::getStructuringElement()。
cv::Mat?cv::getStructuringElement(int shape, cv::Size ksize, cv::Point anchor=cv::Point(-1, -1))
| cv::MOEPH_RECT | 矩形 | |
| cv::MOEPH_ELLIPSE | 橢圓形 | 以ksize.width和ksize.height為兩個半徑做橢圓 |
| cv::MOEPH_CROSS | 交叉 | ,當 i == anchor.y或 j?== anchor.x |
用任意線性濾波器做卷積
? ? ? ? ? ? ? ? ? ??
上述兩個核中,左邊的核是可分的,右邊的是不可分的。一個可分核可以理解成兩個一維核,在卷積時先調用x內核,然后再調用y內核。兩個矩陣進行卷積所產生的消耗可以用兩個矩陣的面積之積近似。如此一來,用n×n的核對面積為A的圖像進行卷積所需要的時間是,但如果分解為n×1和1×n的兩個核,那么代價就是。由此可見,分解卷積核可以提高卷積計算的效率。
用cv::filter2D()進行卷積
void cv::filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, cv::Point anchor=cv::Point(-1, -1), double delta=0, int borderType=cv::BORDER_DEFAULT)
注:如果定義了錨點的位置,那么核的大小可以是偶數,否則必須是奇數。
通過cv::sepFilter2D使用可分核
void cv::sepFilter2D(InputArray src, OutputArray dst, int ddepth, InputArray rowKernel,?InputArray? columnKernel,?cv::Point anchor=cv::Point(-1, -1), double delta=0, int borderType=cv::BORDER_DEFAULT)
注:兩個核的大小應當是n1×1和1×n2,n1和n2不一定相等。
生成卷積核
void cv::getDerivKernel(OutputArray kx, OutputArray ky, int dx, int dy, int ksize, bool normalize=true, int ktype=CV_32F)
作用:生成可分解核,如Sobel和Scharr核。dx和dy是求導順序;ksize是核的大小,可以為1、3、5、7或cv::SCHARR;normalize指示是否核元素規范化,如果是浮點型圖像,設為true,反之設為false;ktype表示濾波器的類型,可以使CV_32F和CV_64F。
cv::Mat cv::getGaussianKernel(int ksize, double sigma, int ktype=CV_32F)
作用:生成高斯核。
α在濾波器需要規范化的時候才起作用。sigma可以為-1,這樣將自動計算,其中
總結
以上是生活随笔為你收集整理的OpenCV的滤波与卷积的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LATEX调整公式、图片与正文间距离,文
- 下一篇: pytorch数据加载时报错OSErro