像素是图像构成的基本单位,像素处理是图像处理的基本操作,可以通过位置索引的形式对图像内的元素进行访问、处理。
二值图像: 是一种特殊的灰度图像,在OPENCV中,将黑定义为0,255定义为白。 在OPENCV中,二值图像/灰度图像以二维数组形式进行存放彩色图像以三维数组形式进行存放(X*Y*3, 通道存放顺序为BGR,X,Y为图像x,y轴向上的像素点个数)。
为了方便理解,我们首先来生成一个8×8大小的数组,用来模拟一个黑色图像,并对其进行简单处理。
【例1】使用OpenCV库来创建一个8x8的黑色图像,所有元素的值都是0,然后修改其中一个像素点的值,并显示修改后的图像。根据题目要求及分析,编写代码如下:
cpp
#include <iostream>
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img = cv::Mat::zeros(8, 8, CV_8UC1);
std::cout << "img=\n" << img << std::endl;
cv::imshow("one", img);
std::cout << "读取像素点img[0,3]=" << img.at<uchar>(0, 3) << std::endl;
img.at<uchar>(0, 3) = 255;
std::cout << "修改后img=\n" << img << std::endl;
std::cout << "读取修改后像素点img[0,3]=" << img.at<uchar>(0, 3) << std::endl;
cv::imshow("two", img);
cv::waitKey();
cv::destroyAllWindows();
return 0;
}
代码分析如下。
● 使用OpenCV库生成了一个8×8大小的二维数组,其中所有的值都是0,数值类型是np.uint8。根据该数组的属性,可以将其看成一个黑色的图像。
● 语句img[0,3]访问的是img第0行第3列的像素点,需要注意的是,行序号、列序号都是从0开始的。
● 语句img[0,3]=255将img中第0行第3列的像素点的像素值设置为"255"。
运行上述程序,会出现名为one和two的两个非常小的窗口,其中:
● 名为one的窗口是一个纯黑色的图像。
● 名为two的窗口在顶部靠近中间位置有一个白点(对应修改后的值255),其他地方也都是纯黑的图像。同时,在控制台会输出如下内容:
cpp
img= [
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]]
读取像素点img[0,3]= 0
修改后img= [
[ 0 0 0 255 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]]
读取修改后像素点img[0,3]= 255
通过本例中两个窗口显示的图像可知,二维数组与图像之间存在对应关系。
【例2】读取一个灰度图像,并对其像素进行访问、修改。根据题目要求,编写代码如下:
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img = cv::imread("lena.bmp", cv::IMREAD_GRAYSCALE);
if (img.empty()) {
std::cout << "无法读取图像" << std::endl;
return -1;
}
cv::imshow("before", img);
cv::waitKey(0);
for (int i = 100; i < 120; i++) {
for (int j = 100; j < 120; j++) {
img.at<uchar>(i, j) = 255;
}
}
cv::imshow("after", img);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
在本例中,使用了一个嵌套循环语句,将图像img中"第100行到119行"与"第100列到119列"交叉区域内的像素值设置为255。从图像img上来看,该交叉区域被设置为白色。运行程序,结果如图1所示,其中:
图1 像素修改示例
● 左图是读取的原始图像。
● 右图是经过修改后的图像。
2.彩色图像RGB模式的彩色图像在读入OpenCV内进行处理时,会按照行方向依次读取该RGB图像的B通道、G通道、R通道的像素点,并将像素点以行为单位存储在ndarray的列中。例如,有一幅大小为R行×C列的原始RGB图像,其在OpenCV内以BGR模式的三维数组形式存储,如图2所示。
图2 RGB图像以三维数组形式存储的情况说明
可以使用表达式访问数组内的值。例如,可以使用image[0][0][0]访问图像image的B通道内的第0行第0列上的像素点,式中:
● 第1个索引表示第0行。
● 第2个索引表示第0列。
● 第3个索引表示第0个颜色通道。
根据上述分析可知,假设有一个红色(其R通道值为255, G通道值为0, B通道值为0)图像,不同的访问方式得到的值如下:
● img[0][0]:访问图像img第0行第0列像素点的BGR值。图像是BGR格式的,得到的数值为[0,0,255]。
● img[0][0][0]:访问图像img第0行第0列第0个通道的像素值。图像是BGR格式的,所以第0个通道是B通道,会得到B通道内第0行第0列的位置所对应的值0。
● img[0][0][1]:访问图像img第0行第0列第1个通道的像素值。图像是BGR格式的,所以第1个通道是G通道,会得到G通道内第0行第0列的位置所对应的值0。
● img[0][0][2]:访问图像img第0行第0列第2个通道的像素值。图像是BGR格式的,所以第2个通道是R通道,会得到R通道内第0行第0列的位置所对应的值255。
彩色图像的像素级处理
【例3】生成一个三维数组,用来观察三个通道值的变化情况。根据题目要求,编写代码如下:
cpp
#include <opencv2/opencv.hpp>
int main() {
// 创建一个300x300大小的蓝色通道图像
cv::Mat blue(300, 300, CV_8UC3, cv::Scalar(0, 0, 255));
cv::imshow("blue", blue);
// 创建一个300x300大小的绿色通道图像
cv::Mat green(300, 300, CV_8UC3, cv::Scalar(255, 0, 0));
cv::imshow("green", green);
// 创建一个300x300大小的红色通道图像
cv::Mat red(300, 300, CV_8UC3, cv::Scalar(0, 255, 0));
cv::imshow("red", red);
// 等待用户按键,然后关闭所有窗口
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
在本例中,分别生成了blue、green、red三个数组,其初始值都是0。接下来,分别改变各通道值。
● 针对数组blue,将其第0个通道的值设置为255。从图像角度来看,图像blue的B通道值为255,其余两个通道值为0,因此图像blue为蓝色图像。
● 针对数组green,将其第1个通道的值设置为255。从图像角度来看,图像green的G通道值为255,其余两个通道值为0,因此图像green为绿色图像。● 针对数组red,将其第2个通道的值设置为255。从图像角度来看,图像red的R通道值为255,其余两个通道值为0,因此图像red为红色图像。运行上述程序,会显示颜色为蓝色、绿色、红色的三幅图像,分别对应数组blue、数组green、数组red。因为黑白印刷无法显示彩色图像,所以请读者运行程序后观察结果。
除了显示图像,还会显示每个数组的输出值,部分输出结果如图3所示。
图3 【例3】程序的部分输出结果