- 本文整理来自Github项目:ImageProcessing100Wen
- 源项目包含python代码和C++代码,本文代码仅含python,需要的可根据序号去源作者项目中获取
- python略熟,所以加了一些注释
- python 中显示图片的代码(自行添加调试):
cv2.imwrite("out.jpg", out) # 保存图像out为out.jpg,若不需要保存则可去掉
cv2.imshow("result", out) # 显示图像out,并给该显示窗口命名为result
cv2.waitKey(0) # 显示图像后,等待按键操作
cv2.destroyAllWindows() # 按键操作后,销毁所有窗口
- 本文中很多Question实际上openCV都提供了函数,可直接调用。本文注重理解,直接对矩阵操作,帮助初学者理解。
Question:模板
这是一个Question的模块框架
代码:
print("这是一个Question的模块框架")
Question1:通道交换
我们熟悉的三原色通道是按RGB顺序储存的,但是openCV读取的图片是以BGR顺序储存的

关于RGB以及接下来的灰度图、二值图,可看:彩色图、灰度图和二值图
代码:
import cv2
img=cv2.imread("imori.jpg")
# 将三个矩阵分别复制
blue=img[:,:,0].copy()
green=img[:,:,1].copy()
red=img[:,:,2].copy()
# 将三个矩阵按RGB顺序赋值给img矩阵,替换原来的数据
# BGR->RGB
img[:, :, 0] = r
img[:, :, 1] = g
img[:, :, 2] = b
Question2:灰度化(Grayscale)
灰度化的图片仅有灰度信息,没有色彩,也就是俗称的黑白照
RGB格式转Gray的公式如下:
Y
=
0.2126
R
+
0.7152
G
+
0.0722
B
Y = 0.2126\ R + 0.7152\ G + 0.0722\ B
Y=0.2126 R+0.7152 G+0.0722 B

代码:
import cv2
import numpy as np
# numpy是科学计算库,对于矩阵运行贼在行,这里是将cv2读取的图片转为了numpy中的float类型
img = cv2.imread("imori.jpg").astype(np.float)
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# r、g、b三个矩阵进行了公式换算,结果保存在out中
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
# 将float类型的out转换为uint8整数类型,因为灰度图矩阵储存的是整数元素
out = out.astype(np.uint8)
Question3:二值化(Thresholding)
相比于常见灰度图用255级表示亮度,二值化只有两种颜色:0(黑色)、1(白色)
如何将灰度图转为二值图,一种最简单的方式是将灰度图中亮度级<128的像素值置为0,亮度级>128的像素值置为1:


代码:
import cv2
import numpy as np
# 读取一张彩色图
img=cv2.imread("sample.jpg").astype(np.float32)
# 图片灰度化
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)
# 图片二值化
gray[gray< 128] = 0 # 将gray矩阵中像素值<128的置0
gray[gray>= 128] = 255 # 将gray矩阵中像素值>128的置1
Question4:大津二值化算法(Otsu)
在Q3中,我们通过人为设定一个阈值,将灰度图中像素分成了两类。但是在实际运用中这个设定的阈值不是万能的,我们能否根据图片信息来自动找到一个最佳的阈值,使得图片二值化更加的合理?
大津算法就是来根据图片本身求最佳阈值的,也成为最大类间方差法。大津算法并不是直接进行二值化,而是得到一个整型值,也即最佳阈值,最后再使用Q3中的方法,进行灰度图的二值化。更详细的介绍和原理
代码中使用的原理计算用每个灰阶做阈值时的类间方差,通过遍历255个灰度级找到最大方差和对应的灰阶值,这就是最佳阈值。

在代码中使用类间方差的公式:需要将计算四个值:两类各像素量占总像素量的比例,两类各自均值
代码:
import cv2
import numpy as np
# Gray scale
def BGR2GRAY(img):
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# Gray scale
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)
return out
# 传入灰度图,返回最佳阈值
def otsu_t(out):
max_sigma=0
max_t=0
w,h=out.shape
for t in range(256):
count0=out[np.where(out<t)] # 得到所有小于阈值的像素的值,暂且称为0类
# where得到符合条件测点的坐标,横坐标和纵坐标分别独立储存,类似[x..],[y..],第一个坐标为[x1,y1],以此类推
# out[[x..],[y..]]表示取出[X1,y1],[x2,y2],[x3,y3]的值;
mean0=count0.mean() if len(count0)>0 else 0 # 计算0类的均值
ocp0=len(count0)/(w*h) # 计算0类像素数量占全部像素量的比例
count1=out[np.where(out>=t)]
mean1=count1.mean() if len(count1)>0 else 0
ocp1=len(count1)/(w*h)
sigma=ocp0*ocp1*((mean0-mean1)**2) # 计算当前阈值_t分割的图像的方差sigma
if sigma>max_sigma: # 如果这次分割的方差更大,则记录方差,并记录下这个阈值_t
max_sigma=sigma
max_t=t
return max_t
# Read image
img = cv2.imread("simple.jpg").astype(np.float32)
H, W, C =img.shape
# 灰度化
out = BGR2GRAY(img)
# 大津法:找到分割该灰度图的最佳阈值
t= otsu_t(out)
Question5:HSV变换
主要指RGB与HSV的相互变换,HAV指色相(Hue)、饱和度(Saturaton)、明度(Value)来表示色彩的一种方式。
- 色相:将颜色使用0°到360°表示,一些典型的对照表如下:
| 红 | 黄 | 绿 | 青色 | 蓝色 | 品红 | 红 |
|---|---|---|---|---|---|---|
| 0° | 60° | 120° | 180° | 240° | 300° | 360° |
- 饱和度:指色彩的纯度或者深度,比如红色颜色是低饱和度时呈现粉色,高饱和度时呈现深红色,总的来说高饱和度的颜色更加鲜艳。(0≤S≤1)
- 明度:即亮度,数值越高越亮越白,数值越低接近黑色(0≤V≤1);
转换步骤:
- RGB→HSV:

- HSV→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()
Question6:减色处理(颜色量化)
我们将图像的颜色种类从2563种降低到43种,即将RGB的取值从0~255变为只取{32,96,160,224},这也被称为色彩量化。色彩值安下面方式定义:

代码:
import cv2
def decrease_color(img):
out = img.copy()
out=out//64*64+32
return out
img=cv2.imread("simple.jpg")
out=decrease_color(img)
cv2.imshow("",out)
cv2.waitKey(0)

Question7:平均池化
所谓的池化,在视觉上可以理解为降低分辨率。比如44的图像降低为22的图像,但这意味着原来的2*2个像素块现在只能用一个像素表示:

平均是指:对于粉色像素块,用粉色像素块的平均值来表示这个区域,如(4+6+8+2)/4=5。
当然除了平均池化,还有最大值池化:即用像素区域中的最大值表示这个区域:

代码:
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()

上述是项目给的源代码,但实际上图像的shape仍为原大小128x128,严格来讲,用8x8网格池化原图像,最终图像shape应该为16x16。下面给出结果为16x16图像的代码,虽然在视觉上效果一样,但是对计算机来讲128x128≠16x16

import cv2
img=cv2.imread("imori.jpg")
import numpy as np
out=np.zeros((16,16,3),dtype="int") # 创建和结果图一样大的矩阵,方便写入结果
for i in range(16):
for j in range(16):
mat=img[i*8:(i+1)*8,j*8:(j+1)*8,...] # 得到每个8*8的区域块
mean=np.mean(mat,axis=(0,1)) # 计算均值,mean是三个值
out[i,j,...]=mean
cv2.imshow("",out.astype(np.uint8))
cv2.waitKey(0)
Question8:最大池化
上面已经讲过,用区域块中数值最大的值来表示该区域
代码:
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()

同样的,该结果图仍为128x128,下面给出16x16的结果图代码:

import cv2
img=cv2.imread("imori.jpg")
import numpy as np
out=np.zeros((16,16,3),dtype="int")
for i in range(16):
for j in range(16):
mat=img[i*8:(i+1)*8,j*8:(j+1)*8,...]
mean=np.max(mat,axis=(0,1)) # 与均值池化代码相比,只是将mean改成了max而已
out[i,j,...]=mean
cv2.imshow("",out.astype(np.uint8))
cv2.waitKey(0)
Question9:高斯滤波
滤波是一种运算,详见卷积核(又称滤波器)。高斯滤波是卷积核的数值符合高斯分布的一种特殊的滤波器。由于高斯滤波器的特性是类似均值运算,所以对图像进行高斯滤波会造成图像的模糊化。
高斯分布公式计算权值:

可以看见,权值最大的是x=y=0时。
尺寸为3*3,sigma=1.3的,且经过归一化的(即Σ=1)高斯分布:

现在使用该滤波器对图像进行高斯滤波,效果如下:

代码:
import numpy as np
import cv2
# 得到一个高斯滤波器
def getGaussianKernel(size=3,sigma=1.3):
K=np.zeros((size,size),dtype=np.float)
padding=size//2
for x in range(size):
for y in range(size):
K[x,y]=1/(2*np.pi*sigma**2)*np.exp(-(((x-padding)**2+(y-padding)**2)/(2*sigma**2)))
K/=K.sum() # 归一化
return K
# 滤波操作
def filter(img,K):
size=K.shape[1]
padding=size//2
if len(img.shape)==3:
H,W,C=img.shape
elif len(img.shape)==2:
H,W=img.shape
C=1
tmp=np.zeros((H+2*padding,W+2*padding,C))
tmp[padding:padding+H,padding:padding+W]=img
out=np.zeros(img.shape)
for h in range(H):
for w in range(W):
for c in range(C):
out[h,w,c]=np.sum(K*tmp[h:h+size,w:w+size,c])
out=out.clip(0,255).astype(np.uint8)
return out
img=cv2.imread("imori_noise.jpg")
K=getGaussianKernel()
out=filter(img,K)
cv2.imshow("",out)
cv2.waitKey(0)
Question10:中值滤波
中值滤波是使用区域内数值居中的像素值代替新的像素。比如带有噪点的图:

对图进行中值滤波:对于3*3区域,得到该区域中排序位于中间的像素值150,使用150代替原来为0的中央像素,如此边成功将噪点0去掉,完成去噪。

结果图如下:

代码:
import numpy as np
import cv2
#中值滤波
def midfilter(img,size):
padding=size//2
if len(img.shape)==3:
H,W,C=img.shape
elif len(img.shape)==2:
H,W=img.shape
C=1
tmp=np.zeros((H+2*padding,W+2*padding,C))
tmp[padding:padding+H,padding:padding+W]=img
out=np.zeros(img.shape)
for h in range(H):
for w in range(W):
for c in range(C):
out[h,w,c]=np.median(tmp[h:h+size,w:w+size,c])
out=out.astype(np.uint8)
return out
img=cv2.imread("imori_noise.jpg")
out=midfilter(img,3)
cv2.imshow("",out)
cv2.waitKey(0)
本文介绍了图像处理的基础操作,包括RGB与BGR通道交换、灰度化、二值化、大津二值化算法、HSV变换、颜色量化、平均池化、最大池化、高斯滤波和中值滤波。通过Python代码实现这些操作,有助于初学者理解图像处理的基本原理。
868

被折叠的 条评论
为什么被折叠?



