本博客所用OpenCV版本爲2.4.3,運行環境爲Visual Studio2012。
學習OpenCV是一個比較漫長的過程,希望我能夠堅持!
(一)從Mat講起
Mat是OpenCV中用於存放圖像的數據結構。我們知道,圖像在計算機中是以數組的形式存放的。Mat正是描述的這樣一種數據結構。通過調用相關方法,我們能夠實現對圖像的輸入輸出以及一些操作。同時,Mat又不止可以作爲圖像容器,它也可以作爲一種比較純粹的描述矩陣這種數學對象的結構。它比C中的IplImage好的地方在於,由於它的“計數器”機制,我們不需要對它進行手動的內存回收,從而避免了常常困擾C/C++程序員的“內存泄露”問題。
讓我們先來看看如何使用Mat來進行圖像的輸入輸出。能夠看到程序按照自己所想輸出圖片還是一件比較令人高興的事情吧!
下面我將直接貼出代碼,再進行解釋。
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
//#include <opencv/cv.hpp>
using namespace std;
using namespace cv;
int main()
{
string namePic="pic.jpg";
//Part 1 Read and Load Image
/************************************************************************/
/* 下面的代碼顯示瞭如何用imread函數進行圖像讀取操作 */
/************************************************************************/
Mat Img_Color=imread (namePic,CV_LOAD_IMAGE_COLOR); //生成了彩色圖
Mat Img_Gray=imread (namePic,CV_LOAD_IMAGE_GRAYSCALE); //生成了灰度圖
/************************************************************************/
/* 下面的代碼顯示瞭如何用imshow函數進行圖像的顯示 */
/************************************************************************/
imshow ("彩色小女孩",Img_Color);
imshow ("灰度小女孩",Img_Gray);
waitKey();
return 0;
}
現在看一下程序運行結果吧:
下面我們來解釋一下代碼:
頭文件包含中除了C++標準庫iostream以外,我們還使用到了highgui.cpp和core.cpp。其中前者包含了對圖像進行IO操作要用到的一些類和宏的定義,後者包含了一些核心內容。
代碼中圖像的讀入操作是使用函數imread()實現的。imread()函數的原型如下:
Mat imread(const string& filename, int flags=1 )
可以看到,返回值是一個Mat對象,第一個參數爲字符串型,表示源圖像的名稱(如果不在源文件目錄下,應該寫成絕對路徑),第二個參數決定了讀入圖像的顏色格式。上例代碼中所出現的CV_LOAD_IMAGE_COLOR和CV_LOAD_IMAGE_GRAYSCALE分別表示讀入彩色圖像和灰度圖像。
代碼中圖像的輸出操作使用了函數imshow()實現。imshow()函數的原型如下:
void imshow(const string& winname, InputArray mat)
可以看到,第一個參數爲字符串型,定義了窗口的名稱,第二個參數爲Mat,表明要顯示的圖片。
如果用過MATLAB,就會發現這兩個函數和MATLAB中的圖像輸入輸出函數名是完全一樣的。而在閱讀OpenCV 的tutorial文檔時,也會發現不少與MATLAB相似的地方,比如在OpenCV中產生單位陣就可以調用Mat::eye()實現,是不是和MATLAB很相似呢??
至於代碼最後的WaitKey函數,是爲了防止代碼運行結果一閃而過而加上去的。函數原型如下:
int waitKey(int delay=0)
參數代表程序運行至此停留的毫秒數,缺省值爲0.代表forever。但是要注意,這裏的延時並不是很準確,而是根據系統當前運行的任務多少而有變化,只是說至少會延遲delay毫秒時間。
Mat的數據結構包含了兩部分數據,一部分是矩陣頭,表明了矩陣的基本信息。另一部分就是用於存儲圖像的矩陣數組了。由於圖像一般都很很大,所以對矩陣數組這部分內存進行頻繁的讀寫複製操作是很費時間的。爲了克服這個問題,OpenCV允許兩個Mat對象有不同的矩陣頭,卻包含同一塊矩陣數據。這樣,在複製操作的時候,就避開了對大塊內存空間的讀寫,節省了時間。比如下面的幾種賦值方式,它們都只是複製了Mat A的矩陣頭,同時複製了指向圖像矩陣的內存的指針,但是卻沒有複製矩陣:
Mat C=A; //利用等號
Mat E(A); //利用Mat的構造函數
如果想要用矩形截取圖像的一部分呢??我們可以使用如下的構造函數:
Mat B=Mat(A,Rect(0,0,100,100)); //截取A的100*100的矩形區域
那麼如果我們處於某種需要,確實要複製矩陣呢??我們可以使用Mat類實例提供的clone方法和copyto方法。如下所示:
Mat G;
A.copyto(G);
Mat H=A.clone();
當然了,我們剛開始介紹Mat的時候就已經說過,Mat不僅可以視爲一個圖像的容器,還可以作爲一個矩陣這樣的數學對象。下面我們就來看一下如何生成這樣的Mat對象實例。
首先,我們可以使用Mat的構造函數,通過查閱文檔可以知道,Mat的構造函數可謂是多種多樣,下面舉的例子只是其中之一,更多內容還是應該訪問網頁http://docs.opencv.org/modules/core/doc/basic_structures.html#Mat來獲得。
Mat B(20,20,CV_8UC1,Scalar(1));
這樣,就建立了一個20*20,並且矩陣全部元素都是1的矩陣。
其次,我們可以使用Create函數。如下所示:
Mat B;
B.create (10,10,CV_8UC3);
不過要注意的是,這樣方法不能同時對B矩陣元素進行初始化操作,就像這個樣子:
或者,我們可以使用MATLAB風格的代碼進行賦值:
Mat E = Mat::eye(4, 4, CV_64F); //生成單位陣
Mat E=Mat::ones(2, 2, CV_32F); //生成全1陣
Mat E=Mat::zeros(2, 2, CV_8UC1);//生成0矩陣
獲取某個矩陣的一行或者一列可以採取下面的方式:Mat RowClone = C.row(1).clone();
關於Mat這種數據結構的簡單介紹就到這裏,更多的還是應該去查閱相關手冊文檔,才能得到更加完整的認識。