二、OpenCV 基础知识
1、OpenCV 的色彩空间
1.1 RGB 和 BGR
最常见的色彩空间就是RGB,人眼也是基于RGB的色彩空间去分辨颜色的。OpenCV 默认使用的是BGR。
RGB和BGR色彩空间的区别在于图片在色彩通道上的排列顺序不同。
1、RGB 如下图所示:
2、BGR 如下图所示:
显示图片的时候需要注意适配图片的色彩空间和显示环境的色彩空间。
比如传入的图片是BGR色彩空间,显示环境是RGB色彩空间,就会出现颜色混乱的情况。
1.2 HSV、HSL 和 YUV
1、HSV(HSB)
- Hue:色相,即色彩,如红色、绿色、蓝色。
- 用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。
- 它们的补色是:黄色为60°,青色为180°,紫色为300°。
- Saturation:饱和度,表示颜色接近光谱色的程度。
- 一种颜色,可以看成是某种光谱色与白色混合的结果。
- 其中光谱色所占的比例愈大,颜色接近光谱色的程度就愈高,颜色的饱和度也就愈高。
- 饱和度高,颜色则深而艳。
- 光谱色的白光成分为0,饱和度达到最高。
- 通常取值范围为0%~100%,值越大,颜色越饱和。
- Value(Brightness):明度,表示颜色明亮的程度。
- 对于光源色,明度值与发光体的光亮度有关;
- 对于物体色,该值和物体的透射比或反射比有关。
- 通常取值范围为0%(黑)~100%(白)。
OpenCV用的最多的色彩空间是HSV。
2、HSL
- HUE:色相,色彩的基本属性,就是平常所说的颜色名称。如红色、黄色等。
- Saturation:色彩的纯度,越高色彩越纯,低则逐渐变灰,取0%~100%的数值。
- Lightness:亮度,取0%~100%
3、HSV 和 HSL 的区别:
4、YUV
YUV,是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知能力,允许降低色度的带宽。
“Y”表示明亮度(Luminance或Luma),也就是灰阶值,“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
YUV的发明是由于彩色电视与黑白电视的过渡时期。
黑白视频只有Y(Luma,Luminance)视频,也就是灰阶值。到了彩色电视规格的制定,是以YUV/YIQ的格式来处理彩色电视图像,把UV视作表示彩度的C(Chrominance或Chroma),如果忽略C信号,那么剩下的Y(Luma)信号就跟之前的黑白电视频号相同,这样一来便解决彩色电视机与黑白电视机的兼容问题。
YUV最大的优点在于只需占用极少的带宽。
为节省带宽起见,大多数YUV格式平均使用的每像素位数都少于24位。主要的抽样(subsample)格式有YCbCr4:2:0、YCbCr4:2:2、YCbCr4:1:1和YCbCr4:4:4。YUV的表示法称为A:B:C表示法:
- 4:4:4表示完全取样。
- 4:2:2表示2:1的水平取样,垂直完全采样。
- 4:2:0表示2:1的水平取样,垂直2:1采样。
- 4:1:1表示4:1的水平取样,垂直完全采样。
1.3 色彩空间的转换
1.3.1 cvtColor() 颜色转换
cvtColor()
用法:
cv2.cvtColor(img, colorspace)
参数说明:
- img:需要转换的图像
- colorspace:将图像转换成何种格式
cv2.COLOR_BGR2RGBA
是将BGR格式转换为RGBA格式
1.3.2 代码实现
import cv2 def callback(value): print(value) cv2.namedWindow('color', cv2.WINDOW_NORMAL) cv2.resizeWindow('color', 640, 480) # OpenCV读取的图像默认是BGR的色彩空间 img = cv2.imread('../resource/dog.jpg') # 定义颜色空间转换列表 2 = to color_spaces = [ cv2.COLOR_BGR2RGBA, cv2.COLOR_BGR2BGRA, cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSV, cv2.COLOR_BGR2YUV ] # 创建Trackbar cv2.createTrackbar('trackbar', 'color', 0, 4, callback) while True: # 获取当前Trackbar值 index = cv2.getTrackbarPos('trackbar', 'color') # 颜色空间转换API cvt_img = cv2.cvtColor(img, color_spaces[index]) cv2.imshow('color', cvt_img) key = cv2.waitKey(10) if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
2、Numpy 基本操作
Numpy是一个经高度优化的Python数值库。OpenCV中用到的矩阵都要转换成Numpy数组,然后再进行后续操作。
在使用Numpy进行基本操作时,都需要导入Numpy库,即import numpy as np
。
2.1 创建矩阵
1、创建数组 array()
- 一维数组
a = np.array([1, 2, 3])
- 二维数组
b = np.array([[1, 3, 5], [2, 4, 6]])
2、创建全 0 / 1 数组 zeros() / ones()
zeros()
用法:
c = np.zeros((480, 640, 3), np.uint8)
参数说明:
- (480, 640, 3):(行的个数, 列的个数, 通道数/层数)
- np.uint8:矩阵中的数据类型
实例1:4 * 4 * 3(方便演示)
c = np.zeros((4, 4, 3), np.uint8) print(c)
[[[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]]]
实例2:4 * 4(可以不指定通道数/层数,即默认1通道/层)
c = np.zeros((4, 4), np.uint8) print(c)
[[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]]
ones()
用法:
d = np.ones((480, 640, 3), np.uint8)
ones() 用法和 zeros() 用法基本一致,其主要区别是:ones() 创建的数组值都为 1 ,而 zeros() 创建的数组都为 0 。
3、创建全值数组 full()
full()
用法:
e = np.full((480, 640, 3), 255, np.uint8)
参数说明:
- (480, 640, 3):(行的个数, 列的个数, 通道数/层数)
- 255:表示每个元素的数值
- np.uint8:矩阵中的数据类型
实例1:
e = np.full((4, 4, 3), 255, np.uint8) print(e)
[[[255 255 255] [255 255 255] [255 255 255] [255 255 255]] [[255 255 255] [255 255 255] [255 255 255] [255 255 255]] [[255 255 255] [255 255 255] [255 255 255] [255 255 255]] [[255 255 255] [255 255 255] [255 255 255] [255 255 255]]]
实例2:当然也可以不指定通道数/层数
e = np.full((4, 4), 255, np.uint8) print(e)
[[255 255 255 255] [255 255 255 255] [255 255 255 255] [255 255 255 255]]
4、创建单元数组 identity() / eye()
identity()
用法:
f = np.identity(3)
参数说明:
- 3:表示几维数组,即 3 * 3(3行3列)
- 主对角线是1,其它是0
实例1:3 * 3
f = np.identity(3) print(f)
[[1. 0. 0.] [0. 1. 0.] [0. 0. 1.]]
实例2:5 * 5
f = np.identity(5) print(f)
[[1. 0. 0. 0. 0.] [0. 1. 0. 0. 0.] [0. 0. 1. 0. 0.] [0. 0. 0. 1. 0.] [0. 0. 0. 0. 1.]]
eye()
用法:
g = np.eye(3, 5, k=3)
参数说明:
- (3, 5):3行5列
- k=3:从下标为3开始的对角线为1,其它为0(下标0,1,2,3,…)
实例1:
g = np.eye(3, 5, k=3) print(g)
[[0. 0. 0. 1. 0.] [0. 0. 0. 0. 1.] [0. 0. 0. 0. 0.]]
实例2:如果不指定k或者k为0,则默认从一个开始(下标为0)也就是主对角线赋1,其它为0.
g = np.eye(3, 5) h = np.eye(3, 5, 0) print(g) print(h)
[[1. 0. 0. 0. 0.] [0. 1. 0. 0. 0.] [0. 0. 1. 0. 0.]] # 效果一样 [[1. 0. 0. 0. 0.] [0. 1. 0. 0. 0.] [0. 0. 1. 0. 0.]]
2.2 检索与赋值[y, x] / [y, x, channel]
1、检索
- [y, x]
img = np.zeros((480, 640, 3), np.uint8) print(img[100, 100])
[0 0 0]
- [y, x, channel]
img = np.zeros((480, 640, 3), np.uint8) print(img[100, 100, 0])
0
channel用来指定通道数,由于OpenCV默认是BGR三通道,所以取值0,1,2
2、赋值
实例1:img[y, x] = 255
img[count, 100] = 255
不指定channel,默认白色。
实例2:img[y, x] = [B, G, R]
通道组合 [B, G, R] 一般只需掌握以下几种:
- [255, 0, 0]:蓝色通道(B)
- [0, 255, 0]:绿色通道(G)
- [0, 0, 255]:红色通道®
- [255, 255, 255]:白色(混合)
img[count, 100] = [0, 0, 255]
img[count, 100] = [255, 255, 255]
实例3:img[y, x, channel] = 255
OpenCV默认是BGR三通道,所以channel可以取0、1或2,即:
- channel = 0:蓝色通道(B)
- channel = 1:绿色通道(G)
- channel = 2:红色通道®
img[count, 100, 0] = 255
代码实现(完整)
import cv2 import numpy as np img = np.zeros((480, 640, 3), np.uint8) # 从矩阵中读取某个元素的值 print(img[100, 100]) print(img[100, 100, 1]) count = 0 # 向矩阵中某个元素赋值 while count < 200: # img[count, 100] = 255 # img[count, 100, 0] = 255 img[count, 100] = [255, 255, 255] count += 1 cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows()
2.3 获取子矩阵[:, :]
1、[y1: y2, x1: x2]
获取像素点x1~x2,y1~y2的区域。
2、[:, :]
或 [:]
获取所有像素点。
import cv2 import numpy as np img = np.zeros((480, 640, 3), np.uint8) roi = img[100:400, 100:600] # roi[:, :] = [0, 0, 255] roi[:] = [0, 0, 255] roi[10:200, 10:200] = [0, 255, 0] cv2.imshow('img', roi) cv2.waitKey(0) cv2.destroyAllWindows()
3、OpenCV 的重要数据结构—Mat
3.1 Mat 介绍
Mat是OpenCV在 C++ 语言中用来表示图像数据的一种数据结构,在 Python 中转换为numpy的ndarray。
- Mat由header和data组成,header中记录了图片的维数、大小和数据类型等数据。
- Mat在C++中的原型:
class CV_EXPORTS Mat{ public: ... int dims; //维数 int rows, cols; //行、列数 uchar *data; //存储数据的指针 int *refcount; //引用计数 ... };
- Mat属性
字段 | 说明 | 字段 | 说明 |
dims | 维度 | channels | 通道数 RGB是3 |
rows | 行数 | size | 矩阵大小 |
cols | 列数 | type | dep + dt + chs CV_8UC3 |
depth | 像素的位深 | data | 存放数据 |
3.2 Mat 拷贝
Mat拷贝时默认为浅拷贝,只拷贝Header中的内容,数据不变。
1、Mat浅拷贝
Mat A A = imread(file, IMREAD_COLOR) Mat B(A)
B 与 A 的 Header 不同,但指向的数据相同,如下代码:
img1 = cv2.imread('../resource/cold.jpg', cv2.IMREAD_COLOR) img2 = img1 img1[10:100, 10:100] = [0, 0, 255]
2、Mat深拷贝
C++中实现方式有两种:
cv::Mat::clone() cv::Mat::copyTo()
将 Data 也重新赋值一份,A 与 B 完全切断。
在Python中:
img3 = img1.copy()
进行深拷贝后,进行图片处理时不影响原图片。
img1 = cv2.imread('../resource/cold.jpg', cv2.IMREAD_COLOR) img3 = img1.copy() img1[10:100, 10:100] = [0, 0, 255]
3.3 代码实现
import cv2 img1 = cv2.imread('../resource/cold.jpg', cv2.IMREAD_COLOR) # 浅拷贝 img2 = img1 # 深拷贝 img3 = img1.copy() img1[10:100, 10:100] = [0, 0, 255] cv2.imshow('img1', img1) cv2.imshow('img2', img2) cv2.imshow('img3', img3) cv2.waitKey(0) cv2.destroyAllWindows()
3.4 访问图像(Mat)的属性
import cv2 img = cv2.imread('../resource/structure.jpg', cv2.IMREAD_COLOR)
方便演示!
1、img.shape
用法:
shape 中包含三个信息:高度、宽度和通道数。
print(img.shape)
(3000, 4500, 3)
即该图像(高:3000,宽:4500,通道数:3)。
2、img.size
用法:
size 即图像占用空间 = 高度 * 宽度 * 通道数 。
print(img.size)
40500000
3000 * 4500 * 3 = 40500000
3、img.dtype
用法:
dtype 表示图像中每个元素的位深。
print(img.dtype)
uint8
uint8 表示 8位无符号整型(0~255)。
3.5 通道分离与合并
1、split(mat)
: 分离
import cv2 import numpy as np img = np.zeros((480, 640, 3), np.uint8) b, g, r = cv2.split(img) b[10:100, 10:100] = 255 g[10:100, 10:100] = 255 cv2.imshow('img', img) cv2.imshow('b', b) cv2.imshow('g', g) cv2.waitKey(0) cv2.destroyAllWindows()
2、merge((ch1, ch2, ...))
: 合并
import cv2 import numpy as np img = np.zeros((480, 640, 3), np.uint8) b, g, r = cv2.split(img) b[10:100, 10:100] = 255 g[10:100, 10:100] = 255 img_merge = cv2.merge((b, g, r)) cv2.imshow('img', img) cv2.imshow('b', b) cv2.imshow('g', g) cv2.imshow('img_merge', img_merge) cv2.waitKey(0) cv2.destroyAllWindows()