1. 程式人生 > >[OpenCV學習筆記2][Mat數據類型和操作]

[OpenCV學習筆記2][Mat數據類型和操作]

寬度 返回 創建對象 由於 linux ocl total 中間 width

[Mat數據類型和基本操作]

?.運行環境:Linux(RedHat+OpenCV3.0)

1.Mat的作用:

Mat類用於表示一個多維的單通道或者多通道的稠密數組。能夠用來保存實數或復數的向量、矩陣,灰度或彩色圖像,立體元素,點雲,張量以及直方圖(高維的直方圖使用SparseMat保存比較好)。簡而言之,Mat就是用來保存多維的矩陣的。

2.Mat的常見屬性:

  • data:

uchar類型的指針,指向Mat數據矩陣的首地址。可以理解為標示一個房屋的門牌號;

  • dims:

Mat矩陣的維度,若Mat是一個二維矩陣,則dims=2,三維則dims=3,大多數情況下處理的都是二維矩陣,是一 個平面上的矩陣。

可以理解為房屋是一個一層的平房,三維或更多維的則是多層樓房;

  • rows:

Mat矩陣的行數。可理解為房屋內房間行數;

  • cols:

Mat矩陣的列數。可理解為房屋內房間列數;

  • size():

首先size是一個結構體,定義了Mat矩陣內數據的分布形式,數值上有關系式:

image.size().width==image.cols; image.size().height==image.rows

可以理解為房屋內房間的整體布局,這其中包括了房間分別在行列上分布的數量信息;

  • channels():

Mat矩陣元素擁有的通道數。例如常見的RGB彩色圖像,channels==3;而灰度圖像只有一個灰度分量信息, channels==1。

可以理解為每個房間內放有多少床位,3通道的放了3張床,單通道的放了1張床;

  • depth:

用來度量每一個像素中每一個通道的精度,但它本身與圖像的通道數無關!depth數值越大,精度越高。在 Opencv中,Mat.depth()得到的是一個0~6的數字,分別代表不同的位數,對應關系如下:

enum{CV_8U=0,CV_8S=1,CV_16U=2,CV_16S=3,CV_32S=4,CV_32F=5,CV_64F=6}

其中U是unsigned的意思,S表示signed,也就是有符號和無符號數。

可以理解為房間內每張床可以睡多少人,這個跟房間內有多少床並無關系;

  • elemSize:

elem是element(元素)的縮寫,表示矩陣中每一個元素的數據大小,如果Mat中的數據類型是CV_8UC1,那麽 elemSize==1;如果是CV_8UC3或CV_8SC3,那麽elemSize==3;如果是CV_16UC3或者CV_16SC3,那麽 elemSize==6;即elemSize是以8位(一個字節)為一個單位,乘以通道數和8位的整數倍;

可以理解為整個房間可以睡多少人,這個時候就得累計上房間內所有床位數(通道)和每張床的容納量了;

  • elemSize1:

elemSize加上一個“1”構成了elemSize1這個屬性,1可以認為是元素內1個通道的意思,這樣從命名上拆分後就很 容易解釋這個屬性了:表示Mat矩陣中每一個元素單個通道的數據大小,以字節為一個單位,所以有:

eleSize1==elemSize/channels;

  • step:

可以理解為Mat矩陣中每一行的“步長”,以字節為基本單位,每一行中所有元素的字節總量,是累計了一行中所 有元素、所有通道、所有通道的elemSize1之後的值;

  • step1():

以字節為基本單位,Mat矩陣中每一個像素的大小,累計了所有通道、所有通道的elemSize1之後的值,所以有:

step1==step/elemSize1;

  • type:

Mat矩陣的類型,包含有矩陣中元素的類型以及通道數信息,type的命名格式為CV_(位數)+(數據類型)+(通道數),所有取值如下:

技術分享圖片

3.矩陣的構造、初始化、釋放

註:1、在程序的最開始加上: using namespace cv;  2、把Mat改為 cv::Mat  (由於本人不會C++所有開始有點沒明白如何使用函數)

創建Mat類的方式:1.構造函數 2.create()函數創建對象3.從已有的數據源初始化

1.構造函數

? Mat::Mat()
無參數構造方法;

?Mat::Mat(int rows, int cols, int type)
創建行數為 rows,列數為col,類型為type的圖像;

?Mat::Mat(Size size, int type)
創建大小為 size,類型為type的圖像;

?Mat::Mat(int rows, int cols, int type, const Scalar& s)
創建行數為rows,列數為col,類型為type的圖像,並將所有元素初始化為值s;

?Mat::Mat(Size size, int type, const Scalar& s)
創建大小為 size,類型為type的圖像,並將所有元素初始化為值s

?Mat::Mat(const Mat& m)
將 m 賦值給新創建的對象,此處不會對圖像數據進行復制, m 和新對象共用圖像數據;

?Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP)
創建行數為 rows,列數為col,類型為type的圖像,此構造函數不創建圖像數據所需內存,而是直接使用data所指內存,圖像的行步長由step指定。

?Mat::Mat(Size size, int type, void* data, size_t step=AUTO_STEP)
創建大小為 size,類型為type的圖像,此構造函數不創建圖像數據所需內存,而是直接使用data所指內存,圖像的行步長由step指定。

?Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange)
創建的新圖像為 m 的一部分,具體的範圍由 rowRange 和 colRange 指定,此構造函數也不進行圖像數據的復制操作,新圖像與m共用圖像數據;

?Mat::Mat(const Mat& m, const Rect& roi)
創建的新圖像為 m 的一部分,具體的範圍 roi 指定,此構造函數也不進行圖像數據的復制操作,新圖像與m共用圖像數據。

type的類型有CV_8UC1,CV_16SC1,…,CV_64FC4等。裏面的8U表示8位無符號整數,16S表示16位有符號整數,64F表示64位浮點數(即double類型);C後面的數表示通道數,例如C1表示一個通道的圖像,C4表示4個通道的圖像,以此類推。


如果你需要更多的通道數,需要用宏CV_8UC(n),例如:Mat M(3,2, CV_8UC(5));//創建行數為3,列數為2,通道數為5的圖像

計算機視覺中,圖像的讀取是圖像處理的基礎,圖像就是一系列像素值,OpenCV使用數據結構cv::Mat來存儲圖像。cv::Mat是一個矩陣類,矩陣中每一個元素都代表一個像素,對於灰度圖像,像素用8位無符號數,0表示黑色,255表示白色。對於彩色像素而言,每個像素需要三位這樣的8位無符號數來表示,即三個通道(R,G,B),矩陣則依次存儲一個像素的三個通道的值,然後再存儲下一個像素點。

cv::Mat中,

cols代表圖像的寬度(圖像的列數),

rows代表圖像的高度(圖像的行數),

step代表以字節為單位的圖像的有效寬度,

elemSize返回像素的大小,

channels()方法返回圖像的通道數,

total函數返回圖像的像素數。

像素的大小 = 顏色大小(字節)*通道數,

比如:

三通道short型矩陣(CV_16SC3)的大小為2*3 = 6,

三通道Byte型矩陣(CV_8UC3)的大小為1*3= 3,像素的channels方法返回圖像的通道數,total函數返回圖像的像素數。

RGB圖像的顏色數目是256*256*256,本文對圖像進行量化,縮減顏色數目到256的1/8(即32*32*32)為目標,分別利用一下幾種方法實現,比較幾種方法的安全和效率。

方法一:使用Mat的成員函數ptr<>()

cv::Mat中提供ptr函數訪問任意一行像素的首地址,特別方便圖像的一行一行的橫向訪問,如果需要一列一列的縱向訪問圖像,就稍微麻煩一點。但是ptr訪問效率比較高,程序也比較安全,有越界判斷。

[cpp] view plain copy
  1. int nl = image.rows; //行數
  2. int nc = image.cols * image.channels();
  3. for (int j = 0; j<nl; j++)
  4. {
  5. uchar* data = image.ptr<uchar>(j);
  6. for (int i = 0; i<nc; i++)
  7. {
  8. data[i] = data[i] / div*div + div / 2;
  9. }
  10. }

方法二:使用叠代器遍歷圖像

cv::Mat同樣有標準模板庫(STL),可以使用叠代器訪問數據。

用叠代器來遍歷圖像像素,可簡化過程降低出錯的機會,比較安全,不過效率較低;如果想避免修改輸入圖像實例cv::Mat,可采用const_iterator。iterator有兩種調用方法,cv::MatIterator_<cv::Vec3b>it;cv::Mat_<cv::Vec3b>::iterator it;中間cv::Vec3b是因為圖像是彩色圖像,3通道,cv::Vec3b可以代表一個像素。

[cpp] view plain copy
  1. cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
  2. cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
  3. for (; it != itend; ++it)
  4. {
  5. (*it)[0] = (*it)[0] / div*div + div / 2;
  6. (*it)[1] = (*it)[1] / div*div + div / 2;
  7. (*it)[2] = (*it)[2] / div*div + div / 2;
  8. }

方法三:使用Mat的成員函數at<>()

cv::Mat也是向量,可以使at方法取值,使用調用方法image.at<cv::Vec3b>(j,i),at方法方便,直接給i,j賦值就可以隨意訪問圖像中任何一個像素,其中j表示第j行,i表示該行第i個像素。但是at方法效率是這3中訪問方法中最慢的一個,所以如果遍歷圖像或者訪問像素比較多時,建議不要使用這個方法,畢竟程序的效率還是比程序的可讀性要重要的。下面是完整的調用方法,其運行時間在下面會介紹。

[cpp] view plain copy
  1. for (int j = 0; j< image.rows; j++)
  2. {
  3. for (int i = 0; i< image.cols; i++)
  4. {
  5. image.at<cv::Vec3b>(j, i)[0] = image.at<cv::Vec3b>(j, i)[0] / div*div + div / 2;
  6. image.at<cv::Vec3b>(j, i)[1] = image.at<cv::Vec3b>(j, i)[1] / div*div + div / 2;
  7. image.at<cv::Vec3b>(j, i)[2] = image.at<cv::Vec3b>(j, i)[2] / div*div + div / 2;
  8. } // end of line
  9. }



註意:使用at函數時,應該知道矩陣元素的類型和通道數,根據矩陣元素類型和通道數來確定at函數傳遞的類型,使用的是Vec3b這個元素類型,他是一個包含3個unsigned char類型向量。之所以采用這個類型來接受at的返回值,是因為,我們的矩陣im是3通道,類型為unsigned char類型

完整實例:

[cpp] view plain copy
    1. #include <iostream>
    2. #include < opencv.hpp>
    3. using namespace cv;
    4. using namespace std;
    5. int main()
    6. {
    7. //新建一個uchar類型的3通道矩陣
    8. Mat img(5, 3, CV_8UC3, Scalar(50,50,50));
    9. cout << img.rows << endl; //5
    10. cout << img.cols << endl; //3
    11. cout << img.channels() << endl; //3
    12. cout << img.depth() << endl; //CV_8U 0
    13. cout << img.dims << endl; //2
    14. cout << img.elemSize() << endl; //1 * 3,一個位置,三個通道的CV_8U
    15. cout << img.elemSize1() << endl; //1
    16. cout << img.size[0] << endl; //5
    17. cout << img.size[1] << endl; //3
    18. cout << img.step[0] << endl; //3 * ( 1 * 3 )
    19. cout << img.step[1] << endl; //1 * 3
    20. cout << img.step1(0) << endl; //3 * 3
    21. cout << img.step1(1) << endl; //3
    22. cout << img.total() << endl; //3*5
    23. //-------------------------------------- 地址運算 --------------------------------//
    24. for (int row = 0; row < img.rows; row++)
    25. {
    26. for (int col = 0; col < img.cols; col++)
    27. {
    28. //[row, col]像素的第 1 通道地址被 * 解析(blue通道)
    29. *(img.data + img.step[0] * row + img.step[1] * col) += 15;
    30. //[row, col]像素的第 2 通道地址被 * 解析(green通道)
    31. *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1()) += 15;
    32. //[row, col]像素的第 3 通道地址被 * 解析(red通道)
    33. *(img.data + img.step[0] * row + img.step[1] * col + img.elemSize1() * 2) += 15;
    34. }
    35. }
    36. cout << img << endl;
    37. //-------------------------------------- Mat的成員函數at<>( ) --------------------------------//
    38. for (int row = 0; row < img.rows; row++)
    39. {
    40. for (int col = 0; col < img.cols; col++)
    41. {
    42. img.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
    43. }
    44. }
    45. cout << img << endl;
    46. //-------------------------------------- 使用Mat的成員函數ptr<>() --------------------------------//
    47. for (int row = 0; row < img.rows; row++)
    48. {
    49. // data 是 uchar* 類型的, m.ptr(row) 返回第 row 行數據的首地址
    50. // 需要註意的是該行數據是按順序存放的,也就是對於一個 3 通道的 Mat, 一個像素3個通道值, [B,G,R][B,G,R][B,G,R]...
    51. // 所以一行長度為:sizeof(uchar) * m.cols * m.channels() 個字節
    52. uchar* data = img.ptr(row);
    53. for (int col = 0; col < img.cols; col++)
    54. {
    55. data[col * 3] = 50; //第row行的第col個像素點的第一個通道值 Blue
    56. data[col * 3 + 1] = 50; // Green
    57. data[col * 3 + 2] = 50; // Red
    58. }
    59. }
    60. cout << img << endl;
    61. Vec3b *pix(NULL);
    62. for (int r = 0; r < img.rows; r++)
    63. {
    64. pix = img.ptr<Vec3b>(r);
    65. for (int c = 0; c < img.cols; c++)
    66. {
    67. pix[c] = pix[c] * 2;
    68. }
    69. }
    70. cout << img << endl;
    71. //-------------------------------------- 使用Mat的成員函數ptr<>() --------------------------------//
    72. MatIterator_<Vec3b> it_im, itEnd_im;
    73. it_im = img.begin<Vec3b>();
    74. itEnd_im = img.end<Vec3b>();
    75. for(; it_im != itEnd_im; it_im++)
    76. {
    77. *it_im = (*it_im) * 2;
    78. }
    79. cout << img << endl;
    80. cvWaitKey();
    81. return 0;
    82. }
時間:2018.1.21.1.41{失眠夜總結OpenCV吧想起了一段話送給自己}
{Can not force others to love themselves only to make themselves worthy of love ,and the rely on fate.}
@晚安 Liu在身邊 27

[OpenCV學習筆記2][Mat數據類型和操作]