opencv學習(9)漫水填充、影象金字塔、閾值化的介紹
1、漫水填充
漫水填充法的基本思想:
簡單來說,就是自動選中了和種子點相連的區域,接著將該區域替換成指定的顏色,這是個非常有用的功能,經常用來標記或者分離影象的一部分進行處理或分析.漫水填充也可以用來從輸入影象獲取掩碼區域,掩碼會加速處理過程,或者只處理掩碼指定的畫素點.
以此填充演算法為基礎,類似photoshop的魔術棒選擇工具就很容易實現了。漫水填充(FloodFill)是查詢和種子點聯通的顏色相同的點,魔術棒選擇工具則是查詢和種子點聯通的顏色相近的點,將和初始種子畫素顏色相近的點壓進棧作為新種子
在OpenCV中,漫水填充是填充演算法中最通用的方法。且在OpenCV 2.X中,使用C++重寫過的FloodFill函式有兩個版本。一個不帶掩膜mask的版本,和一個帶mask的版本。這個掩膜mask,就是用於進一步控制哪些區域將被填充顏色
第一個版本的floodFill:
int floodFill(InputOutputArray image, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
第二個版本的floodFill:
int floodFill(InputOutputArray image, InputOutputArray mask, Point seedPoint,Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )
下面是一起介紹的引數詳解。除了第二個引數外,其他的引數都是共用的。
第一個引數,InputOutputArray型別的image, 輸入/輸出1通道或3通道,8位或浮點影象,具體引數由之後的引數具體指明。
第二個引數, InputOutputArray型別的mask,這是第二個版本的floodFill獨享的引數,表示操作掩模,。它應該為單通道、8位、長和寬上都比輸入影象 image 大兩個畫素點的影象。第二個版本的floodFill需要使用以及更新掩膜,所以這個mask引數我們一定要將其準備好並填在此處。需要注意的是,漫水填充不會填充掩膜mask的非零畫素區域。例如,一個邊緣檢測運算元的輸出可以用來作為掩膜,以防止填充到邊緣。同樣的,也可以在多次的函式呼叫中使用同一個掩膜,以保證填充的區域不會重疊。另外需要注意的是,掩膜mask會比需填充的影象大,所以 mask 中與輸入影象(x,y)畫素點相對應的點的座標為(x+1,y+1)。
第三個引數,Point型別的seedPoint,漫水填充演算法的起始點。
第四個引數,Scalar型別的newVal,畫素點被染色的值,即在重繪區域畫素的新值。
第五個引數,Rect*型別的rect,有預設值0,一個可選的引數,用於設定floodFill函式將要重繪區域的最小邊界矩形區域。
第六個引數,Scalar型別的loDiff,有預設值Scalar( ),表示當前觀察畫素值與其部件鄰域畫素值或者待加入該部件的種子畫素之間的亮度或顏色之負差(lower brightness/color difference)的最大值。
第七個引數,Scalar型別的upDiff,有預設值Scalar( ),表示當前觀察畫素值與其部件鄰域畫素值或者待加入該部件的種子畫素之間的亮度或顏色之正差(lower brightness/color difference)的最大值。簡單說第六、第七個引數代表所選定的點的畫素值與鄰接點的畫素值差的範圍;
第八個引數,int型別的flags,操作標誌符,此引數包含三個部分,比較複雜,我們一起詳細看看。
低八位(第0~7位)用於控制演算法的連通性,可取4 (4為預設值) 或者 8。如果設為4,表示填充演算法只考慮當前畫素水平方向和垂直方向的相鄰點;如果設為 8,除上述相鄰點外,還會包含對角線方向的相鄰點。
高八位部分(16~23位)可以為0 或者如下兩種選項識別符號的組合:
FLOODFILL_FIXED_RANGE - 如果設定為這個識別符號的話,就會考慮當前畫素與種子畫素之間的差,否則就考慮當前畫素與其相鄰畫素的差。也就是說,這個範圍是浮動的。
FLOODFILL_MASK_ONLY - 如果設定為這個識別符號的話,函式不會去填充改變原始影象 (也就是忽略第三個引數newVal), 而是去填充掩模影象(mask)。這個識別符號只對第二個版本的floodFill有用,因第一個版本里面壓根就沒有mask引數。
中間八位,上面關於高八位FLOODFILL_MASK_ONLY識別符號中已經說的很明顯,需要輸入符合要求的掩碼。Floodfill的flags引數的中間八位的值就是用於指定填充掩碼影象的值的。但如果flags中間八位的值為0,則掩碼會用1來填充。
而所有flags可以用or操作符連線起來,即“|”。例如,如果想用8鄰域填充,並填充固定畫素值範圍,填充掩碼而不是填充源影象,以及設填充值為38,那麼輸入的引數是這樣:
flags=8 | FLOODFILL_MASK_ONLY | FLOODFILL_FIXED_RANGE | (38<<8)
Floodfill的簡單的呼叫範例:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main( )
{
Mat src = imread("1.jpg");
imshow("【原始圖】",src);
Rect ccomp;
floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20));
imshow("【效果圖】",src);
waitKey(0);
return 0;
}
補充:SetMouseCallback函式
C++: void setMouseCallback(conststring& winname, MouseCallback onMouse, void* userdata=0 )
第一個引數,const string&型別的winname,為視窗的名字。
第二個引數,MouseCallback型別的onMouse,指定窗口裡每次滑鼠時間發生的時候,被呼叫的函式指標。這個函式的原型應該為voidFoo(int event, int x, int y, int flags, void* param);其中event是 CV_EVENT_*變數之一, x和y是滑鼠指標在影象座標系的座標(不是視窗座標系), flags是CV_EVENT_FLAG的組合, param是使用者定義的傳遞到cvSetMouseCallback函式呼叫的引數。
第三個引數,void*型別的userdata,使用者定義的傳遞到回撥函式的引數,有預設值0。
下面是一段綜合的程式碼案例:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
//-----------------------------------【名稱空間宣告部分】---------------------------------------
// 描述:包含程式所使用的名稱空間
//-----------------------------------------------------------------------------------------------
using namespace cv;
using namespace std;
//-----------------------------------【全域性變數宣告部分】--------------------------------------
// 描述:全域性變數宣告
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage, g_grayImage, g_maskImage;//定義原始圖、目標圖、灰度圖、掩模圖
int g_nFillMode = 1;//漫水填充的模式
int g_nLowDifference = 20, g_nUpDifference = 20;//負差最大值、正差最大值
int g_nConnectivity = 4;//表示floodFill函式識別符號低八位的連通值
int g_bIsColor = true;//是否為彩色圖的識別符號布林值
bool g_bUseMask = false;//是否顯示掩膜視窗的布林值
int g_nNewMaskVal = 255;//新的重新繪製的畫素值
//-----------------------------------【ShowHelpText( )函式】----------------------------------
// 描述:輸出一些幫助資訊
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//輸出一些幫助資訊
printf("\n\n\n\t歡迎來到漫水填充示例程式~\n\n");
printf( "\n\n\t按鍵操作說明: \n\n"
"\t\t滑鼠點選圖中區域- 進行漫水填充操作\n"
"\t\t鍵盤按鍵【ESC】- 退出程式\n"
"\t\t鍵盤按鍵【1】- 切換彩色圖/灰度圖模式\n"
"\t\t鍵盤按鍵【2】- 顯示/隱藏掩膜視窗\n"
"\t\t鍵盤按鍵【3】- 恢復原始影象\n"
"\t\t鍵盤按鍵【4】- 使用空範圍的漫水填充\n"
"\t\t鍵盤按鍵【5】- 使用漸變、固定範圍的漫水填充\n"
"\t\t鍵盤按鍵【6】- 使用漸變、浮動範圍的漫水填充\n"
"\t\t鍵盤按鍵【7】- 操作標誌符的低八位使用4位的連線模式\n"
"\t\t鍵盤按鍵【8】- 操作標誌符的低八位使用8位的連線模式\n"
"\n\n\t\t\t\t\t\t\t\t by淺墨\n\n\n"
);
}
//-----------------------------------【onMouse( )函式】--------------------------------------
// 描述:滑鼠訊息onMouse回撥函式
//---------------------------------------------------------------------------------------------
static void onMouse( int event, int x, int y, int, void* )
{
// 若滑鼠左鍵沒有按下,便返回
if( event != CV_EVENT_LBUTTONDOWN )
return;
//-------------------【<1>呼叫floodFill函式之前的引數準備部分】---------------
Point seed = Point(x,y);
int LowDifference = g_nFillMode == 0 ? 0 : g_nLowDifference;//空範圍的漫水填充,此值設為0,否則設為全域性的g_nLowDifference
int UpDifference = g_nFillMode == 0 ? 0 : g_nUpDifference;//空範圍的漫水填充,此值設為0,否則設為全域性的g_nUpDifference
int flags = g_nConnectivity + (g_nNewMaskVal << 8) +
(g_nFillMode == 1 ? CV_FLOODFILL_FIXED_RANGE : 0);//識別符號的0~7位為g_nConnectivity,8~15位為g_nNewMaskVal左移8位的值,16~23位為CV_FLOODFILL_FIXED_RANGE或者0。
//隨機生成bgr值
int b = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
int g = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
int r = (unsigned)theRNG() & 255;//隨機返回一個0~255之間的值
Rect ccomp;//定義重繪區域的最小邊界矩形區域
Scalar newVal = g_bIsColor ? Scalar(b, g, r) : Scalar(r*0.299 + g*0.587 + b*0.114);//在重繪區域畫素的新值,若是彩色圖模式,取Scalar(b, g, r);若是灰度圖模式,取Scalar(r*0.299 + g*0.587 + b*0.114)
Mat dst = g_bIsColor ? g_dstImage : g_grayImage;//目標圖的賦值
int area;
//--------------------【<2>正式呼叫floodFill函式】-----------------------------
if( g_bUseMask )
{
threshold(g_maskImage, g_maskImage, 1, 128, CV_THRESH_BINARY);
area = floodFill(dst, g_maskImage, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
Scalar(UpDifference, UpDifference, UpDifference), flags);
imshow( "mask", g_maskImage );
}
else
{
area = floodFill(dst, seed, newVal, &ccomp, Scalar(LowDifference, LowDifference, LowDifference),
Scalar(UpDifference, UpDifference, UpDifference), flags);
}
imshow("效果圖", dst);
cout << area << " 個畫素被重繪\n";
}
//-----------------------------------【main( )函式】--------------------------------------------
// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
//改變console字型顏色
system("color 2F");
//載入原圖
g_srcImage = imread("1.jpg", 1);
if( !g_srcImage.data ) { printf("Oh,no,讀取圖片image0錯誤~! \n"); return false; }
//顯示幫助文字
ShowHelpText();
g_srcImage.copyTo(g_dstImage);//拷貝源圖到目標圖
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);//轉換三通道的image0到灰度圖
g_maskImage.create(g_srcImage.rows+2, g_srcImage.cols+2, CV_8UC1);//利用image0的尺寸來初始化掩膜mask
namedWindow( "效果圖",CV_WINDOW_AUTOSIZE );
//建立Trackbar
createTrackbar( "負差最大值", "效果圖", &g_nLowDifference, 255, 0 );
createTrackbar( "正差最大值" ,"效果圖", &g_nUpDifference, 255, 0 );
//滑鼠回撥函式
setMouseCallback( "效果圖", onMouse, 0 );
//迴圈輪詢按鍵
while(1)
{
//先顯示效果圖
imshow("效果圖", g_bIsColor ? g_dstImage : g_grayImage);
//獲取鍵盤按鍵
int c = waitKey(0);
//判斷ESC是否按下,若按下便退出
if( (c & 255) == 27 )
{
cout << "程式退出...........\n";
break;
}
//根據按鍵的不同,進行各種操作
switch( (char)c )
{
//如果鍵盤“1”被按下,效果圖在在灰度圖,彩色圖之間互換
case '1':
if( g_bIsColor )//若原來為彩色,轉為灰度圖,並且將掩膜mask所有元素設定為0
{
cout << "鍵盤“1”被按下,切換彩色/灰度模式,當前操作為將【彩色模式】切換為【灰度模式】\n";
cvtColor(g_srcImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0); //將mask所有元素設定為0
g_bIsColor = false; //將識別符號置為false,表示當前影象不為彩色,而是灰度
}
else//若原來為灰度圖,便將原來的彩圖image0再次拷貝給image,並且將掩膜mask所有元素設定為0
{
cout << "鍵盤“1”被按下,切換彩色/灰度模式,當前操作為將【彩色模式】切換為【灰度模式】\n";
g_srcImage.copyTo(g_dstImage);
g_maskImage = Scalar::all(0);
g_bIsColor = true;//將識別符號置為true,表示當前影象模式為彩色
}
break;
//如果鍵盤按鍵“2”被按下,顯示/隱藏掩膜視窗
case '2':
if( g_bUseMask )
{
destroyWindow( "mask" );
g_bUseMask = false;
}
else
{
namedWindow( "mask", 0 );
g_maskImage = Scalar::all(0);
imshow("mask", g_maskImage);
g_bUseMask = true;
}
break;
//如果鍵盤按鍵“3”被按下,恢復原始影象
case '3':
cout << "按鍵“3”被按下,恢復原始影象\n";
g_srcImage.copyTo(g_dstImage);
cvtColor(g_dstImage, g_grayImage, COLOR_BGR2GRAY);
g_maskImage = Scalar::all(0);
break;
//如果鍵盤按鍵“4”被按下,使用空範圍的漫水填充
case '4':
cout << "按鍵“4”被按下,使用空範圍的漫水填充\n";
g_nFillMode = 0;
break;
//如果鍵盤按鍵“5”被按下,使用漸變、固定範圍的漫水填充
case '5':
cout << "按鍵“5”被按下,使用漸變、固定範圍的漫水填充\n";
g_nFillMode = 1;
break;
//如果鍵盤按鍵“6”被按下,使用漸變、浮動範圍的漫水填充
case '6':
cout << "按鍵“6”被按下,使用漸變、浮動範圍的漫水填充\n";
g_nFillMode = 2;
break;
//如果鍵盤按鍵“7”被按下,操作標誌符的低八位使用4位的連線模式
case '7':
cout << "按鍵“7”被按下,操作標誌符的低八位使用4位的連線模式\n";
g_nConnectivity = 4;
break;
//如果鍵盤按鍵“8”被按下,操作標誌符的低八位使用8位的連線模式
case '8':
cout << "按鍵“8”被按下,操作標誌符的低八位使用8位的連線模式\n";
g_nConnectivity = 8;
break;
}
}
return 0;
}
2、影象金字塔與影象尺寸的縮放
OpenCV為我們提供的如下兩種放大或者縮小圖片的尺寸的方式:
<1>resize函式。這是最直接的方式,
<2>pyrUp( )、pyrDown( )函式。即影象金字塔相關的兩個函式,對影象進行向上取樣,向下取樣的操作。
pyrUp、pyrDown其實和專門用作放大縮小影象尺寸的resize在功能上差不多,對影象進行放大和縮小操作。pyrUp、pyrDown在OpenCV的imgproc模組中的Image Filtering子模組裡。而resize在imgproc 模組的Geometric Image Transformations子模組裡。
像金字塔是影象中多尺度表達的一種,最主要用於影象的分割,是一種以多解析度來解釋影象的有效但概念簡單的結構。
一幅影象的金字塔是一系列以金字塔形狀排列的解析度逐步降低,且來源於同一張原始圖的影象集合。
金字塔的底部是待處理影象的高解析度表示,而頂部是低解析度的近似。
我們將一層一層的影象比喻成金字塔,層級越高,則影象越小,解析度越低。
一般情況下有兩種型別的影象金字塔常常出現在文獻和以及實際運用中。他們分別是:
高斯金字塔(Gaussianpyramid): 用來向下取樣,主要的影象金字塔
拉普拉斯金字塔(Laplacianpyramid): 用來從金字塔低層影象重建上層未取樣影象,在數字影象處理中也即是預測殘差,可以對影象進行最大程度的還原,配合高斯金字塔一起使用。
兩者的簡要區別:高斯金字塔用來向下降取樣影象,而拉普拉斯金字塔則用來從金字塔底層影象中向上取樣重建一個影象。
要從金字塔第i層生成第i+1層(我們表示第i+1層為G_i+1),我們先要用高斯核對G_1進行卷積,然後刪除所有偶數行和偶數列。當然的是,新得到影象面積會變為源影象的四分之一。按上述過程對輸入影象G_0執行操作就可產生出整個金字塔。
對影象向上取樣:pyrUp函式
對影象向下取樣:pyrDown函式(和金字塔方向相反)
這裡的向下與向上取樣,是對影象的尺寸而言的(和金字塔的方向相反),向上就是影象尺寸加倍,向下就是影象尺寸減半
對影象的向下取樣:
為了獲取層級為 G_i+1 的金字塔影象,我們採用如下方法:
<1>對影象G_i進行高斯核心卷積
<2>將所有偶數行和列去除
對影象的向上取樣
想放大影象,則需要通過向上取樣操作得到,具體做法如下:
<1>將影象在每個方向擴大為原來的兩倍,新增的行和列以0填充
<2>使用先前同樣的核心(乘以4)與放大後的影象卷積,獲得 “新增畫素”的近似值
resize( )函式
resize( )為OpenCV中專門調整影象大小的函式。
函式原型:
void resize(InputArray src,OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
第一個引數,InputArray型別的src,輸入影象,即源影象,填Mat類的物件即可。
第二個引數,OutputArray型別的dst,輸出影象,當其非零時,有著dsize(第三個引數)的尺寸,或者由src.size()計算出來。
第三個引數,Size型別的dsize,輸出影象的大小;如果它等於零,由下式進行計算:
其中,dsize,fx,fy都不能為0。
第四個引數,double型別的fx,沿水平軸的縮放係數,有預設值0,且當其等於0時,由下式進行計算:
第五個引數,double型別的fy,沿垂直軸的縮放係數,有預設值0,且當其等於0時,由下式進行計算:
第六個引數,int型別的interpolation,用於指定插值方式,預設為INTER_LINEAR(線性插值)。
可選的插值方式如下:
INTER_NEAREST - 最近鄰插值
INTER_LINEAR - 線性插值(預設值)
INTER_AREA - 區域插值(利用畫素區域關係的重取樣插值)
INTER_CUBIC –三次樣條插值(超過4×4畫素鄰域內的雙三次插值)
INTER_LANCZOS4 -Lanczos插值(超過8×8畫素鄰域的Lanczos插值)
若要縮小影象,一般情況下最好用CV_INTER_AREA來插值,
而若要放大影象,一般情況下最好用CV_INTER_CUBIC(效率不高,慢,不推薦使用)或CV_INTER_LINEAR(效率較高,速度較快,推薦使用)。
接著我們來看兩種resize的呼叫範例。
方式一,呼叫範例:
Mat dst=Mat::zeros(512 ,512, CV_8UC3 );//新建一張512x512尺寸的圖片
Mat src=imread(“1.jpg”);
//顯式指定dsize=dst.size(),那麼fx和fy會其計算出來,不用額外指定。
resize(src, dst, dst.size());
方式二、呼叫範例:
Mat dst;
Mat src=imread(“1.jpg”)
//指定fx和fy,讓函式計算出目標影象的大小。
resize(src, dst, Size(), 0.5, 0.5);
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【名稱空間宣告部分】---------------------------------------
// 描述:包含程式所使用的名稱空間
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函式】--------------------------------------------
// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//載入原始圖
Mat srcImage = imread("1.jpg"); //工程目錄下應該有一張名為1.jpg的素材圖
Mat tmpImage,dstImage1,dstImage2;//臨時變數和目標圖的定義
tmpImage=srcImage;//將原始圖賦給臨時變數
//顯示原始圖
imshow("【原始圖】", srcImage);
//進行尺寸調整操作
resize(tmpImage,dstImage1,Size( tmpImage.cols/2, tmpImage.rows/2 ),(0,0),(0,0),3);
resize(tmpImage,dstImage2,Size( tmpImage.cols*2, tmpImage.rows*2 ),(0,0),(0,0),3);
//顯示效果圖
imshow("【效果圖】之一", dstImage1);
imshow("【效果圖】之二", dstImage2);
waitKey(0);
return 0;
}
pyrUp()函式剖析
pyrUp( )函式的作用是向上取樣並模糊一張影象,說白了就是放大一張圖片。
void pyrUp(InputArray src, OutputArraydst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT )
第一個引數,InputArray型別的src,輸入影象,即源影象,填Mat類的物件即可。
第二個引數,OutputArray型別的dst,輸出影象,和源圖片有一樣的尺寸和型別。
第三個引數,const Size&型別的dstsize,輸出影象的大小;有預設值Size(),即預設情況下,由Size(src.cols*2,src.rows*2)來進行計算,且一直需要滿足下列條件:
第四個引數,int型別的borderType,又來了,邊界模式,一般我們不用去管它。
pyrUp函式執行高斯金字塔的取樣操作,其實它也可以用於拉普拉斯金字塔的。
首先,它通過插入可為零的行與列,對源影象進行向上取樣操作,然後將結果與pyrDown()乘以4的核心做卷積,就是這樣。
直接看完整的示例程式:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【名稱空間宣告部分】---------------------------------------
// 描述:包含程式所使用的名稱空間
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函式】--------------------------------------------
// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//載入原始圖
Mat srcImage = imread("1.jpg"); //工程目錄下應該有一張名為1.jpg的素材圖
Mat tmpImage,dstImage;//臨時變數和目標圖的定義
tmpImage=srcImage;//將原始圖賦給臨時變數
//顯示原始圖
imshow("【原始圖】", srcImage);
//進行向上取樣操作
pyrUp( tmpImage, dstImage, Size( tmpImage.cols*2, tmpImage.rows*2 ) );
//顯示效果圖
imshow("【效果圖】", dstImage);
waitKey(0);
return 0;
}
pyrDown()函式剖析
pyrDown( )函式的作用是向下取樣並模糊一張圖片,說白了就是縮小一張圖片。
void pyrDown(InputArray src,OutputArray dst, const Size& dstsize=Size(), int borderType=BORDER_DEFAULT)
第一個引數,InputArray型別的src,輸入影象,即源影象,填Mat類的物件即可。
第二個引數,OutputArray型別的dst,輸出影象,和源圖片有一樣的尺寸和型別。
第三個引數,const Size&型別的dstsize,輸出影象的大小;有預設值Size(),即預設情況下,由Size Size((src.cols+1)/2, (src.rows+1)/2)來進行計算,且一直需要滿足下列條件:
該pyrDown函式執行了高斯金字塔建造的向下取樣的步驟。首先,它將源影象與如下核心做卷積運算:
接著,它便通過對影象的偶數行和列做插值來進行向下取樣操作。
依然是看看完整的示例程式:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【名稱空間宣告部分】---------------------------------------
// 描述:包含程式所使用的名稱空間
//-----------------------------------------------------------------------------------------------
using namespace cv;
//-----------------------------------【main( )函式】--------------------------------------------
// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//載入原始圖
Mat srcImage = imread("1.jpg"); //工程目錄下應該有一張名為1.jpg的素材圖
Mat tmpImage,dstImage;//臨時變數和目標圖的定義
tmpImage=srcImage;//將原始圖賦給臨時變數
//顯示原始圖
imshow("【原始圖】", srcImage);
//進行向下取樣操作
pyrDown( tmpImage, dstImage, Size( tmpImage.cols/2, tmpImage.rows/2 ) );
//顯示效果圖
imshow("【效果圖】", dstImage);
waitKey(0);
return 0;
}
綜合示例
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
//-----------------------------------【巨集定義部分】--------------------------------------------
// 描述:定義一些輔助巨集
//------------------------------------------------------------------------------------------------
#define WINDOW_NAME "【程式視窗】" //為視窗標題定義的巨集
//-----------------------------------【名稱空間宣告部分】--------------------------------------
// 描述:包含程式所使用的名稱空間
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;
//-----------------------------------【全域性變數宣告部分】--------------------------------------
// 描述:全域性變數宣告
//-----------------------------------------------------------------------------------------------
Mat g_srcImage, g_dstImage, g_tmpImage;
//-----------------------------------【全域性函式宣告部分】--------------------------------------
// 描述:全域性函式宣告
//-----------------------------------------------------------------------------------------------
static void ShowHelpText();
//-----------------------------------【ShowHelpText( )函式】----------------------------------
// 描述:輸出一些幫助資訊
//----------------------------------------------------------------------------------------------
static void ShowHelpText()
{
//輸出一些幫助資訊
printf("\n\n\n\t歡迎來到OpenCV影象金字塔和resize示例程式~\n\n");
printf( "\n\n\t按鍵操作說明: \n\n"
"\t\t鍵盤按鍵【ESC】或者【Q】- 退出程式\n"
"\t\t鍵盤按鍵【1】或者【W】- 進行基於【resize】函式的圖片放大\n"
"\t\t鍵盤按鍵【2】或者【S】- 進行基於【resize】函式的圖片縮小\n"
"\t\t鍵盤按鍵【3】或者【A】- 進行基於【pyrUp】函式的圖片放大\n"
"\t\t鍵盤按鍵【4】或者【D】- 進行基於【pyrDown】函式的圖片縮小\n"
"\n\n\t\t\t\t\t\t\t\t by淺墨\n\n\n"
);
}
//-----------------------------------【main( )函式】--------------------------------------------
// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始
//-----------------------------------------------------------------------------------------------
int main( )
{
//改變console字型顏色
system("color 2F");
//顯示幫助文字
ShowHelpText();
//載入原圖
g_srcImage = imread("1.jpg");//工程目錄下需要有一張名為1.jpg的測試影象,且其尺寸需被2的N次方整除,N為可以縮放的次數
if( !g_srcImage.data ) { printf("Oh,no,讀取srcImage錯誤~! \n"); return false; }
// 建立顯示視窗
namedWindow( WINDOW_NAME, CV_WINDOW_AUTOSIZE );
imshow(WINDOW_NAME, g_srcImage);
//引數賦值
g_tmpImage = g_srcImage;
g_dstImage = g_tmpImage;
int key =0;
//輪詢獲取按鍵資訊
while(1)
{
key=waitKey(9) ;//讀取鍵值到key變數中
//根據key變數的值,進行不同的操作
switch(key)
{
//======================【程式退出相關鍵值處理】=======================
case 27://按鍵ESC
return 0;
break;
case 'q'://按鍵Q
return 0;
break;
//======================【圖片放大相關鍵值處理】=======================
case 'a'://按鍵A按下,呼叫pyrUp函式
pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ) );
printf( ">檢測到按鍵【A】被按下,開始進行基於【pyrUp】函式的圖片放大:圖片尺寸×2 \n" );
break;
case 'w'://按鍵W按下,呼叫resize函式
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">檢測到按鍵【W】被按下,開始進行基於【resize】函式的圖片放大:圖片尺寸×2 \n" );
break;
case '1'://按鍵1按下,呼叫resize函式
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">檢測到按鍵【1】被按下,開始進行基於【resize】函式的圖片放大:圖片尺寸×2 \n" );
break;
case '3': //按鍵3按下,呼叫pyrUp函式
pyrUp( g_tmpImage, g_dstImage, Size( g_tmpImage.cols*2, g_tmpImage.rows*2 ));
printf( ">檢測到按鍵【3】被按下,開始進行基於【pyrUp】函式的圖片放大:圖片尺寸×2 \n" );
break;
//======================【圖片縮小相關鍵值處理】=======================
case 'd': //按鍵D按下,呼叫pyrDown函式
pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
printf( ">檢測到按鍵【D】被按下,開始進行基於【pyrDown】函式的圖片縮小:圖片尺寸/2\n" );
break;
case 's' : //按鍵S按下,呼叫resize函式
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ));
printf( ">檢測到按鍵【S】被按下,開始進行基於【resize】函式的圖片縮小:圖片尺寸/2\n" );
break;
case '2'://按鍵2按下,呼叫resize函式
resize(g_tmpImage,g_dstImage,Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ),(0,0),(0,0),2);
printf( ">檢測到按鍵【2】被按下,開始進行基於【resize】函式的圖片縮小:圖片尺寸/2\n" );
break;
case '4': //按鍵4按下,呼叫pyrDown函式
pyrDown( g_tmpImage, g_dstImage, Size( g_tmpImage.cols/2, g_tmpImage.rows/2 ) );
printf( ">檢測到按鍵【4】被按下,開始進行基於【pyrDown】函式的圖片縮小:圖片尺寸/2\n" );
break;
}
//經過操作後,顯示變化後的圖
imshow( WINDOW_NAME, g_dstImage );
//將g_dstImage賦給g_tmpImage,方便下一次迴圈
g_tmpImage = g_dstImage;
}
return 0;
}
3、閾值化
閾值被視作最簡單的影象分割方法。主要是基於影象中物體和場景的灰度差異;
注意:閾值的選取依賴於具體的問題。即物體在不同的影象中有可能會有不同的灰度值;
opencv中:Threshold( )函式和adaptiveThreshold函式能完成這樣的要求。
基本思想:給定一個數組和閾值,然後根據陣列中的每個元素的值是高於還是低於閾值而進行一些處理。
3.1 給定閾值操作:Threshold( )函式
原型:
double threshold(InputArray src,OutputArray dst,double thresh,double maxval,int type)
引數說明:double型別的thresh,是閾值的具體值;double型別的maxval,當第五個引數閾值型別type取CV_THRESH_BINARY或者CV_THRESH_BINARY_INV時閾值型別的最大值;int型別的type,閾值型別。具體的方法:
1)CV_THRESH_BINARY:
如果 src(x,y)>threshold ,dst(x,y) = max_value; 否則,dst(x,y)=0;
2)CV_THRESH_BINARY_INV:
如果 src(x,y)>threshold,dst(x,y) = 0; 否則,dst(x,y) = max_value.
3)CV_THRESH_TRUNC:
如果 src(x,y)>threshold,dst(x,y) = max_value; 否則dst(x,y) = src(x,y).
4)CV_THRESH_TOZERO:
如果src(x,y)>threshold,dst(x,y) = src(x,y) ; 否則 dst(x,y) = 0。
5)CV_THRESH_TOZERO_INV:
如果 src(x,y)>threshold,dst(x,y) = 0 ; 否則dst(x,y) = src(x,y).
3.2自適應閾值操作:adaptiveThreshold( )函式
函式原型:
void adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod,int thresholdType, int blockSize, double C)
引數說明:
第四個引數,int型別的adaptiveMethod,用於指定要使用的自適應閾值演算法,可取值為ADAPTIVE_THRESH_MEAN或者ADAPTIVE_THRESH_GAUSSIAN_C。
第五個引數:int型別的thresholdType,閾值型別。取值必須為THRESH_BINARY、THRESH_BINARY_INV其中之一。
第六個引數:int型別的blockSize,用於計算閾值大小的一個畫素的領域尺寸,取值為3、5、7。
第七個引數:double型別的C,減去平均或者加權平均值後的常數值。通常其為正數,少數情況下為零和負數。
綜合案例:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//-----------------------------------【巨集定義部分】--------------------------------------------
// 描述:定義一些輔助巨集
//------------------------------------------------------------------------------------------------
#define WINDOW_NAME "【程式視窗】" //為視窗標題定義的巨集
//-----------------------------------【全域性變數宣告部分】--------------------------------------
// 描述:全域性變數的宣告
//-----------------------------------------------------------------------------------------------
int g_nThresholdValue = 100;
int g_nThresholdType = 3;
Mat g_srcImage, g_grayImage, g_dstImage;
//-----------------------------------【全域性函式宣告部分】--------------------------------------
// 描述:全域性函式的宣告
//-----------------------------------------------------------------------------------------------
static void ShowHelpText( );//輸出幫助文字
void on_Threshold( int, void* );//回撥函式
//-----------------------------------【main( )函式】--------------------------------------------
// 描述:控制檯應用程式的入口函式,我們的程式從這裡開始執行
//-----------------------------------------------------------------------------------------------
int main( )
{
//【1】讀入源圖片
g_srcImage = imread("1.jpg");
if(!g_srcImage.data ) { printf("讀取圖片錯誤,請確定目錄下是否有imread函式指定的圖片存在~! \n"); return false; }
imshow("原始圖",g_srcImage);
//【2】存留一份原圖的灰度圖
cvtColor( g_srcImage, g_grayImage, COLOR_RGB2GRAY );
//【3】建立視窗並顯示原始圖
namedWindow( WINDOW_NAME, WINDOW_AUTOSIZE );
//【4】建立滑動條來控制閾值
createTrackbar( "模式",
WINDOW_NAME, &g_nThresholdType,
4, on_Threshold );
createTrackbar( "引數值",
WINDOW_NAME, &g_nThresholdValue,
255, on_Threshold );
//【5】初始化自定義的閾值回撥函式
on_Threshold( 0, 0 );
// 【6】輪詢等待使用者按鍵,如果ESC鍵按下則退出程式
while(1)
{
int key;
key = waitKey( 20 );
if( (char)key == 27 ){ break; }
}
}
//-----------------------------------【on_Threshold( )函式】------------------------------------
// 描述:自定義的閾值回撥函式
//-----------------------------------------------------------------------------------------------
void on_Threshold( int, void* )
{
//呼叫閾值函式
threshold(g_grayImage,g_dstImage,g_nThresholdValue,255,g_nThresholdType);
//更新效果圖
imshow( WINDOW_NAME, g_dstImage );
}