• 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中,可以预定义名字空间:

    using namespace cv

       跟一般的cpp程序一样,对于类的参数传递都采用引用传递方式,获得较好的效率。类都有自己的构造函数和析构函数,防止内存的泄漏。并且默认的拷贝构造函数采用的是shallow copy(浅拷贝),若需要deap copy(深拷贝)可求助于cv::Mat的copyTo()方法。这些东西都是cpp的基础知识

    2 cv::Mat_

        cv::Mat_是一个模板类,声明在<opencv2/core/core.hpp>中。

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

        由于cv::Mat类中含有很多模板方法,这些参数类型要到运行期才能确定,但是这种灵活性却使得简单的调用代码复杂,因此就有了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方式进行遍历。调用cv::Mat类的模板方法ptr(int)获得图像矩阵的行指针。(2)三种不同效率的调用方式:slowest是由于两次读内存操作增加了时间;best通过位运算进行去尾操作,效率自然更高,但是必须限制为2的n次方。(3)由于是引用传递,若要保留输入图像image,则在输入参数中增加一个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()方法判断有没有额外的补零(如fft补零到2^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;
        }
    }
     

        这里给出了两种获得迭代器的方法:一种是直接调用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()方法。

  • 相关阅读:
    c:forTokens标签循环输出
    jsp转long类型为date,并且格式化
    spring中@Param和mybatis中@Param使用区别(暂时还没接触)
    734. Sentence Similarity 有字典数组的相似句子
    246. Strobogrammatic Number 上下对称的数字
    720. Longest Word in Dictionary 能连续拼接出来的最长单词
    599. Minimum Index Sum of Two Lists两个餐厅列表的索引和最小
    594. Longest Harmonious Subsequence强制差距为1的最长连续
    645. Set Mismatch挑出不匹配的元素和应该真正存在的元素
    409. Longest Palindrome 最长对称串
  • 原文地址:https://www.cnblogs.com/ahuo/p/3156377.html
Copyright © 2020-2023  润新知