OpenCV 图像的基本表示方法

正文索引 [隐藏]

前言

OpenCV 本身是一个【黑盒】,它为我们提供了图像处理的接口(参数、返回值)。我们只需要掌握接口的正确使用方法,就可以在完全不了解其内部工作原理(算法)的情况下,方便地进行各种复杂的图像处理。但是如果我们完全忽略内部工作原理是如何实现的,是不可能很好地使用OpenCV的,更不可能设计出好的计算机视觉应用系统。因此从上述角度讲,我们可以从两个角度学习 OpenCV:

● 将 OpenCV 作为【白盒】学习:深入学习 OpenCV 每个函数所使用算法的基本原理、每个函数的具体实现细节,进一步加深对图像处理的理解。
● 将 OpenCV 作为【黑盒】学习:仅仅将 OpenCV 作为一个工具来使用,学习的是每个函数内参数的含义和使用方式,学习的目的是更好地使用 OpenCV 函数。

这篇博客主要以理论知识为主来记录我学习图像的基本表示方法的过程。图像主要有二值图像灰度图像彩色图像这三种基本表示方法。

二值图像

二值图像是指仅包含黑色和白色两种颜色的图像。计算机是通过一个栅格状排列的数据集(矩阵)来表示和处理图像。如图是一个字母 A 的图像,计算机在处理该图像时,会首先将其划分为一个个的小方块,每一个小方块就是一个独立的处理单位,称为像素点。接下来,计算机会将其中的白色像素点处理为 1,将黑色像素点处理为 0,以方便进行后续的处理及存储操作。因为图像内只有黑白两种颜色,所以只使用一个比特位(0或1)就能表示。在 OpenCV 中,最小的数据类型是无符号的 8 位二进制值。因此,在 OpenCV 中实际上并没有二值图像这种数据类型,二值图像经常是用 0 表示黑色,用 255 表示白色。我在进行实验的过程中实际上是初始化了一个灰度图像,所以我的白点是 255,字母 A 在计算机内的存储形式如控制台打印所示。

Mat a = Mat(12, 12, CV_8UC1, 255);   //创建空白灰度图像

灰度图像

二值图像表示起来简单方便,但是它只有黑白两种颜色,所表示的图像不够细腻。如果想要表现更多的细节,就需要使用更多的颜色。如图是一幅灰度图像,它采用了更多的数值以体现不同的颜色,因此该图像的细节信息更丰富。 计算机将灰度处理为 256 个灰度级,用数值区间[0, 255]来表示 256 个灰度级的,正好可以用一个Byte(8位二进制值)来表示。其中数值 255 表示纯白色,数值 0 表示纯黑色,其余的数值表示从纯白到纯黑之间不同级别的灰度。

彩色图像

相比于二值图像和灰度图像,彩色图像是一类更常见的图像,它能表现更丰富的细节信息。 神经生理学实验发现,在视网膜上存在三种不同的颜色感受器,能够感受三种不同的颜色:红色绿色蓝色,即三基色。自然界中常见的各种色光都可以通过将三基色按照一定的比例混合构成。除此以外,从光学角度出发,可以将颜色解析为主波长、纯度、明度等。从心理学和视觉角度出发,可以将颜色解析为色调、饱和度、亮度等。通常,我们将上述采用不同的方式表述颜色的模式称为色彩空间。虽然不同的色彩空间具有不同的表示方式,但是各种色彩空间之间可以根据需要按照公式进行转换。在 RGB 色彩空间中,存在 R(red, 红色)通道、G(green, 绿色)通道和 B(blue, 蓝色)这三个通道。每个色彩通道值的范围都在[0, 255]之间,我们用这三个色彩通道的组合表示颜色。换种比较通俗的方式来解释就是,有三个分别装了红色、绿色、蓝色的油漆桶,我们从每个油漆桶中任取容量为 0~255 个单位的油漆,将三种油漆混合就可以调配出一种新的颜色。三种油漆经过不同的组合,共可以调配出所有常见的 256×256×256=16777216 种颜色。一般在 RGB 色彩空间中,图像通道的顺序是 R→G→B。需要注意的是,在 OpenCV 中,通道的顺序是 B→G→R。

● 第 1 个通道是 B 通道
● 第 2 个通道是 G 通道
● 第 3 个通道是 R 通道

因此通常用一个三维数组 Vec3b 来表示一幅 RGB 色彩空间的彩色图像。Vec3b可以看作是vector<uchar, 3>,即一个 uchar 类型的、长度为 3vector 向量。

//这是一段没有任何作用的代码,只是用来了解这个层次
Mat image = Mat::zeros(12, 12, CV_8UC3);  //3通道的RGB图像
int width = a.cols;  //列数,反映矩阵宽度
int height = a.rows; //行数,反映矩阵高度
int dims = a.channels();
cout << "width:" << a.cols << ",height:" << a.rows << ",channels:" << a.channels() << endl;
for(int row = 0; row < width; row++) {
    for(int col = 0; col < height; col++) {
        Vec3b bgr = image.at<Vec3b>(row,col);
        int B = image.at<Vec3b>(row,col)[0];   //B通道数据
        int G = image.at<Vec3b>(row,col)[1];   //G通道数据
        int R = image.at<Vec3b>(row,col)[2];   //R通道数据
    }
}

可以在Image Watch种放大查看像素,能够看到 vec3b 中的三个值。 下面写个实例来验证OpenCV 中,通道的顺序是 B→G→R。新建一个空白的3通道RGB图像,将前100行的所有像素点的Vec3b值为(255, 0, 0),

Mat TydCV::createBGR() {
    Mat a = Mat(300, 300, CV_8UC3, Scalar(0,0,0));
    int width = a.cols;
    int height = a.rows;
    for(int row = 0; row < width; row++) {
        if(row < 100) { //0<=row<100
            for(int col = 0; col < height; col++) {
                Vec3b bgr = a.at<Vec3b>(row,col);
                a.at<Vec3b>(row,col)[0] = 255;
            }
        } else if(row < 200) { //100<=row<200
            for(int col = 0; col < height; col++) {
                Vec3b rgb = a.at<Vec3b>(row,col);
                a.at<Vec3b>(row,col)[1] = 255;
            }
        } else { //200<=row<300
            for(int col = 0; col < height; col++) {
                Vec3b rgb = a.at<Vec3b>(row,col);
                a.at<Vec3b>(row,col)[2] = 255;
            }
        }
    }
    return a;
}