Opencv100问 (一)

主要介绍opencv中常见的一些图像处理操作。
主要参考ImageProcessing100问

通道交换

我们知道,调用cv2.imread()方法读取的图像是按 BGR 顺序排列的!下面我们将其转换成RGB通道顺序,并存储图片

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import cv2
#cv2.imread() 的系数是按 BGR 顺序排列的,我们交换通道为RGB显示
img=cv2.imread('imori.jpg')
b=img[:,:,0].copy()
g=img[:,:,1].copy()
r=img[:,:,2].copy()

img[:,:,0]=r #按RGB通道排列
img[:,:,1]=g
img[:,:,2]=b

cv2.imwrite('1.jpg',img)
cv2.imshow('result',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果

输入(imori.jpg) 输出 (1.jpg)

灰度化(Grayscale)

将图像灰度化。灰度是一种图像亮度的表示方法,可以通过下式计算:

$Y = 0.2126 R + 0.7152 G + 0.0722 B$

当然也可以直接调用方法cv2.cvtColor直接获得。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import cv2
import numpy as np
img=cv2.imread('imori.jpg').astype(np.float)

b=img[:,:,0].copy()
g=img[:,:,1].copy()
r=img[:,:,2].copy()

#方法一
gray=0.2126*r+0.7152*g+0.0722*b
gray=gray.astype(np.uint8)

#方法二
img1=cv2.imread('imori.jpg')
gray1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)

cv2.imwrite('22.jpg',gray1)
cv2.imshow('result',gray1)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果

输入 (imori.jpg) 输出一 (2.jpg) 输出二 (22.jpg)

二值化(Thresholding)

把图像进行二值化。二值化是将图像使用黑(0)和白(255)两种值表示的方法。这里我们将灰度的阈值设置为 128 来进行二值化,即:

1
2
y = { 0 (if y < 128)
255 (else)

当然也可以直接调用方法实现

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import cv2
import numpy as np
img=cv2.imread('imori.jpg').astype(np.float)

b=img[:,:,0].copy()
g=img[:,:,1].copy()
r=img[:,:,2].copy()

#方法一
erzhi=0.2126*r+0.7152*g+0.0722*b
erzhi=erzhi.astype(np.uint8)
th=128
erzhi[erzhi<th]=0
erzhi[erzhi>=th]=255

#方法二
img1=cv2.imread('imori.jpg')
img1=cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
ret,mask=cv2.threshold(img1,128,255,cv2.THRESH_BINARY)

cv2.imwrite('3.jpg',erzhi)
cv2.imshow('result',mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果

输入 (imori.jpg) 输出一 (3.jpg) 输出二 (33.jpg)

大津二值化算法(Otsu’s Method)

大津算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法,从类内方差 和类间方差的比值计算得来:

  1. 小于阈值 t 的类记作 0,大于阈值 t 的类记作 1;
  2. $w0$ 和 $w1$ 是被阈值 t 分开的两个类中的像素数占总像素数的比率(满足 $w0+w1=1$);
  3. $S0^2$, $S1^2$ 是这两个类中像素值的方差;
  4. $M0$, $M1$ 是这两个类的像素值的平均值;

也就是说:

  1. 类内方差: $Sw^2 = w0 \times S0^2 + w1 \times S1^2$
  2. 类间方差: $Sb^2 = w0 \times (M0 - Mt)^2 + w1 \times (M1 - Mt)^2 = w0 \times w1 \times (M0 - M1) ^2$
  3. 图像所有像素的方差: $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
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
import numpy as np
import cv2

# img=cv2.imread('imori.jpg').astype(np.float)
img=cv2.imread('imori.jpg')
H,W,C=img.shape

#灰度
# huidu=0.2126*img[...,2]+0.7152*img[...,1]+0.0722*img[...,0]
# huidu=huidu.astype(np.uint8)
huidu=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

#选择最合适的二值化阈值
max_sigma=0
max_t=0

for t in range(1,255):
v0=huidu[np.where(huidu<t)] #小于阈值部分比例及均值
m0=np.mean(v0) if len(v0)>0 else 0
w0=len(v0)/(H*W)

v1 = huidu[np.where(huidu >= t)] #大于阈值部分比例及均值
m1 = np.mean(v1) if len(v1) > 0 else 0
w1 = len(v1) / (H * W)

sigma=w0*w1*((m0-m1)**2) #找到最好的阈值
if sigma>max_sigma:
max_sigma=sigma
max_t=t

print("阈值为:",max_t)
#根据阈值进行二值化
th=max_t
huidu[huidu<th]=0
huidu[huidu>=th]=255

cv2.imwrite('4.jpg',huidu)
cv2.imshow('result',huidu)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果

输入 (imori.jpg) 输出 (th = 127) (4.jpg)

HSV 变换

将使用 HSV 表示色彩的图像的色相反转。

HSV 即使用色相(Hue)、饱和度(Saturation)、明度(Value)来表示色彩的一种方式。

  1. 色相:将颜色使用0到360度表示,就是平常所说的颜色名称,如红色、蓝色。色相与数值按下表对应:
绿 青色 蓝色 品红
0 60 120 180 240 300 360
  1. 饱和度:是指色彩的纯度,饱和度越低则颜色越黯淡( 0<= S < 1);
  2. 明度:即颜色的明暗程度。数值越高越接近白色,数值越低越接近黑色 ( 0 <= V < 1);

从 RGB 色彩表示转换到 HSV 色彩表示通过以下方式计算:

R,G,B的值在[0, 1]之间:

1
2
3
4
5
6
7
8
9
10
11
Max = max(R,G,B)
Min = min(R,G,B)

H = { 0 (if Min=Max)
60 x (G-R) / (Max-Min) + 60 (if Min=B)
60 x (B-G) / (Max-Min) + 180 (if Min=R)
60 x (R-B) / (Max-Min) + 300 (if Min=G)

V = Max

S = Max - Min

从 HSV 色彩表示转换到 RGB 色彩表示通过以下方式计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
C = S

H' = H / 60

X = C (1 - |H' mod 2 - 1|)

(R,G,B) = (V - C) (1,1,1) + { (0, 0, 0) (if H is undefined)
(C, X, 0) (if 0 <= H' < 1)
(X, C, 0) (if 1 <= H' < 2)
(0, C, X) (if 2 <= H' < 3)
(0, X, C) (if 3 <= H' < 4)
(X, 0, C) (if 4 <= H' < 5)
(C, 0, X) (if 5 <= H' < 6)

当然也可以调用方法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
63
import 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
2
3
4
val = {  32  (  0 <= val <  64)
96 ( 64 <= val < 128)
160 (128 <= val < 192)
224 (192 <= val < 256)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
import cv2
import numpy as np
img=cv2.imread('imori.jpg')
img1=img.copy()

img1 = (img1 // 64) * 64 + 32
#print(img1)

cv2.imwrite("6.jpg", img)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果

输入 (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
21
import 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
2
3
4
5
权值 g(x,y,s) = 1/ (s*sqrt(2 * pi)) * exp( - (x^2 + y^2) / (2*s^2))
标准差 s = 1.3 的 8 近邻 高斯滤波器如下:
1 2 1
K = 1/16 [ 2 4 2 ]
1 2 1
卷积神经网络中的卷积核参数是通过训练得到的,而这里的参数,以及下面介绍的各种滤波器(卷积核)参数都是按照一定方法预先计算得到的。

代码

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
import cv2
import numpy as np

# Read image
img = cv2.imread("imori_noise.jpg")
H, W, C = img.shape

# Gaussian Filter
K_size = 3 #卷积核大小
sigma = 1.3 #一个标准差,为了下面计算卷积核的权重

## Zero padding #0填充,为了使输出图片和原图片保持相同尺寸
pad = K_size // 2
out = np.zeros((H + pad * 2, W + pad * 2, C), dtype=np.float)
out[pad:pad + H, pad:pad + W] = img.copy().astype(np.float) #将原始图片的像素值复制到填充后的全0图片上

## Kernel
K = np.zeros((K_size, K_size), dtype=np.float) #与卷积神经网络训练核参数不同,此处的权重都是基于高斯分布加权平均求得的
for x in range(-pad, -pad + K_size):

for y in range(-pad, -pad + K_size):
K[y + pad, x + pad] = np.exp(-(x ** 2 + y ** 2) / (2 * (sigma ** 2)))
K /= (sigma * np.sqrt(2 * np.pi))
K /= K.sum()

tmp = out.copy()

for y in range(H): #滑动卷积核,使卷积核内权重与图片像素值相乘在求和
for x in range(W):
for c in range(C):
out[pad + y, pad + x, c] = np.sum(K * tmp[y:y + K_size, x:x + K_size, c])

out = out[pad:pad + H, pad:pad + W].astype(np.uint8)

# Save result
cv2.imwrite("9.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果

输入 (imori_noise.jpg) 输出 (9.jpg)

中值滤波(Median filter)

使用中值滤波器(3x 3大小)来对imori_noise.jpg进行降噪处理。

中值滤波器是一种可以使图像平滑的滤波器。这种滤波器用滤波器范围内(在这里是3x3)像素点的中值进行滤波,在这里也采用 Zero Padding。

与高斯滤波很类似,只不过不再需要计算卷积核内权重,而是直接通过卷积核框住的像素值的中位数来代替滤波后的像素值

代码

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
import cv2
import numpy as np

# Read image
img = cv2.imread("imori_noise.jpg")
H, W, C = img.shape

#与高斯滤波很类似,只不过不再需要计算卷积核内权重,而是直接通过卷积核框住的像素值的中位数来代替滤波后的像素值
# Median Filter
K_size = 3

## Zero padding
pad = K_size // 2
out = np.zeros((H + pad*2, W + pad*2, C), dtype=np.float)
out[pad:pad+H, pad:pad+W] = img.copy().astype(np.float)

tmp = out.copy()

for y in range(H):
for x in range(W):
for c in range(C):
out[pad+y, pad+x, c] = np.median(tmp[y:y+K_size, x:x+K_size, c])

out = out[pad:pad+H, pad:pad+W].astype(np.uint8)

# Save result
cv2.imwrite("10.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()

结果

输入 (imori_noise.jpg) 输出 (10.jpg)
------ 本文结束------
bluesliuf wechat
坚持技术分享,欢迎大家扫码交流!