2.2 像素处理
像素是图像构成的基本单位,像素处理是图像处理的基本操作,可以通过位置索引的形式对图像内的元素进行访问、处理。
1.二值图像及灰度图像
需要说明的是,在OpenCV中,最小的数据类型是无符号的8位数。因此,在OpenCV中实际上并没有二值图像这种数据类型,二值图像经常是通过处理得到的,然后使用0表示黑色,使用255表示白色。
可以将二值图像理解为特殊的灰度图像,这里仅以灰度图像为例讨论像素的读取和修改。通过2.1节的分析可知,可以将图像理解为一个矩阵,在面向Python的OpenCV(OpenCV for Python)中,图像就是Numpy库中的数组。一个OpenCV灰度图像是一个二维数组,可以使用表达式访问其中的像素值。例如,可以使用image[0,0]访问图像image第0行第0列位置上的像素点。第0行第0列位于图像的左上角,其中第1个索引表示第0行,第2个索引表示第0列。
为了方便理解,我们首先使用Numpy库来生成一个8×8大小的数组,用来模拟一个黑色图像,并对其进行简单处理。
【例2.1】使用Numpy库生成一个元素值都是0的二维数组,用来模拟一幅黑色图像,并对其进行访问、修改。
分析:使用Numpy库中的函数zeros()可以生成一个元素值都是0的数组,并可以直接使用数组的索引对其进行访问、修改。
根据题目要求及分析,编写代码如下:
import cv2 import numpy as np img=np.zeros((8,8), dtype=np.uint8) print("img=\n", img) cv2.imshow("one", img) print("读取像素点img[0,3]=", img[0,3]) img[0,3]=255 print("修改后img=\n", img) print("读取修改后像素点img[0,3]=", img[0,3]) cv2.imshow("two", img) cv2.waitKey() cv2.destroyAllWindows()
代码分析如下。
● 使用函数zeros()生成了一个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),其他地方也都是纯黑的图像。
同时,在控制台会输出如下内容:
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.2】读取一个灰度图像,并对其像素进行访问、修改。
根据题目要求,编写代码如下:
import cv2 img=cv2.imread("lena.bmp",0) cv2.imshow("before", img) for i in range(10,100): for j in range(80,100): img[i, j]=255 cv2.imshow("after", img) cv2.waitKey() cv2.destroyAllWindows()
在本例中,使用了一个嵌套循环语句,将图像img中“第10行到99行”与“第80列到99列”交叉区域内的像素值设置为255。从图像img上来看,该交叉区域被设置为白色。
运行程序,结果如图2-6所示,其中:
图2-6 像素修改示例
● 左图是读取的原始图像。
● 右图是经过修改后的图像。
2.彩色图像
RGB模式的彩色图像在读入OpenCV内进行处理时,会按照行方向依次读取该RGB图像的B通道、G通道、R通道的像素点,并将像素点以行为单位存储在ndarray的列中。例如,有一幅大小为R行×C列的原始RGB图像,其在OpenCV内以BGR模式的三维数组形式存储,如图2-7所示。
图2-7 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。
为了方便理解,我们首先使用Numpy库来生成一个2×4×3大小的数组,用它模拟一幅黑色图像,并对其进行简单处理。
【例2.3】使用Numpy生成三维数组,用来观察三个通道值的变化情况。
根据题目要求,编写代码如下:
import numpy as np import cv2 #-----------蓝色通道值-------------- blue=np.zeros((300,300,3), dtype=np.uint8) blue[:, :,0]=255 print("blue=\n", blue) cv2.imshow("blue", blue) #-----------绿色通道值-------------- green=np.zeros((300,300,3), dtype=np.uint8) green[:, :,1]=255 print("green=\n", green) cv2.imshow("green", green) #-----------红色通道值-------------- red=np.zeros((300,300,3), dtype=np.uint8) red[:, :,2]=255 print("red=\n", red) cv2.imshow("red", red) #-----------释放窗口-------------- cv2.waitKey() cv2.destroyAllWindows()
在本例中,分别生成了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。因为黑白印刷无法显示彩色图像,所以请读者运行程序后观察结果。
除了显示图像,还会显示每个数组的输出值,部分输出结果如图2-8所示。
图2-8 【例2.3】程序的部分输出结果
【例2.4】使用Numpy生成一个三维数组,用来观察三个通道值的变化情况。
根据题目要求,编写代码如下:
import numpy as np import cv2 img=np.zeros((300,300,3), dtype=np.uint8) img[:,0:100,0]=255 img[:,100:200,1]=255 img[:,200:300,2]=255 print("img=\n", img) cv2.imshow("img", img) cv2.waitKey() cv2.destroyAllWindows()
运行上述程序,会显示如图2-9所示的图像。由于本书为黑白印刷,所以为了更好地观察运行效果,请大家亲自上机运行程序。
图2-9 【例2.4】程序的运行结果
除了显示图像,还会显示img值的情况,部分输出结果如图2-10所示。
图2-10 【例2.4】程序的部分输出结果
【例2.5】使用Numpy生成一个三维数组,用来模拟一幅BGR模式的彩色图像,并对其进行访问、修改。
分析:使用Numpy中的zeros()函数可以生成一个元素值都是0的数组。可以直接使用数组的索引形式对其进行访问、修改。
根据题目要求及分析,编写代码如下:
1. import numpy as np 2. img=np.zeros((2,4,3), dtype=np.uint8) 3. print("img=\n", img) 4. print("读取像素点img[0,3]=", img[0,3]) 5. print("读取像素点img[1,2,2]=", img[1,2,2]) 6. img[0,3]=255 7. img[0,0]=[66,77,88] 8. img[1,1,1]=3 9. img[1,2,2]=4 10. img[0,2,0]=5 11. print("修改后img\n", img) 12. print("读取修改后像素点img[1,2,2]=", img[1,2,2])
本程序进行了如下操作。
● 第2行使用zeros()生成一个2×4×3大小的数组,其对应一个“2行4列3个通道”的BGR图像。
● 第3行使用print语句显示(打印)当前图像(数组)的值。
● 第4行中的img[0,3]语句会访问第0行第3列位置上的B通道、G通道、R通道三个像素点。
● 第5行的img[1,2,2]语句会访问第1行第2列第2个通道位置上的像素点。
● 第6行的img[0,3]=255语句会修改img中第0行第3列位置上的像素值,该位置上的B通道、G通道、R通道三个像素点的值都会被修改为255。
● 第7行的img[0,0]=[66,77,88]语句会修改img中第0行第0列位置上的B通道、G通道、R通道上三个像素点的值,将它们修改为[66,77,88]。
● 第8行的img[1,1,1]=3语句会修改img中第1行第1列第1个通道(G通道)位置上的像素值,将其修改为3。
● 第9行的img[1,2,2]=4语句会修改img中第1行第2列第2个通道(R通道)位置上的像素值,将其修改为4。
● 第10行的img[0,2,0]=5语句会修改img中第0行第2列第0个通道(B通道)位置上的像素值,将其修改为5。
● 最后两行使用print语句观察img和img[1,2,2]的值。
运行上述程序,会在控制台输出如下结果:
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]]] 读取像素点img[0,3]= [0 0 0] 读取像素点img[1,2,2]= 0 修改后img [[[ 66 77 88] [ 0 0 0] [ 5 0 0] [255 255 255]] [[ 0 0 0] [ 0 3 0] [ 0 0 4] [ 0 0 0]]] 读取修改后像素点img[1,2,2]= 4
在本例中,为了方便说明问题,设置的数组比较小。在实际中可以定义稍大的数组,并使用cv2.imshow()将其显示出来,进一步观察处理结果,加深理解。
【例2.6】读取一幅彩色图像,并对其像素进行访问、修改。
根据题目要求,编写代码如下:
1. import cv2 2. img=cv2.imread("lenacolor.png") 3. cv2.imshow("before", img) 4. print("访问img[0,0]=", img[0,0]) 5. print("访问img[0,0,0]=", img[0,0,0]) 6. print("访问img[0,0,1]=", img[0,0,1]) 7. print("访问img[0,0,2]=", img[0,0,2]) 8. print("访问img[50,0]=", img[50,0]) 9. print("访问img[100,0]=", img[100,0]) 10. #区域1 11. for i in range(0,50): 12. for j in range(0,100): 13. for k in range(0,3): 14. img[i, j, k]=255 #白色 15. #区域2 16. for i in range(50,100): 17. for j in range(0,100): 18. img[i, j]=[128,128,128] #灰色 19. #区域3 20. for i in range(100,150): 21. for j in range(0,100): 22. img[i, j]=0 #黑色 23. cv2.imshow("after", img) 24. print("修改后img[0,0]=", img[0,0]) 25. print("修改后img[0,0,0]=", img[0,0,0]) 26. print("修改后img[0,0,1]=", img[0,0,1]) 27. print("修改后img[0,0,2]=", img[0,0,2]) 28. print("修改后img[50,0]=", img[50,0]) 29. print("修改后img[100,0]=", img[100,0]) 30. cv2.waitKey() 31. cv2.destroyAllWindows()
上述程序进行了如下操作。
● 第2行使用imread()函数读取当前目录下的一幅彩色RGB图像。
● 第4行的img[0,0]语句会访问img中第0行第0列位置上的B通道、G通道、R通道三个像素点。
● 第5~7行分别会访问img中第0行第0列位置上的B通道、G通道、R通道三个像素点。
● 第8行的img[50,0]语句会访问第50行第0列位置上的B通道、G通道、R通道三个像素点。
● 第9行的img[100,0]语句会访问第100行第0列位置上的B通道、G通道、R通道三个像素点。
● 第10~14行使用三个for语句的嵌套循环,对图像左上角区域(即“第0行到第49行”与“第0列到第99列”的行列交叉区域,我们称之为区域1)内的像素值进行设定。借助img[i, j, k]=255语句将该区域内的三个通道的像素值都设置为255,让该区域变为白色。
● 第15~18行使用两个for语句的嵌套循环,对图像左上角位于区域1正下方的区域(即“第50行到第99行”与“第0列到第99列”的行列交叉区域,我们称之为区域2)内的像素值进行设定。借助img[i, j]=[128,128,128]语句将该区域内的三个通道的像素值都设置为128,让该区域变为灰色。
● 第19~21行使用两个for语句的嵌套循环,对图像左上角位于区域2正下方的区域(即“第100行到第149行”与“第0列到第99列”的行列交叉区域,我们称之为区域3)内的像素值进行设定。借助img[i, j]=0语句将该区域内的三个通道的像素值都设置为0,让该区域变为黑色。
运行程序,结果如图2-11所示,其中左图是读取的原始图像,右图是经过修改后的图像。
图2-11 【例2.6】程序的运行结果
同时,在控制台会输出如下内容:
访问img[0,0]= [125 137 226] 访问img[0,0,0]= 125 访问img[0,0,1]= 137 访问img[0,0,2]= 226 访问img[50,0]= [114 136 230] 访问img[100,0]= [ 75 55 155] 修改后img[0,0]= [255 255 255] 修改后img[0,0,0]= 255 修改后img[0,0,1]= 255 修改后img[0,0,2]= 255 修改后img[50,0]= [128 128 128] 修改后img[100,0]= [0 0 0]