图像处理100问--python实现(1-20)

python图像处理学习笔记

文章目录

      • 1.二值化 Thresholding
      • 2.大津二值法 Otsu's Method
      • 3.HSV与RGB互相转换
      • 4.色彩减少
      • 5.平均池化
      • 6.最大池化
    • 卷积滤波
      • 7.高斯滤波
      • 8. Motion Filter
      • 9.差分滤波器
      • 10.Sobel与Prewitt滤波器
      • 11.Laplacian滤波器
      • 12.Emboss滤波器
      • 13.LoG滤波器

1.二值化 Thresholding

def binarization(img, th=128):
    img[img < th] = 0
    img[img >= th] = 255
    return img

img得到的数组当作索引,是一个很棒的技巧

2.大津二值法 Otsu’s Method

一种自动确定阈值的二值化方法

PyCharm报错:AttributeError: ‘NoneType’ object has no attribute ‘astype’
大概率是文件路径不对

# Otsu Binarization
def otsu_binarization(img, th=128):
	max_sigma = 0
	max_t = 0

	# determine threshold
	for _t in range(1, 255):
		v0 = out[np.where(out < _t)]  # 找出所有小于_t的数值并组成一个新的数组
		m0 = np.mean(v0) if len(v0) > 0 else 0.
		w0 = len(v0) / (H * W)
		v1 = out[np.where(out >= _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

	# Binarization
	print("threshold >>", max_t)
	th = max_t
	out[out < th] = 0
	out[out >= th] = 255

	return out

定义一个分离度,包含类间方差与类内方差两个影响因素。遍历所有阈值,使得这个分离度取得最高的阈值,便是那个最佳的阈值

3.HSV与RGB互相转换

H:hue,色相,从0取值至360,代表了所有的颜色,例如0和360代表红色,180代表青色
S:saturation,饱和度,0<=S<1,代表颜色纯度。S=max-min
V:value,亮度,0<=V<1。V=max

一张图片求反色,可行的方法是将图片由RGB转向HSV,然后将H+180,再转回RGB

下面的转换程序需要注意这样几点

  1. 使用np.imread读取的图片,通道顺序是(y,x,c)-->计算机是先说图片的高、再说图片的宽、最后说通道
    1.1其中c的顺序是B->G->R
    1.2reshape()与.shape中的通道顺序都是z->y->x
    1.3我们常说一张图片尺寸为 1080 × 720 1080\times 720 1080×720,这个顺序是x->y。口头的说法与imread读取的结果相反
  2. .copy()这种浅拷贝与直接赋值(赋引用)效果很接近
    需要区别的是.deepcopy()
  3. hsv[..., 0]hsv[:,:,0]同含义
  4. np.argmin(img, axis=2)表示按图片中的第三个方向(c方向)进行比较与压缩,求得最小值的索引–>由此便知道了整张图片的每个像素中,哪个通道最弱
    5.解释下.where()的用法,它返回的是索引tuple
>>> a = np.array([2,4,6,8,10])
>>> np.where(a > 5)             # 返回索引
(array([2, 3, 4]),)   
>>> a[np.where(a > 5)]              # 这种套娃情况,等价于 a[a>5]。索引多个元素与matlab一致
array([ 6,  8, 10])

>>> np.where([[0, 1], [1, 0]])
(array([0, 1]), array([1, 0]))

示例程序:

# 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()  # z方向压扁,每个像素的通道最大值
	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,找出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

4.色彩减少

即色彩量化,使得RGB的值只能取[32,96,160,224],可以用来压缩图像,但是也会使得图像质量变差

def dicrease_color(img):
	out = img.copy()

	out = out // 64 * 64 + 32

	return out

//是取商运算符,如7//2结果为3
代码中的out是一个三通道的图像,在对其进行运算时,是对其中的所有数进行操作

5.平均池化

将整个图片网格(grid)化,再对其中的每一个网格进行求均值,得到的均值作为整个网格的值,这种操作就是池化。其实得到的效果与打码无异。

def my_pooling(img, G=8):
    H, W, C = img.shape
    out = np.zeros((H, W, C), np.uint8)  # img中数字的类型是uint8,如果赋值out = np.zeros((H, W, C), np.int),那么默认的dtype为int32,会显示错误
    Nh = int(H/G)
    Wh = int(W/G)
    print(img.dtype)
    print((np.zeros((H, W, C), np.int)).dtype)
    for y in range(Nh):
        for x in range(Wh):
            for c in range(C):
                out[G*y:G*(y+1), G*x:G*(x+1), c] = np.mean(img[G*y:G*(y+1), G*x:G*(x+1), c]).astype(np.int)
				# 左边赋值以下赋了一大片
    return out

注意图像初始化时,其类型一定要是uint8,不能指为int–>默认指的是int32

6.最大池化

与平均池化类似,先将图片网格化,再求每个网格的最大值作为整个网格的代表。相当于加高亮度的打码

卷积滤波

7.高斯滤波

1.高斯滤波常用来去噪,类似作用的还有中值滤波(取中间值)、均值滤波(取平均值)、LoG(Laplacian of Gaussian)滤波器,都可用来去噪。
2.从有无kernel的角度来讲,中值滤波与均值滤波是同一类,它们没有一个固定的卷积kernel
3.高斯滤波的kernel权重由二维高斯函数(二维正态分布)给出,有了kernel以后,对整个图片进行滑动窗口卷积即可。
4.由于图片的长宽一般不是kernel尺寸的的整数倍,因此我们需要在图片的边缘补0,这种方法称为zero padding,并且卷积核要进行归一化操作。
4.1 可以想象,对于一个 3 × 3 3\times 3 3×3的卷积核,那么填充的边缘尺寸应该为1;对于一个 7 × 7 7\times 7 7×7的卷积核,需要填充的尺寸应该是3。由此可以总结出公式: P a d d i n g = S i z e k e r n e l / / 2 Padding = Size_{kernel} // 2 Padding=Sizekernel//2
5.这里的高斯滤波对图像的三个通道都进行了计算,不需要转换为灰度图
6.边缘检测一般都需要针对灰度图进行
7.ndarray的相乘就是对位相乘,得到的是一个同规模的ndarray,对结果矩阵进行求和,就相当于进行了卷积

# 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  # pad就是padding
	out = np.zeros((H + pad * 2, W + pad * 2, C), dtype=np.float)  # 卷积核可以是float
	# 一个图片的上边与下边、左边与右边都有padding,因此尺寸应该如上式计算
	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])
	# 等式左边的pad可以使得不计算图片中的padding
	out = np.clip(out, 0, 255)
	out = out[pad: pad + H, pad: pad + W].astype(np.uint8)
	# 去掉边缘的0,转换uint8

	return out

8. Motion Filter

其卷积核如下所示
K = [ 1 3 0 0 0 1 3 0 0 0 1 3 ] K=\begin{bmatrix} \frac{1}{3}&0&0\\ 0&\frac{1}{3}&0\\ 0&0&\frac{1}{3} \end{bmatrix} K=310003100031
取对角线像素的均值。处理后的结果看起来很动感

def my_motion_filter(img, ksize=3):
    H, W, C = img.shape  # 进行卷积必然需要图片恶尺寸来进行遍历,大写字母表示作为引用的常数
    # 生成kernel
    kernel = np.diag([1]*ksize).astype(float)
    kernel /= ksize

    # Zero Padding
    pad = ksize//2
    out = np.zeros((H+pad*2, W+pad*2, C), dtype=float)
    print(out.shape)  # 130,130,3
    out[pad: pad + H, pad: pad + W] = img.copy().astype(np.float)
    # convolution
    for y in range(H):
        for x in range(W):
            for c in range(C):
                out[pad + y, pad + x, c] = np.sum(kernel * out[y:y + ksize, x:x + ksize, c])   # 乘错了,与kernel相乘的应该是包括padding的
                # 注意这个技巧,左边赋值只包括了padding内,而右边与kernel相乘的部分则是以点(x,y)为中心的,整个滑窗包括了padding
                # out[pad + y, pad + x, c] = np.sum(kernel * out[y: y + ksize, x: x + ksize, c])

    # 仍然很重要的一步,修剪pad,注意赋值时不需要对C通道赋值
    out = out[pad:pad + H, pad:pad + W].astype(np.uint8)
    return out

9.差分滤波器

纵向
K = [ 0 − 1 0 0 1 0 0 0 0 ] K=\begin{bmatrix} 0&-1&0\\ 0&1&0\\ 0&0&0 \end{bmatrix} K=000110000
横向
K = [ 0 0 0 − 1 1 0 0 0 0 ] K=\begin{bmatrix} 0&0&0\\ -1&1&0\\ 0&0&0 \end{bmatrix} K=010010000

直接初始化卷积核然后卷积即可

# different filter
def different_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), dtype=np.float)
	out[pad: pad + H, pad: pad + W] = gray.copy().astype(np.float)
	tmp = out.copy()

	out_v = out.copy()
	out_h = out.copy()

	# vertical kernel
	Kv = [[0., -1., 0.],[0., 1., 0.],[0., 0., 0.]]
	# horizontal kernel
	Kh = [[0., 0., 0.],[-1., 1., 0.], [0., 0., 0.]]

	# filtering
	for y in range(H):
		for x in range(W):
			out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y: y + K_size, x: x + K_size]))
			out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y: y + K_size, x: x + K_size]))

	out_v = np.clip(out_v, 0, 255)
	out_h = np.clip(out_h, 0, 255)

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

	return out_v, out_h

10.Sobel与Prewitt滤波器

11.Laplacian滤波器

K = [ 0 1 0 1 − 4 1 0 1 0 ] K=\begin{bmatrix} 0&1&0\\ 1&-4&1\\ 0&1&0 \end{bmatrix} K=010141010
对灰度图进行二阶导得到的核

12.Emboss滤波器

K = [ − 2 − 1 0 − 1 1 1 0 1 2 ] K=\begin{bmatrix} -2&-1&0\\ -1&1&1\\ 0&1&2 \end{bmatrix} K=210111012
得到的轮廓更加清晰

13.LoG滤波器

对图像进行高斯滤波器平滑之后再使用拉普拉斯滤波器使得图像轮廓更加清晰
这是为了防止拉普拉斯滤波器使噪声增强了

彩色图像分为:索引图像(用索引代表颜色)与真彩色图像(RGB代表图像)
图像质量评价受主观因素的影响

你可能感兴趣的:(图像处理)