本文尝试在Houdini中测试和实现FrankenGan的(部分)功能
1. FrankenGAN
首先介绍一下这篇文章
发表在刚刚结束的Siggraph Asia 2018.
基本思路是:用GAN训练神经网络,给粗糙的建筑体块模型添加模型和贴图细节。

源码在Github上有,
前端是java写的:chordatlas,显示和资源管理等任务
后端是pytorch:bikegan,完成GAN生成的任务
作者将添加细节这个操作分成了很多步,每一步都训练了一个bicycleGAN完成任务
首先会对立面和屋顶进行标注,区分语义。之后通过标注生成贴图。最后用超分辨率提高分辨率。总共9个GAN!
使用bikegan那个工程也会自动下载
生成label这一步有一个特殊操作,为了让输入的图片有尺度信息,输入多加了一个channel,是立面上每一点到立面边缘的距离

这一步在bikegan中是自动生成的

具体来说这九个GAN做了空白->标注,标注->图片的任务
当然了,GAN标注的label并不那么可靠,还是需要一定的正则化

立面的训练用了CMP数据集,大概3000张标注图片。屋顶用了600张,窗框用了1400张,都是人肉标注的。
(其实还好,一千张的话一个人一星期就能标注出来-笔者注)
最终的效果:

看上去效果还不错。
最后评论一下:
草模怎么来的
笔者当然是用他自己的算法了,在另一篇BigSUR的论文里讲了,大概是从Lidar等扫描数据正则化得来的。
另一种方法是传统Procedural的方法,比如CityEngine生成体块模型。
为什么非要用GAN?
给一个空白的立面,传统Procedural的方法对立面进行语义划分也很好啊。用GAN最后也得正则化。笔者倒认为用传统Procedural可能更快效果更好,省去了找数据集和训练的时间。
贴图生成为什么非要用GAN?
当然在游戏里这不太可能,肯定要考虑模型和贴图的共用。就目前,游戏里肯定还是自底向上模块化拼接比较好。只有在对资源不敏感的比如离线环境中,这倒是可能可以快速生成贴图。毕竟如果只作为背景资产,精度就无所谓了,这个生成的肯定比纯色模型好。
2. Houdini中实现
笔者做的很初步,只是一个技术验证,是在Houdini中把原作其中一个给立面做标注的GAN整合了进去。也就是原作empty2windows_f009v2_400这个预训练的模型。
环境:
1 2 3 4 |
Ubuntu 18.04 LTS Houdini 16.5.634 Pytorch 0.4.0 Opencv-python 3.2 ->4.0会导致houdini闪退 |
2.1. 后端改造
把原作的bikegan封装为houdini可用的函数。
原作的test_interactive.py是主入口,做的事情是部署了多个监控进程,每个GAN如果有输入的话,就立即执行那个GAN。
我们要找的是这个函数。
1 2 3 4 |
Interactive("facade labels", "empty2windows_f009v2_400", dataset_mode='multi', fit_boxes=(blank_classes, fit_blank_labels), empty_condition=True, metrics_condition=True, imgpos_condition=True, metrics_mask_color=[0, 0, 255]) |
看一下代码,初始化载入模型,然后开一个进程等待输入,有输入了就运行模型,执行RunG这个函数
facade2label会监测input/facade labels/val这个文件夹,会不会出现一个叫“go”的文件,有了就执行。
1 2 3 4 |
go = os.path.abspath(os.path.join (event.src_path, os.pardir, "go")) if not os.path.isfile(go): return |
运行模型的函数很简单,可以简化成
1 2 3 4 5 6 7 |
data_loader = CreateDataLoader(self.opt) dataset = data_loader.load_data() for i, data in enumerate(dataset): self.model.set_input(data) _, real_A, fake_B, real_B, _ = self.model.test_simple(z, encode_real_B=False) |
从目录文件夹载入图片,然后逐一喂给模型。
这就很简单了,我们知道Houdini Chop可以把图片转化为numpy的ndarray,那我们只需封装一下facade2label,让它输入一个ndarray,也输出ndarray。
择一下原作的代码稍加改动就行。
需要注意的是,原作data.compute_metrics中有一个compute_metrics函数,计算上文提到的里面距离信息。它会输入一个尺度参数,原作代码中通过文件名的@后面的数据读出,详见multi_dataset.py
1 2 3 4 5 |
if '@' in os.path.basename(os.path.splitext(self.AB_paths[index])[0]): unit_size = float(os.path.basename(os.path.splitext(self.AB_paths[index])[0]).split('@')[1].split('_')[0]) # in fraction of the image height unit_size *= metrics_mask.shape[0] # in pixels else: unit_size = metrics_mask.shape[0] |
这个参数我们是需要的,让houdini提供给pytorch后端
最后我们把它封装成facade2label函数,测试代码
1 2 3 4 5 6 7 |
if __name__ == '__main__': AB = Image.open("facade0.png").convert('RGB') AB = AB.resize((256,256), Image.BICUBIC) imgarray = np.array(AB, dtype=np.float32) imgarray /= 255.0 result = facade2label(imgarray, 0.2) save_image(result, "output.png") |
terminal运行能生成结果就是完成了。
2.2. CHOP编写
首先,要从上文写好的facade2label.py路径启动houdini,这样好导入这个库。
建一个operator,选python,类型是composite filter

代码里只需要重写一些Cook函数就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from facade2label import facade2label def cook(cop_node, plane, resolution): input_cop = cop_node.inputs()[0] pixels = np.array(input_cop.allPixels("C"),dtype=np.float32) result = np.reshape(pixels, (256,256,3)) input_geo_node = hou.node("../input_geo") inputgeo = hou.node(input_cop.parm("soppath").eval()) maxsize = inputgeo.geometry().findGlobalAttrib("maxaxis").defaultValue() result = facade2label(result, 3 / maxsize) arraypxs = array.array("f", result.flatten()) cop_node.setPixelsOfCookingPlane(arraypxs) |
这里因为chop的上一个节点是Geometry节点,所以直接去找输入的一个参数就能找到那个sop

其实核心就是这样。
之后做的无非就是一些收尾,比如把这个颜色映射到面片的頂点色上,窗户抠出来正则化


好咯基本就是这样,笔者找了一块地,下载OSM,用它的地块试了一下。
立面没那么理想,凑活能看吧。

参考资料
