一、從文件讀取圖像並顯示
1. 程序
在基於VS2013搭建OpenCV開發環境這篇文章的最後給出了一個簡單的Demo,這個例子跟本篇使用的例子是一樣的。打開C++ IDE並創建一個新的項目,新建一個源文件,粘貼下面的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include
<opencv2\highgui\highgui.hpp> #include
<iostream> using
namespace
cv; using
namespace
std; int
main( int
argc, const
char **
argv) { Mat
img = imread( "earth.jpg" ,
CV_LOAD_IMAGE_UNCHANGED); if
(img.empty()) { cout
<< "圖像加載失敗!"
<< endl; //system("pause"); return
-1; } //創建一個名字爲MyWindow的窗口 namedWindow( "MyWindow" ,
CV_WINDOW_AUTOSIZE); //在MyWindow的窗中中顯示存儲在img中的圖片 imshow( "MyWindow" ,
img); //等待直到有鍵按下 waitKey(0); //銷燬MyWindow的窗口 destroyWindow( "MyWindow" ); return
0; } |
在運行程序之前,將圖片文件(earth.jpg)放到C++文件所在的目錄。運行程序,如下圖所示:
2. 解釋
下面我來解釋一下這個程序。
1
|
#include
<opencv2\highgui\highgui.hpp> |
imread(), namedWindow(), imshow() 和 waitKey() 函數都聲明在這個頭文件中,所以筆記得包含它。
上面的程序中還是用了Mat數據結構,它在”opencv2/core/core.hpp”中聲明的,那爲什麼沒有包含它呢?這是因爲在”opencv2/highgui/highgui.hpp”頭文件中已經包含了core.hpp頭文件,所以不用在我們的程序再次包含了。
1
|
using
namespace
cv; |
“opencv2/core/core.hpp” 和 “opencv2/highgui/highgui.hpp中所有的數據結構和函數都聲明在cv命名空間,所以,必須在我們程序的頭部使用它,否則就要在每個OpenCV的函數和數據結構前面都要加上”cv::”(例如:cv::Mat,cv::imread()等等)。
1
|
Mat
img = imread( "earth.jpg" ,
CV_LOAD_IMAGE_UNCHANGED); |
Mat是在矩陣中存儲圖片的數據結構,它聲明在 “opencv2/core/core.hpp”頭文件中。
imread()是聲明在 “opencv2/highgui/highgui.hpp”的函數,它從文件加載一個圖片並存儲在Mat數據結構中。
imread()函數的聲明如下:
1
|
CV_EXPORTS_W
Mat imread( const
string& filename, int
flags=1 ); |
它的參數:
- filename —— 文件的位置。如果只提供文件名,那麼文件應該和C++文件在同一目錄,否則必須提供圖片的全路徑。
- flags —— 有5個可能的輸入。
- CV_LOAD_IMAGE_UNCHANGED – 在每個通道中,每個像素的位深爲8 bit,通道數(顏色)保持不變。
- CV_LOAD_IMAGE_GRAYSCALE – 位深=8 bit 通道數=1(顏色變灰)
- CV_LOAD_IMAGE_COLOR -位深=?, 通道數=3
- CV_LOAD_IMAGE_ANYDEPTH – 位深不變 ,通道數=?
- CV_LOAD_IMAGE_ANYCOLOR – 位深=?, 通道數不變
上面的值還可以組合使用,比如:
CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR – 位深不變,通道數比便
CV_LOAD_IMAGE_COLOR | CV_LOAD_IMAGE_ANYDEPTH – 位深不變,通道數=3
如果你不確定使用哪個,就是用CV_LOAD_IMAGE_COLOR 。
要理解位深和通道的概念,應該熟悉圖像處理的理論知識,所以下面討論一點這方面的內容。
所有的數字圖像都是由像素組成的,所有的像素都有值。一個像素的最小值爲0,表示黑色。像素的值變大,它的亮度也會增強。每個像素分配的比特的固定數值是255(十進制),也就是說每個像素分配8個bit。所以一個像素的最大值爲255(二進制爲11111111)。
那麼什麼是位深呢?位深就是爲每個像素分配的比特。如果比特是8,每個像素的值可以是0-255。如果是4,每個像素的值可是0-15(二進制中爲1111)。
下面是一個8 bit位深的圖片的簡單模型。每個小矩形表示一個像素。所以每個矩形包含一個0-255的值。
這張圖像的一些屬性:
- 8 bit位深
- 一個通道(所以這是一個灰度圖像)
- 高爲4px
- 寬爲5px
- 分辨率爲4×5
這是一個灰度圖像(黑白圖像),因爲該圖像沒有顏色內容。像素的值越高,圖像就會越亮。像素值越低,圖像就會越暗。
下面是一個彩色圖像的簡單模型。彩色圖像至少包含3個平面:Red,Green和Blue。使用這3種顏色的特定組合可以創建任何顏色。所有的像素都是這3種顏色值的組合。(255,0,0)表示pure red。(0,255,0)表示pure green。(255,0,255)表示pure violate。它的位深爲24,因爲每個像素爲8×3 bit (每個通道8 bit)。
這張圖像的一些屬性:
- 位深24 bit
- 3個通道(所以是彩色圖像)
- 高4px
- 寬5px
- 分辨率爲4×5
上面的模型,左上角的像素是(23,231,46)。它會顯示爲呈綠色的顏色,因爲green值(231)比red(23)和blue(46)都大。
1
|
if
(img.empty()) |
如果imread()函數加載圖像失敗,’img’不會加載任何數據,因此,img.empty()應該返回true。檢查是否成功加載,如果沒有則退出程序是一個好的做法,否則當調用imshow()函數時,程序就會崩潰。
1
|
bool
Mat::empty() |
如果Mat::data==NULL或Mat::total()==0,這個函數返回true。
1
|
system ( "pause" ); |
如果使用Visual Studio,這行註釋的註釋最好取消,因爲它會暫停程序,知道用戶按下任意鍵。如果不取消註釋,程序會立即退出,用戶也就不會看到錯誤信息了。
1
|
void
namedWindow( const
string& winname, int
flags = WINDOW_AUTOSIZE); |
這個函數創建一個窗口。它的參數如下:
- winname——窗口的名字。這個名字會顯示在窗口的標題欄上。
- flags——決定窗口的尺寸。有如下選項:
- WINDOW_AUTOSIZE – 用戶不能改變圖像的尺寸,圖像顯示爲它的原有尺寸
- CV_WINDOW_NORMAL – 調整窗口圖像的尺寸可以改變
1
|
void
imshow( const
string& winname, InputArray mat); |
這個函數在指定名字的窗口中顯示存儲在mat中的圖像。如果窗口使用WINDOW_AUTOSIZE創建的,圖像會顯示爲它的原始尺寸,否則圖像會調整到窗口的尺寸大小。
它的參數:
- winname -窗口的名字。這個名字是namedWindow()函數創建窗口時使用的
- mat – 存儲圖像數據的Mat對象
1
|
int
waitKey( int
delay = 0) |
waitKey()函數通過指定delay(毫秒)等待按鍵的時間。如果delay是0或負數,它會永久等待。如果任意鍵被按下,這個函數就會返回按下鍵的ASCII值,程序繼續執行。如果指定的時間沒有按下鍵,它返回-1,程序繼續執行。
1
|
void
destroyWindow( const
string& winname) |
這個函數關閉名字爲winname的打開的窗口並釋放關聯的內存。這個函數對這個程序來說不是必須的,因爲當程序退出,操作系統通常會關閉所有打開的窗口並釋放關聯的內存。
3. 總結
當運行程序,圖像”earth.jpg”被加載到Mat類型的變量”img”。然後一個名字爲”MyWindow”的窗口打開,接着”img”被加載到窗口中。窗口和圖像一起顯示,直到按下任意鍵。
二、創建一個空圖像並顯示
這個程序和前一個非常像,唯一的不同就是這個程序創建了一個空圖像,而不是從文件中加載已存在的圖像。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include
<opencv2\highgui\highgui.hpp> #include
<iostream> using
namespace
cv; using
namespace
std; int
main( int
argc, const
char **
argv) { Mat
img(500, 1000, CV_8UC3, Scalar(0, 0, 100)); //創建一個圖像
( 3個通道, 8 bit位深, 高500, 寬1000, (0, 0, 100) 分別分配給 Blue, Green and Red. ) if
(img.empty()) { cout
<< "圖像不能加載!"
<< endl; //system("pause"); return
-1; } namedWindow( "MyWindow" ,
CV_WINDOW_AUTOSIZE); imshow( "MyWindow" ,
img); waitKey(0); destroyWindow( "MyWindow" ); return
0; } |
運行結果如下圖:
OpenCV的新函數
1
|
Mat::Mat( int
rows, int
cols, int
type, const
Scalar& s); |
這是Mat的一個構造函數。它使用Scalar對象給定的值初始化Mat對象。
它的參數:
- rows – 2維矩陣的行數 (圖像的高度像素)
- cols – 2維矩陣的列數 ( 圖像的寬度像素)
- type – 指定圖像的位深,數據類型和通道數。我提供 CV_8UC3 ,指定3個通道的8 bit無符號整數,下面是這個參數一些可能的輸入值:
- CV_8UC1 – 單通道8 bit無符號整數
- CV_8UC3 – 3通道8 bit爲無符號整數
- CV_64FC1 – 單通道64 bit 浮點數
如果想詳細瞭解這方面的內容,請參見OpenCV數據結構之Mat一文的“陣列的數據類型”部分。 - s – 使用s給定的值初始化矩陣的每個元素。在上面的程序中,給定Scalar(0,0,100),因此,它使用0初始化第一個通道(Blue),0初始化第二個通道(Green),100初始化第三個通道(Red)。所以,最終的圖像是red。
總結
在這個程序中,我創建了一個高500,寬1000,有3個通道的圖像。每個通道的每個像素分配8 bit的無符號整數(每個像素 8×3=24 bit),每個像素使用(0,0,100)指定值。這意味着,第一個通道總是0,第二個通道也總是0,第三個通道總是100,因此,最終看到的是一個red的圖像。