上一篇:《OpenCV開發筆記(二):cvui互動界面》
下一篇:《OpenCV開發筆記(四):OpenCV圖檔和視訊資料的讀取與存儲》
前言
OpenCV最主要的功能是用于操作圖像,是以圖像的概念貫穿整個OpenCV,與其相關的核心類就是cv::Mat。
圖像與矩陣
圖像指數字圖像。
以上看到的是一張可視化的圖檔,但是在計算機中這副圖像知識一系列亮度各異的店,該圖檔的像素為300 x 200像素的圖,可以用一個300*200的矩陣來表示,舉證元素的值表示這個位置上的像素的亮度,一般來說像素值越大表示該店越亮。
放大“C”後如下圖所示:
每個點對應的亮度可以了解為rgb的值,無符号8位數3維,則一個像素點為3維數組,分别對應RGB的值,在OpenCV中資料類型為:CV_8U3C。
假設M x N,Iij表示第j行j列,對應上圖就是M = 300, N = 200。
注意:在Opencv中三維數組存儲RGB值,存儲顔色通道的順序不是RGB,而是BGR,如下圖:
OpenCV中的矩陣類cv::Mat
定義
Mat貫穿了整個OpenCV。
Mat類的關鍵定義如下:
class CV_EXPORTS Mat
{
public:
// 一系列函數
...
/* flag 參數中包含許多關于矩陣的資訊,如:
-Mat 的辨別
-資料是否連續
-深度
-通道數目
*/
int flags;
// 矩陣的維數,取值應該大于或等于 2
int dims;
// 矩陣的行數和列數,如果矩陣超過 2 維,這兩個變量的值都為-1
int rows, cols;
// 指向資料的指針
uchar* data;
// 指向引用計數的指針
// 如果資料是由使用者配置設定的,則為 NULL
int* refcount;
// 其他成員變量和成員函數
...
}
初始化方式
Mat是一個非常優秀的圖像類,它同時也是一個通用的矩陣類,可以用來建立和操作多元矩陣。有多種方法建立一個Mat對象。
首先了解下資料的類型(舉例:CV_8UC3):
初始化函數
// 預設形式
cv::Mat mat;
// 拷貝構造形式
cv::Mat mat(const cv::Mat& mat);
// 指定行列範圍的拷貝構造
cv::Mat mat(const cv::Mat& mat, const cv::Range& rows, const cv::Range& cols);
// 指定ROI的拷貝構造
cv::Mat mat(const cv::Mat& mat, const cv::Rect& roi);
// 使用多元數組中指定範圍内的資料的拷貝構造
cv::Mat mat(const cv::Mat& mat, const cv::Range* ranges);
// 指定類型和大小(行列)的二維數組(注意:是行在前,列在後)
cv::Mat mat(int rows, int cols, int type);
// 有初始化值的置頂類型和大小(行列)的二維資料
cv::Mat mat(int rows, int cols, int type, const Scalar& s);
// 使用預先存在資料定義的指定類型和大小(行列)的二維數組
cv::Mat mat(int rows, int cols, int type, void *data, size_t step = AUTO_STEP);
// 指定大小和類型的二維數組
cv::Mat mat(cv::Size sz, int type, const Scalar& s);
// 使用預先存在的資料定義的指定大小和類型的二維數組
cv::Mat mat(cv::Size sz, int type, void *data, size_t step = AUTO_STEP);
// 指定類型的多元資料
cv::Mat mat(int ndims, const int *sizes, int type);
// 使用預先存在的資料定義的指定類型的多元數組
cv::Mat mat(int ndims, const int* sizes, int type, void* data, size_t step = AUTO_STEP);
// 使用cv::Vec定義相同類型、大小為n的一維數組
cv::Mat mat(const cv::Vec<T, n>& vec, bool = copyData = true);
// 使用cv::Matx定義相同類型、大小為mxn的二維數組
cv::Mat mat(const cv::Matx<T, m, n>& vec, bool copyData = true);
// 使用STL vector定義相同類型的一維數組
cv::Mat mat(const std::vector<T>& vec, bool copyData = true);
// 使用zeros()函數定義指定大小和類型的cv::Mat(全為0)
cv::Mat mat = cv::Mat::zeros(int rows, int cols, int type);
// 使用ones()函數定義指定大小和類型的cv::Mat(全為0)
cv::Mat ma = cv::Mat::ones(int rows, int cols, int type);
// 使用eye()函數定義指定大小和類型的cv::Mat(恒等矩陣)
cv::Mat mat = cv::Mat::eye(int rows, int cols, int type);
像素值的讀寫
很多時候,我們需要讀取某個像素值,或者設定某個像素值;在更多的時候,
我們需要對整個圖像裡的所有像素進行周遊。OpenCV提供了多種方法來實作圖像的周遊。
方法一:at()函數
cv::Mat grayim(600, 800, CV_8UC1);
cv::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 )
{
cv::Vec3b pixel;
// 注意:opencv通道順序,BGR,非RGB
pixel[0] = i%255; // Blue
pixel[1] = j%255; // Green
pixel[2] = 0; // Red
colorim.at<Vec3b>(i,j) = pixel;
}
}
方法二:使用疊代器
// 周遊所有像素,并設定像素值
// 周遊所有像素,并設定像素值
cv::MatIterator_<uchar> grayit, grayend;
for(grayit = grayim.begin<uchar>(), grayend = grayim.end<uchar>();
grayit != grayend; ++grayit)
{
*grayit = rand()%255;
}
// 周遊所有像素,并設定像素值
cv::MatIterator_<cv::Vec3b> colorit, colorend;
for(colorit = colorim.begin<cv::Vec3b>(), colorend = colorim.end<cv::Vec3b>();
colorit != colorend; ++colorit)
{
(*colorit)[0] = rand()%255; // Blue
(*colorit)[1] = rand()%255; // Green
(*colorit)[2] = rand()%255; // Red
}
方法三:通過資料指針
cv::Mat grayim(600, 800, CV_8UC1);
cv::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 行首像素指針
cv::Vec3b * p = colorim.ptr<cv::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
}
}
圖像局部操作
選擇單行/單列
Mat Mat::row(int i) const
Mat Mat::col(int j) const
示例: A 矩陣的第i行,将這一行的所有元素都乘以2,然後指派給第j行
A.row(j) = A.row(i)*2;
選擇多行/多列
Range是OpenCV 中新增的類,該類有兩個關鍵變量star和end。Range 對
象可以用來表示矩陣的多個連續的行或者多個連續的列。其表示的範圍為從 start到end,包含start。
示例
// 建立一個機關陣
Mat A = Mat::eye(10, 10, CV_32S);
// 提取第 1 到 3 列(不包括 3)
Mat B = A(Range::all(), Range(1, 3));
// 提取B的第 5 至 9 行(不包括 9)
// 其實等價于C = A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range::all());
選擇指定區域
圖像中提取感興趣區域(Region of interest)有兩種方法:
- 方法一:使用構造函數
//建立寬度為 320,高度為 240 的 3 通道圖像
Mat img(Size(320, 240), CV_8UC3);
//roi 是表示 img 中 Rect(10, 10, 100, 100)區域的對象
Mat roi(img, Rect(10, 10, 100, 100));
- 方法二:使用括号運算符
Mat roi2 = img(Rect(10, 10, 100, 100));
當然也可以使用Range對象來定義感興趣區域,如下:
// 用括号運算符
Mat roi3 = img(Range(10, 100), Range(10, 100));
// 用構造函數
Mat roi4(img, Range(10, 100), Range(10, 100));
取對角線元素
矩陣的對角線元素可以使用cv::Mat類的diag()函數擷取:
Mat Mat::diag(int d) const
- 參數d=0時,表示取主對角線;
- 當參數d>0是,表示取主對角線下方的次對線,如當d=1 時,表示取主對角線下方,且緊貼主多角線的元素;
- 當參數d<0時,示取主對角線上方的次對角線。如同 row()和 col()函數, diag()函數也不進行記憶體複制操作,其複雜度也是O(1)。
cv::Mat矩陣支援的運算操作
cv::Mat表達式所支援的運算。
下面的清單中使用 A 和 B 表示 Mat 類型的對象,使用s表示Scalar對象,alpha 表示double值。
- 加法,減法,取負:A+B, A-B, A+s, A-s, s+A, s-A, -A
- 縮放取值範圍:A * alpha
- 矩陣對應元素的乘法和除法:A.mul(B),A/B,alpha/A
- 矩陣乘法:A*B(注意此處是矩陣乘法,而不是矩陣對應元素相乘)
- 矩陣轉置:A.t()
- 矩陣求逆和求僞逆:A.inv()
- 矩陣比較運算:A cmpop B,A cmpop alpha,alpha cmpop A。此處cmpop
- 可以是>,>=,==,!=,<=,<。如果條件成立,則結果矩陣(8U類型矩陣)的對應元素被置為255;否則置0
- 矩陣位邏輯運算: A logicop B, A logicop s, s logicop A, ~A,此處 logicop
- 可以是&,|和^
- 矩陣對應元素的最大值和最小值:min(A, B),min(A, alpha),max(A, B),max(A, alpha)
- 矩陣中元素的絕對值:abs(A)
- 叉積和點積:A.cross(B),A.dot(B)
對矩陣不了解的可以閱讀《線性代數矩陣以及Eigen庫的介紹、編譯和使用》中的矩陣操作解析。