卷积神经网络可视化——Image Kernel

2019-10-23 13:12:37 浏览数 (1)

前言

深度学习领域,解决图像分类问题,最常用的就是卷积神经网络(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

0 人点赞