在本文中,我们将学习如何训练卷积神经网络从彩色图像生成法线贴图。
推荐:用 NSDT编辑器 快速搭建可编程3D场景
1、数据和工具
我们正着手训练神经网络从彩色图像生成法线贴图。 我们将以“成对”的方式做到这一点。 这意味着我们将显示相应图像的网络对,在本例中为彩色图像和法线贴图。 我将使用 Pixar 128 和 Pixar 130 数据集,它们都包含 1993 年以来的 Pixar 纹理。
我对这两个数据集进行了一些预处理,以删除 Alpha 通道并将图像重新缩放为 256×256 像素。 这将帮助我们在 Python 中减少一些预处理。 我还将纹理组织到颜色和普通文件夹中,因此它们很容易加载到我们的代码中。
到目前为止,我一直提倡使用 Google Colab 来训练你的网络。 在处理小数据集和易于训练的拓扑时,Colab 还可以,但随着数据变得更大、拓扑变得更复杂,你最好在机器中训练网络,最好使用具有足够内存的 Nvidia GPU。
您始终可以在裸 Python 中运行代码,但如果您想要更优雅的界面(类似于 Google Colab 中的界面),您可以在计算机中安装 Jupyter Notebook。 我强烈推荐它。 笔记本可以轻松可视化数据和注释代码。 我建议您在常规 Python 安装(而不是 mayapy.exe)中安装它,这样您就不需要解决二进制兼容性问题。 您始终可以保存模型并在训练后将其加载到 Mayapy 中。
安装 Jupyter 非常简单:
pip install jupyter
运行 Jupyter 非常简单:
jupyter notebook
(if you have Python\Scripts in your PATH environment variable)
我们将在本教程中使用的其他库:Keras(带有你选择的后端)、Numpy、Matplotlib、Scikit-Image 和 PyThreeJS。 确保你已安装所有这些。
2、加载和预处理图像
此处显示的所有代码都可以作为 Jupyter Notebook 提供,不过,我将回顾一下最重要的部分。
首先,我们加载图像:
data_dir = '../data/pixar/'
color_imgs = skimage.io.ImageCollection(data_dir + '/color/*.tif')
normal_imgs = skimage.io.ImageCollection(data_dir + '/normal/*.tif')
然后我们将其转换为浮点类型。 请记住,这些是旧的 8 位和 16 位纹理。
color_imgs = skimage.img_as_float([color_imgs[x] for x in range(len(color_imgs))])
normal_imgs = skimage.img_as_float([normal_imgs[x] for x in range(len(normal_imgs))])
所有图像均为 256×256。 这样的图像大小的卷积网络对于你的显卡(2GB 或更少)来说可能太大。 在这种情况下,你可能需要稍微降低图像的分辨率。
此阶段的最后一步是将数据集拆分为训练集和测试集。 我们这样做是为了确保我们只在训练期间未见过的样本上测试网络的性能(否则我们就会作弊)。 我们使用单个随机种子随机化 color_imgs 和 normal_imgs,如下所示:
seed = np.random.randint(999)
np.random.seed(seed); np.random.shuffle(color_imgs)
np.random.seed(seed); np.random.shuffle(normal_imgs)
然后我们将每个集合的前 70% 和后 30% 分为训练集和测试集。 最好通过绘制训练集、测试集或两者的输入和输出来检查一切是否正常。 可以使用 Scikit-image 和 Matplotlib 来实现这一点。
skimage.io.imshow(skimage.img_as_ubyte(color_train_set[0]))
plt.show()
skimage.io.imshow(skimage.img_as_ubyte(normal_train_set[0]))
plt.show()
3、创建并训练生成式卷积神经网络
好的,现在定义模型。 我们将使用 Keras 模型 API。 让我们导入需要的一切。
from keras.models import Model
from keras.layers import Input, Conv2D, AveragePooling2D, UpSampling2D, BatchNormalization
from keras.optimizers import Adam
正如你可能已经猜到的那样,Conv2D 是卷积层。 它是 2D 的,因为卷积可以发生在 1D 和 3D 等其他维度空间中。
AveragePooling2D 是我们将用来降低特征图分辨率的池化类型。
我们将要构建的网络具有对称拓扑,很像自编码器(Autoencoder)。 我们将把彩色图像卷积到潜在空间中,并从中解卷积出法线贴图。 这就是我们导入 UpSampling2D 的原因。 它的作用与池化操作相反。
具有对称拓扑的卷积自编码器的视觉表示
我们还导入 BatchNormalization; 通过标准化神经网络每一层的激活来增强收敛性的一种行之有效的方法。 最后,我们加载 Adam 作为优化器。
以下是我们定义模型的方式:
input_img = Input(shape=(img_size, img_size, 3))
ng = Conv2D(15, (3, 3), activation='relu', padding='same', use_bias=False)(input_img)
ng = BatchNormalization()(ng)
ng = AveragePooling2D((4, 4), padding='same')(ng)
ng = Conv2D(30, (3, 3), activation='relu', padding='same', use_bias=False)(ng)
ng = BatchNormalization()(ng)
ng = UpSampling2D((4, 4))(ng)
ng = Conv2D(15, (3, 3), activation='relu', padding='same', use_bias=False)(ng)
ng = BatchNormalization()(ng)
ng = Conv2D(3, (3, 3), activation='sigmoid', padding='same')(ng)
normal_generator = Model(input_img, ng)
normal_generator.compile(Adam(amsgrad=True), loss='mse')
请注意,我们的 Conv2D 层的窗口大小为 3×3。 较大的窗口可能会提供更好的结果,但会使用更多的内存。 过滤器的数量也是如此。 谈到过滤器,我们随着特征图维数的减少而增加过滤器的数量,这是定义卷积模型的常见做法。
另外值得注意的是填充,设置为“相同”。 当我们这样做时,Keras 会对图像进行足够的填充,以便卷积不会减小其大小。 这对于保持输出图像的大小等于输入图像的大小非常重要。
我们消除了 BatchNormalization 之前的所有层的偏差。 任何神经网络层中的偏差都用于重新调整激活值的目的; BatchNormalization 操作通过标准化激活来执行类似的操作,因此无需同时启用这两种方法。 请注意,我们没有对最后一层进行归一化,我们还使用 sigmoid 激活而不是 ReLU; 这是因为我们想要强制像素值在 0 到 1 之间。
如果想查看网络的拓扑,可以调用normal_generator.summary()。 你应该得到这样的东西:
Keras 模型摘要,包含层和每层的参数(神经元)数量
我们使用 fit函数训练网络:
normal_generator.fit(color_train_set, normal_train_set,
validation_data = (color_test_set, normal_test_set),
batch_size=batch_size,
epochs=100)
4、结果可视化
为了更好地可视化网络训练的进度,我创建了这个函数:
def test_net_with_random_image():
idx = int(np.random.rand(1)*len(color_test_set))
y_legit = skimage.img_as_float(normal_test_set[idx]).reshape(1,img_size,img_size,3)
y_fake = generator.predict(skimage.img_as_float(color_test_set[idx]).reshape(1,img_size,img_size,3))
skimage.io.imshow(skimage.img_as_ubyte(y_legit).reshape(img_size,img_size,3))
plt.show()
skimage.io.imshow(skimage.img_as_ubyte(y_fake).reshape(img_size,img_size,3))
plt.show()
通过使用它,我们可以将真实的法线贴图与给定相应彩色图像的网络输出进行比较。 正如你所期望的,随机启动且未经训练的网络提供了高熵结果:
不过训练 200 个 epoch 后,再次调用测试函数时会得到以下结果:
相当令人印象深刻!
另一种可视化结果的方法是使用 PyThreeJS。 你可能知道 ThreeJS,即 WebGL 框架。 PyThreeJS 是一个包,可让你调用 ThreeJS 并在 Jupyter Notebook 中使用它,这非常适合快速可视化 3D 内容。
使用 PyThreeJS,我们可以加载彩色图像和合成法线贴图到纹理,然后可以在我们想要的任何材质中使用。 通过创建 DataTexture 对象并将 Numpy 数组作为“数据”传递,可以直接从我们经常使用的 Numpy 数组中声明纹理。
idx = int(np.random.rand(1)*len(color_test_set))
x = color_test_set[idx]
y = normal_generator.predict(skimage.img_as_float(color_test_set[idx]).reshape(1,img_size,img_size,3)).reshape(img_size,img_size,3)
color_tex = DataTexture(
data=x,
format="RGBFormat",
type="FloatType",
)
normal_tex = DataTexture(
data=y,
format="RGBFormat",
type="FloatType",
)
然后,我们用简单的材质、相机和灯光创建一个球。
5、结束语
在本教程中,我们学习了如何创建能够进行成对图像翻译的卷积神经网络。 在此过程中,我们学习了如何在 Keras 中创建和连接卷积层,以及如何使用 Scikit-image 可视化 2D 数据和使用 PyThreeJS 可视化 3D 数据。
这些技能对于为不同类型的应用构建和测试许多其他神经网络拓扑至关重要。 卷积网络可用于时间序列、3d 体素和几何。 PyThreeJS 可用于可视化图元、显式网格和铰接图形。 我们正在建立坚实的基础,以便尽快解决更令人兴奋的模型。
原文链接:用CNN生成法线贴图 — BimAnt