前几天在一个技术讨论群里看到有位同学贴了些代码问问题,看了一下代码,虽然没搞明白他的程序问题出在哪,但觉得他的程序在图像遍历时效率较低。在使用OpenCV遍历图像时有好几种方式,这里将自己经常用的遍历方式(指针方式)与那位同学的遍历方式(at方法遍历)做下简单对比,希望能对正在学习OpenCV的同学有帮助。下面的实例代码实际上实现了一个小功能,即图像的反色,反色原理很简单,在一个rgb色彩空间中,可将任何一种颜色看成笛卡尔坐标中的一个点,对于任意点,反色就是计算以(128, 128,128)为中心时该点的对称点,比如rgb(100, 150, 200)对应的反色就是rgb(155, 105, 55)。
(1)at方法遍历。该方法用起来非常简单、省事,对于待遍历的第i行、第j列像素,只需将(i,j)这个像素点空间位置坐标传给at方法即可。用法如下:
int row = img.rows;
int step = img.step;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < step; j++)
{
img.at<uchar>(i, j) = 255 - img.at<uchar>(i, j);
}
}
或者:
int row = img.rows;
int col = img.cols;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
img.at<Vec3b>(i, j)[0] = 255 - img.at<Vec3b>(i, j)[0];
img.at<Vec3b>(i, j)[1] = 255 - img.at<Vec3b>(i, j)[1];
img.at<Vec3b>(i, j)[2] = 255 - img.at<Vec3b>(i, j)[2];
}
}
(2)指针方式遍历。由于图像是按照行和列顺序存储的,所以该方法遍历图像时需要先获取图像每一行的首地址,然后从该行首地址向后遍历至行末尾。用法如下:
int row = img.rows;
int step = img.step;
uchar * pImg = img.data;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < step; j++)
{
pImg[j] = 255 - pImg[j];
}
pImg += img.step;
}
或者:
int row = img.rows;
int step = img.step;
uchar * pImg = NULL;
for (int i = 0; i < row; i++)
{
pImg = img.ptr<uchar>(i);
for (int j = 0; j < step; j++)
{
pImg[j] = 255 - pImg[j];
}
}
我的测试图像分辨率为:4288 * 2848,对于上述代码,debug模式下,使用at方法用时约9700毫秒,使用指针方式用时约140毫秒;release模式下,at方法用时约63毫秒,指针方式用时约30毫秒。可见使用at方法遍历图像时效率很低,比较耗时,推荐使用指针方式。
另外由于像素灰度值范围为0至255,对于某些复杂计算,计算结果难免会超出灰度值范围,这时需要做下数据饱和判断,即结果为负,则取0,结果超出255,则取255。在做饱和判断时可以使用OpenCV提供的saturate_cast函数,对于上述程序,用法为:
pImg[j] = saturate_cast<uchar>(255 - pImg[j]);
不过此时程序用时让人大跌眼镜,release模式下,at方法用时约100毫秒,指针方式用时约62毫秒。即加了饱和判断后,用时增加了近一倍。所以,非必须情况下要慎用这样类似函数。
下面是反色的效果图。