一、图像处理基本操作
1.读取图像
使用的方法:
imread()
cv.imread(filename[,flags])
参数说明:
📊filename:要读取文件的名称
📊flages:
- cv.IMREAD_COLOR: 加载彩色图像。任何图像的透明度都会被忽视。它是默认标志。
- cv.IMREAD_GRAYSCALE:以灰度模式加载图像
- cv.IMREAD_UNCHANGED:加载图像,包括alpha通道
注意
除了这三个标志,你可以分别简单地传递整数1、0或-1。
import cv2 as cv
image_1 = cv.imread('1.png', 1)
image_2 = cv.imread("D:\\learn\\1.png", 1)
3、4行代码效果相同,只不过第四行代码可以读取别的路径下的图片。
2.显示图像
使用的方法:
imshow()
、
waitkey()
、
destroyAllWindows()
-
imshow():显示图像
cv.imshow(winname,mat)
参数说明:
📊 winname:显示图像窗口的名称
📊 mat:要显示的图像
-
waitKey():等待用户按下键盘按键的时间。当用户按下键盘的任意按键时,将执行waitKey() 方法,并且获取此方法的返回值。
retval = cv.waitKey(delay)
参数说明:
📊 retval:与被按下键相对应的ASCII码。例如ESC键的ACSII码为27,当用户按下ESC键时,waitKey()方法返回值时27,如果不是就返回-1。
📊 delay:等待用户按下键盘上按键的时间。单位为ms,如果值为负数,0或者为空时表示无限等待。
-
destroyAllWindows():销毁正在显示图像的窗口
cv.destroyAllWindows()
实例:
import cv2 as cv
image = cv.imread('1.png', 1)
cv.imshow("taylor", image)
retval = cv.waitKey()
cv.destroyAllWindows()
print(retval)
运行结果:按下ESC
27
注意:
1️⃣窗口的名称不能为中文,否则会出现乱码。
2️⃣为了能够正常显示图像,
cv.waitKey()
不能省略,否则图像会一闪而过。
补充:
1️⃣
cv.destroyWindow()
:销毁指定名称的窗口。
import cv2 as cv
image = cv.imread('1.png', 1)
cv.imshow("taylor", image)
retval = cv.waitKey()
cv.destroyWindow("taylor")
print(retval)
2️⃣在特殊情况下,用户可以先创建一个空窗口,然后再将图像传入窗口。这种情况下可以指定窗口的大小。这是通过
cv.nameWindow()
完成的。默认情况下,该标志为
cv.WINDOW_AUTOSIZE
,但是如果将标志指定为
cv.WINDOW_NORMAL
就可以调整窗口的大小。
实例
:
import cv2 as cv
image = cv.imread("1.png")
cv.namedWindow("taylor", cv.WINDOW_NORMAL)
cv.imshow("taylor", image)
cv.waitKey()
cv.destroyAllWindows()
运行结果:窗口是可以用鼠标调整大小的
注意
:代码的第3,4行要显示的窗口名称必须保持一致,否则会出现两个窗口。
3.保存图像
使用方法:
imwrite()
:可用于按照指定路径保存图像。
cv.imwrite(filename,img)
参数说明:
📊filename:保存图像的完整路径。
📊img:要保存的图像。
实例
:
import cv2 as cv
image = cv.imread("1.png", 0)
cv.namedWindow("taylor", cv.WINDOW_NORMAL)
cv.imshow("taylor", image)
cv.imwrite("D:\\learn\\2.png", image)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
4.图像属性
使用方法:shape、size、dtype
1️⃣shape:如果图像是彩色图像,那么获取的是一个由图像的像素列数,行数和通道数组成的数组;
如果图像是灰度图像,那么获取的是一个由图像的像素列数,行数组成的数组。
2️⃣size:获取的是一个包含图像的像素个数,值大小等于像素列数×像素行数×通道数(如果是灰度图像,通道数就是1)。
3️⃣dtype:获取的是图像的数据类型。
import cv2 as cv
image = cv.imread("1.png")
print("图像的色彩属性:")
print("shape = ", image.shape)
print("size = ", image.size)
print("dtype = ", image.dtype)
运行结果:
图像的色彩属性:
shape = (1080, 1920, 3)
size = 6220800
dtype = uint8
二、图像数字化基础
图像数字化指的是用数字表示图像。计算机通常会把像素值处理为处理为256个灰度级别,这256个灰度级别分别用区间[0,255]中的数值表示。其中0表示纯黑色,255表示纯白色。
1.像素
像素是构成数字图像的基本单位。
-
确定像素的位置
首先要确定此图像水平方向和垂直方向上的像素个数。
在OpenCV中正确表示某个像素坐标的表示方法是(y,x)。
实例
:
import cv2 as cv
image = cv.imread("1.png")
px = image[1079, 1919]
-
获取像素的BGR值
上一小节已经获取了坐标为[1079,1919]上的像素px,那么使用
print()
方法打印这个像素得到的就是这个像素BGR值。import cv2 as cv image = cv.imread("1.png") px = image[1079, 1919] print("这个像素的BGR值为", px)
运行 结果为:
这个像素的BGR值为 [179 177 177]
在了解这三组数字代表的含义之前,先了解一下何为三基色。
人眼能感受到红色、绿色和蓝色三种颜色,把这三种颜色称为三基色,同时将三基色以不同比例混合就可以得到不同的颜色。
那么在计算机中要对颜色进行编码,就要使用到色彩空间。
其中较为常用的就是RGB色彩空间。以此为例,在RGB色彩空间中,有三个颜色通道,分别是:红色R、绿色G和蓝色B。并且每个颜色都在[0,255]之间取值。
不过需要注意的是,OpenCV在输出通道顺序的时候是相反的,是按照BGR输出。
在OpenCV中获取像素的BGR值方法有两种:
import cv2 as cv image = cv.imread("1.png") px = image[1079, 1919] print("这个像素的BGR值为", px)
import cv2 as cv image = cv.imread("1.png") print("这个像素的B值为", image[1079, 1919, 0]) print("这个像素的G值为", image[1079, 1919, 1]) print("这个像素的R值为", image[1079, 1919, 2])
运行结果:
参考前文程序输出。 -
修改像素的BGR值
前一节获得了坐标为[1079,1919]的像素的BGR值,如果要修改BGR值,代码为:
import cv2 as cv image = cv.imread("1.png") px = image[1079, 1919] print("这个像素修改前的BGR值为", px) px = [255, 255, 255] print("这个像素修改后的BGR值为", px)
运行结果:
这个像素修改前的BGR值为 [179 177 177] 这个像素修改后的BGR值为 [255, 255, 255]
注意:
如果将每个像素的R、G、B值改为相等大小时,就可以得到灰度图像。全为0时就是纯黑色,全为255时就是纯白色。
如果要修改指定区域中的所有像素就可以使用2层for循环嵌套:
import cv2 as cv image = cv.imread("1.png", 1) cv.namedWindow("taylor", cv.WINDOW_NORMAL) for i in range(0, 79): for j in range(0, 19): image[i, j] = [0, 0, 0] cv.imshow("taylor", image) cv.waitKey() cv.destroyAllWindows()
运行结果:
⚠️
警告
文章刚开头介绍了
imread()
方法,前一节介绍了如何修改指定区域的元素。
比较下面图片中代码的区别:
如果图片是以彩色图像 加载的时候,那么修改指定区域像素的颜色,可以直接使用列表[0,0,0]
如果图像不是以彩色图像加载的时候,那么修改指定区域像素的颜色,不能直接使用列表[0,0,0],否则会报错:
ValueError: setting an array element with a sequence.
必须要使用数组。
Numpy是用于快速数组计算的优化库。因此,简单地访问每个像素值并对其进行修改将非常缓慢,因此不建议使用。
注意
上面的方法通常用于选择数组的区域,例如前5行和后3列。对于单个像素访问,Numpy数组方法array.item()和array.itemset())被认为更好,但是它们始终返回标量。如果要访问所有B,G,R值,则需要分别调用所有的array.item()。更好的像素访问和编辑方法:
# 访问 RED 值 >>> img.item(10,10,2) 59 # 修改 RED 值 >>> img.itemset((10,10,2),100) >>> img.item(10,10,2) 100
2.色彩空间
除了前文介绍的RGB和BGR色彩空间,还有两个比较常见的色彩空间:GRAY和HSV色彩空间。
1.GRAY色彩空间
GRAY色彩空间通常指的是灰度图像,每个像素都是从黑到白([0,255]).0表示纯黑色,255表示纯白色。
2.从RGB/BGR色彩空间到GRAY色彩空间
使用方法:
dst = cv.cvtColor(src,code)
参数说明:
📊dst:转换后的图像;
📊src:转换前的初始图像
📊code:色彩空间转换码
色彩空间转换码 | 含义 |
---|---|
cv.COLOR_BGR2GRAY | 从BGR色彩空间转换到GRAY色彩空间 |
cv.COLOR_RGB2GRAY | 从RGB色彩空间转换到GRAY色彩空间 |
实例
:
import cv2 as cv
image = cv.imread("1.png", 1)
image_GRAY = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.imshow("taylor", image_GRAY)
cv.waitKey()
cv.destroyAllWindows()
1️⃣色彩空间转换码还有很多,读者请自行查阅。
2️⃣虽然色彩空间转换是双向的,但是灰度图像是无法转换成彩色图像的。因为彩色图像在转换成灰度图像的时候丢失了颜色比例,并且无法找回。
3.HSV色彩空间
HSV色彩空间是基于色调、饱和度、亮度而言的。
色调 H 指的是光的颜色。在OpenCV中,色调在[0,180]内取值。红、黄、绿和蓝分别是:0、30、60、120。
饱和度 S 指的是色彩的深浅。在OpenCV中,饱和度在[0,255]内取值。饱和度为0时,图像将变为灰度图像。
亮度 V 指的是光的明暗。在OpenCV中,亮度在[0,255]内取值。
实例
:
import cv2 as cv
image = cv.imread("1.png", 1)
image_GRAY = cv.cvtColor(image, cv.COLOR_BGR2HSV)
cv.imshow("taylor", image_GRAY)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qWW2H1Si-1649246389207)(D:\learn\opencv入门.assets\image-20220406101443885.png)]
3.通道
1.拆分通道
使用方法:
split()
b,g,r = cv.split(bgr_image)
参数说明:
📊b:B通道图像
📊g:G通道图像
📊R:R通道图像
📊bgr_image:一幅RGB图像
拆分一幅BGR图像通道的顺序是B→G→R,因此等号左边必须是“b, g, r”。
实例:
import cv2 as cv
image = cv.imread("1.png", 1)
b, g, r = cv.split(image)
cv.namedWindow("taylor", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_B", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_G", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_R", cv.WINDOW_NORMAL)
cv.imshow("taylor", image)
cv.imshow("taylor_B", b)
cv.imshow("taylor_G", g)
cv.imshow("taylor_R", r)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
有的读者会发现得到的B、G、R通道图像是3幅不同灰度的图像。这是因为在
cv.imshow("taylor_B",b)
时候,BGR这三通道的值都为B通道的值。其余两个同理。所以只要 B=G=R 就可以得到灰度图像。
实例:
import cv2 as cv
image = cv.imread("1.png", 1)
b, g, r = cv.split(image)
cv.namedWindow("taylor", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_B", cv.WINDOW_NORMAL)
for i in range(0, 1079):
for j in range(0, 1919):
a = image[i, j]
image[i, j] = a[0]
cv.imshow("taylor", image)
cv.imshow("taylor_B", b)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
拆分一幅图像的HSV通道。只需先将图像先转换为HSV图像即可,后面步骤一致。
实例:
import cv2 as cv
image = cv.imread("1.png", 1)
image_HSV = cv.cvtColor(image, cv.COLOR_BGR2HSV)
h, s, v = cv.split(image_HSV)
cv.namedWindow("taylor", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_H", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_S", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_V", cv.WINDOW_NORMAL)
cv.imshow("taylor", image_HSV)
cv.imshow("taylor_H", h)
cv.imshow("taylor_S", s)
cv.imshow("taylor_V", v)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v7fPJp6E-1649246389208)(D:\learn\opencv入门.assets\image-20220406183300394.png)]
2.合并通道
合并通道是拆分通道的逆过程。
使用方法:
bgr = cv.merge([b,g,r])
参数说明:
📊bgr:按照B→G→R的顺序合并后得到的图像。
📊b:B通道图像
📊g:G通道图像
📊r:R通道图像
如果用户要按照R→G→B顺序合并,调换顺序即可。
实例:
import cv2 as cv
image = cv.imread("1.png", 1)
b, g, r = cv.split(image)
cv.namedWindow("taylor_original", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_merge_BGR", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_merge_RGB", cv.WINDOW_NORMAL)
cv.imshow("taylor_original", image)
image_merge_BGR = cv.merge([b, g, r])
image_merge_RGB = cv.merge([r, g, b])
cv.imshow("taylor_merge_BGR", image_merge_BGR)
cv.imshow("taylor_merge_RGB", image_merge_RGB)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
相信读者已经发现,按照不同的合并顺序得到的图像也是不一样的。
HSV通道合并同理。不多赘述。
3.合理运用拆分通道和合并通道
在HSV色彩空间内,如果保持其中两个通道的值不变,调整第三个通道,会得到相应的艺术效果。
**实例:**只把H通道的值改为180
import cv2 as cv
cv.namedWindow("taylor_H180", cv.WINDOW_NORMAL)
image = cv.imread("1.png", 1)
image_HSV = cv.cvtColor(image, cv.COLOR_BGR2HSV)
h, s, v = cv.split(image_HSV)
h[:] = 180
image_HSV_merge = cv.merge([h, s, v])
image_HSV2BGR = cv.cvtColor(image_HSV_merge, cv.COLOR_HSV2BGR)
cv.imshow("taylor_H180", image_HSV2BGR)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
⁉️
疑问
:前一段程序中的第十行做出以下变动:
代码变换前后,输出图片的效果是一样的???
使用
type()
查看 h 数据类型的时候发现其类型是
<class 'numpy.ndarray'>
,不是list。
4.alpha通道
前文说明了BGR色彩空间包含了3个通道,在此基础上再加上一个A通道就构成了alpha色彩空间(BRGA色彩空间)。A用于设置图像的透明度。A在区间[0,255]内取值。0表示纯透明。
实例:
import cv2 as cv
cv.namedWindow("taylor_BGRA_A200", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_BGRA", cv.WINDOW_NORMAL)
image = cv.imread("1.png", 1)
image_BRGA = cv.cvtColor(image, cv.COLOR_BGR2BGRA)
b, g, r, a = cv.split(image_BRGA)
a[:] = 200
image_BRGA_A200 = cv.merge([b, g, r, a])
cv.imshow("taylor_BGRA_A200", image_BRGA_A200)
cv.imshow("taylor_BGRA", image_BRGA)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
虽然从输出的图片看不出区别,但是将这两幅图使用
imwrite()
就可以看出区别:
实例:
import cv2 as cv
cv.namedWindow("taylor_BGRA_A200", cv.WINDOW_NORMAL)
cv.namedWindow("taylor_BGRA", cv.WINDOW_NORMAL)
image = cv.imread("1.png", 1)
image_BGRA = cv.cvtColor(image, cv.COLOR_BGR2BGRA)
b, g, r, a = cv.split(image_BGRA)
a[:] = 200
image_BGRA_A200 = cv.merge([b, g, r, a])
cv.imshow("taylor_BGRA_A200", image_BGRA_A200)
cv.imshow("taylor_BGRA", image_BGRA)
cv.imwrite("D:/learn/taylor_BGRA_A200.png", image_BGRA_A200)
cv.imwrite("D:/learn/taylor_BGRA.png", image_BGRA)
cv.waitKey()
cv.destroyAllWindows()
运行结果:
4.使用NumPy操作像素
4.1 概述
array属性:
- shape:返回一个数组,表示array维度
- ndim:一个数字,返回array的维度数目
- size:一个数字,返回array的元素个数
- dtype:一个数组,返回array的数据类型
4.2 数组类型
名称 | 类型 |
---|---|
bool_ | 布尔数据类型 |
int_ | 默认的整数类型,类似C语言中long,int32、int64 |
intc | 和C语言中的int类型一样,一般是int32或者int64 |
intp | 用于索引的整数类型,类似于C中色ssize_t,一般还是int32或int64 |
int8 | 一字节相同的8位整数,-128~127 |
int16 | 两字节(16位),-32768~32767 |
int32、int64 | 不多赘述 |
uint8 | 无符号整数 |
uint16.unit32、uint64 | 不多赘述 |
float_ | float64的简写 |
float16 | 半精度浮点数,包括1个符号位,5个指数位,10个尾数位 |
float32 | 单精度浮点数,包括1个符号位,8个指数位,23个尾数位 |
float64 | 双精度浮点数,包括1个符号位,11个指数位,52个尾数位 |
complex_ | 复数类型,与complex128相同 |
complex64 | 实部和虚部共享32位的数据类型 |
complex128 | 同理 |
str_ | 字符串类型 |
string_ | 字节串类型 |
datatime64 | 日期时间类型 |
timedelta64 | 两个时间之间的间隔 |
每一种数据类型否有对应的数据转换方法
np.int8(3.14)
np.float64(1)
np.float(True)
4.3创建数组
4.3.1 array( )方法
np.array(object,dtype,copy,order,subok,ndim)
- object:任何具有数组接口方法的对象
- dtype:数据类型
- copy:默认为True
- order:元素在内存中出现的顺序,值为K、A、C、F。如果 object 不是数组,则新创建的数组会按照行(C);如果值为F,按照列;如果是数组,则以下成立:C、F、A(原顺序)、K(元素在内存中出现的顺序)
- subok:布尔类型,True:传递子类,否则返回的数组将强制为基类数组
- ndim:指定生成数组的最小维数
# 创建一维和二维数组
import numpy as np
n1 = np.array([1, 2, 3])
n2 = np.array([[1, 2, 3], [4, 5, 6]])
# 创建浮点类型数组
import numpy as np
list1 = [1, 2, 3, 4, 5]
n1 = np.array(list1, dtype=np.float_)
print(n1)
print(n1.dtype)
print(type(n1), type(n1[0]))
******************************
运行结果:
[1. 2. 3. 4. 5.]
float64
<class 'numpy.ndarray'> <class 'numpy.float64'>
# 创建三维数组
import numpy as np
nd1 = [1, 2, 3, 4, 5]
np = np.array(nd1,ndmin=3)
print(np)
print(np.shape)
print(np.ndim)
*********************
[[[1 2 3 4 5]]]
(1, 1, 5)
3
-
ndarray.shape
– 数组的维度。这是一个整数的元组,表示每个维度中数组的大小。对于有
n
行和
m
列的矩阵,
shape
将是
(n,m)
。因此,
shape
元组的长度就是rank或维度的个数
ndim
。
4.3.2 empty( )方法
# 创建指定维度和数据类型的未初始化数组
import numpy as np
n = np.empty([3, 2], dtype=int)
print(n)
*******************
[[1 2]
[3 4]
[5 6]]
4.3.3 创建用0填充的数组
zeros( )
import numpy as np
n = np.zeros([3, 2], dtype=int)
print(n)
4.3.4 创建用1填充的数组
import numpy as np
n = np.ones([3, 2], dtype=int)
print(n)
4.3.5 创建随机数组
n = np.random.randint(low,high,size)
- low:随机数最小值
- high:可选参数,随机数最大值。如果没有,取值范围(0,low)
- size:可选参数,表示数组维数
import numpy as np
n = np.random.randint(1, 10, size=(3, 2))
n
4.4 操作数组
1.
加减乘除运算
import numpy as np
n1 = np.array([[1, 2, 3], [4, 5, 6]])
n2 = np.array([[1, 2, 3], [4, 5, 6]])
print(n1 + n2)
print(n1 - n2)
print(n1 * n2)
print(n1 / n2)
************************
[[ 2 4 6]
[ 8 10 12]]
[[0 0 0]
[0 0 0]]
[[ 1 4 9]
[16 25 36]]
[[1. 1. 1.]
[1. 1. 1.]]
2.比较运算
import numpy as np
n1 = np.array([4,5,6])
n2 = np.array([1,2,3])
print(n1 >= n2)
print(n1 == n2)
print(n1 <= n2)
print(n1 != n2)
************************
[ True True True]
[False False False]
[False False False]
[ True True True]
3.复制数组
numpy提供的array( )方法里有copy参数,但是更常用的是
n2 = n1.copy()
。修改n2不会影响n1.
import numpy as np
n1 = np.array([[1, 2, 3], [4, 5, 6]])
n2 = n1.copy()
print(n1 == n2)
n2[0][1] = 9
print(n2)
********************
[[ True True True]
[ True True True]]
[[1 9 3]
[4 5 6]]
4.5 数组的索引和切片
4.5.1 索引
所谓数组的索引,就是用于标记数组对应元素的唯一数字。从0开始,即数组中的第一个元素索引是0,以此类推。Numpy支持可以使用标准Python语法
x[obj]
的语法对数组进行索引,其中x是数组,obj是选择方式。
import numpy as np
n1 = np.array([1, 2, 3])
print(n1[1])
********************
2
4.5.2 切片式索引
和Python列表的索引类似。
import numpy as np
nd1 = [1, 2, 3, 4, 5]
n1 = np.array(nd1)
print(n1)
print(n1[1])
**************
2
4.5.3 二维数组索引
二维数组的索引可以使用array[n,m],表示第n个数组的第m个元素。
import numpy as np
nd1 = [1, 2, 3, 4, 5]
n1 = np.array([nd1, nd1])
print(n1, end="\t")
print(n1[1], end="\n")
print(n1[1, 2])
print(n1[1][2])
*************
[[1 2 3 4 5]
[1 2 3 4 5]] [1 2 3 4 5]
3
3
# 二维数组切片式索引
4.5.4 创建图像
在OpenCV中黑白图像是二维数组,彩色图像是三维数组。数组中每个元素就是图像对应位置的像素值。因此修改图像像素其实就是修改数组。
数组索引、像素索引、像素坐标关系:
数组行索引 = 像素所在行数 – 1 = 像素纵坐标
数组列索引 = 像素所在列数 – 1 = 像素横坐标