简介
图像本质上就是一个由数值组成的矩阵。矩阵中的每个元素表示一个像素。对于灰度图像(黑白图像),像素是
8
8
8 位无符号数,
0
0
0 表示黑色,
255
255
255表示白色。对于彩色图像,需要用三原色数据来重现不同的可见色,这是因为我们人类的视觉系统是三原色的,视网膜上有三种类型的视锥细胞,它们将颜色信息传递给大脑。这意味着对于彩色图像,每个像素都要对应三个数值。在摄影和数字成像技术中,常用的主颜色通道是红色、绿色和蓝色,因此每3个8位数值组成矩阵的一个元素。
注意,
8
8
8 位通道通常是够用的,但有些特殊的应用程序需要用
16
16
16 位通道(例如医学图像)。
访问像素值
要访问矩阵中的每个独立元素,你只需要指定它的行号和列号。返回的对应元素可以是单个数值,也可以是多通道图像的数值向量。
cv::Mat
类包含有多种方法,可用来访问图像的各种属性:
- 利用公共成员变量
cols
和rows
可得到图像的列数和行数; - 利用
cv::Mat
的at(int y,int x)
方法可以访问元素;at
方法被实现成一个模板方法,在调用时必须指定图像元素的类型;
image.at<uchar>(j, i) = 255;
注意,用户必须保证指定的类型与矩阵内的类型是一致的, at
方法不会进行任何类型转换。
彩色图像的每个像素对应三个部分:红色、绿色和蓝色通道。因此包含彩色图像的cv::Mat类会返回一个向量,向量中包含三个8位的数值。 OpenCV为这样的短向量定义了一种类型,即cv::Vec3b。这个向量包含三个无符号字符( unsigned character)类型的数据。因此,访问彩色像素中元素的方法如下:
image.at<cv::Vec3b>(j, i)[channel] = value;
channel
索引用来指明三个颜色通道中的一个。 OpenCV存储通道数据的次序是蓝色、绿色和红色(因此蓝色是通道0)。
为了说明如何直接访问像素值,我们在图像中加入椒盐噪声( salt-and-pepper noise)。椒盐噪声是一个专门的噪声类型,它随机选择一些像素,把它们的颜色替换成白色或黑色。在现实生活中,如果通信时出错,部分像素的值在传输时丢失,就会发生这种噪声。这里我们只是随机地选择一些像素,把它们设置为白色。
样例:
#include <opencv2/opencv.hpp>
/*对图像img添加椒盐噪声,n表示椒盐像素数量*/
void salt(cv::Mat img, int n)
{
int col, row;
for (int k = 0; k < n; k++) {
// rand 随机生成器
int rand = std::rand();
col = rand % img.cols;
row = rand % img.rows;
if (CV_8UC1 == img.type()) // 灰度图像
img.at<uchar>(row, col) = 255;
else if (CV_8UC3 == img.type()) { // 彩色图像
img.at<cv::Vec3b>(row, col)[0] = 255;
img.at<cv::Vec3b>(row, col)[1] = 255;
img.at<cv::Vec3b>(row, col)[2] = 255;
}
}
}
int main()
{
/*加载图像*/
cv::Mat srcImg = cv::imread("bird.bmp", 1);
const int64 start = cv::getTickCount();
salt(srcImg, 3000); // 访问像素
double duration = (cv::getTickCount() - start) / cv::getTickFrequency();
std::cout << __FILE__ << ": " << __LINE__ << ": duration: " << duration << std::endl;
/*显示图像*/
cv::imshow("srcImg", srcImg);
cv::waitKey();
return 0;
}
现实结果如下:
最后一点需要说明的是:这些修改图像的函数在使用图像作为参数时,都采用了值传递的方式。之所以这样做,是因为它们在复制图像时仍共享了同一块图像数据。因此在需要修改图像内容时,图像参数没必要采用引用传递的方式。
衡量算法性能
为了衡量函数或代码短的运行时间,OpenCv 有两个非常实用的函数:
cv::getTickCount
,该函数返回从最近一次电脑开机到当前的时间周期数;cv::getTickFrequency()
,返回每秒的时钟周期数。
为了获得某个函数的运行时间,我们可以使用如下的程序模板:
const int64 start = cv::getTickCount();
salt(srcImg, 3000); // 调用函数
/*经过的时间段(单位:秒)*/
double duration = (cv::getTickCount() - start) / cv::getTickFrequency();