基于GAN的自动驾驶汽车语义分割
共 10257字,需浏览 21分钟
·
2021-03-22 10:19
点击上方“小白学视觉”,选择加"星标"或“置顶”
重磅干货,第一时间送达
语义分割是计算机视觉中的关键概念之一,语义分割允许计算机通过按类型对图像中的对象进行颜色编码。GAN建立在基于真实内容的基础上复制和生成原始内容的概念上,这使它们适合于在街景图像上进行语义分割的任务,不同部分的分割使在环境中导航的代理能够适当地发挥作用。
我们从一个kaggle数据集获取数据,街景和分割的图像被配对在一起。这意味着为了构建数据集,必须将每个图像分成两部分,以分割每个实例的语义图像和街景图像。
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)
该脚本将每个图像裁剪为两个,并记录像素值和原始图像。原始图像也被记录下来,因此以后无需再进行显示。
import numpy as np
semantic = np.array(semantic)
real = np.array(real)
X = real
y = semantic
将两个列表都转换为numpy数组后,可以直接定义x和y值。实际上,根据目标,你们可以切换x和y值以控制模型的输出。在这种情况下,我们想将真实图像转换为语义图像。但是,稍后我们将尝试训练GAN将语义数据转换为真实数据。
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体系结构,因为它可以将某些层链接在一起。
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)
'binary_crossentropy', optimizer=opt, loss_weights=[0.5]) =
return model
该判别器是pix2pix GAN论文使用的模型的keras实现。使用泄漏的Relu而不是正常的Relu是为了使负值仍然被考虑在内。这增加了收敛速度。鉴别器执行二进制分类,因此在最后一层使用S形,并使用二进制交叉熵作为损失函数。
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
生成器包括多次对初始数据进行编码,直到获得原始图像的特征图为止。然后将此特征图像解码,直到获得完整分辨率的图像为止。这意味着生成器中的大多数层只是编码器和解码器块。在对编码器解码器块进行了精心设计之后,为了构建生成器,没有更多的工作要做。
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值。
def define_gan(g_model, d_model, image_shape):
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)
['binary_crossentropy', 'mse'], optimizer=opt) =
return model
将两个模型连接在一起即可得到完整的GAN。发生器的输出直接馈入鉴别器。
def generate_real_samples(dataset, n_samples, patch_shape):
trainB = dataset
ix = randint(0, trainA.shape[0], n_samples)
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)范围内进行归一化。
def train(d_model, g_model, gan_model, dataset, n_epochs=100, n_batch=10):
n_patch = d_model.output_shape[1]
trainB = dataset
bat_per_epo = int(len(trainA) / n_batch)
n_steps = bat_per_epo * n_epochs
for i in range(n_steps):
X_realB], y_real = generate_real_samples(dataset, n_batch, n_patch)
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)
_, _ = gan_model.train_on_batch(X_realA, [y_real, X_realB])
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会产生更好的结果。
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图像不是很复杂,而且有许多可能使机器掉色的颜色。右边的图像(计算机生成)可以分割成正方形。如果计算这些平方,它将与卷积层的过滤器数量匹配!
语义到真实:
将语义数据转换为真实的街景图像时,我们担心这是不可能的,因为当转换为语义数据时,会丢失大量数据。例如,红色汽车和绿色汽车都变成蓝色,因为汽车是按蓝色像素分类的。这是一个明显的问题。可能具有不同颜色的对象根本没有出现,从而导致图像看起来只有一点点相似。看一下下面的图片: