前言
因為對圖像方面感興趣,所以有空學學OpenCV的使用,並且希望以此為引子,帶領自己入門圖像領域。
先post上幾個參考網站,上面有完整源碼:
- http://docs.opencv.org/2.4.9/ (英文文檔,主要參看)
- http://www.opencv.org.cn/opencvdoc/2.3.2/html/index.html (低版本的中文參考)
- https://github.com/opencv/opencv/tree/master/doc/tutorials (github源碼,有些需要參考會英文文檔)
因為這么多資源,所以就不貼完整代碼,這重點講解某部分,方便自己以后回來查詢。
Mat - 基本的圖像容器
Mat
在以前,opencv使用的是C結構,IplImage。但是使用這個結構有一個缺點就是你需要注意內存的申請和銷毀。幸運的是,在C++我們可以使用一種更智能的結構,Mat。Mat會自動申請內存和銷毀。
Mat由基本的兩部分組成:矩陣頭(包含圖片信息,例如矩陣大小,存儲方法等)和一個指向包含像素點信息的指針。矩陣頭部大小是常數,但是矩陣的大小卻各不相同。
1 Mat A, C; // 只建立頭部
2 A = imread(fname, CV_LOAD_IMAGE_COLOR); 3 4 Mat B(A); // 調用copy構造函數 5 C = A; // 調用assign函數
上面的所有對象都指向同一個矩陣,只是頭部不一樣而已。如果使用其中一個對象改變圖像內容,所有指向這個矩陣的對象都會受影響。copy和assign只是復制頭部的一些信息。
我們可以調用其它方法實現深復制:
1 Mat F = A.clone(); 2 Mat G; 3 A.copyTo(G);
顯式創建Mat
我們可以使用 imwrite() 函數來把一個矩陣寫入到圖片文件。但是為了調試方便,我們還可以使用<<輸出(僅適用於二維矩陣)。
下面是創建Mat對象的各種方法:
- Mat()構造器
1 Mat M(2, 2, CV_8UC3, Scalar(0, 0, 255)); // CV_[多少位][有符號or無符號][類型前綴]C[通道數] 2 cout << M <<endl; 3 // [0, 0, 255, 0, 0, 255; 4 // 0, 0, 255, 0, 0, 255
- 使用C\C++數組構造
1 int sz[3] = {2, 2,2}; 2 Mat L(3, sz, CV_8UC(1), Scalar::all(0)); // 3維的[2, 2, 2]的圖像
- 為已存在IplImage指針構建頭部
1 IplImage* img = cvLoadImage(fname); 2 Mat mtx(img);
- Create() 函數
1 M.create(4, 4, CV_8UC(2)); // 這種方法不能賦初值,只在中心分配內存時使用 2 cout<< M << endl
- Matlab風格的初始化
1 Mat E = Mat::eye(4, 4, CV_64F); 2 Mat O = Mat::ones(2, 2, CV_32F); 3 Mat Z = Mat::zeros(3, 3, CV_8UC1);
- 逗號分隔的初始化小矩陣
1 Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
- 使用clone或copyTo。
1 Mat RowClone = C.row(1).clone(); // randu(RowClone, Scalar::all(0), Scalar::all(255))可以在low和high之間隨機
怎樣遍歷圖片
首先,我們可以使用一段代碼計算程序執行的時間:
1 double t = (double)getTickCount(); 2 // do something 3 t = ((double)getTickCount() - t) / getTickFrequency();
圖像的存儲
在RGB系統中,圖像是這樣存儲的:(注意是BGR的形式,可以使用 isContinunous() 函數查看是否連續存放)
(下面以顏色空間縮減為例子說明)
C風格的讀法
先用指針p指向一行,然后再p[j]形式讀取
1 int channels = I.channels(); 2 int nRows = I.rows; 3 int nCols = I.cols * channels; 4 5 if (I.isContinuous()) { 6 nCols = nCols * nRows; 7 nRows = 1; 8 } 9 10 int i, j; 11 uchar* p; 12 for (i=0; i<nRows; ++i) { 13 for (j=0; j<nCols; ++j) { 14 p = I.ptr<uchar>(i); 15 p[j] = table[p[j]]; //查表替換 16 } 17 }
迭代(安全)方法
迭代器從begin到end,使用(*it)[0]形式讀取
const int channels = I.channels(); switch(channels) { case 1: { MatIterator_<uchar> it, end; for (it = I.begin<uchar>(), end=I.end<uchar>(); it != end; ++it) *it = table[*it]; break; } case 3: { MatIterator_<Vec3b> it, end; for (it = I.begin<Vec3b>(), end=I.end<Vec3b>(); it != end; ++it) (*it)[0] = table[(*it)[0]]; (*it)[1] = table[(*it)[1]]; (*it)[2] = table[(*it)[2]]; } }
通過相關返回值的On-the-fly地址計算
先把矩陣轉換成Mat_,再用_I(i, j)[0]形式讀取
const int channels = I.channels(); switch(channels) { case 1: { for (int i=0; i<I.rows; ++i) for (int j=0; j<I.cols; ++j) I.at<uchar>(i, j) = table[I.at<uchar>(i, j)]; break; } case 3: { Mat_<Vec3b> _I = I; for (int i=0; i<I.rows; ++i) for (int j=0; j<I.cols; ++j) { _I(i, j)[0] = table[_I(i, j)[0]]; _I(i, j)[1] = table[_I(i, j)[1]]; _I(i, j)[2] = table[_I(i, j)[2]]; } I = _I; break; } }
快速實現表替換
Mat lookUpTable(1, 256, CV_8U); uchar* p = lookUpTable.data; for (int i=0; i < 256; ++i) p[i] = table[i]; LUT(I, lookUpTable, I);