简述

我们在图像处理时经常会用到遍历图像像素点的方式,同样是遍历图像像素点,共有很多中方法可以做到;在这些方法中,有相对高效的,也有低效的;不是说低效的方法就不好,不同场景使用不同方法。

方法

下面将一一介绍这些遍历图像像素点的方法:

方法一:数组遍历法1

图像Mat中每个像素点,其实就是一个值(int、float、double、uchar等类型),而Mat是一个二维数组。
1、单通道图像(CV_8UC1);backImg是单通道灰色图。

  1. 1. int height = backImg.rows;
  2. 2. int width = backImg.cols;
  3. 3. for (int i = 0; i < height; i++)
  4. 4. {
  5. 5. for (int j = 0; j < width; j++)
  6. 6. {
  7. 7. int index = i * width + j;
  8. 8. //像素值
  9. 9. int data = (int)backImg.data[index];
  10. 10. }
  11. 11. }

2、三通道图像(CV_8UC3);backImg、colorImg均为三通道彩色图,且大小相同。

  1. 1. int height = backImg.rows;
  2. 2. int width = backImg.cols;
  3. 3.
  4. 4. //差值图
  5. 5. //三个通道中只要有一个差值大于阈值,就进行提取
  6. 6. Mat subImg(height, width, CV_8UC1);
  7. 7. subImg.setTo(0);
  8. 8. for (int i = 0; i < height; i++)
  9. 9. {
  10. 10. for (int j = 0; j < width; j++)
  11. 11. {
  12. 12. int index = i * width + j;
  13. 13. //背景
  14. 14. int b1 = (int)backImg.data[3 * index + 0];
  15. 15. int g1 = (int)backImg.data[3 * index + 1];
  16. 16. int r1 = (int)backImg.data[3 * index + 2];
  17. 17. //当前图
  18. 18. int b2 = (int)colorImg.data[3 * index + 0];
  19. 19. int g2 = (int)colorImg.data[3 * index + 1];
  20. 20. int r2 = (int)colorImg.data[3 * index + 2];
  21. 21.
  22. 22. //th为阈值
  23. 23. if (g1 - g2 > th || b1 - b2 > th || r1 - r2 > th)
  24. 24. {
  25. 25. subImg.data[index] = 255;
  26. 26. }
  27. 27. }
  28. 28. }

方法二:数组遍历法2 — at(i,j)(**)

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。

  1. 1. void colorReduce(Mat& image)
  2. 2. {
  3. 3. for(int i=0;i<image.rows;i++)
  4. 4. {
  5. 5. for(int j=0;j<image.cols;j++)
  6. 6. {
  7. 7. if(image.channels() == 1)
  8. 8. {
  9. 9. //取出灰度图像中i行j列的点
  10. 10. image.at<uchar>(i,j) = image.at<uchar>(i,j) / 2;
  11. 11. }
  12. 12. else if(image.channels() == 3)
  13. 13. {
  14. 14. //取出彩色图像中i行j列第k(0/1/2)通道的颜色点
  15. 15. image.at<Vec3b>(i,j)[0]=image.at<Vec3b>(i,j)[0] / 2;
  16. 16. image.at<Vec3b>(i,j)[1]=image.at<Vec3b>(i,j)[1] / 2;
  17. 17. image.at<Vec3b>(i,j)[2]=image.at<Vec3b>(i,j)[2] / 2;
  18. 18. }
  19. 19. }
  20. 20. }
  21. 21. }

方法三:指针遍历法

1、这种图像遍历方法相比“数组遍历法”更高效。

  1. void colorReduce(Mat& srcImg,Mat& dstImg)
  2. {
  3. dstImg = srcImg.clone();
  4. dstImg.setTo(0);
  5. int height = srcImg.rows;
  6. // 将3通道转换为1通道
  7. int width = srcImg.cols * srcImg.channels();
  8. for(int k = 0;k < height; k++)
  9. {
  10. // 获取第k行的首地址
  11. const uchar* inData = srcImg.ptr<uchar>(k);
  12. uchar* outData = dstImg.ptr<uchar>(k);
  13. //处理每个像素
  14. for(int i = 0; i < width; i++)
  15. {
  16. outData[i] = inData[i] / 2;
  17. }
  18. }
  19. }

程序中将三通道的数据转换为1通道,在建立在每一行数据元素之间在内存里是连续存储的,每个像素三通道像素按顺序存储。
但是这种用法不能用在行与行之间,因为图像在opencv里的存储机制问题,行与行之间可能有空白单元。这些空白单元对图像来说是没有意思的,只是为了在某些架构上能够更有效率,比如intel MMX可以更有效的处理那种个数是4或8倍数的行。
2、但是我们可以申明一个连续的空间来存储图像,这样效率就会更高。图像可以是连续的,也可以是不连续的,Mat提供了一个检测图像是否连续的函数isContinuous(),当图像连通时,我们就可以把图像完全展开,看成是一行。

  1. 1. void colorReduce(Mat& srcImg,Mat& dstImg)
  2. 2. {
  3. 3. int height = srcImg.rows;
  4. 4. int width = srcImg.cols;
  5. 5. dstImg = srcImg.clone();
  6. 6. dstImg.setTo(0);
  7. 7. //判断图像是否连续
  8. 8. if(srcImg.isContinuous() && dstImg.isContinuous())
  9. 9. {
  10. 10. height = 1;
  11. 11. width = width * srcImg.rows * srcImg.channels();
  12. 12. }
  13. 13. for(int i = 0; i< height; ++i)
  14. 14. {
  15. 15. const uchar* inData = srcImg.ptr<uchar>(i);
  16. 16. uchar* outData = dstImg.ptr<uchar>(i);
  17. 17. for(int j = 0; j< width; ++j)
  18. 18. {
  19. 19. outData[j] = inData[j] / 2;
  20. 20. }
  21. 21. }
  22. 22. }

方法四:迭代器遍历法(**)

1、迭代器Matlterator
Matlterator
是Mat数据操作的迭代器,:begin()表示指向Mat数据的起始迭代器,:end()表示指向Mat数据的终止迭代器。迭代器方法是一种更安全的用来遍历图像的方式,首先获取到数据图像的矩阵起始,再通过递增迭代实现移动数据指针。

  1. 1. //三通道彩色图
  2. 2. void colorReduce(Mat srcImage)
  3. 3. {
  4. 4. Mat tempImage = srcImage.clone();
  5. 5.
  6. 6. // 初始化原图像迭代器
  7. 7. MatConstIterator_<Vec3b> srcIterStart = srcImage.begin<Vec3b>();
  8. 8. MatConstIterator_<Vec3b> srcIterEnd = srcImage.end<Vec3b>();
  9. 9.
  10. 10. // 初始化输出图像迭代器
  11. 11. MatIterator_<Vec3b> resIterStart = tempImage.begin<Vec3b>();
  12. 12. MatIterator_<Vec3b> resIterEnd = tempImage.end<Vec3b>();
  13. 13.
  14. 14. while (srcIterStart != srcIterEnd)
  15. 15. {
  16. 16. (*resIterStart)[0] = 255 - (*srcIterStart)[0];
  17. 17. (*resIterStart)[1] = 255 - (*srcIterStart)[1];
  18. 18. (*resIterStart)[2] = 255 - (*srcIterStart)[2];
  19. 19.
  20. 20. srcIterStart++;
  21. 21. resIterStart++;
  22. 22. }
  23. 23. }
  1. 1. //单通道灰色图
  2. 2. void colorReduce(Mat srcImage)
  3. 3. {
  4. 4. Mat tempImage = srcImage.clone();
  5. 5.
  6. 6. // 初始化原图像迭代器
  7. 7. MatConstIterator_<uchar> srcIterStart = srcImage.begin<uchar>();
  8. 8. MatConstIterator_<uchar> srcIterEnd = srcImage.end<uchar>();
  9. 9.
  10. 10. // 初始化输出图像迭代器
  11. 11. MatIterator_<uchar> resIterStart = tempImage.begin<uchar>();
  12. 12. MatIterator_<uchar> resIterEnd = tempImage.end<uchar>();
  13. 13.
  14. 14. while (srcIterStart != srcIterEnd)
  15. 15. {
  16. 16. *resIterStart = 255 - *srcIterStart;
  17. 17. srcIterStart++;
  18. 18. resIterStart++;
  19. 19. }
  20. 20. }

2、迭代器Mat
OpenCV定义了一个Mat的模板子类为Mat
,它重载了operator()让我们可以更方便的取图像上的点。

  1. 1. void colorReduce(Mat &img)
  2. 2. {
  3. 3. uchar t;
  4. 4. //单通道图像
  5. 5. if(img.channels() == 1)
  6. 6. {
  7. 7. Mat_<uchar>::iterator it = img.begin<uchar>();
  8. 8. Mat_<uchar>::iterator itend = img.end<uchar>();
  9. 9. while(it != itend)
  10. 10. {
  11. 11. //相关操作
  12. 12. *it = 0;
  13. 13. it++;
  14. 14. }
  15. 15. }
  16. 16. //三通道图像
  17. 17. else if(img.channels() == 3)
  18. 18. {
  19. 19. Mat_<Vec3b>::iterator it = img.begin<Vec3b>();
  20. 20. Mat_<Vec3b>::iterator itend = img.end<Vec3b>();
  21. 21. while(it != itend)
  22. 22. {
  23. 23. //相关操作
  24. 24. (*it)[0] = 0;
  25. 25. (*it)[1] = 1;
  26. 26. (*it)[2] = 2;
  27. 27. it++;
  28. 28. }
  29. 29. }
  30. 30. }
  1. 1. //单通道图像(CV_8UC1)src是单通道灰色图
  2. 2. //作用:统计图像中白色像素个数
  3. 3. int BaseFun::whiteSums(Mat src)
  4. 4. {
  5. 5. int counter = 0;
  6. 6. //迭代器访问像素点
  7. 7. Mat_<uchar>::iterator it = src.begin<uchar>();
  8. 8. Mat_<uchar>::iterator itend = src.end<uchar>();
  9. 9. for (; it!=itend; ++it)
  10. 10. {
  11. 11. if((*it)>0) counter+=1;//二值化后,像素点是0或者255
  12. 12. }
  13. 13. return counter;
  14. 14. }

方法五:Mat数组遍历法(非迭代器法,Mat迭代器法已在方法四中介绍)

Mat也可以通过下标的方式来遍历图像,但是相比Mat::at()方法,无疑Mat通过下标遍历图像是慢的,不推荐这种方式,如果要使用Mat,最好只是使用迭代器Mat<>::iterator。

  1. 1. //单通道图像
  2. 2. void colorReduce(Mat srcImg)
  3. 3. {
  4. 4. int height = srcImg.rows;
  5. 5. int width = srcImg.cols;
  6. 6. //转化为Mat_<uchar>
  7. 7. Mat_<uchar> tempImg = (Mat_<uchar>&)srcImg;
  8. 8.
  9. 9. for (int i = 0; i < height; i++)
  10. 10. {
  11. 11. for (int j = 0; j < width; j++)
  12. 12. {
  13. 13. tempImg(i,j) = tempImg(i,j) * 2;
  14. 14. printf("%d ",tempImg(i,j));
  15. 15. }
  16. 16. printf("\n");
  17. 17. }
  18. 18. }
  1. 1. //三通道图像
  2. 2. void colorReduce(Mat srcImg)
  3. 3. {
  4. 4. Mat_<Vec3b> tempImg = (Mat_<Vec3b>&)srcImg;
  5. 5. int height = srcImg.rows;
  6. 6. int width = srcImg.cols;
  7. 7. for (int i = 0; i < height; i++)
  8. 8. {
  9. 9. for (int j = 0; j < width; j++)
  10. 10. {
  11. 11. printf("%d ",tempImg(i,j)[0]);
  12. 12. printf("%d ",tempImg(i,j)[1]);
  13. 13. printf("%d ",tempImg(i,j)[2]);
  14. 14. }
  15. 15. printf("\n");
  16. 16. }
  17. 17. }

Mat对应的是CV_8U,Mat对应的是CV8S,Mat对应的是CV32S,Mat对应的是CV32F,Mat对应的是CV64F,对应的数据深度如下:
• 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
解释的图片:
opencv 几种不同遍历图像像素的方法详解 - 图1

总结

目前找到了这些方式来遍历图像像素,全部亲测可用。如有朋友发现新的遍历方法,可一起交流。
本文部分内容参考博客:http://www.cnblogs.com/ronny/p/opencv_road_2.html,感谢这位博主。