主要介绍opencv中常见的一些图像处理操作。
主要参考ImageProcessing100问。
通道交换
我们知道,调用cv2.imread()
方法读取的图像是按 BGR 顺序排列的!下面我们将其转换成RGB通道顺序,并存储图片
代码
1 | import cv2 |
结果
输入(imori.jpg) | 输出 (1.jpg) |
---|---|
灰度化(Grayscale)
将图像灰度化。灰度是一种图像亮度的表示方法,可以通过下式计算:
$Y = 0.2126 R + 0.7152 G + 0.0722 B$
当然也可以直接调用方法cv2.cvtColor
直接获得。
代码
1 | import cv2 |
结果
输入 (imori.jpg) | 输出一 (2.jpg) | 输出二 (22.jpg) |
---|---|---|
二值化(Thresholding)
把图像进行二值化。二值化是将图像使用黑(0)和白(255)两种值表示的方法。这里我们将灰度的阈值设置为 128 来进行二值化,即:
1 | y = { 0 (if y < 128) |
当然也可以直接调用方法实现
代码
1 | import cv2 |
结果
输入 (imori.jpg) | 输出一 (3.jpg) | 输出二 (33.jpg) |
---|---|---|
大津二值化算法(Otsu’s Method)
大津算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法,从类内方差 和类间方差的比值计算得来:
- 小于阈值 t 的类记作 0,大于阈值 t 的类记作 1;
- $w0$ 和 $w1$ 是被阈值 t 分开的两个类中的像素数占总像素数的比率(满足 $w0+w1=1$);
- $S0^2$, $S1^2$ 是这两个类中像素值的方差;
- $M0$, $M1$ 是这两个类的像素值的平均值;
也就是说:
- 类内方差: $Sw^2 = w0 \times S0^2 + w1 \times S1^2$
- 类间方差: $Sb^2 = w0 \times (M0 - Mt)^2 + w1 \times (M1 - Mt)^2 = w0 \times w1 \times (M0 - M1) ^2$
- 图像所有像素的方差: $St^2 = Sw^2 + Sb^2 = (const)$
根据以上的式子,我们用以下的式子计算分离度:
分离度 $X = Sb^2 / Sw^2 = Sb^2 / (St^2 - Sb^2)$
也就是说:
$argmax_{t} X = argmax_{t} Sb^2$
换言之,如果使 $Sb^2 = w0 \times w1 \times (M0 - M1) ^2$ 最大,就可以得到最好的二值化阈值 t。
代码
1 | import numpy as np |
结果
输入 (imori.jpg) | 输出 (th = 127) (4.jpg) |
---|---|
HSV 变换
将使用 HSV 表示色彩的图像的色相反转。
HSV 即使用色相(Hue)、饱和度(Saturation)、明度(Value)来表示色彩的一种方式。
- 色相:将颜色使用0到360度表示,就是平常所说的颜色名称,如红色、蓝色。色相与数值按下表对应:
红 | 黄 | 绿 | 青色 | 蓝色 | 品红 | 红 |
---|---|---|---|---|---|---|
0 | 60 | 120 | 180 | 240 | 300 | 360 |
- 饱和度:是指色彩的纯度,饱和度越低则颜色越黯淡( 0<= S < 1);
- 明度:即颜色的明暗程度。数值越高越接近白色,数值越低越接近黑色 ( 0 <= V < 1);
从 RGB 色彩表示转换到 HSV 色彩表示通过以下方式计算:
R,G,B的值在[0, 1]之间:
1 | Max = max(R,G,B) |
从 HSV 色彩表示转换到 RGB 色彩表示通过以下方式计算:
1 | C = S |
当然也可以调用方法cv2.cvtColor
实现,不过不能自由转变H、S、V的值。
代码
下面是将色相反转(色相值加180),然后再用 RGB 色彩空间表示图片的方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63import cv2
#方法一,但是不能自由转变H、S、V的值
# img=cv2.imread('imori.jpg')
# img_hsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
# cv2.imshow('result',img_hsv)
# cv2.waitKey(0)
import cv2
import numpy as np
#方法二,自己设定改变
img = cv2.imread("imori.jpg").astype(np.float32) / 255.
# RGB > HSV
out = np.zeros_like(img)
max_v = np.max(img, axis=2).copy()
min_v = np.min(img, axis=2).copy()
min_arg = np.argmin(img, axis=2)
H = np.zeros_like(max_v)
H[np.where(max_v == min_v)] = 0
## if min == B
ind = np.where(min_arg == 0)
H[ind] = 60 * (img[..., 1][ind] - img[..., 2][ind]) / (max_v[ind] - min_v[ind]) + 60
## if min == R
ind = np.where(min_arg == 2)
H[ind] = 60 * (img[..., 0][ind] - img[..., 1][ind]) / (max_v[ind] - min_v[ind]) + 180
## if min == G
ind = np.where(min_arg == 1)
H[ind] = 60 * (img[..., 2][ind] - img[..., 0][ind]) / (max_v[ind] - min_v[ind]) + 300
V = max_v.copy()
S = max_v.copy() - min_v.copy()
# 色相反转(色相值加180)
H = (H + 180) % 360
# HSV > RGB
C = S
H_ = H / 60
X = C * (1 - np.abs(H_ % 2 - 1))
Z = np.zeros_like(H)
vals = [[Z, X, C], [Z, C, X], [X, C, Z], [C, X, Z], [C, Z, X], [X, Z, C]]
for i in range(6):
ind = np.where((i <= H_) & (H_ < (i + 1)))
out[..., 0][ind] = (V - C)[ind] + vals[i][0][ind]
out[..., 1][ind] = (V - C)[ind] + vals[i][1][ind]
out[..., 2][ind] = (V - C)[ind] + vals[i][2][ind]
out[np.where(max_v == min_v)] = 0
out = (out * 255).astype(np.uint8)
# Save result
cv2.imwrite("5.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果
输入 (imori.jpg) | 输出一 (55.jpg) | 输出二 (5.jpg) |
---|---|---|
减色处理
将图像的值由${256}^3$压缩至$4^3$,即将 RGB 的值只取 {32, 96, 160, 224}。这被称作色彩量化。色彩的值按照下面的方式定义:
1 | val = { 32 ( 0 <= val < 64) |
代码实现
1 | import cv2 |
结果
输入 (imori.jpg) | 输出 (6.jpg) |
---|---|
平均池化(Average Pooling)
将图片按照固定大小网格分割,网格内的像素值取网格内所有像素的平均值。我们将这种把图片使用均等大小网格分割,并求网格内代表值的操作称为池化(Pooling)。池化操作是卷积神经网络(Convolutional Neural Network)中重要的图像处理方式。平均池化按照下式定义:
1 | v = 1/|R| * Sum_{i in R} v_i |
代码
请把大小为 128x128 的imori.jpg
使用 8x8 的网格做平均池化。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import cv2
import numpy as np
img=cv2.imread('imori.jpg')
pool=img.copy()
H,W,C=img.shape #获取图片的高,宽,通道数
#网格大小
G=8
num_H=int(H/G)
num_W=int(W/G)
for y in range(num_H): #三层循环
for x in range(num_W):
for c in range(C):
pool[G*y:G*(y+1),G*x:G*(x+1),c]=np.mean(pool[G*y:G*(y+1),G*x:G*(x+1),c]) #取均值替换网格内原来的像素值
cv2.imwrite("7.jpg", pool)
cv2.imshow("result", pool)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果
输入 (imori.jpg) | 输出 (7.jpg) |
---|---|
最大池化(Max Pooling)
网格内的值不取平均值,而是取网格内的最大值进行池化操作。只需将平均池化中的np.mean
换成np.max
即可。
结果
输入 (imori.jpg) | 输出 (8.jpg) |
---|---|
高斯滤波(Gaussian Filter)
使用高斯滤波器(3x3 大小,标准差 s=1.3)来对imori_noise.jpg
进行降噪处理吧!
高斯滤波器是一种可以使图像平滑的滤波器,用于去除噪声。可用于去除噪声的滤波器还有中值滤波器,平滑滤波器、LoG(高斯-拉普拉斯)滤波器。
高斯滤波器将中心像素周围的像素按照高斯分布加权平均进行平滑化。这样的(二维)权值通常被称为卷积核或者滤波器。
但是,由于图像的长宽可能不是滤波器大小的整数倍,因此我们需要在图像的边缘补0。这种方法称作 Zero Padding。并且权值(卷积核)要进行归一化操作(sum g = 1)。
1 | 权值 g(x,y,s) = 1/ (s*sqrt(2 * pi)) * exp( - (x^2 + y^2) / (2*s^2)) |
代码
1 | import cv2 |
结果
输入 (imori_noise.jpg) | 输出 (9.jpg) |
---|---|
中值滤波(Median filter)
使用中值滤波器(3x 3大小)来对imori_noise.jpg
进行降噪处理。
中值滤波器是一种可以使图像平滑的滤波器。这种滤波器用滤波器范围内(在这里是3x3)像素点的中值进行滤波,在这里也采用 Zero Padding。
与高斯滤波很类似,只不过不再需要计算卷积核内权重,而是直接通过卷积核框住的像素值的中位数来代替滤波后的像素值
代码
1 | import cv2 |
结果
输入 (imori_noise.jpg) | 输出 (10.jpg) |
---|---|