数字图像基本OP:OpenCV中的访问与操作像素值方法
我们知道,在计算机中一幅图像以数组矩阵的数据结构被存储下来,这里记录总结OpenCV
中遍历访问每一个像素值的方法。
在C++ API
中,访问像素值有以下几种方法:
- 数组索引方法
- 指针方法
- 迭代器方法
而在Python
中,之前说过,图像存储与NumPy
中的ndarray
完全一致,因此访问像素值方法也是如此。
1.数组方法访问像素值
1.1 数组方法介绍
OpenCV
提供了at()
函数,它是一个模板函数,返回指定位置矩阵元素的引用,常用的有:
- 数组方法访问RGB图像:
Mat.at<Vec3b>(h, w)
- 数组方法访问灰度图:
Mat.at<uchar>(h, w)
如上面的函数所示,数组方法访问图像指定坐标处的像素值主要是调用Mat
对象的at
方法,其中(h,w)
好理解,就是要访问的像素值得二维坐标,尖括号里的内容是访问方式,因为RGB图像中有3个通道各自的像素值,因此以Vec3b
的方式来访问,即3个字节元素的vector
向量,而灰度图只有一个元素,类型为uchar
字节数据,因此以uchar
方式访问。
1.2 读入图像并获取长宽等信息
Mat.rows
返回图像的高度,即行数。Mat.cols
返回图像的长度,即列数。Mat.channels()
返回图像的通道数,3为RGB图像,1为灰度图像。
Mat input_image = imread("test_images/opencv.jpg", 1);
if (input_image.empty())
{
cout << "read input error!" << endl;
return -1;
}
imshow("input", input_image);
int height = input_image.rows;
int width = input_image.cols;
int channel = input_image.channels();
1.2 数组方法遍历访问像素值并取反
//数组方法遍历
for (int h = 0; h < height; h++)
{
for (int w = 0; w < width; w++)
{
if (channel == 3)//彩色图像
{
//bgr 是一个vector,包含三个通道的值
Vec3b bgr = input_image.at<Vec3b>(h, w);
bgr[0] = 255 - bgr[0];
bgr[1] = 255 - bgr[1];
bgr[2] = 255 - bgr[2];
input_image.at<Vec3b>(h, w) = bgr;
}
else if (channel == 1)//灰度图像
{
int value = input_image.at<uchar>(h, w);
input_image.at<uchar>(h, w) = 255 - value;
}
}
}
imshow("result", input_image);
- 需要注意的是,
at()
方法代码可读性高,但是代码运行效率较差,所以当需要大规模遍历像素的的时候不推荐使用。
2.指针方法访问像素值
2.1 指针方法介绍
- 得到每一行的首地址:
Mat.ptr<type>(h)
OpenCV
提供了指针方法来访问操作像素值,h是行数,得到每一行的首地址后可以按照这个地址往后对像素值进行遍历。Mat.ptr<type>(h)
返回Mat
对象的第h行的头指针,如下图所示:
对于单通道图像gary,就像上面的图所示:
gray.ptr<uchar>(0)
指向第0行首元素。gray.ptr<uchar>(1)
指向第1行首元素。gray.ptr<uchar>(2)
指向第2行首元素。
可以使用
gray.ptr<uchar>(0)[0]
来访问gray[0][0]。gray.ptr<uchar>(0)[1]
来访问gray[1][0]。gray.ptr<uchar>(0)[2]
来访问gray[0][2]。
对于三通道图像,bgr.pt<uchar>(i)
同样指向第i行的首元素,但是与单通道的图像不一样,三通道的图像中一个像素点有BGR三个像素值,可以看下面的图来区别单通道和三通道Mat
在内存中的实际分布情况(假设内存地址从左往右增加):
单通道内存分布:
三通道内存分布:
因此像gray.ptr<uchar>(0)[0]
那样类型设为uchar
来操作时,bgr.ptr<uchar>(0)[n]
得到的是一个像素点内某个通道的值,因为三通道图像一个像素的有三个值。
所以对于三通道图像,可以使用bgr.ptr<Vec3b>(0)[n]>
来获得一个像素点的值,这个时候vec3b
是一个包含三个值的向量。
2.2 指针方法遍历访问像素值并取反
//指针方法遍历
for (int h = 0; h < height; h++)
{
uchar* current_row = input_image.ptr<uchar>(h);
uchar* flag_row = current_row;
for (int w = 0; w < width; w++)
{
int b = 0, g = 0, r = 0;
if (channel == 3)
{
b = *current_row++;
g = *current_row++;
r = *current_row++;
*flag_row++ = 255 - b;
*flag_row++ = 255 - g;
*flag_row++ = 255 - r;
}
else if (channel == 1)
{
int value = *current_row++;
*flag_row++ = 255 - value;
}
}
}
imshow("result", input_image);
- 指针方法访问速度较快,因此推荐使用。但是要注意
C/C++
的指针操作并不进行类型和越界检查,如果指针访问出错,可能会让程序出现段错误。
3.迭代器方法遍历访问像素值并取反
//迭代器方法
if (channel == 3)
{
Mat_<Vec3b>::iterator ite = input_image.begin<Vec3b>(),
end = input_image.end<Vec3b>();
for (; ite != end; ++ite)
{
(*ite)[0] = 255 - (*ite)[0];
(*ite)[1] = 255 - (*ite)[1];
(*ite)[2] = 255 - (*ite)[2];
}
}
else if (channel == 1)
{
Mat_<uchar>::iterator ite = input_image.begin<uchar>(),
end = input_image.end<uchar>();
for (; ite != end; ++ite)
{
*ite = 255 - *ite;
}
}
imshow("result", input_image);
4.运行结果分析
完整代码:
image_pixels_access.cpp
运行结果:
三种方法的运行时间评估:
在OpenCV中,可以用下面的方法来测试运行时间:
double start = static_cast<double>(getTickCount());
//需要测试时间的代码
/*
...
*/
double end= ((double)getTickCount() - start) / getTickFrequency();
//以毫秒输出
cout << "所用时间为:" << time/1000 << "ms" << endl;
输出时间:
array time: 3.50392e-05ms
pointer time: 1.007e-06ms
iterator time: 6.63757e-05ms
可以看出,指针方法遍历像素值得方法是最快的,几乎比另外两个方法快了一个数量级。
5. Python方法访问像素值
5.1 得到图像矩阵维度信息
在numpy
中,可以使用ndarray
的shape
方法打印出数组的维度信息,该方法返回一个元组,若是RGB图像,则元组长度为3,里面的三个元素分别为行列以及通道数,若元组长度为2,则为灰度图像,两个元素为行列数,没有通道数,如下面的代码,分别以RGB个灰度图方式读入图像后输出shape
:
input = cv.imread("./test_images/opencv.jpg",1)
shape = input.shape
print(shape)
输出:
(273, 508, 3)
input = cv.imread("./test_images/opencv.jpg",0)
shape = input.shape
print(shape)
输出:
(273, 508)
5.2 通过坐标来索引像素值
for row in range(shape[0]):
for col in range(shape[1]):
if len(shape)==3 and shape[2]==3: #RGB
b,g,r = input[row,col]
b = 255-b
g = 255-g
r = 255-r
input[row,col] = [b,g,r]
elif len(shape)==2 : #灰度图
input[row,col] = 255-input[row,col]
从上面的遍历代码可以看出,在RGB图像中对于每一个像素点索引得到的是包含三个元素的列表,对灰度图索引得到的就是一个值。
完整代码:
image_pixels_access.py