关于 OpenCV 官方文档的 GUI功能告一段落,接下来开始核心操作的学习。学习笔记中会记录官方给出的例子,也会给出自己根据官方的例子完成的更改代码,同样彩蛋的实现也会结合多个知识点一起实现一些小功能,来帮助我们对学会的知识点进行结合应用。
如果有喜欢我笔记的请麻烦帮我关注、点赞、评论。谢谢诸位。
学习笔记:
学习笔记目录里面会收录我关于OpenCV系列学习笔记博文,大家如果有什么不懂的可以通过阅读我的学习笔记进行学习。
【OpenCV学习笔记】- 学习笔记目录
内容
- 访问像素值并修改它们
- 访问像素属性
- 设置感兴趣区域(ROI)
- 分割和合并图像
本节中的几乎所有操作都主要与 Numpy 有关而非 OpenCV。熟悉 Numpy 后才能使用 OpenCV 编写更好的优化后代码。
访问和修改像素值
先来理解图像与一般的矩阵或张量的不同之处(不考虑图像的格式,元数据等信息)。
首先,一张图像有自己的属性:宽,高,通道数。
其中宽和高是我们肉眼可见的属性,而通道数则是图像能呈现色彩的属性。
我们都知道,光学三原色是红色,绿色和蓝色,这三种颜色的混合可以形成任意的颜色。
常见的图像的像素通道也是对应的R,G,B三个通道,在OpenCV中,每个通道的取值范围为0~255。(注:还有RGBA,YCrCb,HSV等其他图像通道表示方法)。即,一般彩色图像读进内存之后是一个h * w * c的矩阵,其中h为图像高(相当于矩阵的行),w为图像宽(相当于矩阵列),c为通道数。
下面我们先加载一副彩色图像,更准确地说,是一副黄色图像。
示例代码:
import numpy as np
import cv2
file_path = '../image/2.1.png'
img = cv2.imread(file_path)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
h, w, c = img.shape
# 图像大小
print(h, w, c)
效果图:
控制台/命令行输出:
512 512 3
从下面的代码中可以看到,您可以通过行和列坐标访问像素值。注意,对于常见的RGB 图像,OpenCV的imread函数返回的是一个蓝色(Blue)值、绿色(Green)值、红色(Red)值的数组,维度大小为3。而对于灰度图像,仅返回相应的灰度值。
示例代码:
import numpy as np
import cv2
file_path = '../image/2.1.png'
img = cv2.imread(file_path)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
h, w, c = img.shape
# 图像大小
print(h, w, c)
print(img[100, 100])
# OpenCV的读取顺序为B,G,R,由于图像所有像素为黄色,因此,G=255,R=255
# [ 0 255 255]
控制台/命令行输出:
512 512 3
[ 0 255 255]
访问三个通道的像素:
示例代码:
import numpy as np
import cv2
file_path = '../image/2.1.png'
img = cv2.imread(file_path)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
h, w, c = img.shape
# 图像大小
print(h, w, c)
print(img[100, 100])
# OpenCV的读取顺序为B,G,R,由于图像所有像素为黄色,因此,G=255,R=255
# [ 0 255 255]
# 仅访问蓝色通道的像素
blue = img[0, 0, 0]
print(blue)
# 仅访问绿色通道的像素
green = img[0, 0, 1]
print(green)
# 仅访问红通道的像素
red = img[0, 0, 2]
print(red)
控制台/命令行输出:
512 512 3
[ 0 255 255]
0
255
255
在这里我们会发现img[0, 0]和img[100, 100]输出是一样的,因为这是一张纯色图。
更改像素点
我们可以通过相同方式给对应位置的像素赋值不同颜色。
在这里我通过修改一个小的九宫格像素,我们可以在效果图中很明显有一个黑点出现。
示例代码:
import numpy as np
import cv2
file_path = '../image/2.1.png'
img = cv2.imread(file_path)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
h, w, c = img.shape
# 图像大小
print(h, w, c)
print(img[100, 100])
# OpenCV的读取顺序为B,G,R,由于图像所有像素为黄色,因此,G=255,R=255
# [ 0 255 255]
# 仅访问蓝色通道的像素
blue = img[0, 0, 0]
print(blue)
# 仅访问绿色通道的像素
green = img[0, 0, 1]
print(green)
# 仅访问红通道的像素
red = img[0, 0, 2]
print(red)
# 更改像素点颜色
img[99, 99] = [0, 0, 0]
img[99, 100] = [0, 0, 0]
img[99, 101] = [0, 0, 0]
img[100, 99] = [0, 0, 0]
img[100, 100] = [0, 0, 0]
img[100, 101] = [0, 0, 0]
img[101, 99] = [0, 0, 0]
img[101, 100] = [0, 0, 0]
img[101, 101] = [0, 0, 0]
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
原图:
效果图:
通过 Numpy 数组方法进行更改像素点
Numpy 是一个用于快速阵列计算的优化库。因此,简单地访问每个像素值并修改其值将非常缓慢,并不鼓励这样做。
注意: 上述方法通常用于选择数组的某个区域,比如前 5 行和后 3 列。对于单个像素的访问,可以选择使用 Numpy 数组方法中的 array.item()和 array.itemset(),注意它们的返回值是一个标量。如果需要访问所有的 G、R、B 的值,则需要所有像素分别调用 array.item()
更好访问和编辑像素的方法:
示例代码:
import numpy as np
import cv2
file_path = '../image/2.1.png'
img = cv2.imread(file_path)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
h, w, c = img.shape
# 仅访问蓝色通道的像素
blue = img.item(0, 0, 0)
print(blue)
# 仅访问绿色通道的像素
green = img.item(0, 0, 1)
print(green)
# 仅访问红通道的像素
red = img.item(0, 0, 2)
print(red)
# # 更改像素点颜色
img.itemset((99, 99, 0), 0)
img.itemset((99, 99, 1), 0)
img.itemset((99, 99, 2), 0)
img.itemset((99, 100, 0), 0)
img.itemset((99, 100, 1), 0)
img.itemset((99, 100, 2), 0)
img.itemset((99, 101, 0), 0)
img.itemset((99, 101, 1), 0)
img.itemset((99, 101, 2), 0)
img.itemset((100, 99, 0), 0)
img.itemset((100, 99, 1), 0)
img.itemset((100, 99, 2), 0)
img.itemset((100, 100, 0), 0)
img.itemset((100, 100, 1), 0)
img.itemset((100, 100, 2), 0)
img.itemset((100, 101, 0), 0)
img.itemset((100, 101, 1), 0)
img.itemset((100, 101, 2), 0)
img.itemset((101, 99, 0), 0)
img.itemset((101, 99, 1), 0)
img.itemset((101, 99, 2), 0)
img.itemset((101, 100, 0), 0)
img.itemset((101, 100, 1), 0)
img.itemset((101, 100, 2), 0)
img.itemset((101, 101, 0), 0)
img.itemset((101, 101, 1), 0)
img.itemset((101, 101, 2), 0)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
原图:
效果图:
访问图像属性
图像属性包括行数,列数和通道数,图像数据类型,像素数等。
与一般的numpy.array一样,可以通过 img.shape 访问图像的形状。它返回一组由图像的行、列和通道组成的元组(如果图像是彩色的):
示例代码:
print(img.shape)
(512, 512, 3)
注意: 如果图像是灰度图像,则返回的元组仅包含行数和列数,因此它是检查加载的图像是灰度图还是彩色图的一种很好的方法。
通过 img.size 访问图像的总像素数:
示例代码:
print(img.size)
786432
图像数据类型可以由 img.dtype 获得:
示例代码:
print(img.dtype)
uint8
注意: img.dtype 在调试时非常重要,因为 OpenCV-Python 代码中的大量错误由无效的数据类型引起。
图像中的感兴趣区域
有时您将不得不处理某些图像区域。对于图像中的眼部检测,在整个图像上进行第一次面部检测。当获得面部后,我们单独选择面部区域并在其内部搜索眼部而不是搜索整个原始图像。它提高了准确性(因为眼睛总是在脸上:D)和性能(因为我们在一个小区域搜索)。
使用 Numpy 索引再次获得 ROI(感兴趣区域)。在这里,我选择球并将其复制到图像中的另一个区域:
示例代码:
import numpy as np
import cv2
file_path = '../image/1.jpg'
img = cv2.imread(file_path)
# 复制区域的像素
cow = img[1126:1295, 1800:1991]
# 在指定区域覆盖像素
img[600: 769, 1100:1291] = cow
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.destroyWindow("image")
效果图:
拆分和合并图像通道
有时您需要在 B,G,R 通道图像上单独工作。在这种情况下,您需要将 BGR 图像分割为单个通道。在其他情况下,您可能需要将这些单独的通道合并至 BGR 图像。您可以通过以下方式完成:
示例代码:
# 得到 B、G、R通道
b, g, r = cv2.split(img)
# 合并通道
img = cv2.merge((b, g, r))
或者使用numpy.array的切片方法
示例代码:
# 得到 B 通道
b = img[:,:,0]
假设您要将所有红色像素设置为零,则无需先拆分通道。Numpy 索引更快:
示例代码:
# 修改 R 通道像素为0
img[:,:,2] = 0
警告:
cv2.spilt()是一项代价高昂的操作(就时间而言)。所以只在你需要时再这样做,否则使用 Numpy 索引。
制作图像边界(填充)
如果要在图像周围创建边框,比如相框,可以使用 cv2.copyMakeBorder()。但它有更多卷积运算,零填充等应用。该函数采用以下参数:
-
src:输入的图像
-
top,bottom,left,right:上下左右四个方向上的边界拓宽的值
-
borderType:定义要添加的边框类型的标志。它可以是以下类型:
- cv2.BORDER_CONSTANT:添加一个恒定的彩色边框。该值应作为下一个参数value给出。
- cv2.BORDER_REFLECT:边框将是边框元素的镜像反射,如下所示:fedcba|abcdefgh|hgfedcb
- cv2.BORDER_REFLECT_101 或者 cv2.BORDER_DEFAULT:与上面相同,但略有改动,如下所示: gfedcb | abcdefgh | gfedcba
- cv2.BORDER_REPLICATE:最后一个元素被复制,如下所示: aaaaaa | abcdefgh | hhhhhhh
- cv2.BORDER_WRAP:不好解释,它看起来像这样: cdefgh | abcdefgh | abcdefg
-
value:如果边框类型为 cv2.BORDER_CONSTANT,则这个值即为要设置的边框颜色
下面是一个示例代码,演示了上述所有边框类型,以便更好地理解:
示例代码:
import cv2
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255, 255, 0]
img1 = cv2.imread('../image/OpenCV.jpg')
replicate = cv2.copyMakeBorder(img1, 30, 30, 30, 30, cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img1, 30, 30, 30, 30, cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img1, 30, 30, 30, 30, cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img1, 30, 30, 30, 30, cv2.BORDER_WRAP)
constant = cv2.copyMakeBorder(img1, 30, 30, 30, 30, cv2.BORDER_CONSTANT, value=BLUE)
plt.subplot(231), plt.imshow(img1, 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
plt.show()
效果图: