OpenCV之imgproc 模块. 图像处理(1)图像平滑处理 腐蚀与膨胀(Eroding and Dilating) 更多形态学变换 图像金字塔 基本的阈值操作
圖像平滑處理
目標
本教程教您怎樣使用各種線性濾波器對圖像進行平滑處理,相關OpenCV函數如下:
- blur
- GaussianBlur
- medianBlur
- bilateralFilter
原理
Note
?以下原理來源于Richard Szeliski 的著作?Computer Vision: Algorithms and Applications?以及?Learning OpenCV
-
平滑?也稱?模糊, 是一項簡單且使用頻率很高的圖像處理方法。
-
平滑處理的用途有很多, 但是在本教程中我們僅僅關注它減少噪聲的功用 (其他用途在以后的教程中會接觸到)。
-
平滑處理時需要用到一個?濾波器?。 最常用的濾波器是?線性?濾波器,線性濾波處理的輸出像素值 (i.e.?) 是輸入像素值 (i.e.?)的加權和 :
?稱為?核, 它僅僅是一個加權系數。
不妨把?濾波器?想象成一個包含加權系數的窗口,當使用這個濾波器平滑處理圖像時,就把這個窗口滑過圖像。
-
濾波器的種類有很多, 這里僅僅提及最常用的:
歸一化塊濾波器 (Normalized Box Filter)
-
最簡單的濾波器, 輸出像素值是核窗口內像素值的?均值?( 所有像素加權系數相等)
-
核如下:
高斯濾波器 (Gaussian Filter)
-
最有用的濾波器 (盡管不是最快的)。 高斯濾波是將輸入數組的每一個像素點與?高斯內核?卷積將卷積和當作輸出像素值。
-
還記得1維高斯函數的樣子嗎?
假設圖像是1維的,那么觀察上圖,不難發現中間像素的加權系數是最大的, 周邊像素的加權系數隨著它們遠離中間像素的距離增大而逐漸減小。
Note
?2維高斯函數可以表達為 :
其中??為均值 (峰值對應位置),??代表標準差 (變量??和 變量??各有一個均值,也各有一個標準差)
中值濾波器 (Median Filter)
中值濾波將圖像的每個像素用鄰域 (以當前像素為中心的正方形區域)像素的?中值?代替 。
雙邊濾波 (Bilateral Filter)
- 目前我們了解的濾波器都是為了?平滑?圖像, 問題是有些時候這些濾波器不僅僅削弱了噪聲, 連帶著把邊緣也給磨掉了。 為避免這樣的情形 (至少在一定程度上 ), 我們可以使用雙邊濾波。
- 類似于高斯濾波器,雙邊濾波器也給每一個鄰域像素分配一個加權系數。 這些加權系數包含兩個部分, 第一部分加權方式與高斯濾波一樣,第二部分的權重則取決于該鄰域像素與當前像素的灰度差值。
- 詳細的解釋可以查看?鏈接
源碼
-
本程序做什么?
- 裝載一張圖像
- 使用4種不同濾波器 (見原理部分) 并顯示平滑圖像
-
下載代碼: 點擊?這里
-
代碼一瞥:
解釋
下面看一看有關平滑的OpenCV函數,其余部分大家已經很熟了。
歸一化塊濾波器:
OpenCV函數?blur?執行了歸一化塊平滑操作。
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ blur( src, dst, Size( i, i ), Point(-1,-1) );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }我們輸入4個實參 (詳細的解釋請參考 Reference):
- src: 輸入圖像
- dst: 輸出圖像
- Size( w,h ): 定義內核大小(?w?像素寬度,?h?像素高度)
- Point(-1, -1): 指定錨點位置(被平滑點), 如果是負值,取核的中心為錨點。
高斯濾波器:
OpenCV函數?GaussianBlur?執行高斯平滑 :
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ GaussianBlur( src, dst, Size( i, i ), 0, 0 );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }我們輸入4個實參 (詳細的解釋請參考 Reference):
- src: 輸入圖像
- dst: 輸出圖像
- Size(w, h): 定義內核的大小(需要考慮的鄰域范圍)。??和??必須是正奇數,否則將使用??和?參數來計算內核大小。
- : x 方向標準方差, 如果是??則??使用內核大小計算得到。
- : y 方向標準方差, 如果是??則??使用內核大小計算得到。.
中值濾波器:
OpenCV函數?medianBlur?執行中值濾波操作:
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ medianBlur ( src, dst, i );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }我們用了3個參數:
- src: 輸入圖像
- dst: 輸出圖像, 必須與?src?相同類型
- i: 內核大小 (只需一個值,因為我們使用正方形窗口),必須為奇數。
雙邊濾波器
OpenCV函數?bilateralFilter?執行雙邊濾波操作:
for ( int i = 1; i < MAX_KERNEL_LENGTH; i = i + 2 ){ bilateralFilter ( src, dst, i, i*2, i/2 );if( display_dst( DELAY_BLUR ) != 0 ) { return 0; } }我們使用了5個參數:
- src: 輸入圖像
- dst: 輸出圖像
- d: 像素的鄰域直徑
- : 顏色空間的標準方差
- : 坐標空間的標準方差(像素單位)
結果
-
程序顯示了原始圖像(?lena.jpg) 和使用4種濾波器之后的效果圖。
-
這里顯示的是使用?中值濾波?之后的效果圖:
腐蝕與膨脹(Eroding and Dilating)
目標
本文檔嘗試解答如下問題:
- 如何使用OpenCV提供的兩種最基本的形態學操作,腐蝕與膨脹( Erosion 與 Dilation):
- erode
- dilate
原理
Note
?以下內容來自于Bradski和Kaehler的大作:?Learning OpenCV?.
形態學操作
-
簡單來講,形態學操作就是基于形狀的一系列圖像處理操作。通過將?結構元素?作用于輸入圖像來產生輸出圖像。
-
最基本的形態學操作有二:腐蝕與膨脹(Erosion 與 Dilation)。 他們的運用廣泛:
- 消除噪聲
- 分割(isolate)獨立的圖像元素,以及連接(join)相鄰的元素。
- 尋找圖像中的明顯的極大值區域或極小值區域。
-
通過以下圖像,我們簡要來討論一下膨脹與腐蝕操作(譯者注:注意這張圖像中的字母為黑色,背景為白色,而不是一般意義的背景為黑色,前景為白色):
膨脹
-
此操作將圖像??與任意形狀的內核 (),通常為正方形或圓形,進行卷積。
-
內核??有一個可定義的?錨點, 通常定義為內核中心點。
-
進行膨脹操作時,將內核??劃過圖像,將內核??覆蓋區域的最大相素值提取,并代替錨點位置的相素。顯然,這一最大化操作將會導致圖像中的亮區開始”擴展” (因此有了術語膨脹?dilation?)。對上圖采用膨脹操作我們得到:
背景(白色)膨脹,而黑色字母縮小了。
腐蝕
-
腐蝕在形態學操作家族里是膨脹操作的孿生姐妹。它提取的是內核覆蓋下的相素最小值。
-
進行腐蝕操作時,將內核??劃過圖像,將內核??覆蓋區域的最小相素值提取,并代替錨點位置的相素。
-
以與膨脹相同的圖像作為樣本,我們使用腐蝕操作。從下面的結果圖我們看到亮區(背景)變細,而黑色區域(字母)則變大了。
源碼
下面是本教程的源碼, 你也可以從?here?下載。
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include "highgui.h" #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量 Mat src, erosion_dst, dilation_dst;int erosion_elem = 0; int erosion_size = 0; int dilation_elem = 0; int dilation_size = 0; int const max_elem = 2; int const max_kernel_size = 21;/** Function Headers */ void Erosion( int, void* ); void Dilation( int, void* );/** @function main */ int main( int argc, char** argv ) {/// Load 圖像src = imread( argv[1] );if( !src.data ){ return -1; }/// 創建顯示窗口namedWindow( "Erosion Demo", CV_WINDOW_AUTOSIZE );namedWindow( "Dilation Demo", CV_WINDOW_AUTOSIZE );cvMoveWindow( "Dilation Demo", src.cols, 0 );/// 創建腐蝕 TrackbarcreateTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo",&erosion_elem, max_elem,Erosion );createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo",&erosion_size, max_kernel_size,Erosion );/// 創建膨脹 TrackbarcreateTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo",&dilation_elem, max_elem,Dilation );createTrackbar( "Kernel size:\n 2n +1", "Dilation Demo",&dilation_size, max_kernel_size,Dilation );/// Default startErosion( 0, 0 );Dilation( 0, 0 );waitKey(0);return 0; }/** @function Erosion */ void Erosion( int, void* ) {int erosion_type;if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );/// 腐蝕操作erode( src, erosion_dst, element );imshow( "Erosion Demo", erosion_dst ); }/** @function Dilation */ void Dilation( int, void* ) {int dilation_type;if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( dilation_type,Size( 2*dilation_size + 1, 2*dilation_size+1 ),Point( dilation_size, dilation_size ) );///膨脹操作dilate( src, dilation_dst, element );imshow( "Dilation Demo", dilation_dst ); }解釋
大部分代碼應該不需要解釋了 (如果有任何疑問,請回頭參考前面的教程)。 讓我們來回顧一下本程序的總體流程:
- 裝載圖像 (可以是 RGB圖像或者灰度圖 )
- 創建兩個顯示窗口 (一個用于膨脹輸出,一個用于腐蝕輸出)
- 為每個操作創建兩個 Trackbars:
- 第一個 trackbar “Element” 返回?erosion_elem?或者?dilation_elem
- 第二個 trackbar “Kernel size” 返回?erosion_size?或者?dilation_size?。
- 每次移動標尺, 用戶函數?Erosion?或者?Dilation?就會被調用,函數將根據當前的trackbar位置更新輸出圖像。
讓我們分析一下這兩個函數:
Erosion:
/** @function Erosion */ void Erosion( int, void* ) {int erosion_type;if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );/// 腐蝕操作erode( src, erosion_dst, element );imshow( "Erosion Demo", erosion_dst ); }-
進行?腐蝕?操作的函數是?erode?。 它接受了三個參數:
-
src: 原圖像
-
erosion_dst: 輸出圖像
-
element: 腐蝕操作的內核。 如果不指定,默認為一個簡單的??矩陣。否則,我們就要明確指定它的形狀,可以使用函數?getStructuringElement:
Mat element = getStructuringElement( erosion_type,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );
我們可以為我們的內核選擇三種形狀之一:
- 矩形: MORPH_RECT
- 交叉形: MORPH_CROSS
- 橢圓形: MORPH_ELLIPSE
然后,我們還需要指定內核大小,以及?錨點?位置。不指定錨點位置,則默認錨點在內核中心位置。
-
-
就這些了,我們現在可以對圖像進行腐蝕操作了。
Note
?OpenCV的?erode?函數還有另外的參數,其中一個參數允許你一下對圖像進行多次腐蝕操作。在這個簡單的文檔中沒有用到它,但是你可以參考OpenCV的使用手冊。
Dilation:
下面是膨脹的代碼,你可以看到,它和?Erosion?函數是多么相似。 這里我們同樣可以指定內核的形狀,錨點和大小。
/** @function Dilation */ void Dilation( int, void* ) {int dilation_type;if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }Mat element = getStructuringElement( dilation_type,Size( 2*dilation_size + 1, 2*dilation_size+1 ),Point( dilation_size, dilation_size ) );/// 膨脹操作dilate( src, dilation_dst, element );imshow( "Dilation Demo", dilation_dst ); }結果
-
編譯并使用圖像路徑作為參數運行程序,比如我們使用以下圖像:
下面是操作的結果。 更改Trackbars的位置就會產生不一樣的輸出圖像,自己試試吧。 最后,你還可以通過增加第三個Trackbar來控制膨脹或腐蝕的次數。
更多形態學變換
目標
本文檔嘗試解答如下問題:
- 如何使用OpenCV函數?morphologyEx?進行形態學操作:
- 開運算 (Opening)
- 閉運算 (Closing)
- 形態梯度 (Morphological Gradient)
- 頂帽 (Top Hat)
- 黑帽(Black Hat)
原理
Note
?以下內容來自于Bradski和Kaehler的大作?Learning OpenCV?。
前一節我們討論了兩種最基本的形態學操作:
- 腐蝕 (Erosion)
- 膨脹 (Dilation)
運用這兩個基本操作,我們可以實現更高級的形態學變換。這篇文檔將會簡要介紹OpenCV提供的5種高級形態學操作:
開運算 (Opening)
-
開運算是通過先對圖像腐蝕再膨脹實現的。
-
能夠排除小團塊物體(假設物體較背景明亮)
-
請看下面,左圖是原圖像,右圖是采用開運算轉換之后的結果圖。 觀察發現字母拐彎處的白色空間消失。
閉運算(Closing)
-
閉運算是通過先對圖像膨脹再腐蝕實現的。
-
能夠排除小型黑洞(黑色區域)。
形態梯度(Morphological Gradient)
-
膨脹圖與腐蝕圖之差
-
能夠保留物體的邊緣輪廓,如下所示:
頂帽(Top Hat)
-
原圖像與開運算結果圖之差
黑帽(Black Hat)
-
閉運算結果圖與原圖像之差
源碼
下面是本教程的源碼, 你也可以從?這里?下載。
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量 Mat src, dst;int morph_elem = 0; int morph_size = 0; int morph_operator = 0; int const max_operator = 4; int const max_elem = 2; int const max_kernel_size = 21;char* window_name = "Morphology Transformations Demo";/** 回調函數申明 */ void Morphology_Operations( int, void* );/** @函數 main */ int main( int argc, char** argv ) {/// 裝載圖像src = imread( argv[1] );if( !src.data ){ return -1; }/// 創建顯示窗口namedWindow( window_name, CV_WINDOW_AUTOSIZE );/// 創建選擇具體操作的 trackbarcreateTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );/// 創建選擇內核形狀的 trackbarcreateTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,&morph_elem, max_elem,Morphology_Operations );/// 創建選擇內核大小的 trackbarcreateTrackbar( "Kernel size:\n 2n +1", window_name,&morph_size, max_kernel_size,Morphology_Operations );/// 啟動使用默認值Morphology_Operations( 0, 0 );waitKey(0);return 0;}/** * @函數 Morphology_Operations */ void Morphology_Operations( int, void* ) {// 由于 MORPH_X的取值范圍是: 2,3,4,5 和 6int operation = morph_operator + 2;Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );/// 運行指定形態學操作morphologyEx( src, dst, operation, element );imshow( window_name, dst );}解釋
看一下程序的總體流程:
-
裝載圖像
-
創建顯示形態學操作的窗口
-
創建3個trackbar獲取用戶參數:
-
第一個trackbar?“Operator”?返回用戶選擇的形態學操作類型 (morph_operator).
createTrackbar("Operator:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat",window_name, &morph_operator, max_operator,Morphology_Operations ); -
第二個trackbar?“Element”?返回?morph_elem, 指定內核形狀:
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,&morph_elem, max_elem,Morphology_Operations ); -
第三個trackbar?“Kernel Size”?返回內核大小(morph_size)
createTrackbar( "Kernel size:\n 2n +1", window_name,&morph_size, max_kernel_size,Morphology_Operations );
-
-
每當任一標尺被移動, 用戶函數?Morphology_Operations?就會被調用,該函數獲取trackbar的當前值運行指定操作并更新顯示結果圖像。
/** * @函數 Morphology_Operations */ void Morphology_Operations( int, void* ) {// 由于 MORPH_X的取值范圍是: 2,3,4,5 和 6int operation = morph_operator + 2;Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );/// 運行指定形態學操作morphologyEx( src, dst, operation, element );imshow( window_name, dst );}運行形態學操作的核心函數是?morphologyEx?。在本例中,我們使用了4個參數(其余使用默認值):
- src?: 原 (輸入) 圖像
- dst: 輸出圖像
- operation: 需要運行的形態學操作。 我們有5個選項:
- Opening: MORPH_OPEN : 2
- Closing: MORPH_CLOSE: 3
- Gradient: MORPH_GRADIENT: 4
- Top Hat: MORPH_TOPHAT: 5
- Black Hat: MORPH_BLACKHAT: 6
你可以看到, 它們的取值范圍是 <2-6>, 因此我們要將從tracker獲取的值增加(+2):
int operation = morph_operator + 2;- element: 內核,可以使用函數:get_structuring_element:getStructuringElement <>?自定義。
結果
-
在編譯上面的代碼之后, 我們可以運行結果,將圖片路徑輸入。這里使用圖像:?baboon.png:
-
這里是顯示窗口的兩個截圖。第一幅圖顯示了使用交錯內核和?開運算?之后的結果, 第二幅圖顯示了使用橢圓內核和?黑帽?之后的結果。
圖像金字塔
目標
本文檔嘗試解答如下問題:
- 如何使用OpenCV函數?pyrUp?和?pyrDown?對圖像進行向上和向下采樣。
原理
Note
?以下內容來自于Bradski和Kaehler的大作:?Learning OpenCV?。
- 當我們需要將圖像轉換到另一個尺寸的時候, 有兩種可能:
- 放大?圖像 或者
- 縮小?圖像。
- 盡管OpenCV?幾何變換?部分提供了一個真正意義上的圖像縮放函數(resize, 在以后的教程中會學到),不過在本篇我們首先學習一下使用?圖像金字塔?來做圖像縮放, 圖像金字塔是視覺運用中廣泛采用的一項技術。
圖像金字塔
- 一個圖像金字塔是一系列圖像的集合 - 所有圖像來源于同一張原始圖像 - 通過梯次向下采樣獲得,直到達到某個終止條件才停止采樣。
- 有兩種類型的圖像金字塔常常出現在文獻和應用中:
- 高斯金字塔(Gaussian pyramid):?用來向下采樣
- 拉普拉斯金字塔(Laplacian pyramid):?用來從金字塔低層圖像重建上層未采樣圖像
- 在這篇文檔中我們將使用?高斯金字塔?。
高斯金字塔
-
想想金字塔為一層一層的圖像,層級越高,圖像越小。
-
每一層都按從下到上的次序編號, 層級??(表示為??尺寸小于層級??())。
-
為了獲取層級為??的金字塔圖像,我們采用如下方法:
-
將??與高斯內核卷積:
-
將所有偶數行和列去除。
-
-
顯而易見,結果圖像只有原圖的四分之一。通過對輸入圖像??(原始圖像) 不停迭代以上步驟就會得到整個金字塔。
-
以上過程描述了對圖像的向下采樣,如果將圖像變大呢?:
- 首先,將圖像在每個方向擴大為原來的兩倍,新增的行和列以0填充()
- 使用先前同樣的內核(乘以4)與放大后的圖像卷積,獲得 “新增像素” 的近似值。
-
這兩個步驟(向下和向上采樣) 分別通過OpenCV函數?pyrUp?和?pyrDown?實現, 我們將會在下面的示例中演示如何使用這兩個函數。
Note
?我們向下采樣縮小圖像的時候, 我們實際上?丟失?了一些信息。
源碼
本教程的源碼如下,你也可以從?這里?下載
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <math.h> #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量 Mat src, dst, tmp; char* window_name = "Pyramids Demo";/** * @函數 main */ int main( int argc, char** argv ) {/// 指示說明printf( "\n Zoom In-Out demo \n " );printf( "------------------ \n" );printf( " * [u] -> Zoom in \n" );printf( " * [d] -> Zoom out \n" );printf( " * [ESC] -> Close program \n \n" );/// 測試圖像 - 尺寸必須能被 2^{n} 整除src = imread( "../images/chicky_512.jpg" );if( !src.data ){ printf(" No data! -- Exiting the program \n");return -1; }tmp = src;dst = tmp;/// 創建顯示窗口namedWindow( window_name, CV_WINDOW_AUTOSIZE );imshow( window_name, dst );/// 循環while( true ){int c;c = waitKey(10);if( (char)c == 27 ){ break; }if( (char)c == 'u' ){ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );printf( "** Zoom In: Image x 2 \n" );}else if( (char)c == 'd' ){ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );printf( "** Zoom Out: Image / 2 \n" );}imshow( window_name, dst );tmp = dst;}return 0; }解釋
讓我們來回顧一下本程序的總體流程:
-
裝載圖像(此處路徑由程序設定,用戶無需將圖像路徑當作參數輸入)
/// 測試圖像 - 尺寸必須能被 2^{n} 整除 src = imread( "../images/chicky_512.jpg" ); if( !src.data ){ printf(" No data! -- Exiting the program \n");return -1; } -
創建兩個Mat實例, 一個用來儲存操作結果(dst), 另一個用來存儲零時結果(tmp)。
Mat src, dst, tmp; /* ... */ tmp = src; dst = tmp; -
創建窗口顯示結果
namedWindow( window_name, CV_WINDOW_AUTOSIZE ); imshow( window_name, dst ); -
執行無限循環,等待用戶輸入。
while( true ) {int c;c = waitKey(10);if( (char)c == 27 ){ break; }if( (char)c == 'u' ){ pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 ) );printf( "** Zoom In: Image x 2 \n" );}else if( (char)c == 'd' ){ pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 ) );printf( "** Zoom Out: Image / 2 \n" );}imshow( window_name, dst );tmp = dst; }如果用戶按?ESC?鍵程序退出。 此外,它還提供兩個選項:
-
向上采樣 (按 ‘u’)
pyrUp( tmp, dst, Size( tmp.cols*2, tmp.rows*2 )函數?pyrUp?接受了3個參數:
- tmp: 當前圖像, 初始化為原圖像?src?。
- dst: 目的圖像( 顯示圖像,為輸入圖像的兩倍)
- Size( tmp.cols*2, tmp.rows*2 )?: 目的圖像大小, 既然我們是向上采樣,?pyrUp?期待一個兩倍于輸入圖像(?tmp?)的大小。
-
向下采樣(按 ‘d’)
pyrDown( tmp, dst, Size( tmp.cols/2, tmp.rows/2 )類似于?pyrUp, 函數?pyrDown?也接受了3個參數:
- tmp: 當前圖像, 初始化為原圖像?src?。
- dst: 目的圖像( 顯示圖像,為輸入圖像的一半)
- Size( tmp.cols/2, tmp.rows/2 )?:目的圖像大小, 既然我們是向下采樣,?pyrDown?期待一個一半于輸入圖像(?tmp)的大小。
-
注意輸入圖像的大小(在兩個方向)必須是2的冥,否則,將會顯示錯誤。
-
最后,將輸入圖像?tmp?更新為當前顯示圖像, 這樣后續操作將作用于更新后的圖像。
tmp = dst;
-
結果
-
在編譯上面的代碼之后, 我們可以運行結果。 程序調用了圖像?chicky_512.jpg?,你可以在?tutorial_code/image?文件夾找到它。 注意圖像大小是?, 因此向下采樣不會產生錯誤()。 原圖像如下所示:
-
首先按兩次 ‘d’ 連續兩次向下采樣?pyrDown?,結果如圖:
-
由于我們縮小了圖像,我們也因此丟失了一些信息。通過連續按兩次 ‘u’ 向上采樣兩次?pyrUp?,很明顯圖像有些失真:
基本的閾值操作
目標:
本節簡介:
- OpenCV中的閾值(threshold)函數:?threshold?的運用。
基本理論:
注意:什么是閾值?
-
最簡單的圖像分割的方法。
-
應用舉例:從一副圖像中利用閾值分割出我們需要的物體部分(當然這里的物體可以是一部分或者整體)。這樣的圖像分割方法是基于圖像中物體與背景之間的灰度差異,而且此分割屬于像素級的分割。
-
為了從一副圖像中提取出我們需要的部分,應該用圖像中的每一個像素點的灰度值與選取的閾值進行比較,并作出相應的判斷。(注意:閾值的選取依賴于具體的問題。即:物體在不同的圖像中有可能會有不同的灰度值。
-
一旦找到了需要分割的物體的像素點,我們可以對這些像素點設定一些特定的值來表示。(例如:可以將該物體的像素點的灰度值設定為:‘0’(黑色),其他的像素點的灰度值為:‘255’(白色);當然像素點的灰度值可以任意,但最好設定的兩種顏色對比度較強,方便觀察結果)。
閾值化的類型:
-
OpenCV中提供了閾值(threshold)函數:?threshold?。
-
這個函數有5種閾值化類型,在接下來的章節中將會具體介紹。
-
為了解釋閾值分割的過程,我們來看一個簡單有關像素灰度的圖片,該圖如下。該圖中的藍色水平線代表著具體的一個閾值。
閾值類型1:二進制閾值化
-
該閾值化類型如下式所示:
-
解釋:在運用該閾值類型的時候,先要選定一個特定的閾值量,比如:125,這樣,新的閾值產生規則可以解釋為大于125的像素點的灰度值設定為最大值(如8位灰度值最大為255),灰度值小于125的像素點的灰度值設定為0。
閾值類型2:反二進制閾值化
-
該閾值類型如下式所示:
-
解釋:該閾值化與二進制閾值化相似,先選定一個特定的灰度值作為閾值,不過最后的設定值相反。(在8位灰度圖中,例如大于閾值的設定為0,而小于該閾值的設定為255)。
閾值類型3:截斷閾值化
-
該閾值化類型如下式所示:
-
解釋:同樣首先需要選定一個閾值,圖像中大于該閾值的像素點被設定為該閾值,小于該閾值的保持不變。(例如:閾值選取為125,那小于125的閾值不改變,大于125的灰度值(230)的像素點就設定為該閾值)。
閾值類型4:閾值化為0
-
該閾值類型如下式所示:
-
解釋:先選定一個閾值,然后對圖像做如下處理:1 像素點的灰度值大于該閾值的不進行任何改變;2 像素點的灰度值小于該閾值的,其灰度值全部變為0。
閾值類型5:反閾值化為0
-
該閾值類型如下式所示:
-
解釋:原理類似于0閾值,但是在對圖像做處理的時候相反,即:像素點的灰度值小于該閾值的不進行任何改變,而大于該閾值的部分,其灰度值全部變為0。
代碼示范:
簡單的代碼如下。同樣也可以在網站中?下載?以下代碼。
#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <stdlib.h> #include <stdio.h>using namespace cv;/// 全局變量定義及賦值int threshold_value = 0; int threshold_type = 3;; int const max_value = 255; int const max_type = 4; int const max_BINARY_value = 255;Mat src, src_gray, dst; char* window_name = "Threshold Demo";char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted"; char* trackbar_value = "Value";/// 自定義函數聲明 void Threshold_Demo( int, void* );/** * @主函數 */ int main( int argc, char** argv ) {/// 讀取一副圖片,不改變圖片本身的顏色類型(該讀取方式為DOS運行模式)src = imread( argv[1], 1 );/// 將圖片轉換成灰度圖片cvtColor( src, src_gray, CV_RGB2GRAY );/// 創建一個窗口顯示圖片namedWindow( window_name, CV_WINDOW_AUTOSIZE );/// 創建滑動條來控制閾值createTrackbar( trackbar_type,window_name, &threshold_type,max_type, Threshold_Demo );createTrackbar( trackbar_value,window_name, &threshold_value,max_value, Threshold_Demo );/// 初始化自定義的閾值函數Threshold_Demo( 0, 0 );/// 等待用戶按鍵。如果是ESC健則退出等待過程。while(true){int c;c = waitKey( 20 );if( (char)c == 27 ){ break; }}}/** * @自定義的閾值函數 */ void Threshold_Demo( int, void* ) {/* 0: 二進制閾值 1: 反二進制閾值 2: 截斷閾值 3: 0閾值 4: 反0閾值 */threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );imshow( window_name, dst ); }解釋:
先看一下整個程序的結構:
-
先讀取一副圖片,如果是圖片顏色類型是RGB3色類型,則轉換成灰度類型的圖像。轉換顏色類型可以運用OpenCV中的 cvtColor<> 函數。
src = imread( argv[1], 1 );/// 顏色類型從RGB 轉換成灰度 cvtColor( src, src_gray, CV_RGB2GRAY ); -
然后創建一個窗口來顯示該圖片可以檢驗轉換結果
namedWindow( window_name, CV_WINDOW_AUTOSIZE ); -
接著該程序創建兩個滾動條來等待用戶的輸入:
- 第一個滾動條作用:選擇閾值類型:二進制,反二進制,截斷,0,反0。
- 第二個滾動條作用:選擇閾值的大小。
-
在這里等到用戶拖動滾動條來輸入閾值類型以及閾值的大小,或者是用戶鍵入ESC健退出程序。
-
無論何時拖動滾動條,用戶自定義的閾值函數都將會被調用。
/** * @自定義的閾值函數 */ void Threshold_Demo( int, void* ) {/* 0: 二進制閾值 1: 反二進制閾值 2: 截斷閾值 3: 0閾值 4: 反0閾值 */threshold( src_gray, dst, threshold_value, max_BINARY_value,threshold_type );imshow( window_name, dst ); }就像你看到的那樣,在這樣的過程中,函數 threshold<> 會接受到5個參數:
- src_gray: 輸入的灰度圖像的地址。
- dst: 輸出圖像的地址。
- threshold_value: 進行閾值操作時閾值的大小。
- max_BINARY_value: 設定的最大灰度值(該參數運用在二進制與反二進制閾值操作中)。
- threshold_type: 閾值的類型。從上面提到的5種中選擇出的結果。
結果:
程序編譯過后,從正確的路徑中讀取一張圖片。例如,該輸入圖片如下所示:
首先,閾值類型選擇為反二進制閾值類型。我們希望灰度值大于閾值的變暗,即這一部分像素的灰度值設定為0。從下圖中可以很清楚的看到這樣的變化。(在原圖中,狗的嘴和眼睛部分比圖像中的其他部分要亮,在結果圖中可以看到由于反二進制閾值分割,這兩部分變的比其他圖像的都要暗。原理具體參見本節中反二進制閾值部分解釋)
現在,閾值的類型選擇為0閾值。在這種情況下,我們希望那些在圖像中最黑的像素點徹底的變成黑色,而其他大于閾值的像素保持原來的面貌。其結果如下圖所示:
from: http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/table_of_content_imgproc/table_of_content_imgproc.html#table-of-content-imgproc
總結
以上是生活随笔為你收集整理的OpenCV之imgproc 模块. 图像处理(1)图像平滑处理 腐蚀与膨胀(Eroding and Dilating) 更多形态学变换 图像金字塔 基本的阈值操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenCV之core 模块. 核心功能
- 下一篇: OpenCV之imgproc 模块. 图