问题1 通道交换
读取图像,然后将 RGB \text{RGB} RGB通道替换成 BGR \text{BGR} BGR通道。
下面的代码用于提取图像的红色通道。
注意,cv2.imread() 的系数是按 BGR \text{BGR} BGR顺序排列的!
其中的变量red表示的是仅有原图像红通道的imori.jpg。
import cv2
def BGR2RGB(img):
#获取图像bgr各个通道的色彩
b=img[:,:,0].copy()
g=img[:,:,1].copy()
r=img[:,:,2].copy()
#将原本为bgr的图像更改为rgb
#rgb2bgr
img[:,:,0]=r
img[:,:,1]=g
img[:,:,2]=b
#返回图像
return img
#Read imgae
img=cv2.imread('imori.jpg')#读取图像
img=BGR2RGB(img)#调用函数将bgr的图像更改为rgb
#Save result
cv2.imwrite("out.jpg", img)#保存图像
cv2.imshow("result",img)#查看图像
cv2.waitKey(0)
cv2.destroyAllWindows()
我的理解是imread()函数读取图像的时候会将原本为rgb通道排列的图像更改为bgr排列的图像,但是imwrite()与imshow()读取图象是显示的图像还是为rgb的图像,可能是因为这三个函数生成图像的过程中,色彩通道都是倒着读取的,所以读取图像后只要将图像色彩通道重新转置为rgb,这样利用imwrite()和imshow()就可以生成bgr的图像了。
问题2 灰度化
灰度是一种图像亮度的表示方法,通过下式计算:
𝑌= 0.2126 𝑅 + 0.7152 𝐺 + 0.0722 𝐵
任何一个彩色图像的空间域都是由一个三通道组成,分别对应rgb,拿出来任意一个点,都可以得到三个通道分别对应的像素值。
而关于灰色图像的空间域是由单个强度值表示的,也就是说任何一个点,他都由一个值进行表示。
图像的灰度化指的就是将图像的三维像素点转化为一个一维的数值
彩色图像转换为灰色图像就是将每个通道的数值乘以相应的系数得到一个强度值。
当然三个系数的和为一,为的是保证无论三个通道的数值为多少,都能保证灰度转化后最大的强度值仍旧为255.
#方法1
import cv2
import numpy as np
import matplotlib.pyplot as plt
#0.2126 * r + 0.7152 * g + 0.0722 * b
def RGB2GRAY(img):
return 0.21*img[:,:,0]+0.71*img[:,:,1]+0.07*img[:,:,2]
gray=RGB2GRAY(img/255)#由于默认读取的数据为0-255的整型数据,所以需要我们转成0-1的归一化数据
img=cv2.imread("imori.jpg")[:,:,[2,1,0]]#读取图像,并将图像更改为rgb通道顺序,因为matplotlib读取顺序为顺位读取
#先显示正常图像
plt.imshow(img)#负责对图像进行处理,并显示其格式,但是不能显示。
plt.figure()
plt.imshow(gray,cmap='gray')
plt.show()#显示图像
#方法2
import cv2
import numpy as np
def RGB2GRAY(img):
b=img[:,:,0].copy()
g=img[:,:,1].copy()
r=img[:,:,2].copy()
out=0.21*r+0.71*b+0.07*g
out=out.astype(np.uint8)#转换数据类型
return out
img=cv2.imread('imori.jpg').astype(np.float)
# Grayscale
out = RGB2GRAY(img)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
图像二值化
把图像进行二值化吧。
二值化是将图像使用黑和白两种颜色表示的方法。
我们将灰度的阈值设置为
128
128
128来进行二值化,即:
y
=
{
0
(
if
y
<
128
)
255
(
else
)
y= \begin{cases} 0& (\text{if}\quad y < 128) \ 255& (\text{else}) \end{cases}
y={0(ify<128) 255(else)
import cv2
import numpy as np
import matplotlib.pyplot as plt
#先对图像进行一个灰度化处理
def RGB2GRAY(img):
return 0.21*img[:,:,0]+0.71*img[:,:,1]+0.07*img[:,:,2]
#图像灰度化处理
def binarzation(img,th=0.5):
bin=np.copy(img)
bin[img>th]=1
bin[img<th]=0
return bin
img=cv2.imread('imori.jpg')[:,:,[2,1,0]]
gray=RGB2GRAY(img/255)
bin=binarzation(gary)
plt.subplot(121)
plt.imshow(gray,cmap='gray')
plt.subplot(122)
plt.imshow(bin,cmap='gray')
大津二值化算法(Otsu’s Method)
使用大津算法来二值化图像吧。
大津算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法。
从类内方差和类间方差的比值计算得来:
小于阈值
t
t
t的类记作
0
0
0,大于阈值
t
t
t的类记作
1
1
1;
w
0
w_0
w0和
w
1
w_1
w1是被阈值
t
t
t分开的两个类中的像素数占总像素数的比率(满足
w
0
+
w
1
=
1
w_0+w_1=1
w0+w1=1);
S
0
2
{S_0}^2
S02,
S
1
2
{S_1}^2
S12是这两个类中像素值的方差;
M
0
M_0
M0,
M
1
M_1
M1是这两个类的像素值的平均值;
即:
类内方差:
S
w
2
=
w
0
S
0
2
+
w
1
S
1
2
{S_w}^2=w_0\ {S_0}^2+w_1\ {S_1}^2
Sw2=w0 S02+w1 S12
类间方差:
S
b
2
=
w
0
(
M
0
−
M
t
)
2
+
w
1
(
M
1
−
M
t
)
2
=
w
0
w
1
(
M
0
−
M
1
)
2
{S_b}^2 = w_0 \ (M_0 - M_t)^2 + w_1\ (M_1 - M_t)^2 = w_0\ w_1\ (M_0 - M_1) ^2
Sb2=w0 (M0−Mt)2+w1 (M1−Mt)2=w0 w1 (M0−M1)2
图像所有像素的方差:
S
t
2
=
S
w
2
+
S
b
2
=
常数
{S_t}^2 = {S_w}^2 + {S_b}^2 = \text{常数}
St2=Sw2+Sb2=常数
根据以上的式子,我们用以下的式子计算分离度
X
X
X:^1
X = S b 2 S w 2 = S b 2 S t 2 − S b 2 X = \frac{{S_b}^2}{{S_w}^2} = \frac{{S_b}^2}{{S_t}^2 - {S_b}^2} X=Sw2Sb2=St2−Sb2Sb2
也就是说: arg max t X = arg max t S b 2 \arg\max\limits_{t}\ X=\arg\max\limits_{t}\ {S_b}^2 argtmax X=argtmax Sb2 换言之,如果使 S b 2 = w 0 w 1 ( M 0 − M 1 ) 2 {S_b}^2={w_0}\ {w_1}\ (M_0 - M_1)^2 Sb2=w0 w1 (M0−M1)2最大,就可以得到最好的二值化阈值 t t t。
import cv2
import numpy as np
#灰度化
def BGR2GRAY(img):
b = img[:,:,0].copy()
g = img[:,:,1].copy()
r = img [:,:,2].copy()
gray_img = 0.2126*r+0.7152*g+0.0722*b
gray_img = gray_img.astype(np.uint8)
return gray_img
#大津二值化算法
def otsu(gray_img):
h = gray_img.shape[0]
w = gray_img.shape[1]
threshold_t = 0
max_g = 0
#遍历每一个灰度层
for t in range(255):
#使用numpy直接对数组进行计算
n0 = gray_img[np.where(gray_img < t)]
n1 = gray_img[np.where(gray_img>=t)]
w0 = len(n0)/(h*w)
w1 = len(n1)/(h*w)
u0 = np.mean(n0) if len(n0)>0 else 0
u1 = np.mean(n1) if len(n1)>0 else 0
g = w0*w1*(u0-u1)**2
if g > max_g :
max_g = g
threshold_t = t
print ('类间方差最大阈值:',threshold_t)
gray_img[gray_img<threshold_t] = 0
gray_img[gray_img>threshold_t] = 255
return gray_img
#这里直接将数据转换成float32了,方便后续计算
img = cv2.imread('imori.jpg').astype(np.float32)
gray_img = BGR2GRAY(img)
otsu_img = otsu(gray_img)
cv2.imshow('otsu_img',otsu_img)
cv2.waitKey(0)#等待3000ms=3s
cv2.destroyAllWindows()
问题五: HSV \text{HSV} HSV变换
将使用 HSV \text{HSV} HSV表示色彩的图像的色相反转吧!
HSV \text{HSV} HSV即使用**色相(Hue)、饱和度(Saturation)、明度(Value)**来表示色彩的一种方式。
色相:将颜色使用 0 ∘ 0^{\circ} 0∘到 36 0 ∘ 360^{\circ} 360∘表示,就是平常所说的颜色名称,如红色、蓝色。色相与数值按下表对应:
红 黄 绿 青色 蓝色 品红 红
0
∘
0^{\circ}
0∘
6
0
∘
60^{\circ}
60∘
12
0
∘
120^{\circ}
120∘
18
0
∘
180^{\circ}
180∘
24
0
∘
240^{\circ}
240∘
30
0
∘
300^{\circ}
300∘
36
0
∘
360^{\circ}
360∘
饱和度:是指色彩的纯度,饱和度越低则颜色越黯淡(
0
≤
S
<
1
0\leq S < 1
0≤S<1);
明度:即颜色的明暗程度。数值越高越接近白色,数值越低越接近黑色( 0 ≤ V < 1 0\leq V < 1 0≤V<1);
从 RGB \text{RGB} RGB色彩表示转换到 HSV \text{HSV} HSV色彩表示通过以下方式计算:
RGB \text{RGB} RGB的取值范围为 [ 0 , 1 ] [0, 1] [0,1],令: Max = max ( R , G , B ) Min = min ( R , G , B ) \text{Max}=\max(R,G,B)\ \text{Min}=\min(R,G,B) Max=max(R,G,B) Min=min(R,G,B) 色相: H = { 0 ( if Min = Max ) 60 G − R Max − Min + 60 ( if Min = B ) 60 B − G Max − Min + 180 ( if Min = R ) 60 R − B Max − Min + 300 ( if Min = G ) H=\begin{cases} 0&(\text{if}\ \text{Min}=\text{Max})\ 60\ \frac{G-R}{\text{Max}-\text{Min}}+60&(\text{if}\ \text{Min}=B)\ 60\ \frac{B-G}{\text{Max}-\text{Min}}+180&(\text{if}\ \text{Min}=R)\ 60\ \frac{R-B}{\text{Max}-\text{Min}}+300&(\text{if}\ \text{Min}=G) \end{cases} H={0(if Min=Max) 60 Max−MinG−R+60(if Min=B) 60 Max−MinB−G+180(if Min=R) 60 Max−MinR−B+300(if Min=G) 饱和度: S = Max − Min S=\text{Max}-\text{Min} S=Max−Min 明度: V = Max V=\text{Max} V=Max 从 HSV \text{HSV} HSV色彩表示转换到 RGB \text{RGB} RGB色彩表示通过以下方式计算: C = S H ′ = H 60 X = C ( 1 − ∣ H ′ m o d 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 ) C = S\ H' = \frac{H}{60}\ X = C\ (1 - |H' \mod 2 - 1|)\ (R,G,B)=(V-C)\ (1,1,1)+\begin{cases} (0, 0, 0)& (\text{if H is undefined})\ (C, X, 0)& (\text{if}\quad 0 \leq H' < 1)\ (X, C, 0)& (\text{if}\quad 1 \leq H' < 2)\ (0, C, X)& (\text{if}\quad 2 \leq H' < 3)\ (0, X, C)& (\text{if}\quad 3 \leq H' < 4)\ (X, 0, C)& (\text{if}\quad 4 \leq H' < 5)\ (C, 0, X)& (\text{if}\quad 5 \leq H' < 6) \end{cases} C=S H′=60H X=C (1−∣H′mod2−1∣) (R,G,B)=(V−C) (1,1,1)+{(0,0,0)(if H is undefined) (C,X,0)(if0≤H′<1) (X,C,0)(if1≤H′<2) (0,C,X)(if2≤H′<3) (0,X,C)(if3≤H′<4) (X,0,C)(if4≤H′<5) (C,0,X)(if5≤H′<6) 请将色相反转(色相值加 180 180 180),然后再用 RGB \text{RGB} RGB色彩空间表示图片。
import cv2
import numpy as np
# BGR -> HSV
def BGR2HSV(_img):
img = _img.copy() / 255.
hsv = np.zeros_like(img, dtype=np.float32)
# get max and min
max_v = np.max(img, axis=2).copy()
min_v = np.min(img, axis=2).copy()
min_arg = np.argmin(img, axis=2)
# H
hsv[..., 0][np.where(max_v == min_v)]= 0
## if min == B
ind = np.where(min_arg == 0)
hsv[..., 0][ind] = 60 * (img[..., 1][ind] - img[..., 2][ind]) / (max_v[ind] - min_v[ind]) + 60
## if min == R
ind = np.where(min_arg == 2)
hsv[..., 0][ind] = 60 * (img[..., 0][ind] - img[..., 1][ind]) / (max_v[ind] - min_v[ind]) + 180
## if min == G
ind = np.where(min_arg == 1)
hsv[..., 0][ind] = 60 * (img[..., 2][ind] - img[..., 0][ind]) / (max_v[ind] - min_v[ind]) + 300
# S
hsv[..., 1] = max_v.copy() - min_v.copy()
# V
hsv[..., 2] = max_v.copy()
return hsv
def HSV2BGR(_img, hsv):
img = _img.copy() / 255.
# get max and min
max_v = np.max(img, axis=2).copy()
min_v = np.min(img, axis=2).copy()
out = np.zeros_like(img)
H = hsv[..., 0]
S = hsv[..., 1]
V = hsv[..., 2]
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 = np.clip(out, 0, 1)
out = (out * 255).astype(np.uint8)
return out
# Read image
img = cv2.imread("imori.jpg").astype(np.float32)
# RGB > HSV
hsv = BGR2HSV(img)
# Transpose Hue
hsv[..., 0] = (hsv[..., 0] + 180) % 360
# HSV > RGB
out = HSV2BGR(img, hsv)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
openCV可以直接实现图像的色相转换
import cv2
img = cv2.imread('imori.jpg')
cv2.imshow('RGB',img)
HSV_img = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
cv2.imshow('HSV',HSV_img)
cv2.waitKey(0)#等待3000ms=3s
cv2.destroyAllWindows()
问题六:减色处理
我们将图像的值由 25 6 3 256^3 2563压缩至 4 3 4^3 43,即将 RGB \text{RGB} RGB的值只取 32 , 96 , 160 , 224 {32, 96, 160, 224} 32,96,160,224。这被称作色彩量化。色彩的值按照下面的方式定义: val = { 32 ( 0 ≤ var < 64 ) 96 ( 64 ≤ var < 128 ) 160 ( 128 ≤ var < 192 ) 224 ( 192 ≤ var < 256 ) \text{val}= \begin{cases} 32& (0 \leq \text{var} < 64)\ 96& (64\leq \text{var}<128)\ 160&(128\leq \text{var}<192)\ 224&(192\leq \text{var}<256) \end{cases} val={32(0≤var<64) 96(64≤var<128) 160(128≤var<192) 224(192≤var<256)
import cv2
import numpy as np
# Dicrease color
def dicrease_color(img):
out = img.copy()
out = out // 64 * 64 + 32
return out
# Read image
img = cv2.imread("imori.jpg")
# 减色处理操作
out = dicrease_color(img)
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
问题七:平均池化(Average Pooling)
将图片按照固定大小网格分割,网格内的像素值取网格内所有像素的平均值。
我们将这种把图片使用均等大小网格分割,并求网格内代表值的操作称为池化(Pooling)。
池化操作是**卷积神经网络(Convolutional Neural Network)**中重要的图像处理方式。平均池化按照下式定义: v = 1 ∣ R ∣ ∑ i = 1 R v i v=\frac{1}{|R|}\ \sum\limits_{i=1}^R\ v_i v=∣R∣1 i=1∑R vi 请把大小为 128 × 128 128\times128 128×128的imori.jpg使用 8 × 8 8\times8 8×8的网格做平均池化。
import cv2
import numpy as np
# average pooling
def average_pooling(img, G=8):
out = img.copy()
H, W, C = img.shape
Nh = int(H / G)
Nw = int(W / G)
for y in range(Nh):
for x in range(Nw):
for c in range(C):
out[G*y:G*(y+1), G*x:G*(x+1), c] = np.mean(out[G*y:G*(y+1), G*x:G*(x+1), c]).astype(np.int)
return out
# Read image
img = cv2.imread("imori.jpg")
# Average Pooling
out = average_pooling(img)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
问题八:最大池化(Max Pooling)
网格内的值不取平均值,而是取网格内的最大值进行池化操作。
import cv2
import numpy as np
# max pooling
def max_pooling(img, G=8):
# Max Pooling
out = img.copy()
H, W, C = img.shape
Nh = int(H / G)
Nw = int(W / G)
for y in range(Nh):
for x in range(Nw):
for c in range(C):
out[G*y:G*(y+1), G*x:G*(x+1), c] = np.max(out[G*y:G*(y+1), G*x:G*(x+1), c])
return out
# Read image
img = cv2.imread("imori.jpg")
# Max pooling
out = max_pooling(img)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
问题九:高斯滤波(Gaussian Filter)
使用高斯滤波器( 3 × 3 3\times3 3×3大小,标准差 σ = 1.3 \sigma=1.3 σ=1.3)来对imori_noise.jpg进行降噪处理吧!
高斯滤波器是一种可以使图像平滑的滤波器,用于去除噪声。可用于去除噪声的滤波器还有中值滤波器(参见问题十),平滑滤波器(参见问题十一)、LoG滤波器(参见问题十九)。
高斯滤波器将中心像素周围的像素按照高斯分布加权平均进行平滑化。这样的(二维)权值通常被称为卷积核(kernel)或者滤波器(filter)。
但是,由于图像的长宽可能不是滤波器大小的整数倍,因此我们需要在图像的边缘补 0 0 0。这种方法称作Zero Padding。并且权值 g g g(卷积核)要进行归一化操作( ∑ g = 1 \sum\ g = 1 ∑ g=1)。
按下面的高斯分布公式计算权值: g ( x , y , σ ) = 1 2 π σ 2 e − x 2 + y 2 2 σ 2 g(x,y,\sigma)=\frac{1}{2\ \pi\ \sigma^2}\ e^{-\frac{x^2+y^2}{2\ \sigma^2}} g(x,y,σ)=2 π σ21 e−2 σ2x2+y2
标准差 σ = 1.3 \sigma=1.3 σ=1.3的 8 − 8- 8−近邻高斯滤波器如下: K = 1 16 [ 1 2 1 2 4 2 1 2 1 ] K=\frac{1}{16}\ \left[ \begin{matrix} 1 & 2 & 1 \ 2 & 4 & 2 \ 1 & 2 & 1 \end{matrix} \right] K=161 [121 242 121]
import cv2
import numpy as np
# Gaussian filter
def gaussian_filter(img, K_size=3, sigma=1.3):
if len(img.shape) == 3:
H, W, C = img.shape
else:
img = np.expand_dims(img, axis=-1)
H, W, C = img.shape
## 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)
## prepare 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 /= (2 * np.pi * sigma * sigma)
K /= K.sum()
tmp = out.copy()
# filtering
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 = np.clip(out, 0, 255)
out = out[pad: pad + H, pad: pad + W].astype(np.uint8)
return out
# Read image
img = cv2.imread("imori_noise.jpg")
# Gaussian Filter
out = gaussian_filter(img, K_size=3, sigma=1.3)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
问题十:中值滤波(Median Filter)
使用中值滤波器( 3 × 3 3\times3 3×3大小)来对imori_noise.jpg进行降噪处理吧!
中值滤波器是一种可以使图像平滑的滤波器。这种滤波器用滤波器范围内(在这里是 3 × 3 3\times3 3×3)像素点的中值进行滤波,请在这里也采用Zero Padding。
import cv2
import numpy as np
# Median filter
def median_filter(img, K_size=3):
H, W, C = img.shape
## 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()
# filtering
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)
return out
# Read image
img = cv2.imread("imori_noise.jpg")
# Median Filter
out = median_filter(img, K_size=3)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()