1 cv::Matcv::Mat是一個n維矩陣類,聲明在<opencv2/core/core.hpp>中。 class CV_EXPORTS Mat { public: //a lot of methods … /*! includes several bit-fields: - the magic signature - continuity flag - depth - number of channels */ int flags; //! the matrix dimensionality, >= 2 int dims; //! the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions int rows, cols; //! pointer to the data uchar* data; //! pointer to the reference counter; // when matrix points to user-allocated data, the pointer is NULL int* refcount; //ohter members … }; 由于OpenCV 2對代碼結(jié)構(gòu)做了重新部署,所有的類和方法都定義在名字空間cv中,可以預(yù)定義名字空間: using namespace cv 跟一般的cpp程序一樣,對于類的參數(shù)傳遞都采用引用傳遞方式,獲得較好的效率。類都有自己的構(gòu)造函數(shù)和析構(gòu)函數(shù),防止內(nèi)存的泄漏。并且默認(rèn)的拷貝構(gòu)造函數(shù)采用的是shallow copy(淺拷貝),若需要deap copy(深拷貝)可求助于cv::Mat的copyTo()方法。這些東西都是cpp的基礎(chǔ)知識 2 cv::Mat_cv::Mat_是一個模板類,聲明在<opencv2/core/core.hpp>中。 template<typename _Tp> class CV_EXPORTS Mat_ : public Mat { public: //some specific methods }; 由于cv::Mat類中含有很多模板方法,這些參數(shù)類型要到運(yùn)行期才能確定,但是這種靈活性卻使得簡單的調(diào)用代碼復(fù)雜,因此就有了cv::Mat_類來簡化代碼。如 cv::Mat image = cv::imread('img.jpg'); image.at<uchar>(j, i) = 255; cv::Mat_<uchar> im2 = image; im2(j, i) = 255; 代碼明顯好看了。 3 Scanning an image以color Reduction操作為例,指針方式代碼如下: /** * An example of color reduction for scanning an image with pointers * div = 2^n * */ void colorReduce(const cv::Mat& image, cv::Mat& result, int div) { int nl = image.rows; int nc = image.cols * image.channels(); if (image.isContinuous()) { nc = nc * nl; nl = 1; } int n = static_cast<int>( log(static_cast<double>(div)) / log(2.0)); for (int j = 0; j < nl; j++) { // get the addresses of input and output row const uchar *data_in = image.ptr<uchar>(j); //give you the address of an image row uchar *data_out = result.ptr<uchar>(j); for (int i = 0; i < nc; i++) { //slowest data[i] = data[i] - data[i] % div + div / 2; //middle data[i] = data[i] / div * div + div / 2; //best uchar mask = 0xFF << n; //div = 16, n = 4, mask = 0xF0 data_out[i] = (data_in[i] & mask) + div / 2; //data[i] - data[i] % div + div / 2 } } } (1)上面是采用Pointer方式進(jìn)行遍歷。調(diào)用cv::Mat類的模板方法ptr(int)獲得圖像矩陣的行指針。(2)三種不同效率的調(diào)用方式:slowest是由于兩次讀內(nèi)存操作增加了時間;best通過位運(yùn)算進(jìn)行去尾操作,效率自然更高,但是必須限制為2的n次方。(3)由于是引用傳遞,若要保留輸入圖像image,則在輸入?yún)?shù)中增加一個result用于保存輸出圖像。 以下是更快的方式: void colorReduce_f(cv::Mat& image, int div) { int nl = image.rows; int nc = image.cols; if (image.isContinuous()) { nc = nc * nl; nl = 1; } int n = static_cast<int>( log(static_cast<double>(div)) / log(2.0)); uchar mask = 0xff << n; for (int j = 0; j < nl; j++) { uchar *data = image.ptr<uchar>(j); for (int i = 0; i < nc; i++) { *data++ = *data & mask + div / 2; *data++ = *data & mask + div / 2; *data++ = *data & mask + div / 2; } } } 其中,isContinuous()方法判斷有沒有額外的補(bǔ)零(如fft補(bǔ)零到2^n),如果是連續(xù)的,就可以直接當(dāng)作一維數(shù)組來處理。另外,在每一個循環(huán)里連續(xù)執(zhí)行三次以提高效率。(忘細(xì)里講應(yīng)該跟時空局部性原理有關(guān)) 以下是迭代器方式: void colorReduce_2(cv::Mat& image, int div) { //obtain iterator cv::Mat_<cv::Vec3b>::iterator iter = image.begin<cv::Vec3b>(); //a template method cv::Mat_<cv::Vec3b>::iterator iterd = image.end<cv::Vec3b>(); //a template method //do not use template method, more efficient cv::Mat_<cv::Vec3b> cimage = image; cv::Mat_<cv::Vec3b>::iterator iter = cimage.begin(); cv::Mat_<cv::Vec3b>::iterator iterd = cimage.end(); for (; iter != iterd; ++iter) { (*iter)[0] = (*iter)[0] / div * div + div / 2; (*iter)[1] = (*iter)[1] / div * div + div / 2; (*iter)[2] = (*iter)[2] / div * div + div / 2; } } 這里給出了兩種獲得迭代器的方法:一種是直接調(diào)用cv::Mat的模板方法begin()和end();另一種是通過cv::Mat_的begin()和end()。這一點(diǎn)跟stl庫是兼容的。注意對于彩色圖像,迭代器指向的cv::Vec3b類型的三元組。 4 Scanning an image with neighbor access方法還是用的指針,因?yàn)樾矢?,但基本的東西還是不變的,代碼如下: /** *An example of Sharpen for scanning an image with neighbor access */ void sharpen(const cv::Mat& image2, cv::Mat& result) { cv::Mat image; cv::cvtColor(image2, image, CV_BGR2GRAY); result.create(image.size(), image.type()); for (int j = 1; j < image.rows - 1; j++) { const uchar* previous = image.ptr<const uchar>(j - 1); const uchar* current = image.ptr<const uchar>(j); const uchar* next = image.ptr<const uchar>(j + 1); uchar* output = result.ptr<uchar>(j); for (int i = 1; i < image.cols - 1; i++) { *output++ = cv::saturate_cast<uchar> ( 5 * current[i] - current[i - 1] -current[i + 1] - previous[i] - next[i]); } } result.row(0).setTo(cv::Scalar(0)); result.row(result.rows - 1).setTo(cv::Scalar(0)); result.col(0).setTo(cv::Scalar(0)); result.col(result.cols - 1).setTo(cv::Scalar(0)); } 這是一個基于拉普拉斯算子的空間域銳化工作,模板(kernel)為一個3階矩陣。這里用到了cv::cvtColor()函數(shù),用于圖像顏色空間的轉(zhuǎn)換,在<opencv2/imgproc/imgproc.hpp>中聲明。這里還用到了cv::saturate_cast()模板方法,保證得到的值是在有意義的值域范圍內(nèi),比如消除濾波中的振鈴效應(yīng)等等。 opencv中還提供了cv::filter2D()函數(shù)來實(shí)現(xiàn)二維濾波,估計采用的是fft2()的方法,當(dāng)模板(kernel)較大時采用這個函數(shù),代碼如下: void sharpen2D(const cv::Mat& image2, cv::Mat& result) { cv::Mat image; cv::cvtColor(image2, image, CV_BGR2GRAY); result.create(image.size(), image.type()); cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0)); kernel.at<float>(1, 1) = 5.0; kernel.at<float>(0, 1) = -1.0; kernel.at<float>(2, 1) = -1.0; kernel.at<float>(1, 0) = -1.0; kernel.at<float>(1, 2) = -1.0; //filter the image cv::filter2D(image, result, image.depth(), kernel); } 這里把模板就當(dāng)成是一個圖像(實(shí)際上負(fù)值是沒有意義的)。關(guān)于濾波就有各種各樣的點(diǎn),只能到頻域?yàn)V波在復(fù)習(xí)了。 5 Simple image arithmetic這里其實(shí)是opencv2提供的一些矩陣操作的函數(shù)。 算術(shù)運(yùn)算:cv::add(), cv::addWeighted(), cv::scaleAdd(); cv::subtract, cv::absdiff; cv::multiply; cv::divide, 還可以通過mask參數(shù)來掩模不需要處理的位。 位運(yùn)算:cv::bitwise_and, cv::bitwise_or, cv::bitwise_xor, cv::bitwise_not cv::max, cv::min 其他運(yùn)算:cv::sqrt, cv::pow, cv::abs, cv::cuberoot, cv::exp, cv::log 上面這些函數(shù)都是針對矩陣的每一個元素對應(yīng)操作的。更方便的是,矩陣的加減乘除、bitwise operators….都被重載了。inv()求逆、t()求轉(zhuǎn)置、determinant()求行列式、norm()求范數(shù)、cross()求兩個向量的叉乘、dot()求兩個向量的點(diǎn)乘。 當(dāng)需要將一個多通道圖像分離時,調(diào)用cv::split()方法,用一個std::vector來保存中間量,最后又可以調(diào)用cv::merge()方法合成,代碼如下: std::vector<cv::Mat> planes; cv::split(image1, planes); planes[0] += image2; cv::merge(planes, result); 6 Region of interest直接上代碼吧: void addROI(cv::Mat& image, cv::Mat& logo) { cv::Mat imageROI; imageROI = image(cv::Rect(385, 270, logo.cols, logo.rows)); //基本的相加方式 cv::addWeighted(imageROI, 1.0, logo, 3.0, 0., imageROI); //掩模方式,將logo有值的位置上的image值置零 cv::Mat mask = cv::imread("..\\images\\logo.bmp", 0); logo.copyTo(imageROI, mask); } 在獲取ROI時使用了cv::Rect類表示一個矩形框,包括偏移、大小屬性。imageROI當(dāng)然是in-place的引用方式,會改變輸入圖像的值。還有通過定義兩個方向上的cv::Range來實(shí)現(xiàn),都差不多。顯然,操作符“()”是被重載的,返回一個子塊。還可以通過行、列方式來指定,通過調(diào)用cv::Mat的rowRange()和colRange()方法。 |
|