转载自:Python学习笔记 | opencv图像处理(二)通道
原创 维克少 我是维克少 9月10日
上一篇推送我们介绍了一个很重要的概念,叫做位深度。简单来说,一个图片的位深度决定了像素的大小,从而决定一幅图片的颜色。
上篇推送:Python学习笔记 | opencv图像处理(一)
对计算机来说,照片的尺寸和深度决定了一张照片的所有信息,然而,为了方便颜色处理,我们定义了“通道”这一概念,用于分离一个像素点的元素。例如,之前提到的RGB颜色模型,就用到了三个通道(R、G、B)。
RGB模式下通道分离
例如上面的图片,左上角是原始图片。在RGB色彩模式下,每个像素点是由红绿蓝三种光混合而来的,每一个颜色占用一个通道,因此RGB模式下一共有三个通道。
我们得到了如下关系:
位深度 = 通道数 × 每个通道基本元素的字节数 × 8
RGB模式下的图片,位深度为24 bit,共有3个通道,每个通道的基本元素为1字节(B),1字节等于8比特。
RGB是一种非常常用的三通道颜色模型,在RGB的基础上,人们添加了一个控制透明度的通道Alpha,得到了RGBA颜色模型。
RGBA模式下修改透明度
假如我有一张3通道(例如RGB)24-bit位深度的照片,那么按照道理,这张照片每一个像素点的值都应当在0到255的范围内。然而,当我们对图片矩阵数据进行数学运算时,矩阵里的元素有可能会超出0到255的范围,此时,0至255的范围就会限制我们的发挥。因此,在定义我们的矩阵画布时,需要同时定义数据类型。
在OpenCV中,一张画布应该确定如下信息:
数据类型:
8U(8位无符号整数),8S(8位有符号整数),
16U(16位无符号整数),16S(16位有符号整数),
32S(32位有符号整数),32F(单精度浮点数),
64F(双精度浮点数)
通道数量:C1,C2,C3,C4(分别对应1-4个通道)
对各种数据类型进行解释:
图片数据类型 | 取值范围 | 矩阵数据类型(dtype) |
8U |
[0, 255] |
np.uint8 |
8S |
[-128, 127] |
np.int8 |
16U |
[0, 65535] |
np.uint16 |
16S |
[-32768, 32767] |
np.int16 |
32S |
[-231, 231-1] |
np.int32 |
32F |
[0.0, 1.0] |
np.float32 |
64F |
[0.0, 1.0] |
np.float64 |
规律:
对于一个n位的二进制数:
无符号整数的取值范围,二进制下n个0一直到n个1,转变为十进制,取值范围是0至2n-1
例如:8U的取值范围是(0000 0000)2至(1111 1111)2,转化成十进制就是0至28-1,即0至255;
有符号整数的取值范围相比于无符号整数,最高位由1表示负数,0表示正数,因此真正表示数字的位数为n-1,转变成十进制,取值范围为-2n-1-1至2n-1-1;同时,考虑到+0与-0在数量上没有差别,因此,原本用于表示-0的(1000 0000 0000 ... 0000)2用于表示最小数,十进制下取值范围下限加一,变成-2n-1至2n-1-1,
例如:8U的取值范围是(1111 1111)2至(0111 1111)2,最高位表示正负号,因此转化成十进制为-27至27-1,即-128至127;
单双浮点数可以理解为通俗意义上的小数,具体的编码方式可以参考技术文件IEEE 754,平时经常用到的就是32位单精度浮点数与64位双精度浮点数
上面三组数据进行排列组合,就会得出组合:
通道1 | 通道2 | 通道3 | 通道4 | |
深度0 |
CV_8UC1=0 |
CV_8UC2=8 |
CV_8UC3=16 |
CV_8UC4=24 |
深度1 |
CV_8SC1=1 |
CV_8SC2=9 |
CV_8SC3=17 |
CV_8SC4=25 |
深度2 |
CV_16UC1=2 |
CV_16UC2=10 |
CV_16UC3=18 |
CV_16UC4=26 |
深度3 |
CV_16SC1=3 |
CV_16SC2=11 |
CV_16SC3=19 |
CV_16SC4=27 |
深度4 |
CV_32SC1=4 |
CV_32SC2=12 |
CV_32SC3=20 |
CV_32SC4=28 |
深度5 |
CV_32FC1=5 |
CV_32FC2=13 |
CV_32FC3=21 |
CV_32FC4=29 |
深度6 |
CV_64FC1=6 |
CV_64FC2=14 |
CV_64FC3=22 |
CV_64FC4=30 |
我们曾经说过,OpenCV处理图片的本质,是把图片转化成矩阵,通过矩阵的运算得到新的图片。
首先,我们需要创建一个空的画布,最简单的方法是创建一个所有元素为0的矩阵,方法如下:
import numpy as np
canvas = np.zeros(shape=(400,400,3), dtype=np.uint8)
# size里填入的三个数依次为宽(行数)、长(列数)、通道数(C3)
# dtype规定该画布里每个数据都在0-255之间(8U)
# 此时画布类型为8UC3(一般RGB颜色模型下的图片就是8UC3类型的)
此时我们可以在画布里填上任意数,试试看效果
draw1 = np.copy(canvas) # 在复制的图片上操作,是一个好习惯
draw1[:, :, :] = 255
# []是切片用法,此处用“,”分割3个维度——行,列,通道
# 此处把255这个值赋给这个画布所有行,所有列,所有3个通道里的所有元素
cv2.imshow('255', draw1) # 展示draw1
cv2.waitKey()
如图,所有像素全部转变成了白色
当然,这个矩阵里的每个数据都应当在0到255之间,但如果我们直接给矩阵赋一个超出该范围的值,结果会怎么样呢?
draw2 = np.copy(canvas) # 在复制的图片上操作,是一个好习惯
draw2[:, :, :] = 256
# 此处把256这个值赋给这个画布所有行,所有列,所有3个通道里的所有元素
cv2.imshow('256', draw1) # 展示draw2
cv2.waitKey()
所有像素全部转变成了黑色
发现得到了一张黑色的图片,这是为什么呢?
当运算结果超出机器数所能表示的范围,机器就会溢出(spillover),比如说,对(1111 1111)2即(255)10加1时,数据应当变为(1 0000 0000)2,但是uint8下仅仅可以保留8个bit,因此溢出的第九位会被舍去,变成(0000 0000)2,也就是10进制下的0。这样256被解读成了0,画布变成了黑色(0,0,0)。
总之,空的画布在OpenCV的主要作用在于暂时储存需要修改的部分并且进行运算操作,下面将展示一些常用的通道操作。
此时我们可以利用numpy的切片工具,对canvas的局部进行操作。
import numpy as np
import cv2
img = cv2.imread('阳光猫猫头.png', cv2.IMREAD_COLOR)
# 矩阵方法
r = np.copy(img)[:, :, 2]
g = np.copy(img)[:, :, 1]
b = np.copy(img)[:, :, 0]
# 注:OpenCV默认的通道顺序并非RGB,而是BGR
# OpenCV的内置方法
b, g, r = cv2.split(img)
此时我们单独观察R,G,B三个通道
cv2.imshow('R', r)
cv2.imshow('G', g)
cv2.imshow('B', b)
cv2.waitKey()
RGB灰度图
我们获得了三个通道的灰度图,这个操作在经常用于磨皮,在图像识别中也经常用此方法压缩信息、提取特征。
观察发现,此时这只猫猫头整体红色系偏强,蓝色系偏弱,我们希望对此调整,红色减弱,蓝色增强。
import cv2
import numpy
img = cv2.imread('阳光猫猫头.png', cv2.IMREAD_COLOR)
# 使用OpenCV的内置方法切割通道
b, g, r = cv2.split(img)
r_new = r - 20 # 红色全部下降20
b_new = b + 20 # 蓝色全部上升20
# 注意不要用太大的数字,否则数据会溢出,得到奇怪的效果
# 不过很建议大家尝试一下
调整完通道以后,我们还需要重新合并图像
# 矩阵方法
img_new = np.zeros(img.shape, np.uint8)
img_new[:, :, 0] = b_new
img_new[:, :, 1] = g
img_new[:, :, 2] = r_new
# 图像方法
img_new = cv2.merge([b_new, g, r_new])
# 输出
cv2.imwrite('阳光猫猫头_2.png', img_new)
原图(左)与修改后(右)对比
为了方便操作,我们会把一张图片分成几个通道进行处理
图片里的每个元素,都有自己的取值范围,数据处理时,要注意数据是否会出现溢出的情况
创建空画布
np.zeros(shape, dtype) # 纯黑画布
np.ones(shape, dtype) * 255 # 纯白画布
分离通道
矩阵法(切片工具)
图像法 cv2.split(img)
合并通道
矩阵法(对空画布赋值)
图像法 cv2.merge([b, r, g])
OpenCV——像素数据类型总结 [https://www.cnblogs.com/farewell-farewell/p/5914685.html]
32 位的有符号整数的取值范围以及数值溢出 [https://blog.csdn.net/sytyp111/article/details/115170758]
百度百科——IEEE 754 [https://baike.baidu.com/item/IEEE%20754]
float32与float64 [https://blog.csdn.net/linkequa/article/details/107722310]