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