Zum Inhalt

Einlesen des Würfels

Das Ziel dieser Unteraufgabe ist es, den physischen Würfel mit der Kamera im Robotik-Labor so zu erfassen, dass er in der RoboDK-Simulation als 3D-Würfel angezeigt werden kann und, dass er genutzt werden kann, um die Lösung des Würfels zu berechnen.

Zeigen der Seiten

Zum Einlesen des Würfels werden Bildverarbeitungsoperationen genutzt: Alle sechs Seiten des Würfels werden von dem KUKA in die Kamera gehalten, die von jeder Seite ein Bild aufnimmt. Auf jedem der Bilder werden die Farben der einzelnen Flächen erkannt, wobei jeweils eine Seite des Würfels als np.ndarray der Größe 3x3 von TileColors zwischengespeichert werden. Die Anordnung der Werte im Array entspricht der Anordnung auf dem Würfel in der gegebenen Orientierung im Bild (siehe Abbildung 1).

Abbildung 1. Links: Input der Bildverarbeitung. Rechts: Gewünschter Output der Bildverarbeitung (vereinfachte Darstellung)

Um den kompletten Würfel aus den sechs Seiten rekonstruieren zu können, müssen die relativen Bewegungen zwischen den aufgenommenen Bildern bekannt sein, was in unserem Fall gegeben ist, da die Seiten des Würfels immer in einer festen Reihenfolge und relativen Orientierung gezeigt werden.

Definierte Targets

In der RoboDK-Szene wurden drei Ziele definiert, die nacheinander angefahren werden, um drei Seiten des Würfels zu zeigen. Um die verbleibenden drei Seiten des Würfels zeigen zu können, muss der Würfel zwischendurch in der Halterung abgelegt werden und umgegriffen werden. Anschließend werden erneut die drei Ziele angefahren. Bei den drei Zielen handelt es sich um show_y-, show_z+ und show_y+, die jeweils nach der Achse des Tool-Koordinatensystems benannt ist, die in Richtung der Kamera zeigt (siehe Abbildung 2).

Abbildung 2. Fest definierte Joint-Targets, die zum Zeigen der Würfelseiten benutzt werden. Von links nach rechts: show_y-, show_z+, show_y+

Die Ziele sind in RoboDK als Joint-Targets definiert, was bedeutet, dass die Auslenkung der Roboterachsen gespeichert wird und nicht die Pose im kartesischen Koordinatensystems bezüglich des Eltern-Koordinatensystems. Da letztere redundant sein kann, wenn die inverse Kinematik mehrere Lösungen hervorbringt, kann man nicht davon ausgehen, dass die Achsen wie gewollt gedreht sind. Dies ist problematisch, da beim Anfahren der kommenden Ziele die Grenze einer Achse erreicht werden kann, weshalb eine komplizierte Route gewählt wird, oder, im schlimmsten Fall, keine kollisionsfreie Route gefunden werden kann. Das Ansteuern von Joint-Targets ist unter Joint Targets genauer beschrieben.

Ansteuerung der Kamera

Die IDS uEye-Kamera, die in der Robotik verbaut ist, kann über das Python-Paket pyueye angesteuert werden. Leider ist dieses Paket nicht dokumentiert, sodass ausschließlich anhand der Namen der aufrufbaren Funktionen geschätzt werden kann, was diese tun. Daher basiert die aktuelle Implementierung auf dem Beispielprogramm SimpleLive_Pyueye_OpenCV.py, das uns von Hermann bereitgestellt wurde.

Alternative Bildquellen

Es können verschiedene Bildquellen für die Erfassung eines Würfels genutzt werden (definiert in core.cv.ImageSource): DirImageSource, RoboDKImageSource und PyUEyeImageSource. Jede Bildquelle ist eine Unterklasse der abstrakten Klasse ImageSource und stellt die Funktion get_image() bereit, die unabhängig von der konkreten Klasse ein Bild liefert. Anschließend kann auf die gewonnenen Bilder die Bildverarbeitung angewandt werden, um die gezeigten Farben zu extrahieren. So ist das Erhalten eines Bildes abstrahiert von der konkreten Implementierung. Im folgenden werden die drei konkreten Bildquellen detaillierter beschrieben:

DirImageSource

Diese Bildquelle basiert auf einer Menge von vorab aufgenommenen Bildern, die aus einem gegebenen Verzeichnis geladen werden. Dies eignet sich besonders gut zum Testen der Bildverarbeitungsalgorithmen, da die Bilder sich nicht bei jedem Durchlauf verändern (Reproduzierbarkeit der Ergebnisse) und war darüber hinaus sehr nützlich, da wir nicht ständig am Roboter arbeiten konnten. Die Bilder werden über OpenCV eingelesen und in einer Liste gespeichert und bei jedem Aufruf von get_image() wird das aktuell indizierte Bild zurückgegeben und der Zähler um eins erhöht. Im Unterordner images des GitHub-Repositories gibt es zu Testzwecken einige Ordner, die jeweils sechs Bilder mit den sechs Seiten des Würfels beinhalten.

RoboDKImageSource

Diese Bildquelle entspricht einer virtuellen Kamera in der RoboDK-Simulation, die ungefähr dort am Käfig angebracht ist, wo sich auch die Kamera im echten Robotik-Labor befindet. Mithilfe dieser Bildquelle kann der komplette Prozess des Würfeleinlesens, Berechnung der Lösung und Pfadplanung auch ausschließlich in der Simulation stattfinden, ohne Übertragung von Informationen aus der Realwelt. Auf diese gerenderten Bilder kann ebenso die Bildverarbeitung angewandt werden. Die künstliche Kamera hat eine Fokuslänge von 6 und einen Öffnungswinkel von 16°, da die Parameter der uEye-Kamera nicht ermittelt werden konnten, und diese Werte ein optisch ähnliches Bild erzeugen. Ein Beispiel zeigt Abbildung 3. Da die RoboDK-API es nicht erlaubt, ein solches Kamerabild direkt in den Arbeitsspeicher abzulegen, sondern nur erlaubt, ein Bild als Datei zu speichern, wird das entstandene Bild in ein temporäres Verzeichnis geschrieben, mit OpenCV gelesen und anschließend gelöscht (siehe RoboDKImageSource.get_image()).

Abbildung 3. Ein durch RoboDKImageSource generiertes Bild.

PyUEyeImageSource

Dies ist die eigentliche, physische Bildquelle, die zur Kommunikation mit der Kamera in der Robotik verwendet wird. Wie bei den anderen Bildquellen werden hier nur Bilder extrahiert, wenn eine Seite des Würfels der Kamera zugewandt ist. Hier gab es einige Probleme bezüglich einer konsistenten Belichtung, da der Kameratreiber über keine sinnvolle Autobelichtung verfügt. Dies hatte zur Folge, dass oft Helligkeitsunterschiede aufgrund von leichten Wetterveränderungen direkt in dem Kamerabild sichtbar wurden und somit die Bildverarbeitung ständig mit leicht unterschiedlichen Farben arbeiten musste, was fehleranfällig war. In einem stabileren Bildverarbeitungsansatz (siehe Clustering-Ansatz) konnten diese Belichtungsprobleme ausreichend kompensiert werden. Um für eine ausreichende Belichtung zu sorgen, sollte die rechte Lampe oben im Käfig angeschaltet werden. Damit es aufgrund der hochreflektierenden Oberflächen des Würfels nicht zu Spiegelungen kommt, wird der Würfel leicht schräg ins Bild gehalten und lediglich die rechte der beiden Lampen verwendet (siehe Aufbau).

Abbildung 4. Ein durch die PyUEyeImageSource generiertes Bild.


Bildverarbeitung

Zunächst wurde ein einfacher Ansatz zur Erkennung und Zuweisung der Farben angewandt. Da dieser in einigen Situationen fehleranfällig war, wurde eine weitere Variante entwickelt, die sich als robuster herausgestellt hat.

Einfacher Ansatz

Abbildung 5. Darstellung des HSV-Farbraums

Im einfachen Ansatz wurde das gesamte Bild in den HSV-Farbraum konvertiert, da dieser sich zur Extraktion von Farben gut eignet -- die Farbtöne der Pixel werden in dem Hue-Wert (H), die Sättigung in dem Saturation-Wert (S) und die Helligkeit in dem Value-Wert (V) angegeben (siehe Abbildung 5). Für jede der gesuchten Farben blau, rot, grün, orange, weiß und gelb wird demnach ein Bereich im HSV-Modell angegeben, welcher derjenigen Farbe entspricht (siehe find_colours() und mask_colour()) -- hierfür wird die OpenCV-Funktion cv2.inRange() benutzt. Pro untersuchter Farbe entsteht eine binäre Bildmaske, welche im Bereich liegende Pixel als weiße Pixel angibt. Auf diesem Binärbild lassen sich mittels der OpenCV-Funktion cv2.findContours() gut Konturen finden, die weiter verarbeitet werden können. Da nur nach Quadraten gesucht wird, werden sämtliche Konturen herausgefiltert, die nicht die Charakteristika eines Quadrats aufweisen (Seitenverhältnis nahe 1, Fläche ist ähnlich groß wie die des kleinsten umschließenden Rechtecks -- siehe is_square_contour()) und nicht in dem erwarteten Größenbereich liegen.

Abbildung 6. Links: Bild, auf das die Bildverarbeitung angewandt wird. Rechts: Visualisierung der erkannten Farben als Quadrat-ähnliche Konturen im Bild. Graue Konturen sind aufgrund der Form verworfene Konturen

Es kann sein, dass einige Plättchen der gezeigten Seite aufgrund von Helligkeitsverläufen nicht richtig erkannt werden (wie die weiße Seite in Abbildung 6). Für diesen Fall wird eine achsparallele Box (Bounding Box) generiert, die alle akzeptierten Konturen beinhaltet. Mit dem Wissen, dass in jeder Spalte und Zeile der Würfelseite 3 Flächen gesucht werden, kann in der achsparallelen Box in genau diesen Punkten nach der Farbe gesucht werden (siehe get_colour()). Anhand der achsparallelen Box kann auch die Zuordnung für das resultierende 3x3-Array stattfinden.

Clustering-Ansatz

Da die Kamera im Robotik-Labor über keine richtige Autobelichtung verfügt, kam es bei verschiedenen Tests am Roboter zu sehr unterschiedlich belichteten Ergebnissen (siehe Abbildung 7). In vielen Fällen konnten die Farben richtig extrahiert werden, aber bei ähnlichen Farben, wie z.B. rot und orange oder weiß und gelb kam es manchmal zu Fehlerkennungen, die eine Rekonstruktion des echten Würfels fehlschlagen ließ. Es war schwierig, eine globale Eingrenzung der Werte im HSV-Farbraum zu finden, sodass alle Farben in vielen verschiedenen Belichtungsverhältnissen gut erkannt werden.

Abbildung 7. Unterschiedliche Ergebnisse bei unterschiedlicher Belichtung der Szene.

Hier entstand die Idee, dass ein Algorithmus die Farben des Würfels zunächst sammelt und erst nachdem alle sechs Seiten angeschaut wurden, eine Unterteilung in die gesuchten Farben zu machen. Vorher passierte dies auf jedem Bild direkt, ohne alle Seiten (bzw. möglicherweise alle Farben) gesehen zu haben -- anhand von Erfahrungswerten. Mit den gewonnenen, gesammelten Farben kann ein Clustering-Verfahren genutzt werden, um Gruppen von häufig vorkommenden Farben zu finden. Hierfür eignet sich das K-Means-Clustering gut, weil es auf ungelabelten Daten arbeitet, wie sie in diesem Fall vorliegen. Außerdem ist die Anzahl der gesuchten Cluster genau bekannt. Eine Visualisierung von gesammelten Farben, ihre Darstellung im HSV-Raum und die gefundenen Cluster-Zentren zeigt Abbildung 8. Für das K-Means-Clustering wurde die Python-Schnittstelle der Bibliothek scikit-learn, insbesondere die Klasse sklearn.cluster.KMeans genutzt. Die Zentren konnten mithilfe des Parameters init initialisiert werden (siehe auch build_clusters()). Es wurden zu erwartende Werte im HSV-Raum für die Farben rot, grün, blau, orange, gelb, weiß und schwarz als initiale Zentren genutzt, wodurch auch die Zuweisung von Cluster zu Farbe gemacht werden kann. Die Kategorie Schwarz wurde für maskierte Pixel benutzt und ist nur ein Auffangbehälter für diese und ähnliche Farben.

Abbildung 8. Darstellung von Pixeln, die zu lokalisierten Flächen des Zauberwürfels gehören, im HSV-Raum mit berechneten Cluster-Zentren

Um die Farben der Würfelflächen überhaupt extrahieren zu können, wird erneut Bildverarbeitung angewendet: Es werden wieder Quadrat-ähnliche Konturen gesucht. Hierfür wird zunächst der bilaterale Filter angewendet -- eine kantenerhaltende Glättung --, um die Plättchen möglichst gut erkennbar zu machen (cv2.bilateralFilter()). Anschließend wird eine Canny-Kantendetektion (cv2.Canny()) angewendet, um ein Kanten-Binärbild zu erzeugen, worauf wiederum Konturen detektiert werden können (hier kommt erneut OpenCV's cv2.findContours() zum Zuge). Diese Konturen können -- wie vorher -- anhand ihrer Größe und Form überprüft werden, ob sie geeignete Kandidaten sind (is_square_contour()). Aus allen Kandidaten wird nun eine binäre Maske generiert, die alle Quadrat-ähnlichen Konturen im Bild maskiert. Diese Maske wird auf das ursprüngliche Bild angewandt, es wird nur der relevante Bereich anhand der umschließenden Box ausgeschnitten und das Ergebnis wird skaliert, um die Anzahl der Proben zu reduzieren. Die einzelnen Schritte sind in Abbildung 9 dargestellt.

Abbildung 9. Darstellung der einzelnen Bildverarbeitungsschritte, um Pixelwerte für das Clustering zu sammeln. Oben links: Ursprungsbild. Oben rechts: Nach Anwendung des Bilateralfilters. Unten links: Nach Anwendung der Canny-Kantenextraktion und Hervorhebung Quadrat-ähnlicher Konturen. Unten rechts: Maskiertes Originalbild (hier mit weniger Sättigung und Helligkeit dargestellt)

In Abbildung 10 sind die Cluster-Diagramme für unterschiedlich belichtete Bilder, sowie weiße und schwarze Würfel gezeigt. Hier wird einerseits deutlich, wie unterschiedlich die verschiedenen Szenarien sind und andererseits, wie gut sich der Clustering-Ansatz eignet.

Abbildung 10: Der Clustering-Ansatz mit verschiedenen Belichtungen und Würfeln. Oben links: Schwarzer Würfel bei dunkler Belichtung. Oben rechts: Weißer Würfel bei normaler Belichtung. Unten links: Schwarzer Würfel bei dunkler Belichtung. Unten rechts: Schwarzer Würfel bei sehr heller Belichtung

Es ist auch etwas Zeit in das Finden der richtigen Darstellung der Farben für das Clustering Verfahren geflossen. Zunächst wurde eine lineare Darstellung des HSV-Farbraums gewählt, so wie die Daten nach der Umwandlung vom BGR-Raum in den HSV-Raum durch OpenCV's Funktion cv2.cvtColor() vorliegen (H, S, und V-Kanal werden als X, Y und Z für Clustering und Darstellung verwendet). Der H-Kanal liegt im Bereich von 0 bis 180 (0° bis 360°) vor, der S- und V-Kanal jeweils von 0 bis 255. Hierbei gab es anfänglich drei Probleme: Einerseits liegt rot, eine der gesuchten Farben, genau um den Bereich 0° bzw. 360°, da die Skala eigentlich an dieser Stelle neu beginnt. In einer linearen Darstellung sind die Werte von 360° und 0° jedoch am komplett anderen Ende der Skala, was ein Clustering für dieselbe Klasse unmöglich macht. Daher wurde zuvor das Bild im BGR-Raum invertiert und anschließend in den HSV-Raum konvertiert, um rot als cyan darstellen zu können, welches bei ca. 180° im HSV-Raum liegt, was ein Clustering dort ermöglicht. Die anderen Farben wurden durch die Invertierung ebenfalls im Farbton verschoben, was aber nicht die Folge hatte, dass eine Farbe die Skala überschreitet.

Ein zweites Problem war, dass einige Farben relativ dicht beieinander lagen (z.B. rot und orange) und somit beispielsweise Ausreißer von orange als rot klassifiziert wurden, weil das rote Cluster-Zentrum näher lag. Hierfür wurde eine Skalierung des H-Kanals um das 10-fache vorgenommen, um so diesen Kanal zu strecken und ein "Überschwappen" in andere Farben schwieriger zu machen. Effektiv ist somit der Zugehörigkeitsbereich eines Cluster-Zentrums nicht mehr kreisförmig, sondern ellipsenförmig, wenn später die 10-fache Skalierung wieder rückgängig gemacht wird. Ein ellipsenförmiger Zugehörigkeitsbereich war für die gesammelten Daten deutlich passender.

Das dritte Problem war, dass die "Farbe" weiß nicht als Punkt, sondern als Teil einer Ebene dargestellt wurde, worauf ein Clustering auch schwierig anzuwenden ist. Als Abhilfe wurde für weiß kein Clustering angewendet, sondern es direkt anhand des Helligkeitskanals überprüft. Abbildung 11 zeigt ein Clustering-Diagramm im linearen Zustand, wobei die Farben invertiert wurden und der Hue-Kanal 10-fach gestreckt wurde.

Abbildung 11. Eine frühe Version der Clustering-Darstellung. Die Cluster-Zentren sind als blaue Quadrate dargestellt

Schließlich kam die Idee, den HSV-Raum, wie typisch, zylindrisch darzustellen. Dies konnte folgendermaßen berechnet werden:

wobei für den Hue-Kanal, für den Saturation-Kanal und und für die X- bzw. Y-Koordinate im Diagramm und für das Clustering stehen. Der Value-Kanal wurde direkt als Z-Koordinate verwendet. Abbildung 12 (links) zeigt das Ergebnis nach der Umrechnung und dem Clustering. Eine weitere Verbesserung konnte durch die Nutzung der Nearest-Neighbor-Interpolation (statt bilinearer Interpolation, die bei OpenCV standardmäßig genutzt wird) erreicht werden, da mit ihr bei der Skalierung der maskierten Werte keine Mischfarben erzeugt werden. Abbildung 13 (rechts) zeigt dieselben Ausgangsdaten wie links, wenn eine bilineare Interpolation genutzt wird. Allerdings hat diese Verbesserung kaum Änderungen am Clustering-Verfahren bzw. dem Finden von Cluster-Zentren ergeben.

Abbildung 13. Darstellung der maskierten Pixelfarben im HSV-Raum mit Nearest-Neighbor-Interpolation (links) und bilinearer Interpolation (rechts)


Übertragung in 3D-Repräsentation

Zu erkennen, welche Farben auf den Seiten des Würfels abgebildet sind, ist die eine Sache, dieses Wissen zu einem Würfel in 3D-Repräsentation zusammenzufassen die nächste. Hierfür stellte sich für uns zunächst die Frage, wie die gefundenen Seiten zueinander orientiert sind. Durch die statische Methode, die alle Seiten des Würfels in Richtung Kamera zeigt, ist die relative Orientierung der gezeigten Seiten fest vorgegeben (siehe Abbildung 14). Es ist für die Rekonstruktion des Würfels in 3D nicht notwendig, dass eine bestimmte Seite zuerst gezeigt wird. Der Würfel kann anfangs also beliebig in die Halterung gelegt werden.

Abbildung 14. Bilder von den Seiten eines Würfels in der Reihenfolge, wie sie aufgenommen wurden (von oben links nach unten rechts). Zwischen Bild 3 und 4 findet ein Umgreifen statt.

Hierbei sollte man auch die interne Repräsentation des Würfels im Kopf behalten, die noch einmal in Abbildung 15 dargestellt ist: Jede Farbe ist einem 3D-Vektor im rechtshändigen Koordinatensystems des Würfels zugewiesen und anders herum. Darüber hinaus sollte man sich bewusst sein, dass sich das mittlere Plättchen jeder Seite nicht ändert, egal, wie man den Würfel verdreht. Also verdrehen sich auch die mittleren Plättchen zueinander nicht, sodass ihre Zuweisung dem festen Modell von Abbildung 15 entspricht.

Abbildung 15. Zuweisungen von Farben und Koordinatensystemachsen für die interen Repräsentation des Würfels.

Definierung eines Hilfskoordinatensystems

Wir möchten nun das Ergebnis der Bildverarbeitung, sechs 3x3-Arrays, welches die Farben der gezeigten Seiten angibt, auf die einzelnen Seiten des Würfels auftragen. Dies findet in der Methode core.cube.Cube3D.Cube3D.set_faces() statt. Die Arrays -- die Ergebnisse der Bildverarbeitung -- sind im sogenannten cv-Koordinatensystem angegeben, wobei die Anordnung der Flächen sich an der Anordnung im jeweiligen Bild orientiert. Wir benötigen also eine Transformation von unserem cv-Koordinatensystem in das Würfelkoordinatensystem (cube-Koordinatensystem, wie in Abbildung 15 angegeben). Hierfür haben wir uns ein sogenanntes camera-Koordinatensystem innerhalb des Würfelkoordinatensystems erstellt, welches zwischen den beiden Koordinatensystemen transformiert, wobei die x-Achse des camera-Koordinatensystems in Richtung der x-Achse des cv-Koordinatensystems und die y-Achse des camera-Koordinatensystems in die y-Achse des cv-Koordinatensystems zeigt (wie in OpenCV befindet sich der Ursprung des cv-Koordinatensystems oben links). Das cv-Koordinatensystem ist bezüglich des camera-Koordinatensystems definiert, welches wiederum bezüglich des cube-Koordinatensystems definiert ist. Die z-Achse des camera-Koordinatensystems lässt sich durch die Rechte-Hand-Regel mit den beiden bereits bekannten Achsen berechnen. In diesem Fall "schaut" die z-Achse in Richtung der gezeigten Seite. Somit lässt sich die z-Achse sehr einfach berechnen, nämlich indem die Farbe des mittleren Plättchens der gezeigten Seite in den zugehörigen Vektor mit der Funktion core.cube.FacePosition.FacePosition.to_vector() umgewandelt wird. Dieser Vektor wird negiert, da er in Richtung des Würfels zeigen soll und entspricht somit der z-Achse des camera-Koordinatensystems.

Für die Angabe der x- und y- Achse reicht es nicht, sich eine Seite anzuschauen, da diese Seite beliebig verdreht sein kann. Es reicht allerdings, eine benachbarte Seite und die relative Rotation zu ihr zu kennen. In Abbildung 13 wurde zuerst die grüne Seite und danach die weiße Seite gezeigt. Da durch den statischen Ablauf der Methode main.read_faces() bekannt ist, wie der Würfel zwischen den beiden Bildern gedreht wurde, nämlich um die x-Achse des Tool-Koordinatensystems, also "nach rechts" aus Sicht der Kamera, kann herausgefunden werden, welches für beide Seiten der Vektor ist, der nach "unten" zeigt. In dem oben genannten Beispiel, wo zuerst die grüne und danach die weiße Seite gezeigt wird, kann daraus geschlossen werden, dass für beide Seiten die orangene Seite unten ist. Dies wird durch die Bildung des Kreuzprodukts beider Vektoren nach der rechten-Hand-Regel, welches den gemeinsamen Vektor nach unten angibt, berechnet. Dieser Vektor, der nach unten zeigt, kann also direkt als y-Achse für das camera-Koordinatensystem aufgefasst werden. Die letzte, fehlende Achse, die x-Achse des camera-Koordinatensystems, lässt sich aus dem Kreuzprodukt nach dem rechtshändigen System aus den beiden bereits gegebenen Vektoren berechnen. Schließlich muss noch ein Versatz von 1 in x- und y-Richtung beachtet werden, da sich der Ursprung des cv-Koordinatensystems "oben links" befindet.

Übertragen der Farben

Somit ist das benötigte camera-Koordinatensystem und damit auch die Transformation von cv-Koordinatensystem zu cube-Koordinatensystem bekannt. Nun können die einzelnen detektierten Plättchen auf den Würfel übertragen werden. Zur Erinnerung: In der internen Repräsentation werden keine Farben für die Plättchen gespeichert, sondern jeder Sub-Würfel (auch "Cubie" oder "Segment" genannt) eine eigene Basis hat, die seine Ausrichtung angibt. Die Farben, wie sie in der Simulation angezeigt werden, ergeben sich aus den Vektoren, die vom Würfel weg zeigen, und in die entsprechende Farbe umgewandelt werden. Um die ensprechende Farbe zu setzen, muss also nur der zugehörige Vektor gesetzt werden.

Für jede gezeigte Seite werden die entsprechenden Segmente des 3D-Würfels ausgewählt, sodass sie quasi als 2D-Array im 3D-Raum vorliegen. Mithilfe des definierten camera-Koordinatensystems kann nun für jedes 3D-Segment der zugehörige Index im cv-Koordinatensystem ermittelt werden:

wobei der Ursprung (origin) des Würfelsegments im cs-Koordinatensystem und die Basis des Koordinatensystems cs ist (cs steht hierbei für ein beliebiges Koordinatensystem). Es wird die inverse Basis benutzt, weil jeweils von dem äußeren Koordinatensystem ins innere Koordinatensystem transformiert wird. Diese Transformation findet in der Funktion core.cube.Cube3D.Cube3D.set_2d_face() statt. Somit kann die Farbe für das entsprechende Segment herausgefunden werden. Diese wird anschließend in den zugehörigen Vektor umgewandelt, welcher so gesetzt wird, dass er in die gleiche Richtung zeigt wie der Vektor des Mittelplättchens der gezeigten Seite. Hiermit ist erst einmal nur ein Basisvektor gesetzt, die anderen sind noch undefiniert und werden im weiteren Verlauf gesetzt. Abbildung 16 soll den ganzen Sachverhalt verdeutlichen. Hier werden die Koordinatensysteme gezeigt, die für das Setzen der ersten Seite (grün wie in Abbildung 14) benötigt werden. In dem Segment-Koordinatensystem wird beim Übertragen der ersten Seite bei dem angezeigten Segment nur der y-Vektor (dieser gibt die Farbe "rot" an und zeigt in die Richtung der grünen Seite) gesetzt.

Abbildung 16. Darstellung von Koordinatensystemen, die für das Setzen einer Seite (hier die grüne Seite) benötigt werden. Die Koordinatensysteme sind jeweils entlang der y-Achse beschriftet. Das camera-Koordinatensystem besitzt hier nur zur Visualisierung einen Versatz, eigentlich bestitzt es den gleichen Ursprung wie das cube-Koordinatensystem. Ein Segment-Koordinatensystem ist als Beispiel leicht hervorgehoben: Hier zeigt der positive y-Vektor (grün) in die Richtung der grünen Fläche (negative x-Richtung).

Setzen der Basen

Diese Technik wird iterativ auf alle Seiten angewendet, wodurch die Basisvektoren bekannter werden. Bei Segmenten, wo drei Seiten sichtbar sind, ist die Basis nach dem Übertragen aller Seiten eindeutig. Bei Segmenten, wo zwei Seiten bekannt sind, wird der dritte, fehlende Basisvektor über ein Kreuzprodukt, was der rechten-Hand-Regel folgt, berechnet. Bei Mittelstücken, wo nur eine Seite bekannt ist, wird die Standardbasis benutzt, da die Mittelstücke niemals gedreht werden und somit immer der Orientierung wie im cube-Koordinatensystem entsprechen. Das berechnen fehlender Basisvektoren findet innerhalb der Methode core.spatial.CoordinateSystem.CoordinateSystem.assume_base() statt.

Setzen der Würfel-Basis bezüglich der Halterung

Schließlich muss noch festgelegt werden, wie der Würfel am Ende des Vorgangs in der Halterung liegt, damit die Richtigen Bewegungen auf den Würfel angewandt werden. Hierfür wird die Basis des Würfel-Koordinatensystems bezüglich des mount-Koordinatensystems (die Halterung) gesetzt, indem die zuletzt betrachtete Seite vom Roboter weg und die davor betrachtete Seite in Richtung Boden zeigt. So liegt der Würfel in der Halterung vor, nachdem die Methode main.read_faces() ausgeführt wurde. Der dritte Basisvektor wird wieder über das Kreuzprodukt aus den beiden bekannten Vektoren nach der rechten-Hand-Regel berechnet. Die Basis des Würfels bezüglich der Halterung als Ergebnis der Methode core.cube.Cube3D.Cube3D.set_faces() zurückgegeben.

Übertragen in Map-Repräsentation

Für den Solver wird eine "aufgefaltete" Version des Würfels benötigt, da diese besser zur Berechnung einer Lösung geeignet ist. Diese Repräsentation ist eine Map der Länge 54, wobei jedes Farbplättchen eine eindeutige Bezeichnung in der Map hat (siehe hierfür auch core.solver.Cube.Cube). Die Übertragung der erstellten 3D-Repräsentation in die Map-Repräsentation erfolgt ähnlich wie bei dem Setzen der eingelesenen Farbplättchen: Es wird ein Hilfskoordinatensystem erstellt, das zwischen Würfel-Koordinatensystem und einem 2D-Koordinatensystem pro Seite vermittelt. Dieses 2D-Koordinatensystem wird anschließend elementweise in die Map geschrieben. Das Ganze geschieht in der Funktion core.solver.Cube.Cube.from_3d_representation().


Im nächsten Kapitel erfährst du, wie aus dem nun gewonnen Wissen über den echten Würfel eine Lösung für diesen berechnet wird.