1. 程式人生 > 實用技巧 >opencv 入門必學姿勢

opencv 入門必學姿勢

影象基本操作

一般來說,灰度圖用 2 維矩陣表示,彩色(多通道)影象用 3 維矩陣(M× N × 3)表示。對於影象顯示來說,目前大部分裝置都是用無符號 8 位整數(型別為 CV_8U)表示畫素亮度。影象資料在計算機記憶體中的儲存順序為以影象最左上點(也可能是最左下點)開始。如果是多通道影象,比如 RGB 影象,則每個畫素用三個位元組表示。在 OpenCV 中,RGB 影象的通道順序為BGR 。

Mat 類定義:

class CV_EXPORTS Mat
{
public:
  //一系列函式
  ……
  /*flag引數中包含很多關於矩陣的資訊,如:
    -Mat的標識
    -資料是否連續
    -深度
    -通道數目
  
*/   int flags;   //矩陣的維數,>=2   int dims;   //矩陣的行數和列數,如果矩陣超過2維,這兩個變數的值都為 -1   int rows,clos;   //只想資料的指標   uchar *data;   //指向引用計數的指標,如果資料是由使用者分配的,則為NULL   int *refcount;   //其他成員變數和成員函式    …… };

建立Mat物件

Mat 類提供了一系列建構函式,可以方便的根據需要建立 Mat 物件。下面是一個使用建構函式建立物件的例子

Mat M(3,2,CV_8UC3,Scalar(0,0,255));
cout<<"
M = "<<endl<<" "<<M<<endl;

第一行程式碼建立一個行數(高度)為 3,列數(寬度)為 2 的影象,影象元素是 8 位無符號整數型別,且有三個通道。影象的所有畫素值被初始化為(0, 0,255)。由於 OpenCV 中預設的顏色順序為 BGR,因此這是一個全紅色的影象。

該段程式碼的輸出如圖:

常用的建構函式:

 1 //無參構造
 2 Mat::Mat();
 3 
 4 //建立行數為rows,列數為cols,型別為type的影象
 5 Mat::Mat(int rows, int cols, int type);
 6 
 7
//建立大小為size,型別為type的影象 8 Mat::Mat(Size size, int type); 9 10 //建立行為rows,列為cols,型別為type的影象,並將所有元素初始化為值s 11 Mat::Mat(int rows, int cols, int type, const Scalar &s); 12 13 //建立大小為 size,型別為 type 的影象,並將所有元素初始化為值s 14 Mat::Mat(Size size, int type, const Scalar &s); 15 16 //將 m 賦值給新建立的物件,此處不會對影象資料進行復制,m 和新物件共用影象資料 17 Mat::Mat(const Mat &m); 18 19 //建立行數為 rows,列數為 col,型別為 type 的影象,此建構函式不建立影象資料所需記憶體,而是直接使用 data 所指記憶體,影象的行步長由 step指定 20 Mat::Mat(int rows, int cols, int type, void *data, size_t step=AUTO_STEP); 21 22 //建立大小為 size,型別為 type 的影象,此建構函式不建立影象資料所需記憶體,而是直接使用 data 所指記憶體,影象的行步長由 step 指定 23 Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP); 24 25 //建立的新影象為 m 的一部分,具體的範圍由 rowRange 和 colRange 指定,此建構函式也不進行影象資料的複製操作,新影象與 m 共用影象資料 26 Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange); 27 28 //建立的新影象為 m 的一部分,具體的範圍 roi 指定,此建構函式也不進行影象資料的複製操作,新影象與 m 共用影象資料 29 Mat::Mat(const Mat& m, const Rect& roi);

這些建構函式中,很多都涉及到型別type。type可以是CV_8UC1,CV_16SC1,…,CV_64FC4 等。裡面的 8U 表示 8 位無符號整數,16S 表示 16 位有符號整數,64F表示 64 位浮點數(即 double 型別);C 後面的數表示通道數,例如 C1 表示一個通道的影象,C4 表示 4 個通道的影象,以此類推。

create() 函式建立物件

除了在建構函式中可以建立影象,也可以使用 Mat 類的 create()函式建立影象。如果 create()函式指定的引數與影象之前的引數相同,則不進行實質的記憶體申請操作;如果引數不同,則減少原始資料記憶體的索引,並重新申請記憶體。使用方法如下面例程所示:

1 Mat M(2,2, CV_8UC3);//建構函式建立影象
2 M.create(3,2, CV_8UC2);//釋放記憶體重新建立影象

需要注意的時,使用 create()函式無法設定影象畫素的初始值。

Matlab 風格的建立物件方法

OpenCV 2 中提供了 Matlab 風格的函式,如 zeros(),ones()和 eyes()。這種方法使得程式碼非常簡潔,使用起來也非常方便。使用這些函式需要指定影象的大小和型別,使用方法如下:

1 Mat Z = Mat::zeros(2,3, CV_8UC1);
2 cout << "Z = " << endl << " " << Z << endl;
3 Mat O = Mat::ones(2, 3, CV_32F);
4 cout << "O = " << endl << " " << O << endl;
5 Mat E = Mat::eye(2, 3, CV_64F);
6 cout << "E = " << endl << " " << E << endl;

該程式碼中,有些 type 引數如 CV_32F 未註明通道數目,這種情況下它表示單通道。上面程式碼的輸出結果如圖所示。

矩陣的基本元素表達

對於單通道影象,其元素型別一般為 8U(即 8 位無符號整數),當然也可以是 16S、32F 等;這些型別可以直接用 uchar、short、float 等 C/C++語言中的基本資料型別表達。

如果多通道影象,如 RGB 彩色影象,需要用三個通道來表示。在這種情況下,如果依然將影象視作一個二維矩陣,那麼矩陣的元素不再是基本的資料型別。OpenCV 中有模板類 Vec,可以表示一個向量。OpenCV 中使用 Vec 類預定義了一些小向量,可以將之用於矩陣元素的表達。

 1 typedef Vec<uchar, 2> Vec2b;
 2 typedef Vec<uchar, 3> Vec3b;
 3 typedef Vec<uchar, 4> Vec4b;
 4 typedef Vec<short, 2> Vec2s;
 5 typedef Vec<short, 3> Vec3s;
 6 typedef Vec<short, 4> Vec4s;
 7 typedef Vec<int, 2> Vec2i;
 8 typedef Vec<int, 3> Vec3i;
 9 typedef Vec<int, 4> Vec4i;
10 typedef Vec<float, 2> Vec2f;
11 typedef Vec<float, 3> Vec3f;
12 typedef Vec<float, 4> Vec4f;
13 typedef Vec<float, 6> Vec6f;
14 typedef Vec<double, 2> Vec2d;
15 typedef Vec<double, 3> Vec3d;
16 typedef Vec<double, 4> Vec4d;
17 typedef Vec<double, 6> Vec6d;

例如 8U 型別的 RGB 彩色影象可以使用 Vec3b,3 通道 float 型別的矩陣可以使用 Vec3f。
對於 Vec 物件,可以使用[]符號如運算元組般讀寫其元素,如:

1 Vec3b color; //用 color 變數描述一種 RGB 顏色
2 color[0]=255; //B 分量
3 color[1]=0; //G 分量
4 color[2]=0; //R 分量

畫素值的讀寫

at()函式

函式 at()來實現讀去矩陣中的某個畫素,或者對某個畫素進行賦值操作。下面兩行程式碼演示了 at()函式的使用方法。

1 uchar value = grayim.at<uchar>(i,j);//讀出第 i 行第 j 列畫素值
2 grayim.at<uchar>(i,j)=128; //將第 i 行第 j 列畫素值設定為 128

如果要對影象進行遍歷,可以參考下面的例程。這個例程建立了兩個影象,分別是單通道的 grayim 以及 3 個通道的 colorim,然後對兩個影象的所有畫素值進行賦值,最後現實結果。

 1 #include <iostream>
 2 #include "opencv2/opencv.hpp"
 3 using namespace std;
 4 using namespace cv;
 5 int main(int argc, char* argv[])
 6 {
 7     Mat grayim(600, 800, CV_8UC1);
 8     Mat colorim(600, 800, CV_8UC3);
 9     
10      //遍歷所有畫素,並設定畫素值
11     for(int i = 0; i < grayim.raws; ++i)
12         for(int j = 0; j < grayim.cols; ++j)
13             grayim.at<uchar>(i,j) = (i + j) % 255;
14 
15     //遍歷所有畫素,並設定畫素值
16     for(int i = 0; i < colorim.raws; ++i)
17         for(int j = 0; j < colorim.cols; ++j)
18         {
19             Vec3b pixel;
20             pixel[0] = i%255; //blue
21             pixel[1] = j%255; //green
22             pixel[2] = 0; //red
23             colorim.at<Vec3b>(i,j) = pixel;
24         }
25     //顯示結果
26     imshow("grayim",grayim);
27     imshow("colorim",colorim);
28     waitKey(0);
29     return 0 ;
30 }

執行結果如下:

使用迭代器

 

 1 #include <iostream>
 2 #include "opencv2/opencv.hpp"
 3 using namespace std;
 4 using namespace cv;
 5 
 6 int main(int argc, char* argv[])
 7 {
 8     Mat grayim(600, 800, CV_8UC1);
 9     Mat colorim(600, 800, CV_8UC3);
10 
11     //遍歷所有畫素,並設定畫素值
12     MatIterator_<uchar> grayit, grayend;
13     for( grayit = grayim.begin<uchar>(), grayend = grayim.end<uchar>(); grayit != grayend; ++grayit)
14         *grayit = rand()%255;
15 
16     //遍歷所有畫素,並設定畫素值
17     MatIterator_<Vec3b> colorit, colorend;
18     for( colorit = colorim.begin<Vec3b>(), colorend = colorim.end<Vec3b>(); colorit != colorend; ++colorit)
19     {
20         (*colorit)[0] = rand()%255; //Blue
21         (*colorit)[1] = rand()%255; //Green
22         (*colorit)[2] = rand()%255; //Red
23     }
24     //顯示結果
25     imshow("grayim", grayim);
26     imshow("colorim", colorim);
27     waitKey(0);
28     return 0;
29 }

通過資料指標

如果你非常注重程式的執行速度,那麼遍歷畫素時,建議使用指標。下面的例程演示如何使用指標來遍歷影象中的所有畫素。

 1 #include <iostream>
 2 #include "opencv2/opencv.hpp"
 3 using namespace std;
 4 using namespace cv;
 5 
 6 int main(int argc, char* argv[])
 7 {
 8     Mat grayim(600, 800, CV_8UC1);
 9     Mat colorim(600, 800, CV_8UC3);
10 
11     //遍歷所有畫素,並設定畫素值
12     for( int i = 0; i < grayim.rows; ++i)
13     {
14         //獲取第 i 行首畫素指標
15         uchar * p = grayim.ptr<uchar>(i);
16         //對第 i 行的每個畫素(byte)操作
17         for( int j = 0; j < grayim.cols; ++j )
18             p[j] = (i+j)%255;
19     }
20 
21     //遍歷所有畫素,並設定畫素值
22     for( int i = 0; i < colorim.rows; ++i)
23     {
24          //獲取第 i 行首畫素指標
25         Vec3b * p = colorim.ptr<Vec3b>(i);
26         for( int j = 0; j < colorim.cols; ++j )
27         { 
28             p[j][0] = i%255; //Blue
29             p[j][1] = j%255; //Green
30             p[j][2] = 0; //Red     
31          }
32     }
33 
34     //顯示結果
35     imshow("grayim", grayim);
36     imshow("colorim", colorim);
37     waitKey(0);
38     return 0;
39 }