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