我是 OpenCV 乃至 C++ 的初學(xué)者,過去的兩個(gè)禮拜里剛把一個(gè) Matlab 程序轉(zhuǎn)化成使用了 OpenCV 的 C++ 代碼,過程中遇到了很多困難。這些困難有些來源于我對 Matlab 和 OpenCV 之間語言和類庫設(shè)計(jì)差異的不了解,有些是單純的不知道 Matlab 函數(shù)的 OpenCV 對照物造成的,還有一些是由于 OpenCV 并不具有一些功能的函數(shù)。我打算將這些天的所學(xué)所得寫下來,借鑒《NumPy for Matlab Users》,姑且將這個(gè)系列叫作《OpenCV for Matlab Users》。這篇是這個(gè)系列的第 2 篇,因?yàn)榇篌w結(jié)構(gòu)最早完成,所以也就先出來了。整個(gè)系列的大體安排如下:
- OpenCV for Matlab Users (1) - MATLAB 與 OpenCV / C++ 設(shè)計(jì)上的差異
- OpenCV for Matlab Users (2) - cv::Mat 類的屬性和方法
- OpenCV for Matlab Users (3) - MATLAB 函數(shù)的 OpenCV 對應(yīng)實(shí)現(xiàn)
- OpenCV for Matlab Users (4) - MATLAB 與 OpenCV / C++ 易搞混的語法
- OpenCV for Matlab Users (5) - OpenCV 自身易搞混的函數(shù)辨析
下面開始本章正文:
以我淺薄的 C++ 知識來看,一個(gè)對象包括成員變量和成員方法兩類,有些貌似是成員變量,其實(shí)是方法,這篇先按照是成員變量還是方法分為兩類。目前成員變量只有 5 個(gè):rows,cols,dims,data,step,還有一些比如像 channels,depth,elemSize,empty,isContinuous,size,total,type 貌似是成員變量,其實(shí)是沒有參數(shù)的成員方法。
1. 成員變量
-
rows
成員變量,返回矩陣的行數(shù)
int imgRows = img.rows;
std::cout<<"imgRows = "<<imgRows<<std::endl;
-
cols
是成員變量,返回矩陣的列數(shù)
int imgCols = img.cols;
std::cout<<"imgCols = "<<imgCols<<std::endl;
-
data
成員變量data事實(shí)上是指向已分配的內(nèi)存塊的指針,包括圖像數(shù)據(jù)。當(dāng)不存在數(shù)據(jù)時(shí),它被簡單設(shè)置為0.
uchar *data = img.data;
std::cout<<"*data = "<<(int)*data<<std::endl;
std::cout<<"data[0] = "<<(int)data[0]<<std::endl;
-
dims
成員變量,返回一個(gè)圖像的維數(shù),但是好奇怪,我以為會有 3 維的,結(jié)果返回的還是 2
int imgDims = img.dims;
std::cout<<"imgDims = "<<imgDims<<std::endl;
-
step
成員變量,代表以字節(jié)為單位的圖像的行寬,即列數(shù)(包括填補(bǔ)像素),即使你的圖像元素類型不是 uchar,step 仍然帶代表著行的字節(jié)數(shù)
int imgStep = img.step;
std::cout<<"imgStep = "<<imgStep<<std::endl;
2. 成員方法
-
at
成員函數(shù),at(int y, int x) 可以用來存取圖像元素。 但是必須在編譯期知道圖像的數(shù)據(jù)類型,因?yàn)?cv::Mat 可以存放任意數(shù)據(jù)類型的元素。所以使用 at 方法要指定數(shù)據(jù)類型,而且 at 方法本身不會進(jìn)行任何數(shù)據(jù)類型轉(zhuǎn)換。
cv::Vec3b vec3b = img.at<cv::Vec3b>(0,0);
uchar vec3b0 = img.at<cv::Vec3b>(0,0)[0];
uchar vec3b1 = img.at<cv::Vec3b>(0,0)[1];
uchar vec3b2 = img.at<cv::Vec3b>(0,0)[2];
std::cout<<"vec3b = "<<vec3b<<std::endl;
std::cout<<"vec3b0 = "<<(int)vec3b0<<std::endl;
std::cout<<"vec3b1 = "<<(int)vec3b1<<std::endl;
std::cout<<"vec3b2 = "<<(int)vec3b2<<std::endl;
cv::Vec3b,即由三個(gè) unsigned char 組成的向量。
-
channels
方法,返回通道數(shù)
int imgChannels = img.channels();
std::cout<<"imgChannels = "<<imgChannels<<std::endl;
-
clone
貌似也是深拷貝,但是除了返回值類型不同,clone 返回 cv::Mat,而 copyTo 是 void 類型,其余 clone 跟 copyTo 有什么區(qū)別我還沒搞清楚
cv::Mat cloneMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat cloneMat2 = cloneMat1.clone();
cloneMat1.at<double>(0,0) = 0.0;
std::cout<<"cloneMat1 = "<<cloneMat1<<std::endl;
std::cout<<"cloneMat2 = "<<cloneMat2<<std::endl;
-
col
返回指定的一列(從 0 開始)
cv::Mat colMat = img.col(0);
std::cout<<"colMat.size() = "<<colMat.size()<<std::endl;
-
colRange
方法,返回若干列組成的矩陣
cv::Mat colRangeImg = img.colRange(imgCols / 2, imgCols);
cv::imshow("colRangeImg",colRangeImg);
cv::waitKey();
-
convertTo
方法,在縮放或不縮放的情況下轉(zhuǎn)換為另一種指定的數(shù)據(jù)類型
cv::Mat doubleImg;
img.convertTo(doubleImg, CV_64FC4);
std::cout<<"doubleImg.channels() = "<<doubleImg.channels()<<std::endl;
cv::imshow("doubleImg", doubleImg);
cv::waitKey();
-
copyTo
把矩陣深拷貝賦值給另一個(gè)矩陣
cv::Mat copyMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat copyMat2;
copyMat1.copyTo(copyMat2);
copyMat1.at<double>(0,0) = 0.0;
std::cout<<"copyMat1 = "<<copyMat1<<std::endl;
std::cout<<"copyMat2 = "<<copyMat2<<std::endl;
-
create
create 類似Mat(nrows,ncols,type [,fillValue])構(gòu)造函數(shù),把當(dāng)前對象重新綁定到一個(gè)新的矩陣對象上
cv::Mat createMat = cv::Mat::ones(3,4,CV_64F);
createMat.create(5,6, CV_8UC(2));
std::cout<<"createMat = "<<createMat<<std::endl;
-
cross
計(jì)算兩個(gè) 3 元素向量的叉乘積,注意,必須是 3 個(gè)元素的。
cv::Mat crossVec1 = cv::Mat::ones(1,3,CV_64F);
cv::Mat crossVec2 = cv::Mat::ones(1,3,CV_64F);
cv::Mat crossMat = crossVec1.cross(crossVec2);
std::cout<<"crossMat = "<<crossMat<<std::endl;
-
depth
方法,該方法返回矩陣元素深度(每個(gè)單獨(dú)的通道類型)的標(biāo)識符。
int imgDepth = img.depth();
std::cout<<"imgDepth = "<<imgDepth<<std::endl;
-
diag
抽取矩陣對角線上的元素,返回的是一個(gè) min(rows,cols) * channels 的矩陣。
cv::Mat diagMat = img.diag();
//std::cout<<"diagMat = "<<diagMat<<std::endl;
std::cout<<"diagMat.rows = "<<diagMat.rows<<std::endl;
std::cout<<"diagMat.cols = "<<diagMat.cols<<std::endl;
-
dot
內(nèi)積運(yùn)算,最后返回一個(gè) double 類型的數(shù)。兩個(gè)矩陣必須是相同大小的。如果是非單行或者非單列矩陣,那么結(jié)果相當(dāng)于把兩個(gè)矩陣?yán)梢恍谢蛘咭涣泻笞鰞?nèi)積。多個(gè)通道的,每個(gè)通道的內(nèi)積會被加起來。
cv::Mat onesMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat onesMat2 = cv::Mat::ones(3,4,CV_64F);
double dotVal = onesMat1.dot(onesMat2);
std::cout<<"dotVal = "<<dotVal<<std::endl;
-
elemSize
方法,返回圖像(矩陣)像素(元素)大小 (以字節(jié)為單位),因?yàn)槲覀冞@里讀入的 PNG 格式,有 4 個(gè)通道,每個(gè)通道都是 uchar 類型的,所以是返回 4 個(gè)字節(jié)。
int elementSize = img.elemSize();
std::cout<<"elementSize = "<<elementSize<<std::endl;
-
elemSize1
方法,以字節(jié)為單位返回每個(gè)矩陣元素通道的大小,結(jié)果也就是上面的 elemSize 方法得到的除以通道數(shù)。
int elementSize1 = img.elemSize1();
std::cout<<"elementSize1 = "<<elementSize1<<std::endl;
-
empty
方法,如果是個(gè)空矩陣,則返回 true。
bool isEmpty = img.empty();
std::cout<<"isEmpty = "<<isEmpty<<std::endl;
-
eye
返回單位矩陣,跟 Matlab 類似,先是行數(shù),然后列數(shù),最后指定元素類型
cv::Mat eyeMat = cv::Mat::eye(3,4,CV_64F);
std::cout<<"eyeMat = "<<eyeMat<<std::endl;
-
isContinuous
方法,返回矩陣是否連續(xù)
bool isContinuous = img.isContinuous();
std::cout<<"isContinuous = "<<isContinuous<<std::endl;
-
ones
方法,產(chǎn)生全一矩陣,跟matlab的ones類似,先是行數(shù),然后是列數(shù),就是必須要顯式地指定數(shù)據(jù)元素(像素)類型
cv::Mat onesMat = cv::Mat::ones(4, 3, CV_64F);
std::cout<<"onesMat = "<<onesMat<<std::endl;
-
ptr
函數(shù),為了簡化指針運(yùn)算,ptr 函數(shù)可以得到圖像給定行的首地址。ptr 函數(shù)是一個(gè)模板函數(shù),它返回第 j 行的首地址:
uchar *ptr = img.ptr<uchar>(0);
std::cout<<"*ptr = "<<(int)*ptr<<std::endl;
std::cout<<"ptr[0] = "<<(int)ptr[0]<<std::endl;
-
reshape
方法,返回一個(gè)改變了形狀的矩陣,但要注意的是,跟 Matlab 不同,OpenCV 中的 reshape 的第一個(gè)參數(shù)是 通道數(shù),第二個(gè)參數(shù)是 行數(shù),且只有這兩個(gè)參數(shù),如果想維持通道數(shù)不變,那對應(yīng)位置填 0 即可。
cv::Mat reshapeMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat reshapeMat2 = reshapeMat1.reshape(0, 2);
std::cout<<"reshapeMat1 = "<<reshapeMat1<<std::endl;
std::cout<<"reshapeMat2 = "<<reshapeMat2<<std::endl;
-
row
方法,返回矩陣特定的某一行(行號從 0 開始)
cv::Mat rowMat = img.row(0);
std::cout<<"rowMat.size() = "<<rowMat.size()<<std::endl;
-
rowRange
方法,取規(guī)定的行,返回一個(gè)子矩陣
int imgRows = img.rows;
std::cout<<"imgRows = "<<imgRows<<std::endl;
-
setTo
方法,將矩陣元素都設(shè)置為某個(gè)值
cv::Mat setMat = cv::Mat::ones(3,4,CV_64F);
setMat.setTo(cv::Scalar(0));
std::cout<<"setMat = "<<setMat<<std::endl;
setMat.row(0).setTo(2);
std::cout<<"setMat = "<<setMat<<std::endl;
-
size
方法,返回一個(gè) cv::Size 對象,使用情形如下:
cv::Size imgSize = img.size();
int imgHeight = imgSize.height;
int imgWidth = imgSize.width;
std::cout<<"imgSize = "<<imgSize<<std::endl;
std::cout<<"imgHeight = "<<imgHeight<<std::endl;
std::cout<<"imgWidth = "<<imgWidth<<std::endl;
需要注意的是,它只能返回2維的尺寸,還有就是它先返回的是列數(shù),然后才是行數(shù),行數(shù)列數(shù)分別可以用 height 和 width 兩個(gè)成員變量得到。
-
t
函數(shù),返回當(dāng)前對象的轉(zhuǎn)置矩陣
cv::Mat tMat1 = cv::Mat::ones(3,4,CV_64F);
tMat1.at<double>(1,2) = 0.0;
cv::Mat tMat2 = tMat1.t();
std::cout<<"tMat1 = "<<tMat1<<std::endl;
std::cout<<"tMat2 = "<<tMat2<<std::endl;
-
total
方法,該方法返回?cái)?shù)組元素(如果該數(shù)組表示圖像的像素?cái)?shù))的數(shù)目,注意這個(gè)跟 Matlab 里面的 numel 可不同,Matlab 是將對象當(dāng)作是矩陣(張量)來處理,只不過圖像剛好可以用矩陣和張量來表示,本質(zhì)上Matlab還是一個(gè)通用的數(shù)學(xué)軟件,而 OpenCV 是講對象當(dāng)作圖像來看待,是專門為圖像而設(shè)計(jì)的,所以 即使這個(gè)圖像有 RGB 3 個(gè)通道,total 返回的也只是 height * width,不過這也對,這就是像素?cái)?shù)。
int imgPixels = img.total();
std::cout<<"imgPixels = "<<imgPixels<<std::endl;
-
type
方法,返回圖像像素的數(shù)據(jù)類型
int imgType = img.type();
std::cout<<"imgType = "<<imgType<<std::endl;
具體返回的數(shù)值對應(yīng)什么類型,可以看這篇文章《LIST OF MAT TYPE IN OPENCV》
-
zeros
方法,產(chǎn)生全零矩陣,跟 Matlab 的 zeros 類似,先是行數(shù),然后是列數(shù),就是必須要顯式地指定數(shù)據(jù)元素(像素)類型
cv::Mat zerosMat = cv::Mat::zeros(4, 3, CV_64F);
std::cout<<"zerosMat = "<<zerosMat<<std::endl;
3. 內(nèi)存管理相關(guān)
有關(guān)引用計(jì)數(shù)的一些函數(shù),作為普通用戶,我們并不需要了解。
-
addref
該方法遞增與矩陣數(shù)據(jù)關(guān)聯(lián)的引用計(jì)數(shù),通常情況下,為避免內(nèi)存泄漏,不應(yīng)顯式調(diào)用該方法。
-
release
在必要的情況下,遞減引用計(jì)數(shù)并釋放該矩陣。
-
~Mat
4. 待日后補(bǔ)充
下面是一些我目前還沒有用到,或者在 Reference Manual 里面搜不到相關(guān)介紹的,留待日后補(bǔ)充:
-
adjustROI
-
AUTO_STEP
-
copySize
-
checkVector
-
CONTINUOUS_FLAG
-
SUBMATRIX_FLAG
-
allocate
-
assignTo
-
step1
-
deallocate
-
datastart
-
datalimit
-
dataend
-
flags
-
initEmpty
-
isSubmatrix
-
locateROI
-
MAGIC_VAL
-
operator cv::Matx<_Tp, m, n>
-
operator cv::Vec<_Tp, n>
-
operator_CvMat
-
operator CvMatND
-
operator IplImage
-
operator std::vector<_Tp, std::allocator><_Tp>>
-
operator()
-
operator=
-
push_back_
-
pop_back
-
refcount
-
reserve
-
mul
-
inv
-
begin
-
end
集合終止位置的迭代器,但是end 方法得到的迭代器其實(shí)已經(jīng)超出了集合。這也意味著迭代過程必須在迭代器到達(dá)這個(gè)位置時(shí)結(jié)束
-
push_back
方法,如果對矩陣做,是添加行的。
還有一些函數(shù),根據(jù) Reference Manual 的應(yīng)該是采用 cv::resize() 這樣的方式調(diào)用,比如
resize
最后,上述代碼整體如下:
#include <opencv2/opencv.hpp>
#include <iostream>
int main(int argc, char **argv)
{
std::string imgPathStr("D:\\openCV_build\\doc\\opencv-logo2.png");
cv::Mat img = cv::imread(imgPathStr,-1); // Read image
//cv::imshow("img",img);
//cv::waitKey();
std::vector<cv::Mat> rgbImg;
cv::split(img,rgbImg);
cv::Mat rgbImg_R = rgbImg[2];
cv::Mat rgbImg_G = rgbImg[1];
cv::Mat rgbImg_B = rgbImg[0];
// Attributes
int imgRows = img.rows;
std::cout<<"imgRows = "<<imgRows<<std::endl;
int imgCols = img.cols;
std::cout<<"imgCols = "<<imgCols<<std::endl;
// Methods
cv::Mat rowRangeImg = img.rowRange(imgRows / 2, imgRows);
//cv::imshow("rowRangeImg",rowRangeImg);
//cv::waitKey();
cv::Mat colRangeImg = img.colRange(imgCols / 2, imgCols);
//cv::imshow("colRangeImg",colRangeImg);
//cv::waitKey();
cv::Size imgSize = img.size();
int imgHeight = imgSize.height;
int imgWidth = imgSize.width;
std::cout<<"imgSize = "<<imgSize<<std::endl;
std::cout<<"imgHeight = "<<imgHeight<<std::endl;
std::cout<<"imgWidth = "<<imgWidth<<std::endl;
int imgChannels = img.channels();
std::cout<<"imgChannels = "<<imgChannels<<std::endl;
int imgType = img.type();
std::cout<<"imgType = "<<imgType<<std::endl;
int imgPixels = img.total();
std::cout<<"imgPixels = "<<imgPixels<<std::endl;
bool isContinuous = img.isContinuous();
std::cout<<"isContinuous = "<<isContinuous<<std::endl;
int elementSize = img.elemSize();
std::cout<<"elementSize = "<<elementSize<<std::endl;
int elementSize1 = img.elemSize1();
std::cout<<"elementSize1 = "<<elementSize1<<std::endl;
int imgDepth = img.depth();
std::cout<<"imgDepth = "<<imgDepth<<std::endl;
bool isEmpty = img.empty();
std::cout<<"isEmpty = "<<isEmpty<<std::endl;
int imgDims = img.dims;
std::cout<<"imgDims = "<<imgDims<<std::endl;
cv::Mat onesMat = cv::Mat::ones(4, 3, CV_64F);
std::cout<<"onesMat = "<<onesMat<<std::endl;
cv::Mat zerosMat = cv::Mat::zeros(4, 3, CV_64F);
std::cout<<"zerosMat = "<<zerosMat<<std::endl;
int imgStep = img.step;
std::cout<<"imgStep = "<<imgStep<<std::endl;
cv::Mat doubleImg;
img.convertTo(doubleImg, CV_64FC4);
std::cout<<"doubleImg.channels() = "<<doubleImg.channels()<<std::endl;
//cv::imshow("doubleImg", doubleImg);
//cv::waitKey();
cv::Mat createMat = cv::Mat::ones(3,4,CV_64F);
createMat.create(5,6, CV_8UC(2));
//std::cout<<"createMat = "<<createMat<<std::endl;
cv::Mat copyMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat copyMat2;
copyMat1.copyTo(copyMat2);
copyMat1.at<double>(0,0) = 0.0;
//std::cout<<"copyMat1 = "<<copyMat1<<std::endl;
//std::cout<<"copyMat2 = "<<copyMat2<<std::endl;
cv::Mat cloneMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat cloneMat2 = cloneMat1.clone();
cloneMat1.at<double>(0,0) = 0.0;
//std::cout<<"cloneMat1 = "<<cloneMat1<<std::endl;
//std::cout<<"cloneMat2 = "<<cloneMat2<<std::endl;
cv::Mat eyeMat = cv::Mat::eye(3,4,CV_64F);
std::cout<<"eyeMat = "<<eyeMat<<std::endl;
cv::Mat diagMat = img.diag();
//std::cout<<"diagMat = "<<diagMat<<std::endl;
std::cout<<"diagMat.rows = "<<diagMat.rows<<std::endl;
std::cout<<"diagMat.cols = "<<diagMat.cols<<std::endl;
cv::Mat rowMat = img.row(0);
std::cout<<"rowMat.size() = "<<rowMat.size()<<std::endl;
cv::Mat colMat = img.col(0);
std::cout<<"colMat.size() = "<<colMat.size()<<std::endl;
cv::Mat onesMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat onesMat2 = cv::Mat::ones(3,4,CV_64F);
double dotVal = onesMat1.dot(onesMat2);
std::cout<<"dotVal = "<<dotVal<<std::endl;
cv::Mat crossVec1 = cv::Mat::ones(1,3,CV_64F);
cv::Mat crossVec2 = cv::Mat::ones(1,3,CV_64F);
cv::Mat crossMat = crossVec1.cross(crossVec2);
std::cout<<"crossMat = "<<crossMat<<std::endl;
cv::Mat tMat1 = cv::Mat::ones(3,4,CV_64F);
tMat1.at<double>(1,2) = 0.0;
cv::Mat tMat2 = tMat1.t();
std::cout<<"tMat1 = "<<tMat1<<std::endl;
std::cout<<"tMat2 = "<<tMat2<<std::endl;
cv::Mat reshapeMat1 = cv::Mat::ones(3,4,CV_64F);
cv::Mat reshapeMat2 = reshapeMat1.reshape(0, 2);
std::cout<<"reshapeMat1 = "<<reshapeMat1<<std::endl;
std::cout<<"reshapeMat2 = "<<reshapeMat2<<std::endl;
cv::Mat setMat = cv::Mat::ones(3,4,CV_64F);
setMat.setTo(cv::Scalar(0));
std::cout<<"setMat = "<<setMat<<std::endl;
setMat.row(0).setTo(2);
std::cout<<"setMat = "<<setMat<<std::endl;
uchar *data = img.data;
std::cout<<"*data = "<<(int)*data<<std::endl;
std::cout<<"data[0] = "<<(int)data[0]<<std::endl;
uchar *ptr = img.ptr<uchar>(0);
std::cout<<"*ptr = "<<(int)*ptr<<std::endl;
std::cout<<"ptr[0] = "<<(int)ptr[0]<<std::endl;
cv::Vec3b vec3b = img.at<cv::Vec3b>(0,0);
uchar vec3b0 = img.at<cv::Vec3b>(0,0)[0];
uchar vec3b1 = img.at<cv::Vec3b>(0,0)[1];
uchar vec3b2 = img.at<cv::Vec3b>(0,0)[2];
std::cout<<"vec3b = "<<vec3b<<std::endl;
std::cout<<"vec3b0 = "<<(int)vec3b0<<std::endl;
std::cout<<"vec3b1 = "<<(int)vec3b1<<std::endl;
std::cout<<"vec3b2 = "<<(int)vec3b2<<std::endl;
std::system("PAUSE");
return 0;
}
初寫于 2015-04-28,未完待續(xù)。
首發(fā)于 Yimian Dai's Homepage,轉(zhuǎn)載請注明出處。
參考資料:
OpenCV參考手冊之Mat類詳解(一)
OpenCV參考手冊之Mat類詳解(二)
OpenCV參考手冊之Mat類詳解(三)
《OpenCV 2 計(jì)算機(jī)視覺編程手冊》