1. 程式人生 > >opencv 學習筆記--影象的基本操作(一)

opencv 學習筆記--影象的基本操作(一)

基礎概念:

一副尺寸為M*N的影象可以用一個M*N的矩陣來表示。 一般來說,灰度圖用2維矩陣來表示,彩色(多通道)影象用3維矩陣(M*N*3)表示。對於影象顯示來說,在大部分裝置中都是用無符號8位整數(型別為CV_8U)表示畫素亮度。
l(ij) 表示第i行j列的畫素值,如果是多通道影象,比如RGB影象,則每個畫素用三個位元組表示。在OpenCV中,RGB影象的通道順序為BGR.
Mat類 早期OpenCV中,使用lpllmage和CvMat資料結構來表示影象,這兩個資料型別都是C語言結構,需要手動管理記憶體,包括如何記憶體的申請和釋放。為了使開發者專注於演算法設計,新版的OpenCV中引入了Mat類。
Mat類的優點是能夠自動管理記憶體,程式碼結構更加簡介。 class CV_EXPORTS Mat
{
public:
//一系列函式
...
/* flag 引數中包含許多關於矩陣的資訊,如:
-Mat 的標識
-資料是否連續
-深度
-通道數目
*/
int flags; 
//矩陣的維數,取值應該大於或等於 2
int dims;
//矩陣的行數和列數,如果矩陣超過 2 維,這兩個變數的值都為-1
int rows, cols;
//指向資料的指標
uchar* data;
//指向引用計數的指標
//如果資料是由使用者分配的,則為 NULL
int* refcount;
23//其他成員變數和成員函式
...
};
Mat M(3,2, CV_8UC3, Scalar(0,0,255));
cout << "M = " << endl << " " << M << endl;
建立一個行數(高度)為3, 列數(寬度)為2的影象,影象元素是8位無符號整數型別,且有3個通道,根據OpenCV中預設的顏色順序為BGR, 因此這是一個全紅色的影象,同時直接輸出M例項中的所有畫素。 程式碼輸出結果:
常用的建構函式有:
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)
24建立行數為 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。 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 的影象。
----------------------------------------------------------------------------------------------------------------------------------------  除了在建構函式中可以建立影象,也可以使用 Mat 類的 create()函式建立圖
像。如果 create()函式指定的引數與影象之前的引數相同,則不進行實質的記憶體
申請操作; 如果引數不同, 則減少原始資料記憶體的索引, 並重新申請記憶體。使用
方法如下面例程所示:
Mat M(2,2, CV_8UC3);//建構函式建立影象
M.create(3,2, CV_8UC2);//釋放記憶體重新建立影象
----------------------------------------------------------------------------------------------------------------------------------------- 矩陣的基本元素:  對於單通道影象,其元素型別一般為 8U(即 8 位無符號整數),當然也可以
是 16S、32F 等;這些型別可以直接用 uchar、short、float 等 C/C++語言中的基本
資料型別表達。
如果多通道影象,如 RGB 彩色影象,需要用三個通道來表示。在這種情況下,如果依
然將影象視作一個二維矩陣,那麼矩陣的元素不再是基本的資料型別。
26OpenCV 中有模板類 Vec,可以表示一個向量。OpenCV 中使用 Vec 類預定義了一
些小向量, 可以將之用於矩陣元素的表達。
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,3 通道 float 型別的矩陣可以
使用 Vec3f。
對於 Vec 物件,可以使用[]符號如運算元組般讀寫其元素,如:
Vec3b color; //用 color 變數描述一種 RGB 顏色
color[0]=255; //B 分量
color[1]=0; //G 分量
color[2]=0; //R 分量
------------------------------------------------------------------------------------------------------------------------------------------ 畫素的訪問: 函式 at()來實現讀去矩陣中的某個畫素,或者對某個畫素進行賦值操作。下
面兩行程式碼演示了 at()函式的使用方法。
uchar value = grayim.at<uchar>(i,j);//讀出第 i 行第 j 列畫素值
grayim.at<uchar>(i,j)=128; //將第 i 行第 j 列畫素值設定為 128
如果要對影象進行遍歷,可以參考下面的例程。這個例程建立了兩個影象,
分別是單通道的 grayim 以及 3 個通道的 colorim,然後對兩個影象的所有畫素值
進行賦值,最後現實結果。
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
Mat grayim(600, 800, CV_8UC1);
Mat colorim(600, 800, CV_8UC3);
//遍歷所有畫素,並設定畫素值
for( int i = 0; i < grayim.rows; ++i)
for( int j = 0; j < grayim.cols; ++j )
grayim.at<uchar>(i,j) = (i+j)%255;
//遍歷所有畫素,並設定畫素值
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;
}
//顯示結果
imshow("grayim", grayim);
imshow("colorim", colorim);
28waitKey(0);
return 0;
}
需要注意的是,如果要遍歷影象,並不推薦使用 at()函式。使用這個函式的
優點是程式碼的可讀性高,但是效率並不是很高。
----------------------------------------------------------------------------------------------------------------------------------------- 如果你熟悉 C++的 STL 庫,那一定了解迭代器(iterator)的使用。迭代器可
以方便地遍歷所有元素。Mat 也增加了迭代器的支援,以便於矩陣元素的遍歷。
下面的例程功能跟上一節的例程類似,但是由於使用了迭代器,而不是使用行數
和列數來遍歷,所以這兒沒有了 i 和 j 變數,影象的畫素值設定為一個隨機數。
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
Mat grayim(600, 800, CV_8UC1);
Mat colorim(600, 800, CV_8UC3);
//遍歷所有畫素,並設定畫素值
MatIterator_<uchar> grayit, grayend;
29for(
grayit
=
grayim.begin<uchar>(),
grayend =
colorend =
grayim.end<uchar>(); grayit != grayend; ++grayit)
*grayit = rand()%255;
//遍歷所有畫素,並設定畫素值
MatIterator_<Vec3b> colorit, colorend;
for(
colorit
=
colorim.begin<Vec3b>(),
colorim.end<Vec3b>(); colorit != colorend; ++colorit)
{
(*colorit)[0] = rand()%255; //Blue
(*colorit)[1] = rand()%255; //Green
(*colorit)[2] = rand()%255; //Red
}
//顯示結果
imshow("grayim", grayim);
imshow("colorim", colorim);
waitKey(0);
return 0;
}
-----------------------------------------------------------------------------------------------------------------------------------------
使用指標訪問影象的畫素: 當程式規模較大,且邏輯複雜時,查詢指標錯誤十分困難。對於不熟悉指標
的程式設計者來說,指標就如同噩夢。如果你對指標使用沒有自信,則不建議直接通
過指標操作來訪問畫素。雖然 at()函式和迭代器也不能保證對畫素訪問進行充分
的檢查,但是總是比指標操作要可靠一些。
如果你非常注重程式的執行速度,那麼遍歷畫素時,建議使用指標。下面的
例程演示如何使用指標來遍歷影象中的所有畫素。此例程實現的操作跟第 3.5.1
節中的例程完全相同。例程程式碼如下:
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
Mat grayim(600, 800, CV_8UC1);
Mat colorim(600, 800, CV_8UC3);
//遍歷所有畫素,並設定畫素值
for( int i = 0; i < grayim.rows; ++i)
{
//獲取第 i 行首畫素指標
uchar * p = grayim.ptr<uchar>(i);
//對第 i 行的每個畫素(byte)操作
for( int j = 0; j < grayim.cols; ++j )
p[j] = (i+j)%255;
}
//遍歷所有畫素,並設定畫素值
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
}
31}
//顯示結果
imshow("grayim", grayim);
imshow("colorim", colorim);
waitKey(0);
return 0;
}