《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()

再看下图:

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

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

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

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

相关推荐
江上清风山间明月3 小时前
Flutter DragTarget拖拽控件详解
android·flutter·ios·拖拽·dragtarget
debug_cat5 小时前
AndroidStudio Ladybug中编译完成apk之后定制名字kts复制到指定目录
android·android studio
编程洪同学10 小时前
Spring Boot 中实现自定义注解记录接口日志功能
android·java·spring boot·后端
氤氲息12 小时前
Android 底部tab,使用recycleview实现
android
Clockwiseee12 小时前
PHP之伪协议
android·开发语言·php
小林爱12 小时前
【Compose multiplatform教程08】【组件】Text组件
android·java·前端·ui·前端框架·kotlin·android studio
小何开发13 小时前
Android Studio 安装教程
android·ide·android studio
开发者阿伟14 小时前
Android Jetpack LiveData源码解析
android·android jetpack
weixin_4381509914 小时前
广州大彩串口屏安卓/linux触摸屏四路CVBS输入实现同时显示!
android·单片机
CheungChunChiu15 小时前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd