《python深度学习》读书笔记(7) - 计算机视觉深度学习进阶

图像分割示例

训练数据可下载

shell 复制代码
!wget http://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz
!wget http://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz
!tar -xf images.tar.gz
!tar -xf annotations.tar.gz

图片和图片的掩码

python 复制代码
import os

import matplotlib.pyplot as plt
from keras.src.utils import load_img, img_to_array


def display_target(target_array):
    # 原始标签是123 我们减1 标签的值变成0~2 然后乘以127
    # 原始标签中 1代表前景 2代表背景 3代表轮廓
    # 标签就是 0黑色 127灰色 254接近白色
    normalized_target_array = (target_array.astype("uint8") - 1) * 127
    plt.axis("off")
    plt.imshow(normalized_target_array[:, :, 0])
    plt.show()



if __name__ == '__main__':
    input_dir = "images/"
    target_dir = "annotations/trimaps/"
    input_img_paths = sorted(
        [os.path.join(input_dir, fname) for fname in os.listdir(input_dir) if fname.endswith(".jpg")]
    )
    target_img_paths = sorted([os.path.join(target_dir, fname) for fname in os.listdir(target_dir) if
                               fname.endswith(".png") and not fname.startswith(".")])
    plt.axis("off")
    # 注意这个show图片的方法 核心是load_image 这个是keras里面提供的方法
    plt.imshow(load_img(input_img_paths[9]))
    # 展示原图
    plt.show()

    # grayscale 代表加载的图具有单一颜色通道
    img = img_to_array(load_img(input_img_paths[9],color_mode="grayscale"))
    display_target(img)

原图:

对应目标的掩码图

准备训练数据

python 复制代码
import os
import random

import matplotlib.pyplot as plt
import numpy as np
from keras.src.utils import load_img, img_to_array


def path_to_input_image(path):
    return img_to_array(load_img(path, target_size=img_size))


def path_to_target(path):
    img = img_to_array(load_img(path, target_size=img_size, color_mode="grayscale"))
    # 使标签变为012
    img = img.astype("uint8") - 1
    return img


def display_target(target_array):
    # 原始标签是123 我们减1 标签的值变成0~2 然后乘以127
    # 原始标签中 1代表前景 2代表背景 3代表轮廓
    # 标签就是 0黑色 127灰色 254接近白色
    normalized_target_array = (target_array.astype("uint8") - 1) * 127
    plt.axis("off")
    plt.imshow(normalized_target_array[:, :, 0])
    plt.show()


if __name__ == '__main__':
    input_dir = "images/"
    target_dir = "annotations/trimaps/"
    input_img_paths = sorted(
        [os.path.join(input_dir, fname) for fname in os.listdir(input_dir) if fname.endswith(".jpg")]
    )
    target_img_paths = sorted([os.path.join(target_dir, fname) for fname in os.listdir(target_dir) if
                               fname.endswith(".png") and not fname.startswith(".")])

    img_size = (200, 200)
    num_imgs = len(input_img_paths)

    # 使用相同的种子来打乱顺序,目的是为了 数据和标签能11对应上
    random.Random(1337).shuffle(input_img_paths)
    random.Random(1337).shuffle(target_img_paths)

    # 将所有图像加载到float32的数组中 掩码加载到uint8的数组中
    # 主要还是数据集很小,所以一次性把图片加载到内存中
    # 3和1 的区别 前面讲过了 3代表 rgb 3个通道
    # 1代表只有1个通道的 掩码图
    input_imgs = np.zeros((num_imgs,) + img_size + (3,), dtype="float32")
    targets = np.zeros((num_imgs,) + img_size + (1,), dtype="uint8")

    print(f"input_imgs.shape: {input_imgs.shape}")
    print(f"targets.shape: {targets.shape}")

    for i in range(num_imgs):
        input_imgs[i] = path_to_input_image(input_img_paths[i])
        targets[i] = path_to_target(target_img_paths[i])
    num_val_samples = 1000
    # 将数据划分为 数据集和验证集
    train_input_imgs = input_imgs[:-num_val_samples]
    train_targets = targets[:-num_val_samples]
    val_input_imgs = input_imgs[-num_val_samples:]
    val_targets = targets[-num_val_samples:]

看下shape

构建模型

python 复制代码
import os
import random

import matplotlib.pyplot as plt
import numpy as np
from keras import layers
from keras.src.utils import load_img, img_to_array
from tensorflow import keras


def path_to_input_image(path):
    return img_to_array(load_img(path, target_size=img_size))


def path_to_target(path):
    img = img_to_array(load_img(path, target_size=img_size, color_mode="grayscale"))
    # 使标签变为012
    img = img.astype("uint8") - 1
    return img


def display_target(target_array):
    # 原始标签是123 我们减1 标签的值变成0~2 然后乘以127
    # 原始标签中 1代表前景 2代表背景 3代表轮廓
    # 标签就是 0黑色 127灰色 254接近白色
    normalized_target_array = (target_array.astype("uint8") - 1) * 127
    plt.axis("off")
    plt.imshow(normalized_target_array[:, :, 0])
    plt.show()


def get_model(img_size, num_classes):
    inputs = keras.Input(shape=img_size + (3,))
    # 将输入图像的尺寸调整到[0.1] 区间
    x = layers.Rescaling(1. / 255)(inputs)
    # padding = same 是避免 边界填充对特征图大小造成影响
    # 这一步的目的是希望最终得到 25 25 256 的图像 将图像编码为较小的特征图
    x = layers.Conv2D(64, 3, strides=2, activation="relu", padding="same")(x)
    x = layers.Conv2D(64, 3, activation="relu", padding="same")(x)
    x = layers.Conv2D(128, 3, strides=2, activation="relu", padding="same")(x)
    x = layers.Conv2D(128, 3, activation="relu", padding="same")(x)
    x = layers.Conv2D(256, 3, strides=2, padding="same", activation="relu")(x)
    x = layers.Conv2D(256, 3, activation="relu", padding="same")(x)

    # 这一步的目的是将 25 25 256的特征图还原到 200 200 3
    # 注意上下的2步 是成对出现的 固定写法
    x = layers.Conv2DTranspose(256, 3, activation="relu", padding="same")(x)
    x = layers.Conv2DTranspose(256, 3, activation="relu", padding="same", strides=2)(x)
    x = layers.Conv2DTranspose(128, 3, activation="relu", padding="same")(x)
    x = layers.Conv2DTranspose(128, 3, activation="relu", padding="same", strides=2)(x)
    x = layers.Conv2DTranspose(64, 3, activation="relu", padding="same")(x)
    x = layers.Conv2DTranspose(64, 3, activation="relu", padding="same", strides=2)(x)

    outputs = layers.Conv2D(num_classes, 3, activation="softmax", padding="same")(x)

    model = keras.Model(inputs, outputs)
    return model


if __name__ == '__main__':
    input_dir = "images/"
    target_dir = "annotations/trimaps/"
    input_img_paths = sorted(
        [os.path.join(input_dir, fname) for fname in os.listdir(input_dir) if fname.endswith(".jpg")]
    )
    target_img_paths = sorted([os.path.join(target_dir, fname) for fname in os.listdir(target_dir) if
                               fname.endswith(".png") and not fname.startswith(".")])

    img_size = (200, 200)
    num_imgs = len(input_img_paths)

    # 使用相同的种子来打乱顺序,目的是为了 数据和标签能11对应上
    random.Random(1337).shuffle(input_img_paths)
    random.Random(1337).shuffle(target_img_paths)

    # 将所有图像加载到float32的数组中 掩码加载到uint8的数组中
    # 主要还是数据集很小,所以一次性把图片加载到内存中
    # 3和1 的区别 前面讲过了 3代表 rgb 3个通道
    # 1代表只有1个通道的 掩码图
    input_imgs = np.zeros((num_imgs,) + img_size + (3,), dtype="float32")
    targets = np.zeros((num_imgs,) + img_size + (1,), dtype="uint8")

    print(f"input_imgs.shape: {input_imgs.shape}")
    print(f"targets.shape: {targets.shape}")

    for i in range(num_imgs):
        input_imgs[i] = path_to_input_image(input_img_paths[i])
        targets[i] = path_to_target(target_img_paths[i])
    num_val_samples = 1000
    # 将数据划分为 数据集和验证集
    train_input_imgs = input_imgs[:-num_val_samples]
    train_targets = targets[:-num_val_samples]
    val_input_imgs = input_imgs[-num_val_samples:]
    val_targets = targets[-num_val_samples:]

    model = get_model(img_size=img_size, num_classes=3)
    model.summary()

看下模型。可以清晰的看到 模型的 转换过程

训练

没有cuda的话,这个训练时间很长。我mac arm 电脑 要10个小时左右。

python 复制代码
model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy")

callbacks = [
    keras.callbacks.ModelCheckpoint("oxford_segmentation.keras",
                                    save_best_only=True)
]

history = model.fit(train_input_imgs, train_targets,
                    epochs=50,
                    callbacks=callbacks,
                    batch_size=64,
                    validation_data=(val_input_imgs, val_targets))

epochs = range(1, len(history.history["val_loss"]) + 1)
loss = history.history["loss"]
val_loss = history.history["val_loss"]
plt.figure()
plt.plot(epochs, loss, "bo", label="Training loss")
plt.plot(epochs, val_loss, "b", label="Validation loss")
plt.title("Traning and validation loss")
plt.legend()
plt.show()

看下损失曲线,大概在25次的时候出现过拟合的现象

使用该模型

我们可以验证一下 该模型的效果

scss 复制代码
def display_mask(pred):
    mask = np.argmax(pred, axis=-1)
    mask *= 127
    plt.axis("off")
    plt.imshow(mask)
    plt.show()
ini 复制代码
# 将数据划分为 数据集和验证集
train_input_imgs = input_imgs[:-num_val_samples]
tarin_targets = targets[:-num_val_samples]
val_input_imgs = input_imgs[-num_val_samples:]
val_targets = targets[-num_val_samples:]

model = keras.models.load_model("oxford_segmentation.keras")
i = 4
test_image = val_input_imgs[i]
plt.axis('off')
plt.imshow(array_to_img(test_image))
plt.show()
mask = model.predict(np.expand_dims(test_image, 0))[0]
display_mask(mask)

原图:

训练后的

中间激活值的可视化

这一小节 主要看下一个输入如何被分解成不同的滤波器 这些滤波器将由神经网络进行学习

ini 复制代码
import matplotlib.pyplot as plt
from keras import layers
from tensorflow import keras
import numpy as np

# 下载测试图像
img_path = keras.utils.get_file(
    fname="cat.jpg",
    origin="https://img-datasets.s3.amazonaws.com/cat.jpg")


def get_img_array(img_path, target_size):
    # 打开图像 并且调整尺寸
    img = keras.utils.load_img(
        img_path, target_size=target_size)
    # 将图像转换为数组
    array = keras.utils.img_to_array(img)
    array = np.expand_dims(array, axis=0)
    return array


if __name__ == '__main__':
    img_tensor = get_img_array(img_path, target_size=(180, 180))
    plt.axis("off")
    plt.imshow(img_tensor[0].astype("uint8"))
    plt.show()

    # 用一下 前面第八章我们的猫狗分类模型
    model = keras.models.load_model("convent_from_scratch_with_aug.keras")
    # 看下这个模型的结构
    model.summary()
    layer_outputs = []
    layer_names = []
    for layer in model.layers:
        if isinstance(layer, (layers.Conv2D, layers.MaxPooling2D)):
            layer_outputs.append(layer.output)
            layer_names.append(layer.name)
    activation_model = keras.Model(inputs=model.input, outputs=layer_outputs)

    activations = activation_model.predict(img_tensor)
    # 第一层其实是一个 (1, 178, 178, 32)
    first_layer_activation = activations[0]
    print(f"activations: {first_layer_activation.shape}")
    # 绘制下第五层的通道 看看啥样,也可以看看其他通道啥样
    plt.matshow(first_layer_activation[0, :, :, 5], cmap="viridis")
    plt.show()

简单看下这个模型的结构

原图:

看上去有点像边缘检测

再改一下代码,把每层的每个通道都可视化一下

python 复制代码
images_per_row = 16
for layer_name, layer_activation in zip(layer_names, activations):
    n_features = layer_activation.shape[-1]
    size = layer_activation.shape[1]
    n_cols = n_features // images_per_row
    display_grid = np.zeros(((size + 1) * n_cols - 1,
                             images_per_row * (size + 1) - 1))
    for col in range(n_cols):
        for row in range(images_per_row):
            channel_index = col * images_per_row + row
            channel_image = layer_activation[0, :, :, channel_index].copy()
            if channel_image.sum() != 0:
                channel_image -= channel_image.mean()
                channel_image /= channel_image.std()
                channel_image *= 64
                channel_image += 128
            channel_image = np.clip(channel_image, 0, 255).astype("uint8")
            display_grid[
            col * (size + 1): (col + 1) * size + col,
            row * (size + 1): (row + 1) * size + row] = channel_image
    scale = 1. / size
    plt.figure(figsize=(scale * display_grid.shape[1],
                        scale * display_grid.shape[0]))
    plt.title(layer_name)
    plt.grid(False)
    plt.axis("off")
    plt.imshow(display_grid, aspect="auto", cmap="viridis")
    plt.show()

再看下图:

可以看出来一个规律 随着层数越多,关于图像视觉的内容越来越少,图像类别的信息则越来越多

随着层数的增多,层所提取的特征越来越抽象

其实很像人类的思考方式,你看过一个场景以后,你能记住这个场景有自行车,但是你记不住这个自行车的细节长啥样。

其实大脑也是将视觉输入完全抽象化的

相关推荐
tangweiguo030519875 分钟前
(Kotlin)Android 高效底部导航方案:基于预定义 Menu 和 ViewPager2 的 Fragment 动态绑定实现
android·开发语言·kotlin
顾林海19 分钟前
Jetpack Pager 使用与原理解析
android·android jetpack
pengyu22 分钟前
系统化掌握Dart网络编程之Dio(一):筑基篇
android·flutter·dart
QING6181 小时前
Kotlin 操作符与集合/数组方法详解——新手指南
android·kotlin·app
张风捷特烈1 小时前
Flutter 伪 3D 绘制#1 | 三维空间
android·flutter
stringwu1 小时前
白话kotlin协程
android
QING6181 小时前
Kotlin 中 == 和 === 的区别
android·kotlin·app
林十一npc1 小时前
MySQL索引与视图综合应用示例解析
android·前端·mysql
QING6183 小时前
Kotlin containsValue用法及代码示例
android·kotlin·源码阅读
QING6183 小时前
Kotlin coerceAtMost用法及代码示例
android·kotlin·源码阅读