基于GAN的自动驾驶汽车语义分割

2021-03-30 16:44:17 浏览数 (1)

语义分割是计算机视觉中的关键概念之一,语义分割允许计算机通过按类型对图像中的对象进行颜色编码。GAN建立在基于真实内容的基础上复制和生成原始内容的概念上,这使它们适合于在街景图像上进行语义分割的任务,不同部分的分割使在环境中导航的代理能够适当地发挥作用。

数据预处理

我们从一个kaggle数据集获取数据,街景和分割的图像被配对在一起。这意味着为了构建数据集,必须将每个图像分成两部分,以分割每个实例的语义图像和街景图像。

代码语言:javascript复制
from PIL import Image
from IPython.display import clear_output
import numpy as np
semantic = []
real = []
semantic_imgs = []
real_imgs = []
counter = 0
for img in img_paths:
    if 'jpg' in img:
        im = Image.open(img)
        left = 0
        top = 0
        right = 256
        bottom = 256
        real_img = im.crop((left, top, right, bottom)) 
        real_imgs.append(real_img)
        real.append(np.array(real_img.getdata()).reshape(256,256,3))
        left = 256
        top = 0
        right = 512
        bottom = 256
        semantic_img = im.crop((left, top, right, bottom)) 
        semantic_imgs.append(semantic_img)
        semantic.append(np.array(semantic_img.getdata()).reshape(256,256,3))
        counter  = 1
        print(counter)
        if counter % 10 == 0:
          clear_output()
    else:
        print(img)

该脚本将每个图像裁剪为两个,并记录像素值和原始图像。原始图像也被记录下来,因此以后无需再进行显示。

代码语言:javascript复制
import numpy as np
semantic = np.array(semantic)
real = np.array(real)
X = real
y = semantic

将两个列表都转换为numpy数组后,可以直接定义x和y值。实际上,根据目标,你们可以切换x和y值以控制模型的输出。在这种情况下,我们想将真实图像转换为语义图像。但是,稍后我们将尝试训练GAN将语义数据转换为真实数据。

构建GAN

代码语言:javascript复制
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy import vstack
from numpy.random import randn
from numpy.random import randint
from keras.utils import plot_model
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers.convolutional import Conv2D,Conv2DTranspose
from keras.layers.pooling import MaxPooling2D
from keras.layers.merge import concatenate
from keras.initializers import RandomNormal
from keras.layers import LeakyReLU
from keras.layers import BatchNormalization
from keras.layers import Activation,Reshape
from keras.optimizers import Adam
from keras.models import Sequential
from keras.layers import Dropout
from IPython.display import clear_output
from keras.layers import Concatenate

当我们使用keras框架构造生成器和鉴别器时,我们需要导入所有必需的图层类型以构造模型。这包括主要的卷积和卷积转置层,以及批处理归一化层和泄漏的relu层。串联层用于构建U-net体系结构,因为它可以将某些层链接在一起。

代码语言:javascript复制
def define_discriminator(image_shape=(256,256,3)):
    init = RandomNormal(stddev=0.02)
    in_src_image = Input(shape=image_shape)
    in_target_image = Input(shape=image_shape)
    merged = Concatenate()([in_src_image, in_target_image])
    d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(merged)
    d = LeakyReLU(alpha=0.2)(d)
    d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
    d = BatchNormalization()(d)
    d = LeakyReLU(alpha=0.2)(d)
    d = Conv2D(256, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
    d = BatchNormalization()(d)
    d = LeakyReLU(alpha=0.2)(d)
    d = Conv2D(512, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
    d = BatchNormalization()(d)
    d = LeakyReLU(alpha=0.2)(d)
    d = Conv2D(512, (4,4), padding='same', kernel_initializer=init)(d)
    d = BatchNormalization()(d)
    d = LeakyReLU(alpha=0.2)(d)
    d = Conv2D(1, (4,4), padding='same', kernel_initializer=init)(d)
    patch_out = Activation('sigmoid')(d)
    model = Model([in_src_image, in_target_image], patch_out)
    opt = Adam(lr=0.0002, beta_1=0.5)
    model.compile(loss='binary_crossentropy', optimizer=opt, loss_weights=[0.5])
    return model

该判别器是pix2pix GAN论文使用的模型的keras实现。使用泄漏的Relu而不是正常的Relu是为了使负值仍然被考虑在内。这增加了收敛速度。鉴别器执行二进制分类,因此在最后一层使用S形,并使用二进制交叉熵作为损失函数。

代码语言:javascript复制
def define_encoder_block(layer_in, n_filters, batchnorm=True):
    init = RandomNormal(stddev=0.02)
    g = Conv2D(n_filters, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(layer_in)
    if batchnorm:
        g = BatchNormalization()(g, training=True)
    g = LeakyReLU(alpha=0.2)(g)
    return g
 
def decoder_block(layer_in, skip_in, n_filters, dropout=True):
    init = RandomNormal(stddev=0.02)
    g = Conv2DTranspose(n_filters, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(layer_in)
    g = BatchNormalization()(g, training=True)
    if dropout:
        g = Dropout(0.5)(g, training=True)
    g = Concatenate()([g, skip_in])
    g = Activation('relu')(g)
    return g

生成器包括多次对初始数据进行编码,直到获得原始图像的特征图为止。然后将此特征图像解码,直到获得完整分辨率的图像为止。这意味着生成器中的大多数层只是编码器和解码器块。在对编码器解码器块进行了精心设计之后,为了构建生成器,没有更多的工作要做。

代码语言:javascript复制
def define_generator(image_shape=(256,256,3)):
    init = RandomNormal(stddev=0.02)
    in_image = Input(shape=image_shape)
    e1 = define_encoder_block(in_image, 64, batchnorm=False)
    e2 = define_encoder_block(e1, 128)
    e3 = define_encoder_block(e2, 256)
    e4 = define_encoder_block(e3, 512)
    e5 = define_encoder_block(e4, 512)
    e6 = define_encoder_block(e5, 512)
    e7 = define_encoder_block(e6, 512)
    b = Conv2D(512, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(e7)
    b = Activation('relu')(b)
    d1 = decoder_block(b, e7, 512)
    d2 = decoder_block(d1, e6, 512)
    d3 = decoder_block(d2, e5, 512)
    d4 = decoder_block(d3, e4, 512, dropout=False)
    d5 = decoder_block(d4, e3, 256, dropout=False)
    d6 = decoder_block(d5, e2, 128, dropout=False)
    d7 = decoder_block(d6, e1, 64, dropout=False)
    g = Conv2DTranspose(3, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d7)
    out_image = Activation('tanh')(g)
    model = Model(in_image, out_image)
    return model

使用多个编码器和解码器,我们得到了这个生成器。使用双曲正切可对数据进行归一化,范围从(0,255)到(-1,1)。我们必须记住将数据编码为范围(-1,1),这样才能正确评估生成器的输出和y值。

代码语言:javascript复制
def define_gan(g_model, d_model, image_shape):
    d_model.trainable = False
    in_src = Input(shape=image_shape)
    gen_out = g_model(in_src)
    dis_out = d_model([in_src, gen_out])
    model = Model(in_src, [dis_out, gen_out])
    opt = Adam(lr=0.0002, beta_1=0.5)
    model.compile(loss=['binary_crossentropy', 'mse'], optimizer=opt)
    return model

将两个模型连接在一起即可得到完整的GAN。发生器的输出直接馈入鉴别器。

生成样本

代码语言:javascript复制
def generate_real_samples(dataset, n_samples, patch_shape):
    trainA, trainB = dataset
    ix = randint(0, trainA.shape[0], n_samples)
    X1, X2 = trainA[ix], trainB[ix]
    y = ones((n_samples, patch_shape, patch_shape, 1))
    X1 = (X1 - 127.5) / 127.5
    X2 = (X2 - 127.5) / 127.5
    return [X1, X2], y
 
def generate_fake_samples(g_model, samples, patch_shape):
    X = g_model.predict(samples)
    y = zeros((len(X), patch_shape, patch_shape, 1))
    return X, y

为了使鉴别器起作用,必须同时提供真实样本和计算机生成的样本。但是,该过程并不是那么简单,需要对这些值进行标准化。由于像素值的范围介于0到255之间,因此通过使用等式X1 =(X1–127.5)/ 127.5,所有值都将在(-1,1)范围内进行归一化。

执行程序

代码语言:javascript复制
def train(d_model, g_model, gan_model, dataset, n_epochs=100, n_batch=10):
    n_patch = d_model.output_shape[1]
    trainA, trainB = dataset
    bat_per_epo = int(len(trainA) / n_batch)
    n_steps = bat_per_epo * n_epochs
    for i in range(n_steps):
        [X_realA, X_realB], y_real = generate_real_samples(dataset, n_batch, n_patch)
        X_fakeB, y_fake = generate_fake_samples(g_model, X_realA, n_patch)
        d_loss1 = d_model.train_on_batch([X_realA, X_realB], y_real)
        d_loss2 = d_model.train_on_batch([X_realA, X_fakeB], y_fake)
        g_loss, _, _ = gan_model.train_on_batch(X_realA, [y_real, X_realB])
        print('>%d, d1[%.3f] d2[%.3f] g[%.3f]' % (i 1, d_loss1, d_loss2, g_loss))
        if (i 1) % 100 == 0:
            clear_output()

此功能训练GAN。这里要注意的关键是批次大小。该论文建议使用迷你们批处理(n_batch = 1),但经过一些测试,我们发现批处理大小为10会产生更好的结果。

代码语言:javascript复制
image_shape = (256,256,3)
d_model = define_discriminator()
g_model = define_generator()
gan_model = define_gan(g_model, d_model, image_shape)
train(d_model, g_model, gan_model, [X,y])

该脚本定义图像形状,并调用函数来构造GAN的不同部分。然后,它调用训练功能来训练模型。

结果

真实到语义:

尽管计算机生成的图像模糊,但可以正确对图像中的所有内容进行颜色编码。请记住,计算机无法看到真实图像的实际语义表示!我们认为图像是模糊的,因为真正的256 x 256图像不是很复杂,而且有许多可能使机器掉色的颜色。右边的图像(计算机生成)可以分割成正方形。如果计算这些平方,它将与卷积层的过滤器数量匹配!

语义到真实:

将语义数据转换为真实的街景图像时,我们担心这是不可能的,因为当转换为语义数据时,会丢失大量数据。例如,红色汽车和绿色汽车都变成蓝色,因为汽车是按蓝色像素分类的。这是一个明显的问题。可能具有不同颜色的对象根本没有出现,从而导致图像看起来只有一点点相似。看一下下面的图片:

结论

考虑到该网络仅训练了10个纪元,我们认为该项目是成功的,并且结果似乎很有希望。我们希望人们可以玩弄模型架构和超参数,以提高GAN创建的图像的质量。

下载1:OpenCV-Contrib扩展模块中文版教程

在「小白学视觉」公众号后台回复:扩展模块中文教程,即可下载全网第一份OpenCV扩展模块教程中文版,涵盖扩展模块安装、SFM算法、立体视觉、目标跟踪、生物视觉、超分辨率处理等二十多章内容。

下载2:Python视觉实战项目52讲

在「小白学视觉」公众号后台回复:Python视觉实战项目,即可下载包括图像分割、口罩检测、车道线检测、车辆计数、添加眼线、车牌识别、字符识别、情绪检测、文本内容提取、面部识别等31个视觉实战项目,助力快速学校计算机视觉。

下载3:OpenCV实战项目20讲

在「小白学视觉」公众号后台回复:OpenCV实战项目20讲,即可下载含有20个基于OpenCV实现20个实战项目,实现OpenCV学习进阶。

0 人点赞