OpenCV學(xué)習(xí)之路(三)——Mat對(duì)象基本操作

矩陣的基本元素表達(dá)

對(duì)于單通道圖像,其元素類型一般為 8U(即 8 位無符號(hào)整數(shù)),當(dāng)然也可以是 16S、32F 等;這些類型可以直接用 uchar、short、float 等 C/C++語言中的基本數(shù)據(jù)類型表達(dá)。
如果多通道圖像,如 RGB 彩色圖像,需要用三個(gè)通道來表示。在這種情況下,如果依然將圖像視作一個(gè)二維矩陣,那么矩陣的元素不再是基本的數(shù)據(jù)類型。
OpenCV 中有模板類 Vec,可以表示一個(gè)向量。OpenCV 中使用 Vec 類預(yù)定義了一些小向量,可以將之用于矩陣元素的表達(dá)。

typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b; 
typedef Vec<uchar, 4> Vec4b;

typedef Vec<short, 2> Vec2s; 
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;

typedef Vec<int, 2> Vec2i; 
typedef Vec<int, 3> Vec3i; 
typedef Vec<int, 4> Vec4i;

typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f; 
typedef Vec<float, 4> Vec4f; 
typedef Vec<float, 6> Vec6f;

typedef Vec<double, 2> Vec2d; 
typedef Vec<double, 3> Vec3d; 
typedef Vec<double, 4> Vec4d; 
typedef Vec<double, 6> Vec6d;

例如 8U 類型的 RGB 彩色圖像可以使用 Vec3b,Vec3b對(duì)應(yīng)的通道順序是blue、green、red的uchar類型數(shù)據(jù)。3 通道 float 類型的矩陣可以使用 Vec3f。
對(duì)于 Vec 對(duì)象,可以使用[]符號(hào)如操作數(shù)組般讀寫其元素,如:

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

像素值的讀寫

1.at()函數(shù)
函數(shù) at()來實(shí)現(xiàn)讀去矩陣中的某個(gè)像素,或者對(duì)某個(gè)像素進(jìn)行賦值操作。下
面兩行代碼演示了 at()函數(shù)的使用方法:

uchar value = grayim.at<uchar>(i,j);//讀出第i行第j列像素值
grayim.at<uchar>(i,j)=128; //將第i行第j列像素值設(shè)置為128

對(duì)圖像進(jìn)行遍歷,如下:

Mat colorim(600, 800, CV_8UC3);
//遍歷所有像素,并設(shè)置像素值
for( int i = 0; i < colorim.rows; ++i) {
  for( int j = 0; j < colorim.cols; ++j ){
    Vec3b pixel;
    pixel[0] = i%255; //Blue
    pixel[1] = j%255; //Green
    pixel[2] = 0; //Red
    colorim.at<Vec3b>(i,j) = pixel;
  }
}

需要注意的是,如果要遍歷圖像,并不推薦使用 at()函數(shù)。使用這個(gè)函數(shù)的優(yōu)點(diǎn)是代碼的可讀性高,但是效率并不是很高。
2.使用迭代器
如果你熟悉 C++的 STL 庫,那一定了解迭代器(iterator)的使用。迭代器可以方便地遍歷所有元素。Mat 也增加了迭代器的支持,以便于矩陣元素的遍歷。下面的例程功能跟上一節(jié)的例程類似,但是由于使用了迭代器,而不是使用行數(shù)和列數(shù)來遍歷,所以這兒沒有了 i 和 j 變量,圖像的像素值設(shè)置為一個(gè)隨機(jī)數(shù)。

Mat grayim(600, 800, CV_8UC1);
Mat colorim(600, 800, CV_8UC3);
//遍歷所有像素,并設(shè)置像素值
MatIterator_<uchar> grayit, grayend;
for( grayit = grayim.begin<uchar>(), grayend = grayim.end<uchar>(); grayit != grayend; ++grayit) {
   *grayit = rand()%255;
}
//遍歷所有像素,并設(shè)置像素值
MatIterator_<Vec3b> colorit, colorend;
for( colorit = colorim.begin<Vec3b>(), colorend = colorim.end<Vec3b>(); colorit != colorend; ++colorit) {
  (*colorit)[0] = rand()%255; //Blue
  (*colorit)[1] = rand()%255; //Green
  (*colorit)[2] = rand()%255; //Red
}

3.通過數(shù)據(jù)指針
如果你非常注重程序的運(yùn)行速度,那么遍歷像素時(shí),建議使用指針。下面的例程演示如何使用指針來遍歷圖像中的所有像素。

Mat grayim(600, 800, CV_8UC1);
Mat colorim(600, 800, CV_8UC3);
//遍歷所有像素,并設(shè)置像素值
for( int i = 0; i < grayim.rows; ++i) {
    //獲取第 i 行首像素指針
    uchar * p = grayim.ptr<uchar>(i);
    //對(duì)第 i 行的每個(gè)像素(byte)操作
    for( int j = 0; j < grayim.cols; ++j ) {
        p[j] = (i+j)%255;
    }
}
//遍歷所有像素,并設(shè)置像素值
for( int i = 0; i < colorim.rows; ++i) {
    //獲取第 i 行首像素指針
    Vec3b * p = colorim.ptr<Vec3b>(i);
    for( int j = 0; j < colorim.cols; ++j ) {
        p[j][0] = i%255; //Blue
        p[j][1] = j%255; //Green
        p[j][2] = 0; //Red
    }
}

選取圖像局部區(qū)域

Mat 類 供了多種方便的方法來選擇圖像的局部區(qū)域。使用這些方法時(shí)需要注意,這些方法并不進(jìn)行內(nèi)存的復(fù)制操作。如果將局部區(qū)域賦值給新的 Mat 對(duì)象,新對(duì)象與原始對(duì)象共用相同的數(shù)據(jù)區(qū)域,不新申請(qǐng)內(nèi)存,因此這些方法的執(zhí)行速度都比較快。
1.單行或單列選擇
取矩陣的一行或者一列可以使用函數(shù) row()或 col()。函數(shù)的聲明如下:

Mat Mat::row(int i) const 
Mat Mat::col(int j) const

參數(shù) i 和 j 分別是行標(biāo)和列標(biāo)。例如取出 A 矩陣的第 i 行可以使用如下代碼:

Mat line = A.row(i);

2.用Range選擇多行或多列
Range 是 OpenCV 中新增的類,該類有兩個(gè)關(guān)鍵變量 star 和 end。Range 對(duì)象可以用來表示矩陣的多個(gè)連續(xù)的行或者多個(gè)連續(xù)的列。其表示的范圍為從 start到 end,包含 start,但不包含 end。Range 類的定義如下:

class Range { 
  public:
  ...
  int start, end;
 };

Range 類還 供了一個(gè)靜態(tài)方法 all(),這個(gè)方法的作用如同 Matlab 中的“:”,表示所有的行或者所有的列。

Mat A = Mat::eye(10, 10, CV_32S);  //創(chuàng)建一個(gè)單位陣
Mat B = A(Range::all(), Range(1, 3));  // 取第1到3列(不包括3)
Mat C = B(Range(5, 9), Range::all());  // 取B的第5至9行(不包括9),其實(shí)等價(jià)于 C = A(Range(5, 9), Range(1, 3))

3.感興趣區(qū)域
從圖像中 取感興趣區(qū)域(Region of interest)有兩種方法,一種是使用構(gòu)造
函數(shù),如下例所示:

Mat img(Size(320,240),CV_8UC3);  //創(chuàng)建寬度為 320,高度為 240 的 3 通道圖像
Mat roi(img, Rect(10,10,100,100));  //roi 是表示 img 中 Rect(10,10,100,100)區(qū)域的對(duì)象

除了使用構(gòu)造函數(shù),還可以使用括號(hào)運(yùn)算符,如下:

Mat roi2 = img(Rect(10,10,100,100));

當(dāng)然也可以使用 Range 對(duì)象來定義感興趣區(qū)域,如下:

Mat roi3 = img(Range(10,100),Range(10,100));  //使用括號(hào)運(yùn)算符
Mat roi4(img, Range(10,100),Range(10,100));  //使用構(gòu)造函數(shù)

4.取對(duì)角線元素
矩陣的對(duì)角線元素可以使用 Mat 類的 diag()函數(shù)獲取,該函數(shù)的定義如下:

Mat Mat::diag(int d) const

參數(shù) d=0 時(shí),表示取主對(duì)角線;當(dāng)參數(shù) d>0 是,表示取主對(duì)角線下方的次對(duì)角線,如 d=1 時(shí),表示取主對(duì)角線下方,且緊貼主多角線的元素;當(dāng)參數(shù) d<0 時(shí),表示取主對(duì)角線上方的次對(duì)角線。
如同 row()和 col()函數(shù),diag()函數(shù)也不進(jìn)行內(nèi)存復(fù)制操作,其復(fù)雜度也是 O(1)。

Mat 表達(dá)式

如果矩陣 A 和 B 大小相同,則可以使用如下表達(dá)式:

C = A + B + 1;

其執(zhí)行結(jié)果是 A 和 B 的對(duì)應(yīng)元素相加,然后再加 1,并將生成的矩陣賦給 C變量。
下面給出 Mat 表達(dá)式所支持的運(yùn)算。下面的列表中使用 A 和 B 表示 Mat 類型的對(duì)象,使用 s 表示 Scalar 對(duì)象,alpha 表示 double 值。

  • 加法,減法,取負(fù):A+B,A-B,A+s,A-s,s+A,s-A,-A
  • 縮放取值范圍:A*alpha
  • 矩陣對(duì)應(yīng)元素的乘法和除法: A.mul(B),A/B,alpha/A
  • 矩陣乘法:A*B (注意此處是矩陣乘法,而不是矩陣對(duì)應(yīng)元素相乘)
  • 矩陣轉(zhuǎn)置:A.t()
  • 矩陣求逆和求偽逆:A.inv()
  • 矩陣比較運(yùn)算:A cmpop B,A cmpop alpha,alpha cmpop A。此處 cmpop可以是>,>=,==,!=,<=,<。如果條件成立,則結(jié)果矩陣(8U 類型矩陣)的對(duì)應(yīng)元素被置為 255;否則置 0。
  • 矩陣位邏輯運(yùn)算:A logicop B,A logicop s,s logicop A,~A,此處 logicop可以是&,|和^。
  • 矩陣對(duì)應(yīng)元素的最大值和最小值:min(A, B),min(A, alpha),max(A, B),max(A, alpha)。
  • 矩陣中元素的絕對(duì)值:abs(A)
  • 叉積和點(diǎn)積:A.cross(B),A.dot(B)

Mat_類

Mat_類是對(duì) Mat 類的一個(gè)包裝,其定義如下:

template<typename _Tp> class Mat_ : public Mat {
  public:
  //只定義了幾個(gè)方法
  //沒有定義新的屬性
};

在讀取矩陣元素時(shí),以及獲取矩陣某行的地址時(shí),需要指定數(shù)據(jù)類型。這樣首先需要不停地寫“<uchar>”,讓人感覺很繁瑣,在繁瑣和煩躁中容易犯錯(cuò),如上面代碼中的錯(cuò)誤,用 at()獲取矩陣元素時(shí)錯(cuò)誤的使用了 double 類型。這種錯(cuò)誤不是語法錯(cuò)誤,因此在編譯時(shí)編譯器不會(huì)醒。在程序運(yùn)行時(shí),at()函數(shù)獲取到的不是期望的(i,j)位置處的元素,數(shù)據(jù)已經(jīng)越界,但是運(yùn)行時(shí)也未必會(huì)報(bào)錯(cuò)。這樣的錯(cuò)誤使得你的程序忽而看上去正常,忽而彈出“段錯(cuò)誤”,特別是在代碼規(guī)模很大時(shí),難以查錯(cuò)。

Mat M(600, 800, CV_8UC1);
for(int i = 0; i < M.rows; ++i){
  uchar * p = M.ptr<uchar>(i);  //獲取指針時(shí)需要指定類型
  for(int j = 0; j < M.cols; ++j){
    double d1 = (double) ((i+j)%255);
    //用 at()讀寫像素時(shí),需要指定類型
    M.at<uchar>(i,j) = d1;
    //下面代碼錯(cuò)誤,應(yīng)該使用 at<uchar>()
    //但編譯時(shí)不會(huì) 醒錯(cuò)誤
    //運(yùn)行結(jié)果不正確,d2 不等于 d1
    double d2 = M.at<double>(i,j);
  }
}

使用 Mat_類,可以在變量聲明時(shí)確定元素的類型,訪問元素時(shí)不再需要指定元素類型,即使得代碼簡潔,又減少了出錯(cuò)的可能性。如下:

Mat_<uchar> M1 = (Mat_<uchar>&)M;  //在變量聲明時(shí)指定矩陣元素類型
for( int i = 0; i < M1.rows; ++i) {
  uchar * p = M1.ptr(i);  //不需指定元素類型,語句簡潔
  for( int j = 0; j < M1.cols; ++j ) {
    double d1 = (double) ((i+j)%255);
    //直接使用 Matlab 風(fēng)格的矩陣元素讀寫,簡潔
    M1(i,j) = d1;
    double d2 = M1(i,j);
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容