zoukankan      html  css  js  c++  java
  • 【从零学习openCV】opecv操作像素

    1. 存取像素值


    在opencv中能够直接对cv::Mat类型的图像调用at函数读取或赋值某个像素,我们用个简单的案例来说明:
    //在一张图像上增加椒盐噪声,image为输入图像。n为噪点个数
    void salt(Mat &image, int n)
    {
        for(int k = 0;k < n;k++)
        {
            //随机产生白色噪点
            int i = qrand()%image.cols;
            int j = qrand()%image.rows;
            //假设是灰度图每一个像素的存取类型为uchar,即8bit整型数
            if(image.channels() == 1){     
                image.at<uchar>(j,i) = 255;
            }
            //彩色图像有三个通道,像素存取类型为cv::Vec3b,即由三个uchar组成的向量。这里用下标[i]訪问每一个通道
            else{
                image.at<Vec3b>(j,i)[0] = 255;
                image.at<Vec3b>(j,i)[1] = 255;
                image.at<Vec3b>(j,i)[2] = 255;
            }
        }
    }
    效果例如以下:


    能够看到有非常多白色的噪声点,像雪花一样^_^



    2. 指针遍历图像

    我们用颜色缩减函数来说明用行首地址的方式遍历整个图像的像素。颜色缩减就是将每一个通道的颜色数减少,假设每一个通道的强度都是有unsigned char表示。那么就有256*256*256个颜色数目,假设将每一个通道的颜色数减少为原先的1/8,那个总颜色数就是32*32*32。大概就是这个意思。
    //颜色缩减函数,image为输入图像。div为缩减的倍数
    void colorReduce(Mat&image, int div = 64)
    {
        int nl = image.rows; //图像的行数
        //图像每行的像素数
        int nc = image.cols * image.channels();
        for(int j =0;j<nl;j++)
        {
            //得到第j行的首地址
            uchar* data = image.ptr<uchar>(j);
            //遍历每行的像素
            for(int i =0;i<nc;i++)
            {
               data[i] = data[i]/div*div;    //将每一个像素值都变为div的倍数,即将颜色数缩减了div倍
            }
        }
    }
    效果例如以下:



    当输入图像为连续的时候,即没有对图像行尾填补元素,这时就能够将整个图像看成是一个长为W*H的一维数组。即用第一行的行首指针就能遍历到整个图像的像素:
    //颜色缩减函数。image为输入图像,div为缩减的倍数
    void colorReduce(Mat&image, int div = 64)
    {
        int nl = image.rows; //图像的行数
        //图像每行的像素数
        int nc = image.cols * image.channels();
    
        //假设图像连续
        if(image.isContinuous())
        {
            //reshape函数用于改变矩阵维度
            //图像行数为1,列数为原先的行数乘上列数
            image.reshape(1,image.cols*image.rows);
        }
    
        for(int j =0;j<nl;j++)
        {
            //得到第j行的首地址
            uchar* data = image.ptr<uchar>(j);
            //遍历每行的像素
            for(int i =0;i<nc;i++)
            {
               data[i] = data[i]/div*div;    //将每一个像素值都变为div的倍数,即将颜色数缩减了div倍
            }
        }
    }

    当然opencv还有更为底层的指针。在cv::Mat中。图像数据以unsigned char形式保存在一块内存中。

    这块内存的首地址能够通过data成员变量得到。data是一个unsigned char型的指针,所以循环能够用下面方式:


        //获得图像指针
        uchar *data  = image.data;
        //获得第j行,第i列个像素值,step代表图像的行宽(包含填补像素)
        data = image.data + j*image.step + i*image.elemSize();


    3. 迭代器遍历图像

    事实上说到遍历。非常多人都会想到用迭代器来实现,迭代器是一种特殊的类。专门用来遍历集合中的各个元素,openCV相同为cv::Mat提供了与STL迭代器兼容的迭代器,以下我们还是用颜色缩减为例说明迭代器的使用:
    //颜色缩减函数。image为输入图像。div为缩减的倍数
    void colorReduce(Mat&image, int div = 64)
    {
        //得到初始位置的迭代器
        Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
        //得到终止位置的迭代器
        Mat_<Vec3b>::iterator itend = image.end<Vec3b>();
        //遍历全部像素
        for(; it != itend; ++it){
            (*it)[0] = (*it)[0]/div*div;
            (*it)[1] = (*it)[1]/div*div;
            (*it)[2] = (*it)[2]/div*div;
        }
    }
    效果与用指针遍历的一样。


    4. 以上四种存取像素方式效率对照

    opencv中能够用getTickCount()来測量一段代码的执行时间,此函数返回从上次开机算起的时钟周期数,getTickFrequency()能够得到每秒内的时钟周期数,有这两个函数就能得到随意一段代码的执行时间了。
    我们还是以颜色衰减函数为例,分别用以上四种方法遍历实现。看看执行时间有何不同:
    //at方法
    void colorReduce1(Mat&image, int div = 64)
    {
        int nl = image.rows; //图像的行数
        //图像每行的像素数
        int nc = image.cols * image.channels();
        for(int j =0;j<nl-2;j++)
        {
            for(int i =0;i<nc-2;i++)
            {
                image.at<Vec3b>(j,i)[0] = image.at<Vec3b>(j,i)[0]/div*div;
                image.at<Vec3b>(j,i)[1] = image.at<Vec3b>(j,i)[1]/div*div;
                image.at<Vec3b>(j,i)[2] = image.at<Vec3b>(j,i)[2]/div*div;
            }
        }
    }
    
    //行首指针方法
    void colorReduce2(Mat&image, int div = 64)
    {
        int nl = image.rows; //图像的行数
        //图像每行的像素数
        int nc = image.cols * image.channels();
        for(int j =0;j<nl;j++)
        {
            //得到第j行的首地址
            uchar* data = image.ptr<uchar>(j);
            //遍历每行的像素
            for(int i =0;i<nc;i++)
            {
                data[i] = data[i]/div*div;    //将每一个像素值都变为div的倍数,即将颜色数缩减了div倍
            }
        }
    }
    
    //一维数组
    void colorReduce3(Mat&image, int div = 64)
    {
        int nl = image.rows; //图像的行数
        //图像每行的像素数
        int nc = image.cols * image.channels();
    
        //假设图像连续
        if(image.isContinuous())
        {
            //reshape函数用于改变矩阵维度
            //图像行数为1,列数为原先的行数乘上列数
            image.reshape(1,image.cols*image.rows);
        }
    
        for(int j =0;j<nl;j++)
        {
            //得到第j行的首地址
            uchar* data = image.ptr<uchar>(j);
            //遍历每行的像素
            for(int i =0;i<nc;i++)
            {
                data[i] = data[i]/div*div;    //将每一个像素值都变为div的倍数,即将颜色数缩减了div倍
            }
        }
    }
    
    //迭代器方法
    void colorReduce4(Mat&image, int div = 64)
    {
        //得到初始位置的迭代器
        Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
        //得到终止位置的迭代器
        Mat_<Vec3b>::iterator itend = image.end<Vec3b>();
        //遍历全部像素
        for(; it != itend; ++it){
            (*it)[0] = (*it)[0]/div*div;
            (*it)[1] = (*it)[1]/div*div;
            (*it)[2] = (*it)[2]/div*div;
        }
    }
    
    //測试4种像素遍历方式执行时间
    void calrunTime(int v,Mat&image)
    {
        double duration;
        duration = static_cast<double>(getTickCount());
        for(int i = 0;i<10;i++)   //执行十次取平均值
        {
            switch(v)
            {
            case 1:
                colorReduce1(image);
                break;
            case 2:
                colorReduce2(image);
                break;
            case 3:
                colorReduce3(image);
                break;
            case 4:
                colorReduce4(image);
                break;
            default:
                break;
            }
        }
        duration = static_cast<double>(getTickCount()) - duration;
        duration /= getTickFrequency()/100;        //执行时间,以ms为单位
        qDebug()<<"duration"<<v<<":"<<duration<<"ms";
    }



    可见用at的方式读取像素效率最低,用迭代器速度也比較慢。效率最高的方式还是使用指针读取。
    好了,本篇就到此结束吧。过两天继续更^_^


    參考书籍

    《openCV2计算机视觉编程手冊》

    (转载请注明作者和出处:Shawn-HT  http://blog.csdn.net/shawn_ht 未经同意请勿用于商业用途)





  • 相关阅读:
    I-Cache和D-cache
    socat使用
    反射
    属性方法
    getitem
    文件打开编辑和函数参数
    python3编码问题个人理解
    正文内容 python3编码问题
    进度条制作
    集合关系
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5184181.html
Copyright © 2011-2022 走看看