简述
我们在图像处理时经常会用到遍历图像像素点的方式,同样是遍历图像像素点,共有很多中方法可以做到;在这些方法中,有相对高效的,也有低效的;不是说低效的方法就不好,不同场景使用不同方法。
方法
方法一:数组遍历法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或者255
12. }
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,感谢这位博主。