Wie kann ich einen Punkt von einem Vektorraum in den anderen transformieren, wenn ich jeweils 4 Fixpunkte in beiden Räumen habe?

Hallo zusammen :smile:

Ich möchte (um genau zu sein in C#) die Koordinaten aus einem Koordinatensystem KK (geliefert von einem Kinect-Sensor) auf das Koordinatensystems einer Projektionseinheit KP mappen. Dabei ist leider KP im verhältnis zu KK nicht nur skaliert oder verschoben, sondern frei verzerrt. Genauer gesagt habe ich beispielsweise solche Koordinaten:

        //oben links
        beam_ol = new Vector3(31, 27, 0),
        kinect_ol = new Vector3(0.67, 0.83, 0),

        //unten links
        beam_ul = new Vector3(34, 42, 0),
        kinect_ul = new Vector3(0.74, 0.14, 0),

        //oben rechts
        beam_or = new Vector3(51, 1, 0),
        kinect_or = new Vector3(-1.36, 0.59, 0),

        //unten rechts
        beam_ur = new Vector3(54, 12, 0),
        kinect_ur = new Vector3(-1.4, 0.17, 0); 

Wie kriege ich das mathematisch raus? Ich hatte bereits die Idee, das ganze als lineares Gleichungssystem zu beschreiben und durch einen least squares solver zu lösen. Das wäre dann vermutlich:

a=
31 27
34 42
51 1
54 12

b=
0.67 0.83
0.74 0.14
-1.36 0.59
-1.4 0.17

im Beispiel. Mit einem solver (bspw. link) bekomme ich dann als Transformationsmatrix

x=
-0.0311 0.0081
0.0467 0.0026

raus. Wenn ich aber mit dieser Versuche bspw den ersten Punkt umzurechnen:

(31 27) * x

= (0.3, 0.32)

Bekomme ich ein völlig falsches Ergebnis. Irgendwas habe ich scheinbar falsch gemacht, aber was? :smiley:

Vielen dank im voraus für eure Tipps!

Hallo!

Erstmal:

Formal hat man

b=Xa, nicht aX.

Und wenn ich für dein Beispiel mal die erste Komponente berechne, komme ich auf

−0,0311×31+0,0081×27= −0,7454

Jetzt zu deinem Problem: Mit der Matrix als lineare Abbildung zwischen den Koordinatensystemen muss deren Ursprung auf jeden Fall identisch sein: X*0=0

Da du sicher auch eine Verschiebung drin hast, muß der Ansatz zumindest noch nen Verschiebungsvektor enthalten:

b=X*a+y

Zum Lösen braucht es also noch mehr Messpunkte…

Vereinfacht könntest aber erstmal einen Messpunkt dort aufnehmen, wo die kinect (bzw das Koordinatensystem von a) den Ursprung hat. Die abgelesene Koordinate des Projektors ist dann nämlich y.
Das könntest du zunächst mal händisch einbauen, und dann mal schauen, wie gut die (dann neue) matrix funktioniert.

Übrigens, zur least-squares Methode gehört auf jeden Fall, sich die ermittelten Unsicherheiten bzw das finale „square“ anzuschauen, um bewerten zu können, ob das überhaupt geklappt hat. Vermutlich hat es das nämlich nicht.

Hallo, und vielen Dank für die hilfreichen Hinweise. Ich habe nun nochmal alle 4 Punkte zusammen mit dem Ursprungspunkt nachgemessen, und komme damit zu diesen Matrizen:

a=
0.94 0.76
-1.24 0.5
-1.38 0.14
0.75 0.12

b=
28 30
51 0
32 43
54 11

zusammen mit dem Usprung von a, der bei b dem vektor U_b= (41, 39) entspricht, habe ich dann bei b von allen Koordinaten U_b abgezogen, damit sie für die Transformation den gleichen Ursprung haben:

b=
-13 -9
10 -39
13 -28
-9 4

Mit dem solver bekomme ich dann für obiges a und das „genullte“ b auf diese Matrix:

X=
-10.0966 16.6194
-4.9081 -34.3939

allerdings bekomme ich auch hier mit der richtigen Reihenfolge:

b=X*a völlig falsche Werte, bspw.

X* (0.75 0.12)_T=(-5.578 -7.808)_T

mit der Translation draufgerechnet komme ich dann zu (-5.578 -7.808)_T + (41 39)_T=(35,42 31,2)_T

Ich kann mir ja vorstellen dass die least square Methode manchmal geanuer und manchmal weniger genau ist, aber diese Ergebnisse sind ja nicht mal in der Nähe. Ist mein Ansatz denn überhaupt richtig? Gibt es alternativen?

Hi!

Es wäre möglich, daß deine transformation neben der Verschiebung weitere nicht lineare Elemente hat. Aber die sollten intuitiv nicht so groß sein, daß das ganze nicht halbwegs funktioniert.

Ein least-square Algorithmus kann auch davon galloppieren, was bei einem linearen Problem aber nicht passiert.

Vielleicht gibt’s auch noch ein anderes Problem.

Was mir grade noch auffällt: du zeigst grade nur 2D-Koordinaten, due dritte Komponente ist 0,was sie rein technisch kaum sein kann. Mit sowas kann man sich auch Schell mal was nicht lineares einfangen.

Ich find deine Aufgabenstellung aber sehr interessant, und würde gern selber mal mit den Zahlen spielen, kann das aber erst am Wochenende. Könntest du mal vollständige 3D-Koordinaten angeben? Gerne auch mal mehr als 4 Punkte.

Hi,

vielen dank dass du dich der Sache annimmst :slight_smile:

Die Koordinaten haben tatsächlich nur 2 Dimensionen:

  • Bei der Kinect ist es die berechnete x/y Koordinate in der Warenauslage, auf die gezeigt wird. Diese wird bereits erfolgreich verwendet um zu erkennen, auf welches Produkt gezeigt wurde. Daher scheint diese verlässlich zu sein.

  • Beim Beamer handelt es sich hierbei eigentlich um die Rotation und Neigung des Beleuchtungsteils. Die Werte liegen zwischen 0 und 255 und verursachen eine Rotation oder Neigung im Bereich von 180°. So wie ich es montiert habe, bewirkt aber eine Änderung der Rotation eine Bewegung in der einen Achse (x), und eine Änderung der Neigung eine Bewegung in der dazu orthogonalen Achse (y). Ich schick dir gleich per PN ein Bild des Setups, dann wird wahrscheinlich etwas klarer was gemeint ist. Ganz linear ist diese Bewegung vermutlich nicht, wir haben aber bei einem anderen Setup, bei dem die Kinect und Beamer genau an der gleichen Stelle und gleich ausgerichtet sind, brauchbare Ergebnisse durch eine einfache Hochskalierung und Verschiebung der Koordinaten erhalten können.

Hier ist der Beamer im Vergleich zur Kinect aus technischen Gründen etwas entfernt und verdreht montiert, wodurch diese Verzerrung entsteht. Hat letzteres möglicherweise ebenfalls eine Auswirkung?

Hier noch ein paar zusätzliche Koordinatenpaare:

(0.084,0.342) -> (36,28)
(-0.71,0.34) ->(46,17)
(-1.08,0.3) -> (50,11)
(-0.577,0.13) -> (46,26)
(0.36,0.267) -> (35,35)
(0.59,0.11) -> (34,41)

Zur Bestimmung derer habe ich den Beamer an die gegebene Position gefahren, auf den beleuchteten Punkt gezeigt und daraufhin die über die letzten 10 Frames gemittelten Zeigewerte von der Kinect abgelesen. Ist nicht 100% genau, aber genauer muss die Lösung nachher auch nicht sein.

Hallo!

Ich habe mir das jetzt mal angeguckt, und schreib das nochmal zusammen.

Aufgabe

Es gibt korrespondierende 2D-Koordinaten von Punkten im Koordinatensystem eines Beamers (b )und einer Kinect (k).
Es soll eine Transformation gefunden werden, um die Koordinaten aus den Koordinatensystem Beamers in das der Kinect umzurechnen. Du hattest mir weitere Koordinaten per PN geschickt, daher gibts insgesamt diese Werte:

b_i=
[[36. 28.]
 [46. 17.]
 [50. 11.]
 [46. 26.]
 [35. 35.]
 [34. 41.]
 [31. 27.]
 [34. 42.]
 [51.  1.]
 [54. 12.]]

k_i=
[[ 0.084  0.342]
 [-0.71   0.34 ]
 [-1.08   0.3  ]
 [-0.577  0.13 ]
 [ 0.36   0.267]
 [ 0.59   0.11 ]
 [ 0.67   0.83 ]
 [ 0.74   0.14 ]
 [-1.36   0.59 ]
 [-1.4    0.17 ]]

Die Transformation soll mit der Methode der kleinsten Quadrate gefunden werden.

Grafisch

Hier sind die gegebenen Koordinaten mal grafisch aufgetragen. Die des Beamers wurden schonmal durch 20 dividiert, damit das vernünftig in ein Diagramm passt. Hier wird das Ergebnis schon etwas vorweg genommen:

Transformation

Eine Matrix M alleine ist nicht ausreichend, es bracht mindestens noch einen Verschiebungsvektor s, sofern der Ursprung beider Koordinatensysteme nicht gleich ist. Die Transformation sollte also so aussehen:

k=M*b+s

Startwerte

Es ist für einen Optimierungsalgorithmus immer gut, wenn man die zu optimierenden Werte schon halbwegs gut vorgibt, denn dann sind weniger Optimierungsschritte nötig. Sehr oft versagt so ein Algorithmus auch, wenn man keine passablen Startwerte angibt.

Schaut man sich die Daten Diagramm an und spielt ein wenig damit, passt zusätzlich zum Faktor 1/20 eine Drehung um 120° sowie eine Verschiebung um (0,2; 2,6) schon ganz gut (cyan). Eine Verzerrung hab ich nicht vorgegeben.

Das ist jetzt per Hand gemacht, kann man natürlich auch automatisieren. (Idee: Suche zwei Koordinaten, die möglichst weit voneinander entfernt sind, und berechne den Winkel dazwischen. Die Differenz der Winkel in beiden Koordinatensystemen ist dann die notwendige Drehung. Also die einen Koordinaten alle um den Winkel drehen. Danach Mittelpunkt aller Koordinaten bestimmen, die Differenz ist dann der Offsetvektor)

Optimierungsfunktion

Der Algorithmus versucht durch Variation der Parameter (Elemente der Matrix und des Offsetvektors), bestimmte Ergebniswerte zu minimieren. In dem Fall macht es Sinn, den Abstand zwischen Beamerkoordinaten nach der Transformation und Kinectkoordinaten zu minimieren. Im Idealfall ist der ja für alle Punkte 0. Deshalb habe ich eine Funktion geschrieben, die die Transformation durchführt, und dann eine Liste der Abstände zurück gibt.
Der Algorithmus gibt die Parameter vor, guckt sich die Ergebnisse an, dreht etwas an den Parametern, schaut wieder auf die Ergebnisse, …und so weiter, bis er meint, daß die Parameter gut sind.

Ergebnis

Folgendes kommt dann raus, wenn ich das Problem lösen lasse.
Die Punkte sind in rot in der Grafik oben, und man sieht, das das eigentlich ganz gut aussieht. Der erste Punkt liegt ziemlich daneben, genauso wie die beiden rechten. Man könnte bei den rechten fragen, warum die Daten nicht noch mehr in die Breite, nach links gezogen wurden. Aber dann würden die beiden Punkte bei x=0,5, die jetzt schon zu weit links sind, noch weiter nach links gezogen. Das passt also.

M= [-0.07513453  0.0174213 ]
   [-0.04600035 -0.03105147]

s= [2.44682097]
   [2.98529246]

Mal mit dem letzten Punkt ausprobiert, das ist der ganz links (unten):

[-0.07513453  0.0174213 ]  *  [54]  +  [2.44682097]  =  [-1.40140902]
[-0.04600035 -0.03105147]     [12]     [2.98529246]     [ 0.12865592]

Das passt gut zur Koordinate [-1.4, 0.17] der Kinect, der y-Wert ist was klein, das sieht man auch in der Grafik.

Residuen

Residuen sind die Ergebniswerte der Optimierungsfunktion, sprich, in dem Fall die Abstände zwischen den transformierten Koordinaten des Beamers und denen der Kinect:

[0.18744622
0.00349813
0.05809584
0.07110722
0.07022332
0.04159181
0.1364712
0.11829252
0.01975152
0.04136749]

Auch hier zeigt sich wieder, daß der erste, der siebte und der achte Punkt einen deutlich größeren Abstand hat.

Nochmal zu den Daten:

Kinect und Beamer hängen mit etwas Abstand unter der Decke, wie ich deiner PN entnehme. Da deiner Koordinaten nur zweidimensional sind, führt das zu folgendem Problem: Der Beamer mag vielleicht senkrecht nach unten auf einen Stapel Käsescheiben leuchten. Dabei ist es egal, wie dick der Stapel ist.
Die Kinect schaut von der Seite auf das ganze, und da ändert sich die Koordinate der obersten Scheibe natürlich mit der Dicke des Stapels!
Das heißt, natürlich kann das auch theoretisch nie zu 100% passen!

Code

Ich hab das ganze in Python gerechnet. Das kann mit numpy ganz gut mit Matrizen etc. umgehen, sicher kann man sowas auch in C# realisieren. Die einzig wichtige Abhängigkeit ist ne Bibliothek mit dem least-squares solver.

Eins sehe ich grade: Ich habe in diesem Code um 130° gedreht, oben waren es nur 120°. Das führt zu minimal anderen Ergebnissen.

from math import *
import numpy as np
from scipy.optimize import leastsq

# data
kinect=np.array([[0.084, 0.342], [-0.71, 0.34], [-1.08, 0.3], [-0.577, 0.13], [0.36, 0.267], [0.59, 0.11] , \
                 [0.67, 0.83], [0.74, 0.14], [-1.36, 0.59], [-1.4, 0.17]], dtype=np.double).transpose()

beamer=np.array([[36, 28], [46, 17], [50, 11], [46, 26], [35, 35], [34, 41]  , \
                 [31, 27], [34, 42], [51, 1], [54, 12]], dtype=np.double).transpose()

# functions
def transform(x):
   '''Apply matrix and offset to all points from b and return result.
   One can to the transformation for all points in b in one go!'''
   mat=np.matrix([[x[0], x[1]],[x[2], x[3]]])
   offset=[[x[4]], [x[5]]]
   result= mat.dot(beamer).getA() + offset
   return result

def residuum(x):
   '''Calculates distance between transformed beamer and kinect coords
   This function must have exactly one parameter, being a 1d array of values'''
   diff= kinect - transform(x)
   ret=[]
   for i in diff.transpose():
      ret.append(sqrt(i[0]**2 + i[1]**2))
   return ret

print("b=")
print(beamer.transpose())
print("-"*20)

print("k=")
print(kinect.transpose())
print("-"*20)

########################
# guess: Rotate by 130, divide by 20, and shift a little
a=130.0
rotation= np.matrix([[cos(radians(a)), sin(radians(a))], [-sin(radians(a)), cos(radians(a))]]) / 20
shift=np.array([[0.2],[2.6]])
guess= rotation.dot(beamer).getA() + shift

#########################
# Initial parameter values - 1d array of parameters
x=np.concatenate((rotation.getA(), shift), axis=None)

print("Start values:")
print(x)
print("-"*20)

# Here, run the solver:
result=leastsq(residuum, x)

print("Final values:")
print(result[0])
print("M=")
print(result[0][:4].reshape(2,2))
print("s=")
print(result[0][4:].reshape(2,1))
print("-"*20)

print("Residuum:")
print(residuum(result[0]))

#########################
# This is just for drawing:

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

plt.grid(True)
plt.axes().set_aspect('equal', 'datalim')

plt.plot(kinect[0], kinect[1], 'bo-', label='Kinect')
plt.plot(beamer[0] / 20, beamer[1] / 20, 'go-', label='Beamer/20')
plt.plot(guess[0], guess[1], 'co-', label='Beamer/20, rot by 130deg, shift by (0.2;2.6)')
final=transform(result[0])
plt.plot(final[0], final[1], 'ro-', label='Final')

plt.legend()
plt.show()

Nö. Die dritte Dimension ist nur gut versteckt.

Überleg dir mal, was die zwei Dimensionen eigentlich bedeuten. Und wie du an die dritte rankommst. Sonst wird das nämlich keine Umrechnung, sondern nur eine irgendwie-Schätzung.

WOW! Eine super Antwort, man sieht dass du richtig viel zeit rein gesteckt hast. Vielen, vielen Dank auch für die schön aufbereitete Übersicht deiner Arbeit. Ich werde mich diese Woche daran machen, das ganze bei mir umzusetzen.

Ein kleiner Drift ist nicht weiter tragisch, solange die gezeigte Position halbwegs realistisch bleibt. Die von der Kinect gelieferten Daten sind auch nicht besonders genau. Die Probanden werden der Erfahrung nach eher den Lichtpunkt interaktiv verschieben, sodass ein hinreichend kleiner Offset unbemerkt bleibt.

Ich habe nun den Algorithmus bei der Theke umgesetzt und ich muss sagen, ich bin begeistert :smiley: Ich habe zwar die andere Richtung gebraucht (von Kinect auf Beamerkoordinaten statt umgekehrt), aber mit der detaillierten Anleitung konnte ich Problemlos auch diese Richtung implementieren. Die Bewegungen fühlen sich tatsächlich bei der Benutzung „richtig“ an und ermöglichen eine inutitive Selektion der Produkte. Die minimalen Residuen von 1-2° sind laut Aussage der bisherigen Testpersonen nicht spürbar.

Vielen vielen Dank nochmal!!