2.2访问图像像素

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/webzhuce/article/details/80871184

  前面提到图像处理最基本的是操作图像的像素,学会如何存取其像素,是一切编写图像处理应用的基础。我们通过实现在图像中加入椒盐噪点来说明三种访问像素的方式。椒盐噪点是一种特殊的噪点,顾名思义,它随机地将部分像素设置为白色或黑色。在传输过程中,如果部分像素值丢失,那么这种噪点就会出现。在我们的代码里,将随机挑选若干像素,并将其设置为白色。

直接访问像素

  为了访问像素,需要在代码中指定像素 所在的行和列。如果图像是单通道的,返回值是单个数值;如果图像是多通道的,返回值则是一组向量(Vector)。

void MainWindow::saltDirectly(cv::Mat &image, int n)
{
   for(int k = 0; k < n; k++){
       //rand()是随机数生成函数
       int i = rand() % image.cols;
       int j = rand() % image.rows;
       if(image.channels() == 1){  //gray image
           image.at<uchar>(i,j) = 255;
       }else if(image.channels() == 3) {//color image
           image.at<cv::Vec3b>(j,i)[0] = 255;
           image.at<cv::Vec3b>(j,i)[1] = 255;
           image.at<cv::Vec3b>(j,i)[2] = 255;
       }
   }
}

   Mat类有若干成员函数 可以获得图像的属性。公有成员变量cols和rows给出了图像的宽和高。成员函数at(int y, int x)可以用来访问像素。但是必须爱编译期间知道图像的数据类型,因为Mat类可以存放任意数据类型的元素。这也是这个函数应用模板函数来实现的原因。这也意味着,当调用该函数时,需要使用以下方式指定数据类型:

    image.at<uchar>(i,j) = 255;

注意:一定要确保指定的数据类型要和矩阵中的数据类型相符合。at方法本身不会进行任何数据类型转换。
  对于彩色图像,每个像素由三个部分构成:RGB。因此,一个包含彩色图像的Mat会返回一个由8位数组成的向量。OpenCV将此类向量定义为cv::Vec3b,即由三个unsigned char组成的向量。这也解释了为什么访问像素的代码可以写成以下形式:

 image.at<cv::Vec3b>(j,i)[channel] = value;

其中,索引值channel标明了颜色通道号。

使用指针访问像素

  为了简化指针运算,Mat类提供了ptr函数可以得到图像任意行的首地址。ptr函数是一个模板函数,它返回第j行的首地址。

uchar* data = image.ptr<uchar>(j);
void MainWindow::saltByPointer(cv::Mat &image, int n)
{
    for(int k = 0; k < n; k++){
        //rand()Is a random number generating function
        int i = rand() % image.cols;
        int j = rand() % image.rows;
        uchar* data = image.ptr<uchar>(j);
        for(int c = 0; c < image.channels(); c++ )
            data[i*image.channels()+ c] = 255;
     }
}

   在一个彩色图像中,图像数据缓冲区中的前三个字符对应图像左上角的三个通道值,接下来的三个字节对应第一行的第二个像素。依次类推(注意:OpenCV默认使用BGR,因此第一个通道通常是蓝色)。一个宽为W、高为H的图像需要一个大小由W*H*3个uchar构成的内存块。成员变量cols代表图像的宽度(图像的列数),rows代表图像的高度,step代表以字节为单位的图像的有效宽度。图像的通道数可以由channels方法得到:对于灰度图像来说是1,对于彩色图像来说是3。total函数返回矩阵的像素个数。像素大小可以由elemSize函数得到:对于一个三通道的short型矩阵(CV_16SC3),elemSize返回6。

底层指针运算

  在类Mat中,图像数据以unsigned char形式保存在一块内存中。这块内存的首地址可以通过data成员变量得到。data是一个unsigned char类型的指针。

uchar* data = image.data;

从当前行到下一行可以通过对指针加上行宽完成:

data += image.step;   //下一行

通常而言,可以通过如下方式获得第j行、第i列像素的地址:

//(j,i)处的像素地址为&image.at(j,i)
data = image.data + j* image.step + i * elemSize();

即使这种方式确实行之有效,我们依然不建议使用这种处理方式。因为这种方式除了容易出错,还不适用带有“感兴趣区域”的图像。

使用迭代器访问像素

  在面对对象的编程中,遍历数据集合通常是通过迭代器来完成的。迭代器是一种特殊的类,它专门来遍历集合中的各个元素,同时隐藏了在给定的集合上元素迭代的具体实现方式。这种信息隐藏原则的使用过使得遍历集合更加容易。另外,不管数据类型是什么,我们都可以使用相似的方式遍历集合。标准模板库(STL)为每个容器类型都提供了迭代器。OpenCV同样为Mat类提供了与STL迭代器兼容的迭代器。

void MainWindow::saltByIterator(cv::Mat &image, int n)
{
    for(int k = 0; k < n; k++){
        //rand()Is a random number generating function
        int i = rand() % image.cols;
        int j = rand() % image.rows;
        cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
        it += (j * image.cols + i);
        for(int c= 0; c < image.channels(); c++)
            (*it)[c] = 255;
     }
}

  一个Mat类实例的迭代器可以通过创建一个cv::Mat Iterator_的实例来得到。类似于cv::Mat_,下划线意味着cv::Mat Iterator_是一个模板类。之所以如此是由于迭代器来访问像素,就就必须在编译期知道像素的数据类型。一个图像迭代器可以用如下方式声明:

cv::Mat  Iterator<cv::Vec3b>::iterator it;

另外一种方式是使用定义在Mat_内部的迭代器类型:

cv::Mat_<cv::Vec3b>::iterator it;

  初始位置(图像的左上角)的迭代器通常是通过begin方法得到。对于Mat的实例,可以通过image.begin()获得。也可以通过对迭代器进行代数运算。例如:如果想从图像的第二行开始,那么可以用image.begin() + image.cols来初始化迭代器。集合终止位置的迭代器可以通过end方法得到。但是,end方法得到的迭代器其实已经超过了集合。这也也意味着迭代过程必须在迭代器到达这个位置时结束。end方法得到的迭代器也可以进行代数运算。

原始图片:

这里写图片描述

添加椒盐噪声后:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/webzhuce/article/details/80871184