前言
OpenCV1時代采用基于C語言接口構(gòu)建函數(shù)庫,使用名為IplImage的結(jié)構(gòu)體在內(nèi)存中存儲圖像,其問題在于需要用戶手動管理內(nèi)存,如果不手動釋放內(nèi)存會造成內(nèi)存泄漏。
OpenCV2引入面向?qū)ο缶幊趟枷耄尤肓艘粋€c 接口,使用Mat類數(shù)據(jù)結(jié)構(gòu)作為主打,可以實現(xiàn)自動內(nèi)存管理,且擴展性大大提高。
Mat概述
對于Mat類,首先要知道的是
1)不必手動為其開辟空間;
2)不必再在不需要時將空間釋放。
但手動做還也是可以的:大多數(shù)OpenCV函數(shù)仍會手動地為輸出數(shù)據(jù)開辟空間。當傳遞一個已經(jīng)存在的 Mat 對象時,開辟好的矩陣空間會被重用。
Mat是一個類,由兩個數(shù)據(jù)部分組成:矩陣頭(包含矩陣尺寸、存儲方法、存儲地址等)和一個指向存儲所有像素值矩陣的指針。
Mat類最重要的一點是淺拷貝和深拷貝問題。由于OpenCV處理圖像時很多時候沒有必要重新復(fù)制一份圖像矩陣,因而采用了引用計數(shù)機制。其思路是讓每個Mat對象有自己的信息頭,但共享一個圖像矩陣(矩陣指針指向同一地址)。賦值運算符和拷貝構(gòu)造函數(shù)只復(fù)制矩陣頭和矩陣指針,而不復(fù)制矩陣。
Mat A , C;
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 為矩陣開辟內(nèi)存
Mat B(A); // 拷貝構(gòu)造函數(shù)
C = A; // 賦值
這里A、B、C矩陣頭不同,但指向了相同的圖像矩陣,其中一個對象對矩陣數(shù)據(jù)進行改變也會影響其他對象。如果矩陣屬于多個Mat對象,由最后一個使用它的對象進行內(nèi)存清理。這通過引用計數(shù)來判斷,每復(fù)制一個Mat對象,計數(shù)器加一,每釋放一個計數(shù)器減一,當計數(shù)器值為0時矩陣就會被清理。
如果需要進行對象的深拷貝可以采用clone()函數(shù)或者copyTo()。
Mat A;
A = imread(argv[1], CV_LOAD_IMAGE_COLOR);
Mat B = A.clone();
Mat C;
A.copyTo(C);
Mat類的數(shù)據(jù)成員
/*
flag的詳細解釋可以看 https://blog.csdn.net/yiyuehuan/article/details/43701797
0-2位 depth:每一個像素的位數(shù),也就是每個通道的位數(shù),即數(shù)據(jù)類型(如CV_8U)
enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }
8U 表示 8 位無符號整數(shù),16S 表示 16 位有符號整數(shù),64F表示 64 位浮點數(shù)
3-11位 number of channels:代表通道數(shù)channels,最高512位
0-11位共同代表type:矩陣元素的類型,即通道數(shù)和數(shù)據(jù)類型(如CV_8UC3、CV_16UC2)
14位 continuity flag:代表Mat的內(nèi)存是否連續(xù)
15位 submat flag:代表該Mat是否為某一個Mat的submatrix
16-31位 the magic signature:用來區(qū)分Mat的類型,如果Mat和SparseMat
*/
int flags;
//矩陣的維數(shù),一般大于2
int dims;
//矩陣的行數(shù)與列數(shù),超過2維矩陣時(-1,-1)
int rows, cols;
//指向存放矩陣數(shù)據(jù)的內(nèi)存
uchar* data;
//用來控制ROI區(qū)域,來獲取一些圖像的局部切片,減少計算量或者特殊需求的。
const uchar* datastart;
const uchar* dataend;
const uchar* datalimit;
//如果需要創(chuàng)建一個新矩陣的內(nèi)存空間,會調(diào)用MatAllocator類作為分配符進行內(nèi)存的分配。
MatAllocator* allocator;
//interaction with UMat
//UMatData結(jié)構(gòu)體總有一個成員refcount:記錄了矩陣的數(shù)據(jù)被其他變量引用了多少次
UMatData* u;
//返回矩陣大小
MatSize size;
//矩陣元素尋址,step[i]表示第i維的總大小,單位字節(jié)
//對于2維矩陣:step[0]是矩陣中一行元素的字節(jié)數(shù),step[1]是矩陣中一個元素的字節(jié)數(shù)
//以下公式可以得到Mat中任意元素地址
//addr(M{i,j})=M.data M.step[0]?i M.step[1]?j;
MatStep step;
此外其他版本還存在以下成員:
elemSize :矩陣一個元素占用的字節(jié)數(shù),例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes。
elemSize1 :矩陣元素一個通道占用的字節(jié)數(shù),例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2 bytes = elemSize / channels。
Mat類的構(gòu)造函數(shù)
//1、默認構(gòu)造函數(shù),無參數(shù)
Mat::Mat();
//2、行數(shù)為rows,列數(shù)為cols,類型為type(如CV_8UC1、CV_16UC2)
Mat(int rows, int cols, int type);
//3、矩陣大小為size,類型為type
//注意size的構(gòu)造函數(shù)是Size_(_Tp _width,_Tp _height) 先列后行
Mat(Size size, int type);
//4、行數(shù)為rows,列數(shù)為cols(或矩陣大小為size),類型為type,所有元素初始化為s
//Scalar表示具有4個元素的數(shù)組,如Scalar(a,b,b),其原型為Scalar_<double>
Mat(int rows, int cols, int type, const Scalar& s);
Mat(Size size, int type, const Scalar& s);
//5、矩陣維數(shù)為ndims,sizes為指定ndims維數(shù)組形狀的整數(shù)數(shù)組,所有元素初始化為s
Mat(int ndims, const int* sizes, int type);
Mat(const std::vector<int>& sizes, int type);
Mat(int ndims, const int* sizes, int type, const Scalar& s);
Mat(const std::vector<int>& sizes, int type, const Scalar& s);
//6、拷貝構(gòu)造函數(shù),將m賦值給新創(chuàng)建的對象,淺拷貝
Mat(const Mat& m);
//7、行數(shù)為rows,列數(shù)為cols,類型為type,矩陣數(shù)據(jù)為data,直接使用data所指內(nèi)存,淺拷貝
Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);
Mat(Size size, int type, void* data, size_t step=AUTO_STEP);
Mat(int ndims, const int* sizes, int type, void* data, const size_t* steps=0);
Mat(const std::vector<int>& sizes, int type, void* data, const size_t* steps=0);
//8、創(chuàng)建的新圖像為m的一部分,范圍由rowRange和colRange指定,新圖像與m共用圖像數(shù)據(jù),淺拷貝
Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());
Mat(const Mat& m, const Range* ranges);
Mat(const Mat& m, const std::vector<Range>& ranges);
//創(chuàng)建的新圖像為m的一部分,具體的范圍由矩陣對象roi指定
//Rect的成員函數(shù)有x,y,width,height,分別為左上角點的坐標好矩陣寬和高
Mat(const Mat& m, const Rect& roi);
創(chuàng)建Mat對象
1、使用Mat()構(gòu)造函數(shù)
Mat M(2,2, CV_8UC3, Scalar(0,0,255));
需要指定行數(shù)、列數(shù)、存儲元素的數(shù)據(jù)類型以及每個矩陣點的通道數(shù)。
2、使用Mat()構(gòu)造函數(shù)2
int sz[3] = {2,2,2};
Mat L(3,sz, CV_8UC(1), Scalar::all(0));
常用于創(chuàng)建一個超過兩維的矩陣:指定維數(shù),然后傳遞一個指向一個數(shù)組的指針,這個數(shù)組包含每個維度的尺寸,其余的相同
3、為已存在IplImage指針創(chuàng)建信息頭(一般不用)
IplImage* img = cvLoadImage("greatwave.png", 1);
Mat mtx(img); // convert IplImage* -> Mat
4、利用create()函數(shù)
這個創(chuàng)建方法不能為矩陣設(shè)初值,它只是在改變尺寸時重新為矩陣數(shù)據(jù)開辟內(nèi)存
M.create(4,4, CV_8UC(2));//4X4的圖像矩陣,通道數(shù)為2,沒有初值
5、MATLAB形式的初始化方式: zeros(), ones(), eyes()
Mat E = Mat::eye(4, 4, CV_64F);//4X4的單位矩陣
Mat O = Mat::ones(2, 2, CV_32F);//2X2的全為1矩陣
Mat Z = Mat::zeros(3,3, CV_8UC1);//3X3的零矩陣
6、小矩陣使用逗號分隔式初始化
Mat C = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1); //3X3的單位矩陣
7、使用clone()或copyto()為已存在對象創(chuàng)建新信息頭
Mat RowClone = C.row(1).clone();//復(fù)制 C中的第2行[0,1,0]作為新矩陣(深拷貝)
遍歷Mat對象
首先假設(shè)需要對圖像將那些顏色空間縮減,如imgnew?=imgold?/10?10。對于較大的圖像,一般不是對每個像素點每個通道的值進行如上計算,而是預(yù)先計算所有可能的值,構(gòu)建查找表,然后利用查找表對其直接復(fù)制。
//構(gòu)建查找表table
int divideWith;
cin >> divideWith;
uchar table[256];
for (int i = 0; i < 256; i)
table[i] = divideWith* (i/divideWith);
1、ptr指針單像素單通道值訪問
即通過uchar* ptr =img.ptr<char>(i); 得到第i行首地址,然后采用指針運算( )或者操作符[]遍歷。
Mat& ScanImage(Mat& img,const uchar* const table)
{
CV_Assert(img.depth() != sizeof(uchar))//只接收uchar類型矩陣
int channels = img.channel();
int nRows = img.rows * channels;//注意行數(shù)乘以通道數(shù)
int nCols = img.cols;
if(img.isContinuous())//如果存儲連續(xù)則可以使用一次循環(huán)
{
nCols *= nRows;
nRows = 1;
}
uchar* ptr;
for(int i=0; i<nRows; i)
{
ptr = img.ptr<char>(i);
for( j=0; j<nCols; j)
{
ptr[j] = table[ptr[j]]
//*ptr = table[*ptr]
}
}
return I;
}
2、ptr指針單像素訪問
利用cv::Vec3b *ptr =img.ptr<cv::Vec3b>(i);得到第i行第一個像素的3個通道地址。
Mat& ScanImage(Mat& img,const uchar* const table)
{
CV_Assert(img.depth() != sizeof(uchar))//只接收uchar類型矩陣
int channels = img.channel();
int nRows = img.rows;//每一行
int nCols = img.cols;
Vec3b *ptr;
for(int i=0; i<nRows; i)
{
ptr = img.ptr<Vec3b>(i);
for( j=0; j<nCols; j)
{
ptr[j][0] = table[ptr[j][0]];
ptr[j][1] = table[ptr[j][1]];
ptr[j][2] = table[ptr[j][2]];
}
}
return I;
}
3、對data操作
data會從Mat中返回指向矩陣第一行第一列的指針。常用來檢查圖像是否被成功讀入(如果指針為NULL則表示無輸入)。當矩陣式連續(xù)存儲時,可以通過data遍歷矩陣。
也可以采用addr(M{i,j})=M.data M.step[0]?i M.step[1]?j;得到每個元素的地址,但是不常用。
if(I.isContinuous())
{
uchar* p = img.data;
for( unsigned int i =0; i < ncol * nrows; i)
*p = table[*p];
}
4、迭代器iterator訪問
使用迭代器操作像素,與STL庫用法相似,只需要獲得圖像矩陣的begin和end,然后增加迭代直至從begin到end。將*操作符添加在迭代指針前,即可訪問當前指向的內(nèi)容。
迭代器可以采用MatIterator_以及Mat的模板子類Mat_,它重載了operator()。
MatIterator_<uchar> it = img.begin<uchar>();
Mat_<uchar>::iterator it = img.begin<uchar>();
Mat& ScanImage(Mat& I,const uchar* const table)
{
CV_Assert(img.depth() != sizeof(uchar));
const int channels = img.channels();
switch(channels)
{
case 1:
{
Mat_<uchar>::iterator it = img.begin<uchar>();
Mat_<uchar>::iterator itend = img.end<uchar>();
for(;it != itend; it)
*it = table[*it];
break;
}
case 3:
{
Mat_<Vec3b>::iterator it = img.begin<uchar>();
Mat_<Vec3b>::iterator itend = img.end<uchar>();
for(; it != itend ; it)
{
(*it)[0] = table[(*it)[0]];
(*it)[1] = table[(*it)[1]];
(*it)[2] = table[(*it)[2]];
}
}
}
return img;
}
5、動態(tài)地址計算(at)
該方法一般用于獲取或更改圖像中的某個元素(或隨機元素),而不用于進行全圖掃描。它的基本用途是要確定你試圖訪問的元素的所在行數(shù)與列數(shù)。
在debug模式下該方法會檢查你的輸入坐標是否有效或者超出范圍,如果坐標有誤則會輸出一個標準的錯誤信息;在release模式下,它和其他的區(qū)別僅僅是對于矩陣的每個元素,都會獲取一個新的行指針,然后通過該指針和[]操作獲取元素。
Mat& ScanImage(Mat& img,const uchar* const table)
{
CV_Assert(img.depth() != sizeof(uchar));
const int channels = img.channels();
switch(channels)
{
case 1:
{
for(int i=0; i<img.rows; i)
for(int j=0; j<img.cols; i)
img.at<uchar>(i,j) = table[img.at<uchar>(i,j)];
break;
}
case 3:
{
for(int i=0; i<img.rows; i)
{
for(int j=0; j<img.cols; i)
{
img.at<vec3b>(i,j)[0] = table[img.at<vec3b>(i,j)[0]];
img.at<vec3b>(i,j)[1] = table[img.at<vec3b>(i,j)[1]];
img.at<vec3b>(i,j)[2] = table[img.at<vec3b>(i,j)[2]];
}
}
break;
}
}
return img;
}
6、LUT函數(shù)
LUT(Look up table)被官方推薦用于實現(xiàn)批量圖像元素查找和更改。對于修改像素值,OpenCV提供函數(shù)operationsOnArray:LUT()<lut>,直接實現(xiàn)該操作,而不需要掃描圖像。
//建立mat類對象用于查表
Mat LookUpTable(1,256,CV_8U);
uchar* p = LookUpTable.data;
for(int i=0; i<256; i)
p[i] = table[i];
//調(diào)用函數(shù)(I時輸入,J是輸出)
LUT(I, LookUpTable, J);
一般認為,采用ptr指針訪問圖像像素的效率更高;而迭代器則被認為是更安全的方式,僅需要獲得圖像矩陣的begin和end;at方法一般不用于對圖像進行遍歷;而調(diào)用LUT函數(shù)可以獲得最快的速度,因為OpenCV庫可以通過英特爾線程架構(gòu)啟用多線程。其他也存在一些比較偏的遍歷方法,不過這幾種最為常見,效率和安全性也相對較好。
來源:http://www./content-4-148551.html
|