Wie sortiere ich meine Pfoten?


121

In meiner vorherigen Frage habe ich eine hervorragende Antwort erhalten , mit deren Hilfe ich feststellen konnte, wo eine Pfote auf eine Druckplatte trifft. Jetzt habe ich jedoch Probleme, diese Ergebnisse mit den entsprechenden Pfoten zu verknüpfen:

Alt-Text

Ich habe die Pfoten manuell kommentiert (RF = rechts vorne, RH = rechts hinten, LF = links vorne, LH = links hinten).

Wie Sie sehen, gibt es eindeutig ein sich wiederholendes Muster, das bei fast jeder Messung wieder auftritt. Hier ist ein Link zu einer Präsentation von 6 Versuchen, die manuell kommentiert wurden.

Mein erster Gedanke war, Heuristiken zu verwenden, um die Sortierung durchzuführen, wie:

  • Die Gewichtsbelastung zwischen Vorder- und Hinterpfote beträgt ~ 60-40%.
  • Die Hinterpfoten haben im Allgemeinen eine kleinere Oberfläche;
  • Die Pfoten sind (oft) räumlich in links und rechts unterteilt.

Ich bin jedoch etwas skeptisch gegenüber meinen Heuristiken, da sie bei mir versagen würden, sobald ich auf eine Variation stoße, an die ich nicht gedacht hatte. Sie werden auch nicht in der Lage sein, Messungen von lahmen Hunden zu bewältigen, die wahrscheinlich eigene Regeln haben.

Darüber hinaus wird die von Joe vorgeschlagene Anmerkung manchmal durcheinander gebracht und berücksichtigt nicht, wie die Pfote tatsächlich aussieht.

Aufgrund der Antworten, die ich auf meine Frage zur Peakerkennung innerhalb der Pfote erhalten habe , hoffe ich, dass es fortschrittlichere Lösungen zum Sortieren der Pfoten gibt. Insbesondere, weil die Druckverteilung und deren Verlauf für jede einzelne Pfote unterschiedlich sind, fast wie bei einem Fingerabdruck. Ich hoffe, es gibt eine Methode, mit der ich meine Pfoten gruppieren kann, anstatt sie nur in der Reihenfolge ihres Auftretens zu sortieren.

Alt-Text

Deshalb suche ich nach einer besseren Möglichkeit, die Ergebnisse mit der entsprechenden Pfote zu sortieren.

Für jeden, der sich der Herausforderung stellt, habe ich ein Wörterbuch mit allen in Scheiben geschnittenen Arrays zusammengestellt, die die Druckdaten jeder Pfote (gebündelt durch Messung) und der Scheibe enthalten, die ihre Position (Position auf der Platte und in der Zeit) beschreibt.

Zu clarfiy: walk_sliced_data ist ein Wörterbuch, das ['ser_3', 'ser_2', 'sel_1', 'sel_2', 'ser_1', 'sel_3'] enthält, die die Namen der Messungen sind. Jede Messung enthält ein anderes Wörterbuch [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] (Beispiel aus 'sel_1'), das die extrahierten Auswirkungen darstellt.

Beachten Sie auch, dass „falsche“ Stöße, z. B. wenn die Pfote teilweise gemessen wird (räumlich oder zeitlich), ignoriert werden können. Sie sind nur nützlich, weil sie beim Erkennen eines Musters helfen können, aber nicht analysiert werden.

Und für alle Interessierten führe ich einen Blog mit allen Updates zum Projekt!


1
Ja, der Ansatz, den ich verwendet habe, funktioniert nicht ganz. Der Ansatz, den ich verwendet habe, besteht darin, nur die Auswirkungen zu ordnen und anzunehmen, dass die erste zu berührende Pfote mit der fünften zu berührenden Pfote identisch ist, und so weiter. (dh die Auswirkungen bestellen und ein Modulo 4 verwenden). Das Problem dabei ist, dass manchmal die hinteren Pfoten vom Sensorpad abprallen, nachdem die erste Pfote aufgesetzt hat. In diesem Fall entspricht die erste Pfote, die aufprallt, der vierten oder dritten Pfote, die aufprallt. Hoffentlich macht das Sinn.
Joe Kington

1
Würde ich die Bilder richtig interpretieren, indem ein Zeh jedes Hinterfußes deutlich weniger Druck ausübt als der Rest? Es scheint auch, dass der Zeh immer nach innen zeigt, dh zum Schwerpunkt des Hundes. Könnten Sie das als Heuristik einbeziehen?
Thomas Langston

1
Ich gebe zu, dass meine begrenzten Bildverarbeitungsfähigkeiten etwas verrostet sind, aber ist es leicht möglich, den geringsten steilen Gradienten des großen Mittelkissens jeder Pfote zu nehmen? Es scheint, dass der Winkel der geringsten Steilheit immens helfen würde (ein handgezeichnetes Beispiel für die Pfoten veröffentlicht: imgur.com/y2wBC imgur.com/yVqVU imgur.com/yehOc imgur.com/q0tcD )
user470379

Könnten Sie bitte klarstellen, wie die Daten in walk_sliced_datastrukturiert sind? Ich sehe ein Wörterbuch mit Wörterbüchern von 3D-Arrays. Wenn ich die dritte Dimension fixiere und die ersten beiden als Bild zeichne, sehe ich Pfoten.
Steve Tjoa

@ Thomas, ja, jede Pfote ist eindeutig auf unterschiedliche Weise geladen. Ich weiß, was das Programm tun soll, aber ich habe keine Ahnung, wie ich es programmieren soll ... @Steve, ich habe unten eine Klarstellung hinzugefügt :-)
Ivo Flipse

Antworten:


123

In Ordung! Ich habe es endlich geschafft, dass etwas konsequent funktioniert! Dieses Problem hat mich mehrere Tage lang angezogen ... Lustiges Zeug! Entschuldigen Sie die Länge dieser Antwort, aber ich muss einige Dinge etwas näher erläutern ... (Obwohl ich möglicherweise einen Rekord für die längste Nicht-Spam-Stackoverflow-Antwort aller Zeiten aufstellen kann!)

Als Randnotiz verwende ich den vollständigen Datensatz, zu dem Ivo in seiner ursprünglichen Frage einen Link bereitgestellt hat . Es handelt sich um eine Reihe von rar-Dateien (eine pro Hund), die jeweils mehrere verschiedene Versuchsläufe enthalten, die als ASCII-Arrays gespeichert sind. Anstatt zu versuchen, eigenständige Codebeispiele in diese Frage zu kopieren und einzufügen , finden Sie hier ein Bitbucket-Quecksilber-Repository mit vollständigem, eigenständigem Code. Sie können es mit klonen

hg clone https://joferkington@bitbucket.org/joferkington/paw-analysis


Überblick

Wie Sie in Ihrer Frage festgestellt haben, gibt es im Wesentlichen zwei Möglichkeiten, um das Problem anzugehen. Ich werde beide auf unterschiedliche Weise verwenden.

  1. Verwenden Sie die (zeitliche und räumliche) Reihenfolge der Pfotenstöße, um zu bestimmen, welche Pfote welche ist.
  2. Versuchen Sie, den "Pfotenabdruck" nur anhand seiner Form zu identifizieren.

Grundsätzlich funktioniert die erste Methode so, dass die Pfoten des Hundes dem in Ivos Frage oben gezeigten trapezartigen Muster folgen, schlägt jedoch fehl, wenn die Pfoten diesem Muster nicht folgen. Es ist ziemlich einfach, programmgesteuert zu erkennen, wenn es nicht funktioniert.

Daher können wir die Messungen, bei denen es funktioniert hat, verwenden, um einen Trainingsdatensatz (von ~ 2000 Pfotenstößen von ~ 30 verschiedenen Hunden) zu erstellen, um zu erkennen, welche Pfote welche ist, und das Problem reduziert sich auf eine überwachte Klassifizierung (mit einigen zusätzlichen Falten). .. Die Bilderkennung ist etwas schwieriger als ein "normales" überwachtes Klassifizierungsproblem.


Musteranalyse

Um auf die erste Methode einzugehen: Wenn ein Hund normal läuft (nicht rennt!) (Was einige dieser Hunde möglicherweise nicht sind), erwarten wir, dass die Pfoten in der folgenden Reihenfolge aufschlagen: vorne links, hinten rechts, vorne rechts, hinten links , Vorne links usw. Das Muster kann entweder mit der vorderen linken oder der vorderen rechten Pfote beginnen.

Wenn dies immer der Fall wäre, könnten wir die Auswirkungen einfach nach der anfänglichen Kontaktzeit sortieren und sie mit einem Modulo 4 nach Pfoten gruppieren.

Normale Aufprallsequenz

Selbst wenn alles "normal" ist, funktioniert dies nicht. Dies ist auf die trapezartige Form des Musters zurückzuführen. Eine Hinterpfote fällt räumlich hinter die vorherige Vorderpfote.

Daher fällt der Aufprall der Hinterpfote nach dem ersten Aufprall der Vorderpfote häufig von der Sensorplatte und wird nicht aufgezeichnet. In ähnlicher Weise ist der letzte Pfotenaufprall oft nicht der nächste Pfote in der Sequenz, da der Pfotenaufprall, bevor er von der Sensorplatte auftrat und nicht aufgezeichnet wurde.

Verpasste Hinterpfote

Trotzdem können wir anhand der Form des Pfotenaufprallmusters bestimmen, wann dies geschehen ist und ob wir mit einer linken oder rechten Vorderpfote begonnen haben. (Ich ignoriere tatsächlich Probleme mit dem letzten Aufprall hier. Es ist jedoch nicht allzu schwer, ihn hinzuzufügen.)

def group_paws(data_slices, time):   
    # Sort slices by initial contact time
    data_slices.sort(key=lambda s: s[-1].start)

    # Get the centroid for each paw impact...
    paw_coords = []
    for x,y,z in data_slices:
        paw_coords.append([(item.stop + item.start) / 2.0 for item in (x,y)])
    paw_coords = np.array(paw_coords)

    # Make a vector between each sucessive impact...
    dx, dy = np.diff(paw_coords, axis=0).T

    #-- Group paws -------------------------------------------
    paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
    paw_number = np.arange(len(paw_coords))

    # Did we miss the hind paw impact after the first 
    # front paw impact? If so, first dx will be positive...
    if dx[0] > 0: 
        paw_number[1:] += 1

    # Are we starting with the left or right front paw...
    # We assume we're starting with the left, and check dy[0].
    # If dy[0] > 0 (i.e. the next paw impacts to the left), then
    # it's actually the right front paw, instead of the left.
    if dy[0] > 0: # Right front paw impact...
        paw_number += 2

    # Now we can determine the paw with a simple modulo 4..
    paw_codes = paw_number % 4
    paw_labels = [paw_code[code] for code in paw_codes]

    return paw_labels

Trotz alledem funktioniert es häufig nicht richtig. Viele der Hunde im vollständigen Datensatz scheinen zu rennen, und die Pfotenstöße folgen nicht der gleichen zeitlichen Reihenfolge wie beim Gehen des Hundes. (Oder vielleicht hat der Hund nur schwere Hüftprobleme ...)

Abnormale Aufprallsequenz

Glücklicherweise können wir immer noch programmgesteuert feststellen, ob die Pfotenstöße unserem erwarteten räumlichen Muster folgen oder nicht:

def paw_pattern_problems(paw_labels, dx, dy):
    """Check whether or not the label sequence "paw_labels" conforms to our
    expected spatial pattern of paw impacts. "paw_labels" should be a sequence
    of the strings: "LH", "RH", "LF", "RF" corresponding to the different paws"""
    # Check for problems... (This could be written a _lot_ more cleanly...)
    problems = False
    last = paw_labels[0]
    for paw, dy, dx in zip(paw_labels[1:], dy, dx):
        # Going from a left paw to a right, dy should be negative
        if last.startswith('L') and paw.startswith('R') and (dy > 0):
            problems = True
            break
        # Going from a right paw to a left, dy should be positive
        if last.startswith('R') and paw.startswith('L') and (dy < 0):
            problems = True
            break
        # Going from a front paw to a hind paw, dx should be negative
        if last.endswith('F') and paw.endswith('H') and (dx > 0):
            problems = True
            break
        # Going from a hind paw to a front paw, dx should be positive
        if last.endswith('H') and paw.endswith('F') and (dx < 0):
            problems = True
            break
        last = paw
    return problems

Obwohl die einfache räumliche Klassifizierung nicht immer funktioniert, können wir daher mit hinreichender Sicherheit feststellen, wann sie funktioniert.

Trainingsdatensatz

Aus den musterbasierten Klassifikationen, bei denen es richtig funktioniert hat, können wir einen sehr großen Trainingsdatensatz korrekt klassifizierter Pfoten erstellen (~ 2400 Pfotenstöße von 32 verschiedenen Hunden!).

Wir können jetzt anfangen zu sehen, wie eine "durchschnittliche" vordere linke Pfote usw. aussieht.

Dazu benötigen wir eine Art "Pfotenmetrik", die für jeden Hund die gleiche Dimension hat. (Im vollständigen Datensatz gibt es sowohl sehr große als auch sehr kleine Hunde!) Ein Pfotenabdruck eines irischen Elchhundes ist sowohl viel breiter als auch viel "schwerer" als ein Pfotenabdruck eines Zwergpudels. Wir müssen jeden Pfotenabdruck neu skalieren, damit a) sie die gleiche Anzahl von Pixeln haben und b) die Druckwerte standardisiert sind. Zu diesem Zweck habe ich jeden Pfotenabdruck erneut auf ein 20x20-Raster abgetastet und die Druckwerte basierend auf dem maximalen, minimalen und mittleren Druckwert für den Pfotenaufprall neu skaliert.

def paw_image(paw):
    from scipy.ndimage import map_coordinates
    ny, nx = paw.shape

    # Trim off any "blank" edges around the paw...
    mask = paw > 0.01 * paw.max()
    y, x = np.mgrid[:ny, :nx]
    ymin, ymax = y[mask].min(), y[mask].max()
    xmin, xmax = x[mask].min(), x[mask].max()

    # Make a 20x20 grid to resample the paw pressure values onto
    numx, numy = 20, 20
    xi = np.linspace(xmin, xmax, numx)
    yi = np.linspace(ymin, ymax, numy)
    xi, yi = np.meshgrid(xi, yi)  

    # Resample the values onto the 20x20 grid
    coords = np.vstack([yi.flatten(), xi.flatten()])
    zi = map_coordinates(paw, coords)
    zi = zi.reshape((numy, numx))

    # Rescale the pressure values
    zi -= zi.min()
    zi /= zi.max()
    zi -= zi.mean() #<- Helps distinguish front from hind paws...
    return zi

Nach all dem können wir uns endlich ansehen, wie eine durchschnittliche Pfote links vorne, hinten rechts usw. aussieht. Beachten Sie, dass dies über> 30 Hunde mit sehr unterschiedlichen Größen gemittelt wird und wir anscheinend konsistente Ergebnisse erzielen!

Durchschnittliche Pfoten

Bevor wir jedoch eine Analyse dieser durchführen, müssen wir den Mittelwert (die durchschnittliche Pfote für alle Beine aller Hunde) subtrahieren.

Mittlere Pfote

Jetzt können wir die Unterschiede zum Mittelwert analysieren, die etwas leichter zu erkennen sind:

Differentialpfoten

Bildbasierte Pfotenerkennung

Ok ... Wir haben endlich eine Reihe von Mustern, mit denen wir versuchen können, die Pfoten abzugleichen. Jede Pfote kann als 400-dimensionaler Vektor (von der paw_imageFunktion zurückgegeben) behandelt werden, der mit diesen vier 400-dimensionalen Vektoren verglichen werden kann.

Wenn wir nur einen "normalen" überwachten Klassifizierungsalgorithmus verwenden (dh anhand eines einfachen Abstands herausfinden, welches der 4 Muster einem bestimmten Pfotenabdruck am nächsten kommt), funktioniert dies leider nicht konsistent. Tatsächlich ist es nicht viel besser als zufällige Zufälle im Trainingsdatensatz.

Dies ist ein häufiges Problem bei der Bilderkennung. Aufgrund der hohen Dimensionalität der Eingabedaten und der etwas "unscharfen" Natur der Bilder (dh benachbarte Pixel haben eine hohe Kovarianz) ergibt ein einfaches Betrachten des Unterschieds eines Bildes von einem Vorlagenbild kein sehr gutes Maß für die Ähnlichkeit ihrer Formen.

Eigenpfoten

Um dies zu umgehen, müssen wir eine Reihe von "Eigenpfoten" erstellen (genau wie "Eigengesichter" bei der Gesichtserkennung) und jeden Pfotenabdruck als eine Kombination dieser Eigenpfoten beschreiben. Dies ist identisch mit der Hauptkomponentenanalyse und bietet im Grunde eine Möglichkeit, die Dimensionalität unserer Daten zu reduzieren, sodass der Abstand ein gutes Maß für die Form ist.

Da wir mehr Trainingsbilder als Dimensionen haben (2400 vs 400), ist es nicht erforderlich, eine "ausgefallene" lineare Algebra für die Geschwindigkeit zu erstellen. Wir können direkt mit der Kovarianzmatrix des Trainingsdatensatzes arbeiten:

def make_eigenpaws(paw_data):
    """Creates a set of eigenpaws based on paw_data.
    paw_data is a numdata by numdimensions matrix of all of the observations."""
    average_paw = paw_data.mean(axis=0)
    paw_data -= average_paw

    # Determine the eigenvectors of the covariance matrix of the data
    cov = np.cov(paw_data.T)
    eigvals, eigvecs = np.linalg.eig(cov)

    # Sort the eigenvectors by ascending eigenvalue (largest is last)
    eig_idx = np.argsort(eigvals)
    sorted_eigvecs = eigvecs[:,eig_idx]
    sorted_eigvals = eigvals[:,eig_idx]

    # Now choose a cutoff number of eigenvectors to use 
    # (50 seems to work well, but it's arbirtrary...
    num_basis_vecs = 50
    basis_vecs = sorted_eigvecs[:,-num_basis_vecs:]

    return basis_vecs

Dies basis_vecssind die "Eigenpfoten".

Eigenpfoten

Um diese zu verwenden, punktieren wir einfach (dh Matrixmultiplikation) jedes Pfotenbild (als 400-dimensionaler Vektor anstelle eines 20x20-Bildes) mit den Basisvektoren. Dies gibt uns einen 50-dimensionalen Vektor (ein Element pro Basisvektor), mit dem wir das Bild klassifizieren können. Anstatt ein 20x20-Bild mit dem 20x20-Bild jeder "Vorlagen" -Pfote zu vergleichen, vergleichen wir das 50-dimensionale, transformierte Bild mit jeder 50-dimensionalen transformierten Vorlagenpfote. Dies ist viel weniger empfindlich gegenüber kleinen Abweichungen in der genauen Positionierung jedes Zehs usw. und reduziert die Dimensionalität des Problems im Grunde genommen auf die relevanten Abmessungen.

Eigenpfotenbasierte Pfotenklassifikation

Jetzt können wir einfach den Abstand zwischen den 50-dimensionalen Vektoren und den "Schablonen" -Vektoren für jedes Bein verwenden, um zu klassifizieren, welche Pfote welche ist:

codebook = np.load('codebook.npy') # Template vectors for each paw
average_paw = np.load('average_paw.npy')
basis_stds = np.load('basis_stds.npy') # Needed to "whiten" the dataset...
basis_vecs = np.load('basis_vecs.npy')
paw_code = {0:'LF', 1:'RH', 2:'RF', 3:'LH'}
def classify(paw):
    paw = paw.flatten()
    paw -= average_paw
    scores = paw.dot(basis_vecs) / basis_stds
    diff = codebook - scores
    diff *= diff
    diff = np.sqrt(diff.sum(axis=1))
    return paw_code[diff.argmin()]

Hier sind einige der Ergebnisse: Alt-Text Alt-Text Alt-Text

Verbleibende Probleme

Es gibt immer noch einige Probleme, insbesondere bei Hunden, die zu klein sind, um einen klaren Pfotenabdruck zu erzeugen ... (Dies funktioniert am besten bei großen Hunden, da die Zehen bei der Auflösung des Sensors deutlicher voneinander getrennt sind.) Auch teilweise Pfotenabdrücke werden damit nicht erkannt System, während sie mit dem trapezförmigen Muster-basierten System sein können.

Da die Eigenpfotenanalyse jedoch von Natur aus eine Abstandsmetrik verwendet, können wir die Pfoten in beide Richtungen klassifizieren und auf das auf Trapezmustern basierende System zurückgreifen, wenn der kleinste Abstand der Eigenpfotenanalyse vom "Codebuch" einen bestimmten Schwellenwert überschreitet. Ich habe dies jedoch noch nicht implementiert.

Puh ... das war lang! Mein Hut ist weg von Ivo, weil ich so eine lustige Frage habe!


2
Gute Antwort. Ich habe auch versucht, die Eigenpfote zu verwenden, war aber nicht so ausdauernd wie Sie. Ein Problem, das ich sehe, ist die Pfotenregistrierung, dh die Gesichtsregistrierung ist die Gesichtserkennung. Haben Sie Probleme bei der Normalisierung der Position und Rotation jeder Pfote festgestellt? Wenn ja, kann die Pfote möglicherweise vor der PCA zu einem invarianten Translationsrotationsmerkmal vorverarbeitet werden.
Steve Tjoa

2
@Steve, ich habe nicht versucht, sie zu drehen, obwohl ich einige Diskussionen mit Joe darüber geführt habe, wie ich sie weiter verbessern kann. Um mein Projekt vorerst fertig zu stellen, habe ich alle Pfoten manuell mit Anmerkungen versehen, damit ich es einpacken kann. Glücklicherweise können wir damit auch verschiedene Trainingssätze erstellen, um die Erkennung empfindlicher zu machen. Zum Drehen der Pfoten hatte ich vor, die Zehen zu benutzen, aber wie Sie in meinem Blog lesen können, ist das nicht so einfach, wie meine erste Frage es aussehen ließ ...
Ivo Flipse

@Basic Ja, ich habe zum Hosting meiner eigenen Website gewechselt und den gesamten Wordpress-Inhalt verschoben, aber ich konnte meinen Kommentar hier nicht mehr bearbeiten. Sie sollten sie hier finden können: flipserd.com/blog/ivoflipse/post/improving-the-paw-detection
Ivo Flipse

4

Wenn Sie die Informationen ausschließlich auf der Grundlage der Dauer verwenden, können Sie meiner Meinung nach Techniken aus der Modellierungskinematik anwenden. nämlich inverse Kinematik . In Kombination mit Orientierung, Länge, Dauer und Gesamtgewicht ergibt sich ein gewisses Maß an Periodizität, was hoffentlich der erste Schritt sein könnte, um Ihr Problem des "Sortierens der Pfoten" zu lösen.

All diese Daten könnten verwendet werden, um eine Liste von begrenzten Polygonen (oder Tupeln) zu erstellen, die Sie verwenden können, um nach Schrittgröße und dann nach Pfote [Index] zu sortieren.


2

Kann der Techniker, der den Test ausführt, die erste Pfote (oder die ersten beiden) manuell eingeben? Der Prozess könnte sein:

  • Zeigen Sie dem Techniker die Reihenfolge der Schritte und fordern Sie ihn auf, die erste Pfote mit Anmerkungen zu versehen.
  • Beschriften Sie die anderen Pfoten anhand der ersten Pfote und lassen Sie den Techniker Korrekturen vornehmen oder den Test erneut ausführen. Dies ermöglicht lahme oder dreibeinige Hunde.

Ich habe tatsächlich Anmerkungen zu den ersten Pfoten, obwohl sie nicht fehlerfrei sind. Die erste Pfote ist jedoch immer eine Vorderpfote und würde mir nicht helfen, die Hinterpfoten zu trennen. Darüber hinaus ist die Bestellung nicht perfekt, wie Joe erwähnt hat, da dafür beide Fronten zu Beginn die Platte berühren müssen.
Ivo Flipse

Die Anmerkungen wären nützlich, wenn die Bilderkennung verwendet wird. Aufgrund der 24 Messungen, die ich habe, wären mindestens 24 Pfoten bereits mit Anmerkungen versehen. Wenn sie dann in 4 Gruppen zusammengefasst würden, sollten zwei davon eine angemessene Menge einer der Vorderpfoten enthalten, die ausreicht, um einen Algorithmus für die Clusterbildung ziemlich sicher zu machen.
Ivo Flipse

Sofern ich sie nicht falsch lese, zeigen die verknüpften kommentierten Versuche, dass sich die Hinterpfote in 4 von 6 Versuchen zuerst berührt.
Jamie Ide

Ah, ich meinte zeitlich. Wenn Sie die Feile durchlaufen, sollte die Vorderpfote immer die erste sein, die die Platte berührt.
Ivo Flipse
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.