Import von mhx2 Modellen in Blender und Erstellung von Lerndaten

Im vorherigen Beitrag habe ich erläutert wie zufällige Modelle im großen Stil mit Hilfe von MakeHuman erstellt werden können. In diesem Artikel möchte ich kurz zeigen wie Modelle in Blender importiert werden können und anschließend wie mit einem Script Lerndaten für ein Neuronales Netz erstellt werden können.

Import von mhx2 Modellen

Modellimport in Blender
Das importierte Modell aus MakeHuman
Zunächst muß das nötige Plugin für Blender hier herruntergeladen werden. Die Anleitung zur Aktivierung ist sehr anschaulich auf der Seite des Entwicklers des Plugins erklärt. Ein Modell kann nun über File -> Import -> MakeHuman (.mhx2) importiert werden. Das in MakeHuman erstellte Modell wird dann in der Mitte der Szene erscheinen. In Blender kann das Modell dann weiter wie gewünscht bearbeitet werden.

Erstellung von Lerndaten

Um nun Input- und Output-Werte für ein neuronales Netz zu generieren muss zunächst geklärt werden wie die Daten beschaffen sein sollen. Das Ziel ist es möglichst viel Information in möglichst wenig Ausgängen und Eingängen unterzubringen. Ein späterer Artikel wird sich detailliert mit dieser Problematik beschäftigern, der einfachheit halber wird für die ersten Modelle ein Tiefenbild generiert das 1:1 auf ein Eingangs-Bild passt. Beispiele sind in der unteren Galerie aufgeführt.

Die Input- und Output-Daten werden im Bmp-Format abgespeichert dieses ist zwar speicherintensiv aber dafür schneller lesbar. Die Bilder haben eine Auflösung von 64×64 Pixel das heisst für die Anzahl der Eingänge 64 x 64 x 3 = 12288 (Länge x Breite x rot/grün/blau), die Anzahl der Ausgänge wird bei 64 x 64 = 4096 liegen da nur Informationen über die Entfernung der Pixel vorhanden sind.

Scripting in Blender

Die korrespondierenden Bilder wurden mit Hilfe eines Scripts (hier herunterzuladen) erstellt welches für die nicht selbsterklärenden Teile im Detail erläutert wird.

def f(x):
    return (e**-((x-0.5)*1.6))/8+1/8

Zunächst wurde eine Funktion definiert die die Kopfgröße im Verhältnis zur Körpergröße approximiert. Diese Funktion wird benötigt um die richtige Position für die Kamera zu finden die das Bild schießen wird. Diese Exponentialfunktion hat sich nach mehrfachee Anpassungen als praktikable erwiesen.

Das Verhältnis der Größes des Kopfes zur gesamten Körpergröße
Position der Kamera

Die genauere Berechnung der Kameraposition is anhand der Illustration in rot genauer aufgeschlüsselt. Mit h = Größe des Modells, Zk = Höhe der Kamera, α = Kamerawinkel, a = Kopfgröße/2 und Yk der Abstand der Kamera. Der Kamerawinkel wurde berechnet über die Entfernung, bei der halben Höhe des Modells, die notwendig ist damit ein Modell vollständig von der Kamera aufgenommen werden kann. Bei einem 1,6m großem Modell musste die Kamera 3,2m entfernt sein. So ergibt sich α zu:

\alpha=atan\bigg(\frac{0.8}{3.2}\bigg)=14^{\circ}

Die weiteren Parameter wurden errechnet zu:

a=\frac{f(h)}{2}\cdot h
Z_k=h-\frac{f(h)}{2}\cdot h = h - a
Y_k=\frac{a}{tan(\alpha)}

def importModel(filename):
    bpy.ops.import_scene.makehuman_mhx2(filepath='D:/makehumanModels/' + filename + '.mhx2')

    body = bpy.data.objects[filename + ':Body']
    eyes = bpy.data.objects[filename + ':High-poly']

    body.scale = Vector((0.1,0.1,0.1)) # Körper richtig skallieren
    eyes.scale = Vector((0.1,0.1,0.1)) # Augen richtig skallieren

    h = body.dimensions[2] # Größe abtragen (Z-Koordinate)
    a = f(h) * h /2 # Kopfgröße/2

    alpha = 14 * pi / 180 # Kamerawinkel in rad

    z_cam = h - a  # Kamerahöhe
    y_cam = - a / tan(alpha)


    setCam(y_cam,z_cam)

    light = bpy.data.objects['Lamp']
    light.location = Vector((0,-1,1.5))
    light.rotation_euler = Euler((pi/2,0,0))

Mit der Methode importModel() wird das Modell in die Szene geladen. Es werden außerdem mit setCam() die Orientierung und Position der Kamera eingestellt damit ein gutes Bild vom Kopf des Modells geschossen werden kann.

def generateDepthmapNodes():
    
    if not bpy.context.scene.use_nodes:
        bpy.context.scene.use_nodes = True
        
    nodesField = bpy.context.scene.node_tree
    for currentNode in nodesField.nodes:
        nodesField.nodes.remove(currentNode)
        
    renderNode = nodesField.nodes.new(type = 'CompositorNodeRLayers')
    renderNode.location = (0, 0)
    compositeNode = nodesField.nodes.new(type = 'CompositorNodeComposite')
    compositeNode.location = (200, 0)
    nodesField.links.new(renderNode.outputs['Image'], compositeNode.inputs['Image'])
    normalizeNode = nodesField.nodes.new(type = 'CompositorNodeNormalize')
    normalizeNode.location = (200, -200)
    nodesField.links.new(renderNode.outputs['Z'], normalizeNode.inputs[0])
    outputNode = nodesField.nodes.new(type = 'CompositorNodeOutputFile')
    outputNode.name = 'FileOutput'
    outputNode.location = (400, -100)
    nodesField.links.new(normalizeNode.outputs[0], outputNode.inputs['Image'])
    outputNode.base_path = 'D:\\makehumanModels\\output\\'
    outputNode.file_slots[0].format.file_format = 'BMP'

Diese Methode wird einmalig aufgerufen und erstellt im Node-Editor die notwendigen Knoten und Verbindungen um aus den Bilddaten des Rendings das Tiefenbild zu erstellen. Die Z-Buffer Werte werden zusaätzlich noch normalisiert um eine höhere Auflösung zu bekommen.

Nodes-Pipeline für die Erstellung der Output-Daten

Die Methoden generateInput() und generateOutput() nehmen einige Einstellungen vor und erstellen dann das jeweilige Bild. Der gesamte Programmablauf ist dann wie folgt:

generateDepthmapNodes()  
 
for x in range(10):
    
    filename = 'Model_'+ str(x)
    clearScene()
    importModel(filename)
    generateInput(filename)
    generateOutput(filename)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.