OpenCV中提供了许多操作图像的函数,但是有时候我们需要直接操作像素来实现我们的功能,这篇文章总结了OpenCV中常见的操作像素的方法。
像素类型
不同的图像有不同的像素类型,不过对于不同的像素类型,需要在模板参数传入不同的值。首先像素的数据类型包括CV_32U,CV_32S,CV_32F,CV_8U,CV_8UC3等,那这些类型都是什么含义呢。第一个数字表示比特数,第二个数字就表示C++中数据类型,如果还有后面两个字符,这两个字符表示通道数。例如对于CV_32U,表示具有32比特的unsigned int类型;对于CV_8UC3,表示具有8比特,并且有三个通道的unsigned char类型。对于这个类型,可以使用**type()**来获取。
根据这些类型,又可以分为两种,一种单通道的,一种多通道的。单通道的一个像素用一个数值表示即可,而多通道的一个像素需要用多个像素表示,最常用的三通道就需要RGB这三个数值来表示。那具体的通道顺序是什么呢?在OpenCV中,则是按BGR的顺序来存储的,用数字代替就是B用channels[0]来获取,G用channels[1]来获取,R用channels[2]来获取。Opencv的设计者就是这样设计的,记住就好了。
访问像素
cv::Mat有个at()方法,可以访问图像的单个像素,同时at()方法又是一个模板方法,所以在使用的时候需要传入图像像素的类型,而且这个类型不像C++中的运算类型一样可以自动转换,所以必须准确的传入图像元素类型。
对于单通道元素来说,元素类型为unsigned char的情况下,可以这样访问
image.at<uchar>(j,i)= value;
对于三通道元素彩色图像来说,那么可以这样
image.at<cv::Vec3b>(j,i)[channel]= value;
或者直接使用数组赋值还更加方便
image.at<cv::Vec3b>(j,i) = cv::Vec3b(a,b,c);
Vec是opencv中的向量类型,它模板是Vec<T,N>,所以Vec3b表示3个unsigned char组成的向量,Vec2f表示由两个float组成的向量,任何形式的向量都可以用这个Vec来表示的。
Mat_ 也是一个模板类,注意它有一个下划线,以与Mat作为区别。在实际使用中,Mat_ 与 Mat 的操作函数没有多大区别,只不过Mat_需要在创建时定义元素类型,以后再调用它的方法是就不需要再传入数据的类型,而且还定义了一个操作符()来获取元素的位置。
cv::Mat_<uchar> image(image1);
image(20,30) = value;
或者使用
image.at(20,30) = value;
这两个类之间的区别就是一个是定义时指定类型,一个是使用时指定类型,可以按照不同的情况来使用。
使用指针遍历像素
cv::Mat还有个访问指针的方法 ptr(),它能够返回指定行的地址,然后就可以移动指针访其他的像素。
uchar *data = image.ptr<uchar>(j);
上面代码中data或获取第j行的首地址。遍历时,前三个字节表示的是第一个像素的BGR值,注意BGR值顺序,接下来三个字节是第二个像素的值。那像素个数有限,想要停止怎么办?可以先提前获取这一行中像素的个数,然后到空指针了就停止这轮访问,然后在换到其他行进行访问。
int n = image.cols * image.channels();
用图像的列数与通道数相乘就获得了图像的每行的像素数。
而对于图像,有时候在内存中会为了对齐而对末尾的像素有填充,而有时候没有填充。可以使用isContinue()来访问图像是否有填充,对于没有填充的图像,即连续的图像来说,便利的时候就可以只要一层循环就可以了,他会自己换行将图像变成一维的来处理。
create()方法的用处,这个方法先检查Mat对象是否已经分配了数据缓存区,以及缓存区的数据类型与需要的类型是否相等。如果相同则可以直接返回已分配好的缓存区,否则重新创建一个再返回。可以在需要另外单独保存图片时使用,判断传入的result image参数是否已经分配数据缓存区。
使用迭代器进行遍历
C++中的STL便设计了一套迭代器以用来访问元素,Opencv中也按照STL的设计原则设计了一套迭代器来访问像素,用法基本与STL类似。
对Mat类型来说,他的迭代器类型可以使用MatIterator_或者Mat_::Iterator类型,具体使用如下
cv::MatIterator_ <cv::Vec3b> it;
或者
cv::Mat_<cv::Vec3b>::iterator it;
用这两个迭代器便可以指定Mat对象的迭代器,注意需要传入模板参数。对迭代器的初始化与C++中的STL一致。
it = image.begin<cv::Vec3b>();
it = image.end<cv::Vec3b>();
遍历也和前面指针一样,从图像左上角第一个像素开始遍历三个字节,然后第二个字节,依次遍历,到第一行遍历完后,就会到第二行来遍历。不过我们也可以修改迭代器的位置,而不需要一个一个遍历。
//跳过20个像素
it += 20;
//跳到下一行同一位置
it += image.cols;
有时候不希望迭代器修改像素的值,则可以使用常量迭代器,即有const属性的迭代器,如果在遍历过程中试图修改像素值则会报错。
cv::MatConstIterator_<cv::Vec3b> it;
或者
cv::Mat_<cv::Vec3b>::const_iterator it;
以上便是Mat的迭代器的使用方法,在模板参数里都需要传入像素类型,前面讲过Mat_对象只需要在定义时指定像素类型,此后使用便不需要在指定类型,对于它的迭代器也是如此,请注意他与Mat对象的区别。
cv::Mat_<uchar> image(image1);
cv::MatIterator_ <cv::Vec3b> it = image.begin();
访问图像像素的几种方法也介绍完了,考虑到程序的运行速度方面,通常at方法适合于访问元素,但不适合遍历元素;指针或者迭代器适合用于遍历元素。
欢迎大家关注公众号“计算机视觉与机器学习”