简述
我们在图像处理时经常会用到遍历图像像素点的方式,同样是遍历图像像素点,共有很多中方法可以做到;在这些方法中,有相对高效的,也有低效的;不是说低效的方法就不好,不同场景使用不同方法。
方法
方法一:数组遍历法1
图像Mat中每个像素点,其实就是一个值(int、float、double、uchar等类型),而Mat是一个二维数组。
1、单通道图像(CV_8UC1);backImg是单通道灰色图。
1. int height = backImg.rows;2. int width = backImg.cols;3. for (int i = 0; i < height; i++)4. {5. for (int j = 0; j < width; j++)6. {7. int index = i * width + j;8. //像素值9. int data = (int)backImg.data[index];10. }11. }
2、三通道图像(CV_8UC3);backImg、colorImg均为三通道彩色图,且大小相同。
1. int height = backImg.rows;2. int width = backImg.cols;3.4. //差值图5. //三个通道中只要有一个差值大于阈值,就进行提取6. Mat subImg(height, width, CV_8UC1);7. subImg.setTo(0);8. for (int i = 0; i < height; i++)9. {10. for (int j = 0; j < width; j++)11. {12. int index = i * width + j;13. //背景14. int b1 = (int)backImg.data[3 * index + 0];15. int g1 = (int)backImg.data[3 * index + 1];16. int r1 = (int)backImg.data[3 * index + 2];17. //当前图18. int b2 = (int)colorImg.data[3 * index + 0];19. int g2 = (int)colorImg.data[3 * index + 1];20. int r2 = (int)colorImg.data[3 * index + 2];21.22. //th为阈值23. if (g1 - g2 > th || b1 - b2 > th || r1 - r2 > th)24. {25. subImg.data[index] = 255;26. }27. }28. }
方法二:数组遍历法2 — at(i,j)(**) 
Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。
1. void colorReduce(Mat& image)2. {3. for(int i=0;i<image.rows;i++)4. {5. for(int j=0;j<image.cols;j++)6. {7. if(image.channels() == 1)8. {9. //取出灰度图像中i行j列的点10. image.at<uchar>(i,j) = image.at<uchar>(i,j) / 2;11. }12. else if(image.channels() == 3)13. {14. //取出彩色图像中i行j列第k(0/1/2)通道的颜色点15. image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0] / 2;16. image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1] / 2;17. image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2] / 2;18. }19. }20. }21. }
方法三:指针遍历法
1、这种图像遍历方法相比“数组遍历法”更高效。
void colorReduce(Mat& srcImg,Mat& dstImg){dstImg = srcImg.clone();dstImg.setTo(0);int height = srcImg.rows;// 将3通道转换为1通道int width = srcImg.cols * srcImg.channels();for(int k = 0;k < height; k++){// 获取第k行的首地址const uchar* inData = srcImg.ptr<uchar>(k);uchar* outData = dstImg.ptr<uchar>(k);//处理每个像素for(int i = 0; i < width; i++){outData[i] = inData[i] / 2;}}}
程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。
但是这种用法不能用在行与行之间,因为图像在opencv里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。
2、但是我们可以申明一个连续的空间来存储图像,这样效率就会更高。图像可以是连续的,也可以是不连续的,Mat提供了一个检测图像是否连续的函数isContinuous(),当图像连通时,我们就可以把图像完全展开,看成是一行。
1. void colorReduce(Mat& srcImg,Mat& dstImg)2. {3. int height = srcImg.rows;4. int width = srcImg.cols;5. dstImg = srcImg.clone();6. dstImg.setTo(0);7. //判断图像是否连续8. if(srcImg.isContinuous() && dstImg.isContinuous())9. {10. height = 1;11. width = width * srcImg.rows * srcImg.channels();12. }13. for(int i = 0; i< height; ++i)14. {15. const uchar* inData = srcImg.ptr<uchar>(i);16. uchar* outData = dstImg.ptr<uchar>(i);17. for(int j = 0; j< width; ++j)18. {19. outData[j] = inData[j] / 2;20. }21. }22. }
方法四:迭代器遍历法(**)
1、迭代器Matlterator
Matlterator是Mat数据操作的迭代器,:begin()表示指向Mat数据的起始迭代器,:end()表示指向Mat数据的终止迭代器。迭代器方法是一种更安全的用来遍历图像的方式,首先获取到数据图像的矩阵起始,再通过递增迭代实现移动数据指针。
1. //三通道彩色图2. void colorReduce(Mat srcImage)3. {4. Mat tempImage = srcImage.clone();5.6. // 初始化原图像迭代器7. MatConstIterator_<Vec3b> srcIterStart = srcImage.begin<Vec3b>();8. MatConstIterator_<Vec3b> srcIterEnd = srcImage.end<Vec3b>();9.10. // 初始化输出图像迭代器11. MatIterator_<Vec3b> resIterStart = tempImage.begin<Vec3b>();12. MatIterator_<Vec3b> resIterEnd = tempImage.end<Vec3b>();13.14. while (srcIterStart != srcIterEnd)15. {16. (*resIterStart)[0] = 255 - (*srcIterStart)[0];17. (*resIterStart)[1] = 255 - (*srcIterStart)[1];18. (*resIterStart)[2] = 255 - (*srcIterStart)[2];19.20. srcIterStart++;21. resIterStart++;22. }23. }
1. //单通道灰色图2. void colorReduce(Mat srcImage)3. {4. Mat tempImage = srcImage.clone();5.6. // 初始化原图像迭代器7. MatConstIterator_<uchar> srcIterStart = srcImage.begin<uchar>();8. MatConstIterator_<uchar> srcIterEnd = srcImage.end<uchar>();9.10. // 初始化输出图像迭代器11. MatIterator_<uchar> resIterStart = tempImage.begin<uchar>();12. MatIterator_<uchar> resIterEnd = tempImage.end<uchar>();13.14. while (srcIterStart != srcIterEnd)15. {16. *resIterStart = 255 - *srcIterStart;17. srcIterStart++;18. resIterStart++;19. }20. }
2、迭代器Mat
OpenCV定义了一个Mat的模板子类为Mat,它重载了operator()让我们可以更方便的取图像上的点。
1. void colorReduce(Mat &img)2. {3. uchar t;4. //单通道图像5. if(img.channels() == 1)6. {7. Mat_<uchar>::iterator it = img.begin<uchar>();8. Mat_<uchar>::iterator itend = img.end<uchar>();9. while(it != itend)10. {11. //相关操作12. *it = 0;13. it++;14. }15. }16. //三通道图像17. else if(img.channels() == 3)18. {19. Mat_<Vec3b>::iterator it = img.begin<Vec3b>();20. Mat_<Vec3b>::iterator itend = img.end<Vec3b>();21. while(it != itend)22. {23. //相关操作24. (*it)[0] = 0;25. (*it)[1] = 1;26. (*it)[2] = 2;27. it++;28. }29. }30. }
1. //单通道图像(CV_8UC1)src是单通道灰色图2. //作用:统计图像中白色像素个数3. int BaseFun::whiteSums(Mat src)4. {5. int counter = 0;6. //迭代器访问像素点7. Mat_<uchar>::iterator it = src.begin<uchar>();8. Mat_<uchar>::iterator itend = src.end<uchar>();9. for (; it!=itend; ++it)10. {11. if((*it)>0) counter+=1;//二值化后,像素点是0或者25512. }13. return counter;14. }
方法五:Mat数组遍历法(非迭代器法,Mat迭代器法已在方法四中介绍)
Mat也可以通过下标的方式来遍历图像,但是相比Mat::at()方法,无疑Mat通过下标遍历图像是慢的,不推荐这种方式,如果要使用Mat,最好只是使用迭代器Mat<>::iterator。
1. //单通道图像2. void colorReduce(Mat srcImg)3. {4. int height = srcImg.rows;5. int width = srcImg.cols;6. //转化为Mat_<uchar>7. Mat_<uchar> tempImg = (Mat_<uchar>&)srcImg;8.9. for (int i = 0; i < height; i++)10. {11. for (int j = 0; j < width; j++)12. {13. tempImg(i,j) = tempImg(i,j) * 2;14. printf("%d ",tempImg(i,j));15. }16. printf("\n");17. }18. }
1. //三通道图像2. void colorReduce(Mat srcImg)3. {4. Mat_<Vec3b> tempImg = (Mat_<Vec3b>&)srcImg;5. int height = srcImg.rows;6. int width = srcImg.cols;7. for (int i = 0; i < height; i++)8. {9. for (int j = 0; j < width; j++)10. {11. printf("%d ",tempImg(i,j)[0]);12. printf("%d ",tempImg(i,j)[1]);13. printf("%d ",tempImg(i,j)[2]);14. }15. printf("\n");16. }17. }
 Mat
• CV_8U - 8-bit unsigned integers ( 0..255 )
• CV_8S - 8-bit signed integers ( -128..127 )
• CV_16U - 16-bit unsigned integers ( 0..65535 )
• CV_16S - 16-bit signed integers ( -32768..32767 )
• CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
• CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
• CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
附一张opencv官方给出的关于Mat解释的图片:
总结
目前找到了这些方式来遍历图像像素,全部亲测可用。如有朋友发现新的遍历方法,可一起交流。
本文部分内容参考博客:http://www.cnblogs.com/ronny/p/opencv_road_2.html,感谢这位博主。
