1. 程式人生 > >OpenCv學習筆記(二)—cv::Mat學習

OpenCv學習筆記(二)—cv::Mat學習

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

                由於在寫上一篇影象的資料結構時,發現自己只知道CvMat,竟然還有Mat資料結構,真是無知了,看了這麼多程式,貌似沒有看到這個結構。有可能那些程式都是些老版本的例子,這是在2.0以後加上的,所以我也得緊跟呀!以下是自己的學習心得。。。。

一、Mat簡介
    在2001年剛剛出現的時候,OpenCV基於 C 語言介面而建。為了在記憶體(memory)中存放影象,當時採用名為 IplImage 的C語言結構體,時至今日這仍出現在大多數的舊版教程和教學材料。但這種方法必須接受C語言所有的不足,這其中最大的不足要數手動記憶體管理,其依據是使用者要為開闢和銷燬記憶體負責。雖然對於小型的程式來說手動管理內

一、Mat簡介
    在2001年剛剛出現的時候,OpenCV基於 C 語言介面而建。為了在記憶體(memory)中存放影象,當時採用名為 IplImage 的C語言結構體,時至今日這仍出現在大多數的舊版教程和教學材料。但這種方法必須接受C語言所有的不足,這其中最大的不足要數手動記憶體管理,其依據是使用者要為開闢和銷燬記憶體負責。雖然對於小型的程式來說手動管理記憶體不是問題,但一旦程式碼開始變得越來越龐大,你需要越來越多地糾纏於這個問題,而不是著力解決你的開發目標。

   幸運的是,C++出現了,並且帶來類的概念,這給使用者帶來另外一個選擇:自動的記憶體管理(不嚴謹地說)。這是一個好訊息,如果C++完全相容C的話,這個變化不會帶來相容性問題。為此,OpenCV在2.0版本中引入了一個新的C++介面,利用自動記憶體管理給出瞭解決問題的新方法。使用這個方法,你不需要糾結在管理記憶體上,而且你的程式碼會變得簡潔(少寫多得)。但C++介面唯一的不足是當前許多嵌入式開發系統只支援C語言。所以,當目標不是這種開發平臺時,沒有必要使用舊 方法(除非你是自找麻煩的受虐狂碼農)。

   關於 Mat ,首先要知道的是你不必再手動地(1)為其開闢空間(2)在不需要時立即將空間釋放。但手動地做還是可以的:大多數OpenCV函式仍會手動地為輸出資料開闢空間。當傳遞一個已經存在的Mat 物件時,開闢好的矩陣空間會被重用。也就是說,我們每次都使用大小正好的記憶體來完成任務。

   基本上講 Mat 是一個類,由兩個資料部分組成:矩陣頭(包含矩陣尺寸,儲存方法,儲存地址等資訊)和一個指向儲存所有畫素值的矩陣(根據所選儲存方法的不同矩陣可以是不同的維數)的指標。矩陣頭的尺寸是常數值,但矩陣本身的尺寸會依影象的不同而不同,通常比矩陣頭的尺寸大數個數量級。因此,當在程式中傳遞影象並建立拷貝時,大的開銷是由矩陣造成的,而不是資訊頭。OpenCV是一個影象處理庫,囊括了大量的影象處理函式,為了解決問題通常要使用庫中的多個函式,因此在函式中傳遞影象是家常便飯。同時不要忘了我們正在討論的是計算量很大的影象處理演算法,因此,除非萬不得已,我們不應該拷貝大 的影象,因為這會降低程式速度。

二、Mat的基本操作

   這裡展示一個例子解釋一下Mat的基本操作
#include
<cv.h>
#include<highgui.h>#include<iostream>using namespace cv;using namespace std;int main(){/*********************************Mat基本操作-矩陣*******************************************/    //二維三通道矩陣建立    Mat M(2,2, CV_8UC3, Scalar(0,0,255)); //使用建構函式建立矩陣/*CV_8UC3 表示使用8位的 unsigned char 型,每個畫素由三個元素組成三通道,初始化為(0,0,255)*/
    cout << "M = " << endl << " " << M << endl << endl; //格式化輸出    //三維    int sz[3] = {3,3,3};     Mat L(3,sz, CV_8UC(1), Scalar::all(0));/*超過兩維的矩陣:指定維數,然後傳遞一個指向一個數組的指標,這個陣列包含每個維度的尺寸;其餘的相同*/    cout
<< "L = " << endl << " " << M << endl << endl; //格式化輸出/********************************************Mat基本操作-影象*******************************/   Mat A, C;      // 只建立資訊頭部分    A=imread("D:\\openCV\\openCVProject\\openCv筆記\\openCv筆記\\test.jpg", CV_LOAD_IMAGE_COLOR); // 這裡為矩陣開闢記憶體    Mat B(A);                // 使用拷貝建構函式    C = A;                  // 賦值運算子    /* 拷貝建構函式和賦值函式 只拷貝資訊頭和矩陣指標 */    Mat D (A, Rect(10, 10, 100, 100) ); //選取A中一個矩形區域,即只訪問其矩形區域的資訊頭,只是建立資訊頭 Mat E = A(cv::Range::all(), Range(1,3)); // 建立訪問邊界的資訊頭。    /*    要建立一個感興趣區域( ROI ),你只需要建立包含邊界資訊的資訊頭 */    Mat F = A.clone();//複製影象,包括資料    Mat G;    A.copyTo(G);    /* 拷貝矩陣本身(不只是資訊頭和矩陣指標), */ //測試 namedWindow( "a", CV_WINDOW_AUTOSIZE ); namedWindow( "c", CV_WINDOW_AUTOSIZE );  imshow( "a", D); imshow( "c", E );  /****************************************影象的讀取、處理和儲存**************************************/   Mat image;  image = imread( "D:\\openCV\\openCVProject\\openCv筆記\\openCv筆記\\test.jpg", CV_LOAD_IMAGE_COLOR);//匯入影象  if( !image.data )  {     cout<< " No image data \n " ;     return -1;  }  Mat gray_image;  cvtColor( image, gray_image, CV_BGR2GRAY );//轉化為灰度圖  imwrite( "../../images/Gray_Image.jpg", gray_image );//寫入影象  namedWindow( "source", CV_WINDOW_AUTOSIZE );  namedWindow( "Gray image", CV_WINDOW_AUTOSIZE );  imshow( "source", image );  imshow( "Gray image", gray_image );     /*******************************************************************************************/  waitKey(0);  return 0;}

 對於Mat資料結構,在對影象進行處理時要注意:

OpenCV函式中輸出影象的記憶體分配是自動完成的(如果不特別指定的話)。
使用OpenCV的C++介面時不需要考慮記憶體釋放問題。
賦值運算子和拷貝建構函式( ctor )只拷貝資訊頭。
使用函式 clone() 或者copyTo() 來拷貝一副影象的矩陣 

三、掃描影象的方法
#include<cv.h>#include<highgui.h>#include<time.h>#include<iostream>using namespace cv;using namespace std;int main(){    //Mat img(10,10,CV_8UC3,Scalar(0,0,255)); Mat img,img_gray,img_gray2; img=imread("D:\\openCV\\openCVProject\\openCv筆記\\openCv筆記\\test.jpg", CV_LOAD_IMAGE_COLOR);    cvtColor( img, img_gray, CV_BGR2GRAY );//轉化為灰度圖 img_gray.copyTo(img_gray2);    //方式一 for( int i=0;i<img_gray.rows;i++) {  uchar* data = img_gray.ptr<uchar>(i);  for(int j=0;j<img_gray.cols;j++)  {   data[j] = 255;   } } //img.create(10,10,CV_8UC3,Scalar(0,0,255)); //cout << "img = " << endl << " " << img_gray << endl << endl; //格式化輸出    //方式二 W*H的一幅影象看成是一個1*(w*h)的一個一維陣列 int nc; if(img_gray.isContinuous())//判斷是否被所有的畫素填滿 {  nc = img_gray.rows*img_gray.cols*img_gray.channels();  } else {  cout<<"畫素未填滿,不可用第二種方式"<<endl;  return -1; } uchar* data_2 = img_gray.ptr<uchar>(0);//提取第一個畫素點指標 for(int i=0;i<nc;i++)//遍歷所有的元素 {  data_2[i] = 255; }    //方式三指標掃描 uchar* data_3 = img.data;//單個元素 img.at<uchar>(0,0)=0for(int i=0;i<img.rows;i++)//遍歷所有的元素 {  for(int j=0;j<img.cols;j++)  {             data_3 = img.data + i*img.step + j * img.elemSize();     //對各個通道賦值    data_3[0]=100;             data_3[1]=100;    data_3[2]=100;  } } /*時間函式    double start = getTickCount(); finish = clock();     duration = (double)(finish - start) / CLOCKS_PER_SEC;     */ //方式四 迭代器iterator掃描影象 Mat_<Vec3b>::iterator it = img.begin<Vec3b>();   Mat_<Vec3b>::iterator itend = img.end<Vec3b>();   for (; it!=itend; it++)   {         //對各個通道賦值    (*it)[0] = 200;      (*it)[1] = 200;     (*it)[2] = 200;  }       //測試,根據自己的選擇檢視結果 namedWindow("sorce",WINDOW_AUTOSIZE); namedWindow("result",WINDOW_AUTOSIZE); cv::imshow("sorce",img); cv::imshow("result",img_gray); waitKey(0); return 0;}

以上是對http://blog.csdn.net/yang_xian521/article/details/7182185#的綜合,以下是其博文,正如博主所說的, data_3 = img.data + i*img.step + j * img.elemSize();,int i=0;i<img_gray.rows;i++。。。這種在迴圈中出現的語句識別比較耗時的,注意避免。以下是其博文

1.存取單個畫素值
最通常的方法就是
img.at<uchar>(i,j) = 255;img.at<Vec3b>(i,j)[0] = 255;

如果你覺得at操作顯得太笨重了,不想用Mat這個類,也可以考慮使用輕量級的Mat_類,使用過載操作符()實現取元素的操作。
cv::Mat_<uchar> im2= img; // im2 refers to image   im2(50,100)= 0; // access to row 50 and column 100

2.用指標掃描一幅影象

對於一幅影象的掃描,用at就顯得不太好了,還是是用指標的操作方法更加推薦。先介紹一種上一講提到過的
for (int j=0; j<nl; j++){        uchar* data= image.ptr<uchar>(j);        for (int i=0; i<nc; i++)       {                                   data[i] = 255;        }}

更高效的掃描連續影象的做法可能是把W*H的衣服影象看成是一個1*(w*h)的一個一維陣列,這個想法是不是有點奇葩,這裡要利用isContinuous這個函式判斷影象內的畫素是否填充滿,使用方法如下:
if (img.isContinuous()){        nc = img.rows*img.cols*img.channels();}uchar* data = img.ptr<uchar>(0);for (int i=0; i<nc; i++){        data[i] = 255;}

更低階的指標操作就是使用Mat裡的data指標,之前我稱之為暴力青年,使用方法如下:
uchar* data = img.data;// img.at(i, j)data = img.data + i * img.step + j * img.elemSize();

3.用迭代器iterator掃描影象

和C++STL裡的迭代器類似,Mat的迭代器與之是相容的。是MatIterator_。宣告方法如下:
cv::MatIterator_<Vec3b> it;  

或者是:
cv::Mat_<Vec3b>::iterator it;
掃描影象的方法如下:
Mat_<Vec3b>::iterator it = img.begin<Vec3b>();Mat_<Vec3b>::iterator itend = img.end<Vec3b>();for (; it!=itend; it++){         (*it)[0] = 255;}

4.高效的scan image方案總結
還是用我們之前使用過的getTickCount、getTickFrequency函式測試速度。這裡我就不一一列舉我測試的結果了,直接上結論。測試發現,好的編寫風格可以提高50%的速度!要想減少程式執行的時間,必要的優化包括如下幾個方面:


(1)記憶體分配是個耗時的工作,優化之;
(2)在迴圈中重複計算已經得到的值,是個費時的工作,優化之;舉例:
int nc = img.cols * img.channels();for (int i=0; i<nc; i++){.......}//**************************for (int i=0; i<img.cols * img.channels(); i++){......}

後者的速度比前者要慢上好多。
(3)使用迭代器也會是速度變慢,但迭代器的使用可以減少程式錯誤的發生機率,考慮這個因素,可以酌情優化
(4)at操作要比指標的操作慢很多,所以對於不連續資料或者單個點處理,可以考慮at操作,對於連續的大量資料,不要使用它
(5)掃描連續影象的做法可能是把W*H的衣服影象看成是一個1*(w*h)的一個一維陣列這種辦法也可以提高速度。短的迴圈比長迴圈更高效,即使他們的運算元是相同的

以上的這些優化可能對於大家的程式執行速度提高並不明顯,但它們畢竟是個得到速度提升的好的程式設計策略,希望大家能多采納。
還有就是利用多執行緒也可以高效提高執行速度。OpenMP和TBB是兩種流行的APT,不過對於多執行緒的東西,我是有些迷糊的,呵呵

5.整行整列畫素值的賦值
對於整行或者整列的資料,可以考慮這種方式處理
img.row(i).setTo(Scalar(255));img.col(j).setTo(Scalar(255));
這節就先介紹這麼多攻略吧~希望大家喜歡

參考資料


          1. http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/mat%20-%20the%20basic%20image%20container/mat%20-%20the%20basic%20image%20container.html


          2 http://blog.sina.com.cn/s/blog_73ee929c01010yor.html


                      

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述