前言
深度学习领域,解决图像分类问题,最常用的就是卷积神经网络(Convolutional Neural Network)简称 CNN。之所以称之卷积神经网络,是因为,隐藏层中使用了卷积层,来处理二维(灰度)或三维(RGB)的图像数据。每个卷积层由多个过滤器(Filter)组成,每个过滤器对应一个小矩阵(行列数通常为2或者3),矩阵沿着图像的行列,按一定步长,依次乘积,求和,得到一副新的图像数据,这个过程就是对图像求卷积的过程。
在卷积神经网络的应用中,对图像求卷积得到一副新的图像(如果步长大于1,新图像会缩小),通常用于提取图像的特征,每个过滤器也被称为一个特征映射。但是,图像卷积不只是用在深度神经网络提取特征,在深度学习应用之前,图像卷积已被大量应用在图像处理领域。如我们熟悉的 PhotoShop 等图像处理软件中的,模糊,锐化,浮雕,轮廓线,以及边沿检测,也是通过图像卷积实现的。不同的是,深度学习的卷积矩阵数值是由模型训练出来的,没有规律,而图像处理中,矩阵的数值是有规律的,并且形状通常为 3行3列。我们也称这个矩阵为 Image Kernel (事实上,在 Keras 中卷积层参数也使用 kernel 表示过滤器尺寸)。
本文标题叫《卷积神经网络可视化》,事实上,也是为了说明,虽然神经网络的参数就像黑盒子,无法解释,但是卷积层的输出是可以可视化呈现的。本文从图像处理角度,来解释用于图像卷积的不同 Image Kernel 的可视化输出结果。
1. 导入库
代码语言:javascript复制from PIL import Image, ImageDraw
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
2. 实现 Image Kernel 运算
2.1 助手函数
指定图像路径,使用 PIL.Image 读取彩色图像或者灰度图像。
代码语言:javascript复制def read_img(imgpath, gray=True):
img = Image.open(imgpath)
if gray:
img = img.convert("L")
return img
指定图像宽度,按比例缩放图像。
代码语言:javascript复制def resize_width(img, width):
w, h = img.size
height = int(width * h / w)
return img.resize((width, height))
以图像最短边长,从中间截取正方形图像。
代码语言:javascript复制def crop_center(img):
w, h = img.size
c_x, c_y = w/2, h/2
offset = min(w, h) / 2
crop_box = (c_x-offset, c_y-offset, c_x offset, c_y offset)
return img.crop(crop_box)
2.2 指定 kernel 对图像处理
img 为 Image 对象;kernel 为进行卷积运算的矩阵;strides 为卷积运算单次平移步长;mean 为 False 时,求和,为 True 时,求平均值。
代码语言:javascript复制def apply_img_kernel(img, kernel, strides=1, mean=False):
img = np.asarray(img)
h, w = img.shape[:2]
k_h, k_w = kernel.shape[:2]
x_range = range(0, w - k_w 1, strides)
y_range = range(0, h - k_h 1, strides)
if mean:
prosum = lambda a,b: min((a*b).mean(), 255)
else:
prosum = lambda a,b: min((a*b).sum(), 255)
cal = lambda img: np.array([[prosum(img[i:i k_h, j:j k_w], kernel)
for j in x_range]
for i in y_range]).astype(np.uint8)
if len(img.shape) == 2:
data = cal(img)
return Image.fromarray(data)
elif len(img.shape) == 3:
r, g, b = np.transpose(img, (2, 0, 1))
_r, _g, _b = cal(r), cal(g), cal(b)
return Image.merge('RGB', [Image.fromarray(d) for d in [_r, _g, _b]])
3. 不同效果的 Image Kernel
亮化,锐化,模糊,浮雕,轮廓线,边界线。每个 kernel 都是函数,可以调节参数,默认参数取自网站 http://setosa.io/ev/image-kernels
3.1 亮化
代码语言:javascript复制def identity_kernel(iden=1.0):
return np.array([[0, 0, 0],
[0, iden, 0],
[0, 0, 0]])
3.2 锐化
代码语言:javascript复制def sharpen_kernel(inner=5.0, edge=-1.0):
return np.array([[0, edge, 0],
[edge, inner, edge],
[0, edge, 0]])
3.3 模糊
代码语言:javascript复制def blur_kernel(inner=0.25, edge=0.125, corner=0.0625):
return np.array([[corner, edge, corner],
[edge, inner, edge],
[corner, edge, corner]])
3.4 浮雕
代码语言:javascript复制def emboss_kernel(diag=2.0, iden=1.0):
return np.array([[-diag, -iden, 0],
[-iden, iden, iden],
[0, iden, diag]])
3.5 轮廓线
代码语言:javascript复制def outline_kernel(inner=8.0, outer=-1.0):
return np.array([[outer, outer, outer],
[outer, inner, outer],
[outer, outer, outer]])
3.6 边缘检测
代码语言:javascript复制def sobel_kernel(direction, base=None, edge=2.0, corner=1.0):
if base is not None:
edge = base
corner = base / 2
if direction == 'top':
return np.array([[corner, edge, corner], [0, 0, 0], [-corner, -edge, -corner]])
elif direction == 'bottom':
return np.array([[-corner, -edge, -corner], [0, 0, 0], [corner, edge, corner]])
elif direction == 'left':
return np.array([[corner, 0, -corner], [edge, 0, -edge], [corner, 0, -corner]])
elif direction == 'right':
return np.array([[-corner, 0, corner], [-edge, 0, edge], [-corner, 0, corner]])
return identity_kernel()
4. 使用效果
读取图片,并进行缩放裁剪处理。(手机拍摄图片太大,运算时间长,图像显示也很占空间)
tips: gray 参数设置 True, 读取灰度图像。这里演示彩色图像效果。
代码语言:javascript复制img = read_img('yuki.jpeg', gray=False)
img = crop_center(resize_width(img, 320))
助手函数:在一行显示两幅图片,用于对比实施 Kernel 前后的变化。
代码语言:javascript复制def show2imgs(img1, img2, title=None):
fig = plt.figure(figsize=(10, 5))
if title is not None:
fig.suptitle(title)
plt.subplot(121)
plt.axis('off')
plt.imshow(img1)
plt.subplot(122)
plt.axis('off')
plt.imshow(img2)
plt.show()
4.1 亮化
代码语言:javascript复制img_identity = apply_img_kernel(img, identity_kernel(1.6))
show2imgs(img, img_identity)
4.2 锐化
代码语言:javascript复制img_sharpen = apply_img_kernel(img, sharpen_kernel(inner=1.7, edge=-0.08))
show2imgs(img, img_sharpen)
4.3 模糊
代码语言:javascript复制img_blur = apply_img_kernel(img, blur_kernel())
show2imgs(img, img_blur)
4.4 浮雕
代码语言:javascript复制img_emboss = apply_img_kernel(img, emboss_kernel())
show2imgs(img, img_emboss)
4.5 轮廓线
代码语言:javascript复制img_outline = apply_img_kernel(img, outline_kernel(inner=8.9, outer=-1.29), mean=True)
show2imgs(img, img_outline)
4.6 边缘检测
代码语言:javascript复制img_sobel_top = apply_img_kernel(img, sobel_kernel('top', 0.03))
img_sobel_bottom = apply_img_kernel(img, sobel_kernel('bottom', 0.03))
img_sobel_left = apply_img_kernel(img, sobel_kernel('left', 0.03))
img_sobel_right = apply_img_kernel(img, sobel_kernel('right', 0.03))
show2imgs(img_sobel_top, img_sobel_bottom)
show2imgs(img_sobel_left, img_sobel_right)
5. 开源
本文代码开源,可通过github下载:https://github.com/kenblikylee/imgkernel
也可使用 pip 直接安装体验(使用方式见github主页):
代码语言:javascript复制pip install imgkernel