本系列面向 Python 小白,从零开始实战解说 OpenCV 项目实战。
OpenCV 中图像的数据结构是 ndarray 多维数组,对图像的任何操作本质上都是对 ndarray 多维数组的操作和运算。
本节介绍图像的格式和 Numpy 方法图像处理,提供完整例程和运行结果:查看图像属性,像素读取与编辑,创建空白、黑色、白色、随机图像,图像复制,图像裁剪,ROI裁剪,图像拼接、图像通道的拆分与合并。
按照颜色对图像进行分类,可以分为二值图像、灰度图像和彩色图像。
彩色图像可以采用不同的表达方式。OpenCV 使用 BGR 格式,色彩通道按照 B/G/R 的顺序排列;而 matplotlib、PyQt5、Pillow 中使用 RGB 格式,色彩通道按照 R/G/B 的顺序排列的。
一些彩色图像格式还支持透明通道(alpha 通道),每个像素点用 8bit 数字 [0,255] 表示透明度,0 表示完全透明,255 表示完全不透明。
在数字图像处理中,可以根据需要对图像的通道顺序进行转换,或将彩色图像转换为灰度图像、二值图像。
数字图像是通过栅格排列的像素组成的,在计算机中以多维数据集来表示和处理。
OpenCV 的 Python API 是基于 Numpy 来存储和处理多维数组,图像的数据结构是 ndarray 多维数组。OpenCV 中对图像的任何操作,本质上都是对 ndarray 多维数组的操作和运算。
OpenCV 中的二值图像和灰度图像用二维数组 (h, w) 表示,数组中的每个元素表示对应一个像素的灰度,每个像素的位深度为 8位。
OpenCV 中二值图像被作为特殊的灰度图像,每个像素点的值为 0(黑色)或 255(白色)。
OpenCV 中的彩色图像用三维数组 (h, w, ch=3) 表示,数组中的每个元素对应一个像素的某种颜色分量,每个像素的位深度为 24位。
OpenCV 使用 BGR 格式,色彩通道顺序为 B/G/R,因此 B 通道是 img[:, :, 0], G 通道是 img[:, :, 1], R 通道是 img[:, :, 2]。
OpenCV 中图像对象的数据结构是 ndarray 多维数组,因此 ndarray 数组的属性和操作方法也都适用于 OpenCV 的图像对象。例如:
img.ndim:查看图像的维数,彩色图像的维数为 3,灰度图像的维数为 2。
img.shape:查看图像的形状,即图像栅格的行数(高度)、列数(宽度)、通道数。
img.size:查看图像数组元素总数,灰度图像的数组元素总数为像素数量,彩色图像的数组元素总数为像素数量与通道数的乘积。
基本例程:
# 1.11 图像数组的属性
imgFile = "../images/imgLena.tif" # 读取文件的路径
img1 = cv2.imread(imgFile, flags=1) # flags=1 读取彩色图像(BGR)
img2 = cv2.imread(imgFile, flags=0) # flags=0 读取为灰度图像
# cv2.imshow("Demo1", img1) # 在窗口显示图像
# key = cv2.waitKey(0) # 等待按键命令
# 维数(ndim), 形状(shape), 元素总数(size), 元素类型(dtype)
print("Ndim of img1(BGR): {}, img2(Gray): {}".format(img1.ndim, img2.ndim)) # number of rows, columns and channels
print("Shape of img1(BGR): {}, img2(Gray): {}".format(img1.shape, img2.shape)) # number of rows, columns and channels
print("Size of img1(BGR): {}, img2(Gray): {}".format(img1.size, img2.size)) # size = rows * columns * channels
print("Dtype of img1(BGR): {}, img2(Gray): {}".format(img1.dtype, img2.dtype)) # uint8
本例程的运行结果如下:
Ndim of img1(BGR): 3, img2(Gray): 2
Shape of img1(BGR): (512, 512, 3), img2(Gray): (512, 512)
Size of img1(BGR): 786432, img2(Gray): 262144
Dtype of img1(BGR): uint8, img2(Gray): uint8
通过资源管理器查看彩色图像和灰度图像的属性如下图,彩色图像的位深度为 24,灰度图像的位深度为 8。
像素是构成数字图像的基本单位,像素处理是图像处理的基本操作。
对像素的访问、修改,可以使用 Numpy 方法直接访问数组元素。
基本例程:
# 1.13 Numpy 获取和修改像素值
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 读取彩色图像(BGR)
x, y = 10, 10 # 指定像素位置 x, y
# (1) 直接访问数组元素,获取像素值(BGR)
pxBGR = img1[x,y] # 访问数组元素[x,y], 获取像素 [x,y] 的值
print("x={}, y={}\nimg[x,y] = {}".format(x,y,img1[x,y]))
# (2) 直接访问数组元素,获取像素通道的值
print("img[{},{},ch]:".format(x,y))
for i in range(3):
print(img1[x, y, i], end=' ') # i=0,1,2 对应 B,G,R 通道
# (3) img.item() 访问数组元素,获取像素通道的值
print("\nimg.item({},{},ch):".format(x,y))
for i in range(3):
print(img1.item(x, y, i), end=' ') # i=0,1,2 对应 B,G,R 通道
# (4) 修改像素值:img.itemset() 访问数组元素,修改像素通道的值
ch, newValue = 0, 255
print("\noriginal img[x,y] = {}".format(img1[x,y]))
img1.itemset((x, y, ch), newValue) # 将 [x,y,channel] 的值修改为 newValue
print("updated img[x,y] = {}".format(img1[x,y]))
本例程的运行结果如下:
x=10, y=10
img[x,y] = [113 131 226]
img[10,10,ch]: 113 131 226
img.item(10,10,ch): 113 131 226
original img[x,y] = [113 131 226]
updated img[x,y] = [255 131 226]
OpenCV 中图像对象的数据结构是 ndarray 多维数组,因此可以用 Numpy 创建多维数组来生成图像。特别对于空白、黑色、白色、随机等特殊图像,用 Numpy 创建图像非常方便。
Numpy 可以使用 np.zeros()
等方法创建指定大小、类型的图像对象,也可以使用 np.zeros_like()
等方法创建与已有图像大小、类型相同的新图像。
函数说明:
numpy.empty(shape[, dtype, order]) # 返回一个指定形状和类型的空数组
numpy.zeros(shape[, dtype, order]) # 返回一个指定形状和类型的全零数组
numpy.ones(shape[, dtype, order]) # 返回一个指定形状和类型的全一数组
numpy.empty_like(img) # 返回一个与图像 img 形状和类型相同的空数组
numpy.zeros_like(img) # 返回一个与图像 img 形状和类型相同的全零数组
numpy.ones_like(img) # 返回一个与图像 img 形状和类型相同的全一数组
参数说明:
基本例程:
# 1.14 Numpy 创建图像
# 创建彩色图像(RGB)
# (1) 通过宽度高度值创建多维数组
width, height, channels = 400, 300, 3 # 行/高度, 列/宽度, 通道数
imgEmpty = np.empty((width, height, channels), np.uint8) # 创建空白数组
imgBlack = np.zeros((width, height, channels), np.uint8) # 创建黑色图像 RGB=0
imgWhite = np.ones((width, height, channels), np.uint8) * 255 # 创建白色图像 RGB=255
# (2) 创建相同形状的多维数组
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 读取彩色图像(BGR)
imgBlackLike = np.zeros_like(img1) # 创建与 img1 相同形状的黑色图像
imgWhiteLike = np.ones_like(img1) * 255 # 创建与 img1 相同形状的白色图像
# (3) 创建彩色随机图像 RGB=random
import os
randomByteArray = bytearray(os.urandom(width * height * channels))
flatNumpyArray = np.array(randomByteArray)
imgRGBRand = flatNumpyArray.reshape(width, height, channels)
# (4) 创建灰度图像
imgGrayWhite = np.ones((width, height), np.uint8) * 255 # 创建白色图像 Gray=255
imgGrayBlack = np.zeros((width, height), np.uint8) # 创建黑色图像 Gray=0
imgGrayEye = np.eye(width) # 创建对角线元素为1 的单位矩阵
randomByteArray = bytearray(os.urandom(width * height))
flatNumpyArray = np.array(randomByteArray)
imgGrayRand = flatNumpyArray.reshape(width, height) # 创建灰度随机图像 Gray=random
本例程的运行结果如下:
使用 Numpy 的 np.copy() 函数可以进行图像的复制,不能通过直接赋值进行图像的复制。
函数说明:
arr = numpy.copy(img) # 返回一个复制的图像
参数说明:
注意事项:
基本例程:
# 1.15 图像的复制
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 读取彩色图像(BGR)
img2 = img1.copy()
print("img2=img1.copy(), img2 is img1?", img2 is img1)
for col in range(100):
for row in range(100):
img2[col, row, :] = 0
img3 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 读取彩色图像(BGR)
img4 = img3
print("img4=img3, img4 is img3?", img4 is img3)
for col in range(100):
for row in range(100):
img4[col, row, :] = 0
cv2.imshow("Demo1", img1) # 在窗口显示图像
cv2.imshow("Demo2", img2) # 在窗口显示图像
cv2.imshow("Demo3", img3) # 在窗口显示图像
cv2.imshow("Demo4", img4) # 在窗口显示图像
key = cv2.waitKey(0) # 等待按键命令
本例程中,img4=img3 直接赋值,改变 img4 的数值后 img3 的数值也被改变了;img2 = img1.copy(),改变 img2 的数值后 img1 并未发生改变。
本例程的运行结果如下,使用 np.copy() 方法得到的新图像才是深拷贝。
img2=img1.copy(), img2 is img1? False
img4=img3, img4 is img3? True
用 Numpy 的切片方法可以进行图像的裁剪,操作简单方便。
方法说明:
retval = img[y:y+h, x:x+w].copy()
参数说明:
注意事项:
基本例程:
# 1.16 图像的裁剪
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 读取彩色图像(BGR)
xmin, ymin, w, h = 180, 190, 200, 200 # 矩形裁剪区域 (ymin:ymin+h, xmin:xmin+w) 的位置参数
imgCrop = img1[ymin:ymin+h, xmin:xmin+w].copy() # 切片获得裁剪后保留的图像区域
cv2.imshow("DemoCrop", imgCrop) # 在窗口显示 彩色随机图像
key = cv2.waitKey(0) # 等待按键命令
扩展例程:
函数 cv2.selectROI() 可以通过鼠标选择感兴趣的矩形区域(ROI)。
cv2.selectROI(windowName, img, showCrosshair=None, fromCenter=None):
使用 cv2.selectROI(),可以实现对 ROI 的裁剪,详见例程 1.17。
# 1.17 图像的裁剪 (ROI)
img1 = cv2.imread("../images/imgLena.tif", flags=1) # flags=1 读取彩色图像(BGR)
roi = cv2.selectROI(img1, showCrosshair=True, fromCenter=False)
xmin, ymin, w, h = roi # 矩形裁剪区域 (ymin:ymin+h, xmin:xmin+w) 的位置参数
imgROI = img1[ymin:ymin+h, xmin:xmin+w].copy() # 切片获得裁剪后保留的图像区域
cv2.imshow("DemoRIO", imgROI)
cv2.waitKey(0)
用 Numpy 的数组堆叠方法可以进行图像的拼接,操作简单方便。
方法说明:
retval = numpy.hstack((img1, img2, …)) # 水平拼接
retval = numpy.vstack((img1, img2, …)) # 垂直拼接
参数说明:
基本例程:
# 1.18 图像拼接
img1 = cv2.imread("../images/imgLena.tif") # 读取彩色图像(BGR)
img2 = cv2.imread("../images/logoCV.png") # 读取彩色图像(BGR)
img1 = cv2.resize(img1, (400, 400))
img2 = cv2.resize(img2, (300, 400))
img3 = cv2.resize(img2, (400, 300))
imgStackH = np.hstack((img1, img2)) # 高度相同图像可以横向水平拼接
imgStackV = np.vstack((img1, img3)) # 宽度相同图像可以纵向垂直拼接
print("Horizontal stack:\nShape of img1, img2 and imgStackH: ", img1.shape, img2.shape, imgStackH.shape)
print("Vertical stack:\nShape of img1, img3 and imgStackV: ", img1.shape, img3.shape, imgStackV.shape)
cv2.imshow("DemoStackH", imgStackH) # 在窗口显示图像 imgStackH
cv2.imshow("DemoStackV", imgStackV) # 在窗口显示图像 imgStackV
key = cv2.waitKey(0) # 等待按键命令
本例程的运行结果如下:
Horizontal stack:
Shape of img1, img2 and imgStackH: (400, 400, 3) (400, 300, 3) (400, 700, 3)
Vertical stack:
Shape of img1, img3 and imgStackV: (400, 400, 3) (300, 400, 3) (700, 400, 3)
函数 cv2.split() 将 3 通道 BGR 彩色图像分离为 B、G、R 单通道图像。
函数说明:
cv2.split(img[, mv]) -> retval # 图像拆分为 BGR 通道
参数说明:
注意事项:
基本例程:
# 1.19 图像拆分通道
img1 = cv2.imread("../images/imgB1.jpg", flags=1) # flags=1 读取彩色图像(BGR)
cv2.imshow("BGR", img1) # BGR 图像
# BGR 通道拆分
bImg, gImg, rImg = cv2.split(img1) # 拆分为 BGR 独立通道
cv2.imshow("rImg", rImg) # 直接显示红色分量 rImg 显示为灰度图像
# 将单通道扩展为三通道
imgZeros = np.zeros_like(img1) # 创建与 img1 相同形状的黑色图像
imgZeros[:,:,2] = rImg # 在黑色图像模板添加红色分量 rImg
cv2.imshow("channel R", imgZeros) # 扩展为 BGR 通道
print(img1.shape, rImg.shape, imgZeros.shape)
cv2.waitKey(0)
cv2.destroyAllWindows() # 释放所有窗口
本例程的运行结果如下:
(512, 512, 3) (512, 512) (512, 512, 3)
运行结果表明:
扩展例程:
使用 NumPy 切片得到分离通道更为简便,而且运行速度比 cv2.split 更快。
# 1.20 图像拆分通道 (Numpy切片)
img1 = cv2.imread("../images/imgB1.jpg", flags=1) # flags=1 读取彩色图像(BGR)
# 获取 B 通道
bImg = img1.copy() # 获取 BGR
bImg[:, :, 1] = 0 # G=0
bImg[:, :, 2] = 0 # R=0
# 获取 G 通道
gImg = img1.copy() # 获取 BGR
gImg[:, :, 0] = 0 # B=0
gImg[:, :, 2] = 0 # R=0
# 获取 R 通道
rImg = img1.copy() # 获取 BGR
rImg[:, :, 0] = 0 # B=0
rImg[:, :, 1] = 0 # G=0
# 消除 B 通道
grImg = img1.copy() # 获取 BGR
grImg[:, :, 0] = 0 # B=0
plt.subplot(221), plt.title("1. B channel"), plt.axis('off')
bImg = cv2.cvtColor(bImg, cv2.COLOR_BGR2RGB) # 图片格式转换:BGR(OpenCV) -> RGB(PyQt5)
plt.imshow(bImg) # matplotlib 显示 channel B
plt.subplot(222), plt.title("2. G channel"), plt.axis('off')
gImg = cv2.cvtColor(gImg, cv2.COLOR_BGR2RGB)
plt.imshow(gImg) # matplotlib 显示 channel G
plt.subplot(223), plt.title("3. R channel"), plt.axis('off')
rImg = cv2.cvtColor(rImg, cv2.COLOR_BGR2RGB)
plt.imshow(rImg) # matplotlib 显示 channel R
plt.subplot(224), plt.title("4. GR channel"), plt.axis('off')
grImg = cv2.cvtColor(grImg, cv2.COLOR_BGR2RGB)
plt.imshow(grImg) # matplotlib 显示 channel GR
plt.show()
本例程的运行结果如下,GR channel 是消除 B通道(保留 G/R 通道的图像):
函数 cv2.merge() 将 B、G、R 单通道合并为 3 通道 BGR 彩色图像。
函数说明:
cv2.merge(mv[, dst]) -> retval # BGR 通道合并
参数说明:
注意事项:
基本例程:
# 1.21 图像通道的合并
img1 = cv2.imread("../images/imgB1.jpg", flags=1) # flags=1 读取彩色图像(BGR)
bImg, gImg, rImg = cv2.split(img1) # 拆分为 BGR 独立通道
# cv2.merge 实现图像通道的合并
imgMerge = cv2.merge([bImg, gImg, rImg])
cv2.imshow("cv2Merge", imgMerge)
# Numpy 拼接实现图像通道的合并
imgStack = np.stack((bImg, gImg, rImg), axis=2)
cv2.imshow("npStack", imgStack)
print(imgMerge.shape, imgStack.shape)
print("imgMerge is imgStack?", np.array_equal(imgMerge, imgStack))
cv2.waitKey(0)
cv2.destroyAllWindows() # 释放所有窗口
本例程的运行结果如下。imgMerge 与 imgStack 不仅形状相同,而且每个位置的元素相等,表明 cv2.merge() 与 np.stack() 方法合并图像通道的结果是相同的。
(512, 512, 3) (512, 512, 3)
imgMerge is imgStack? True
【本节完】
版权声明:
欢迎关注『Python 小白从零开始 OpenCV 学习课 @ youcans』 原创作品
原创作品,转载必须标注原文链接:https://blog.csdn.net/youcans/article/details/121068795
Copyright 2021 youcans, XUPT
Crated:2021-11-03
欢迎关注『Python 小白从零开始 OpenCV 学习课 @ youcans』 系列,持续更新中
Python 大白从零开始 OpenCV 学习课-1.安装与环境配置
Python 大白从零开始 OpenCV 学习课-2.图像读取与显示
Python 大白从零开始 OpenCV 学习课-3.图像的创建与修改
Python 大白从零开始 OpenCV 学习课-4.图像的叠加与混合