机器视觉学习笔记(3)——常见的像素访问方式

机器视觉学习笔记(3)——常见的像素访问方式

标签: 机器视觉

图像是由一个个像素组成的,各种算法和处理都是基于像素实现的,所以访问像素的操作是最常见的操作方式。常见的像素访问方式有Mat::at访问,指针访问和迭代器访问。

为了详细的说明各个方式访问像素的方法,本文将使用不同的访问像素的方法实现Color Reduce(颜色缩减)的功能。Color Reduce可以将图像的颜色数降低,比如可以将64阶的灰度图降为8阶的灰度图,主要通过整数的除法实现,因为整数的除法可以去尾。比如某像素点的值为M,降阶之后的值就为M/8*8,原先0~63连续值就变为{0, 8, 16, 24, 32, 40, 48, 56}的离散值。本文将处理3通道的彩色图,颜色缩减的倍数为32。

原图:
机器视觉学习笔记(3)——常见的像素访问方式_第1张图片
处理后:
机器视觉学习笔记(3)——常见的像素访问方式_第2张图片

OpenCV2中存储图像的变量类型是Mat (强烈建议放弃OpenCV1中的数据结构和函数),访问像素其实就是访问Mat的元素。

1.Mat::at访问

  • Mat::at方法官方说明如下:

    Returns a reference to the specified array element.
    (返回指定元素的引用)

  • 该方法有各种参数的重载,使用该方法实现的Color Reduce功能代码如下:

void color_reduce1(Mat &image)
{
    for (int i=0; i<image.rows; i++) 
        for (int j=0; j<image.cols; j++)
        {
            if (image.channels() == 1)//如果是单通道图
                image.at<uchar>(i, j) = image.at<uchar>(i, j) /32 * 32;
            else if (image.channels() == 3)//如果是三通道图
            {
                image.at<Vec3b>(i, j)[0] = image.at<Vec3b>(i, j)[0] / 32 * 32;
                image.at<Vec3b>(i, j)[1] = image.at<Vec3b>(i, j)[1] / 32 * 32;
                image.at<Vec3b>(i, j)[2] = image.at<Vec3b>(i, j)[2] / 32 * 32;
            }
        }
}

2.指针访问

  • Mat::ptr方法官方说明如下:

    Returns a pointer to the specified matrix row.
    (返回指定行的指针)

  • 该方法可以返回尖括号指定元素类型的行指针,该指针指向该行第一个元素

void color_reduce2(Mat &image)
{
    int rows = image.rows;//行数
    int rowsCount = image.cols * image.channels();//每行的元素个数
    for (int  i=0; i<rows; i++)
    {
        uchar* data = image.ptr<uchar>(i);//每行首元素地址
        for (int j=0; j<rowsCount; j++)
            data[j] = data[j] / 32 * 32;            
    }
}
  • 如果是3通道图像,元素按照BRG通道的顺序排列,注意是BGRBGR…,而不是BB…GG…RR…
  • 还可以通过Mat::data成员变量来访问像素,该成员是一个uchar指针类型,指向像素的首元素,指向i行j列元素的地址为uchar* data = image.data+i*image.step+j*image.elemSize()

3.迭代器访问

void color_reduce3(Mat &image)
{
    Mat_<Vec3b>::iterator iter = image.begin<Vec3b>();
    for ( ; iter != image.end<Vec3b>(); iter++)
    {
        (*iter)[0] = (*iter)[0] / 32 * 32;//[]的优先级高,所以要先解引用
        (*iter)[1] = (*iter)[1] / 32 * 32;
        (*iter)[2] = (*iter)[2] / 32 * 32;
    }
}
  • 指向const元素的迭代器声明语句为Mat_<Vec3b>::const_iterator iter;
  • 如果使用Mat_定义图片,则begin()和end()就不需要写元素类型了,因为定义时写过了
Mat_<Vec3b> image;
Mat_<Vec3b>::iterator beg = image.begin();
Mat_<Vec3b>::iterator end = image.end();

4.三种访问像素的方法遍历的效率比较

既然访问像素的方法有多种,自然而然就想比较一下。OpenCV提供了测量代码运行时间的函数:

double getTickFrequency()//返回每秒钟的时钟数,即时钟频率
int64 getTickCount()//返回当前时钟数

代码如下:

int main()  
{  
    Mat image;
    image = imread("img.jpg");
    double time;
    time = (double)getTickCount();

    for (int i=0; i<10; i++)//多次运行以减小误差
        color_reduce1(image);//更换测试的函数即可

    time = (double)getTickCount() - time;
    time /= getTickFrequency();
    cout<<time/10<<endl;//除以10输出
}

各方法运行时间如下表所示(单位:秒):

测试方法 Mat::at访问 指针访问 迭代器访问
Debug 0.316153 0.003027 0.414347
Release 0.00158968 0.00070958 0.00610587
  • 绝对运行时间是没有意义的,有意义的是相对运行时间
  • 最快的是指针访问像素的方法,Debug模式下,Mat::at访问运行时间是其104倍,迭代器访问运行时间是其137倍;Release模式下,Mat::at访问运行时间是其2.2倍,迭代器访问运行时间是其8.6

5.总结

  • 以上介绍的访问像素的方法是最基础的访问方法,其它方法大多是在这三种方法基础上衍生出来的
  • Mat::at访问和迭代器访问速度慢,但是安全,稳定性高;指针访问最快,但很危险,操作错误的话甚至会引起程序的崩溃
  • 减小程序运行时间的方法有很多,比如嵌套循环中,尽量内循环次数多,外循环次数少;比如可以检测图片是否连续,再采取相应的处理方式;比如采用位运算
  • 笔者的建议是:如果确切的知道待访问像素的位置,使用Mat::at方法;需要遍历一整行,一整列或者整个图像的时候,使用指针访问,如果怕自己出错,那就使用迭代器访问

你可能感兴趣的:(机器视觉学习笔记(3)——常见的像素访问方式)