zoukankan      html  css  js  c++  java
  • 学习OPENCV(二):操作像素

        本文主要参考了<OpenCV 2 Computer Vision Application Programming Cookbook>和<The OpenCV Reference Manual>。首先讨论了几个基本的结构:cv::Mat,cv::Mat_;随后讨论了遍历图像的两种方式:Pointer和Iterators,以及速度优化的注意点。

    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()方法。

  • 相关阅读:
    VC++学习(1):Windows程序内部运行原理
    VC++学习(9):定制应用程序外观
    VC++学习(7):对话框编程
    VC++学习(3):MFC框架程序剖析
    VC++学习(5):文本编程
    VC++学习(4):简单绘图
    DbgPrint/KdPrint输出格式控制
    常用的正则表达式
    使用geoserver+openLayers加载google地图
    retunValue与opener的用法
  • 原文地址:https://www.cnblogs.com/zjgtan/p/3002962.html
Copyright © 2011-2022 走看看