opencv Mat 像素操做

1 cv::Mat

    cv::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對代碼結構作了從新部署,全部的類和方法都定義在名字空間cv中,能夠預約義名字空間:ide

using namespace cv

   跟通常的cpp程序同樣,對於類的參數傳遞都採用引用傳遞方式,得到較好的效率。類都有本身的構造函數和析構函數,防止內存的泄漏。而且默認的拷貝構造函數採用的是shallow copy(淺拷貝),若須要deap copy(深拷貝)可求助於cv::Mat的copyTo()方法。這些東西都是cpp的基礎知識函數

2 cv::Mat_

    cv::Mat_是一個模板類,聲明在<opencv2/core/core.hpp>中。ui

template<typename _Tp> class CV_EXPORTS Mat_ : public Mat
{
public:
    //some specific methods
};

    因爲cv::Mat類中含有不少模板方法,這些參數類型要到運行期才能肯定,可是這種靈活性卻使得簡單的調用代碼複雜,所以就有了cv::Mat_類來簡化代碼。如spa

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操做爲例,指針方式代碼以下:rest

 
/**
* 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方式進行遍歷。調用cv::Mat類的模板方法ptr(int)得到圖像矩陣的行指針。(2)三種不一樣效率的調用方式:slowest是因爲兩次讀內存操做增長了時間;best經過位運算進行去尾操做,效率天然更高,可是必須限制爲2的n次方。(3)因爲是引用傳遞,若要保留輸入圖像image,則在輸入參數中增長一個result用於保存輸出圖像。code

    如下是更快的方式:orm

 
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()方法判斷有沒有額外的補零(如fft補零到2^n),若是是連續的,就能夠直接看成一維數組來處理。另外,在每個循環裏連續執行三次以提升效率。(忘細裏講應該跟時空局部性原理有關)blog

    如下是迭代器方式:

 
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;
    }
}
 

    這裏給出了兩種得到迭代器的方法:一種是直接調用cv::Mat的模板方法begin()和end();另外一種是經過cv::Mat_的begin()和end()。這一點跟stl庫是兼容的。注意對於彩色圖像,迭代器指向的cv::Vec3b類型的三元組。

4 Scanning an image with neighbor access

    方法仍是用的指針,由於效率高,但基本的東西仍是不變的,代碼以下:

 
/**
*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()函數,用於圖像顏色空間的轉換,在<opencv2/imgproc/imgproc.hpp>中聲明。這裏還用到了cv::saturate_cast()模板方法,保證獲得的值是在有意義的值域範圍內,好比消除濾波中的振鈴效應等等。

     opencv中還提供了cv::filter2D()函數來實現二維濾波,估計採用的是fft2()的方法,當模板(kernel)較大時採用這個函數,代碼以下:

 
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);
}
 

    這裏把模板就當成是一個圖像(實際上負值是沒有意義的)。關於濾波就有各類各樣的點,只能到頻域濾波在複習了。

5 Simple image arithmetic

    這裏實際上是opencv2提供的一些矩陣操做的函數。

    算術運算:cv::add(), cv::addWeighted(), cv::scaleAdd(); cv::subtract, cv::absdiff; cv::multiply; cv::divide, 還能夠經過mask參數來掩模不須要處理的位。

    位運算:cv::bitwise_and, cv::bitwise_or, cv::bitwise_xor, cv::bitwise_not

    cv::max, cv::min

    其餘運算:cv::sqrt, cv::pow, cv::abs, cv::cuberoot, cv::exp, cv::log

    上面這些函數都是針對矩陣的每個元素對應操做的。更方便的是,矩陣的加減乘除、bitwise operators….都被重載了。inv()求逆、t()求轉置、determinant()求行列式、norm()求範數、cross()求兩個向量的叉乘、dot()求兩個向量的點乘。

    當須要將一個多通道圖像分離時,調用cv::split()方法,用一個std::vector來保存中間量,最後又能夠調用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固然是in-place的引用方式,會改變輸入圖像的值。還有經過定義兩個方向上的cv::Range來實現,都差很少。顯然,操做符「()」是被重載的,返回一個子塊。還能夠經過行、列方式來指定,經過調用cv::Mat的rowRange()和colRange()方法。

相關文章
相關標籤/搜索