Aufhebung der Objektivverzerrung eines Kamerabildes in EyesWeb

Aus toolbox_interaktion
Version vom 5. November 2014, 22:19 Uhr von Bruenig (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

Möchte man Positionsdaten aus einem Kamerabild extrahieren, ergibt sich das Problem, dass durch die Einwirkung der in der Regel tonnenförmige Verzerrung des Kameraobjektivs Objekte nicht realitätskonform abgebildet werden. Zur exakten Positionsbestimmung muss also die Objektivverzerrung aufgehoben werden.

Funktionsprinzip

  • klassisches Schachbrettmuster
    Es wird ein Schachbrettmuster definiert, das in ausgedruckter Form parallel zur Kamera im Kamerabild platziert wird (Dateien chess_1.tif, chess_2.tif, chess_3.tif, chess_4.tif).
  • Nach Ablauf eines Countdowns wird das aktuelle Kamerabild, sofern das Schachbrettmuster vom hier zu erstellenden Block erkannt wurde, zwischengespeichert.
  • Direkt danach wird der Countdown automatisch neu gestartet. Während des erneuten Herunterzählens wird das Schachbrettmuster im Kamerabild neu positioniert, sodass am Ende des Countdowns erneut ein Kamerabild zwischengespeichert wird. Diese Prozedur wird noch einmal wiederholt, sodass drei Kamerabilder zwischengespeichert sind.
  • Sollte beim Ablauf des Countdowns kein vollständiges Schachbrettmuster im Kamerabild detektiert werden, wird er neu gestartet, ohne dass das Bild zwischengespeichert wird.
  • Die drei einzelnen Kamerabilder werden nun zur Kamerakalibrierung herangezogen, indem sie vom Block auf das vordefinierte Schachbrettmuster hin analysiert werden beziehungsweise im Prinzip mit dem Idealbild des Schachbrettmusters abgeglichen werden.
  • Nach erfolgter Kalibrierung gibt der Block fortan ein entzerrtes Kamerabild aus, das im weiteren Verlauf eines größeren EyesWeb-Patches verwendet werden kann.
  • Ein Reset-Kommando ermöglicht das Zurücksetzen des Kalibrierungsergebnisses und direkten Neustart des Countdowns.

Softwarevoraussetzungen

  • EyesWeb Version 3.x
  • Microsoft Visual C++ 6.0
  • OpenCV (Beta 5 oder später)

EyesWeb-Patch

EyesWeb-Patch
EyesWeb-Patch zur automatischen Entzerrung
Die Einbindung des Blocks ist einfach (siehe manualundistort.eyw). Ein eingehendes Kamerabild sowie ein ausgehendes Bild, dass nach der Kalibrierung entzerrt ist. Dazu wird vom Block noch der Countdown, der angibt, wann versucht wird, ein Kamerabild zwischenzuspeichern, als Skalarwert ausgegeben. Ein Reset-Button ermöglicht das Zurücksetzen der Kalibrierung bei eventuellen Kalibrierungsfehlern.

Es wurde auch experimentiert bei der Anwendung einer Projektor-Kamera-Kopplung eine automatische Kalibrierungsprozedur zu erstellen (siehe undistort.eyw). . Diese hat allerdings nur dürftige Kalibrierungsergebnisse erzielt und sei hier nur kurz prinzipiell erläutert: Die Projektion wird als erweiterter Windows-Desktop eingerichtet. Hier wird sodann eine Schleife Einzelbildern projiziert, die jeweils das Schachbrettmuster in einer anderen Lage enthalten. Parallel dazu findet die Kalibrierung statt. Nach erfolgter Kalibrierung kann das Display, indem die Einzelbilder auf der Projektion ausgegeben werden, geschlossen werden.

EyesWeb-Block "undistort"

Das Grundgerüst des EyesWeb-Blocks wird mit dem EyesWeb Wizard / EyesWeb Block Creator in Microsoft Visual C++ 6.0 erstellt.

EyesWeb Block Creator

Unter "Block Description" wird festgelegt, dass es sich bei dem zu erstellenden Block um einen passiven Block handelt. Anschließend wird der Reset-Parameter des Blocks definiert (Add Param > [Add...]):

Typ: "Command"; Name: "Reset"

Danach wird ein eingehendes Kamerabild als Eingangssignal definiert ("Add Input" > [Add...]):

Name: "imageIn"; Typ: "Image"; Interface: "IDTImage"

Schließlich werden noch das ausgehende, freigestellte Bild und ein Skalarwert, der anzeigt, ob die Koordinatensperre geöffnet oder geschlossen ist, als Ausgangssignale festgelegt ("Add Output"):

Name: "imageOut"; Typ: "Image"; Interface: "IDTImage"; Any Index: "-1"; In-place Index: "0"
Name: "calibrateCountdown"; Typ: "Scalar"; Interface: "IDTScalar" ; Any Index: "-1"; In-place Index: "-1"

Durch das Setzen des In-place Indexes vom auszugebenden Bild auf 0 wird das eingehende Bild (imageIn) an das auszugebende Bild (imageOut) schlicht weitergeleitet. Während dieser Weiterleitung werden später die notwendigen Operationen durchgeführt.

Nach Abschluss des Block Creators liegt das Grundgerüst des neuen EyesWeb-Blocks vor. Nun muss im Menu von MS Visual C++ unter [Projekt] > [Einstellungen] als erstes noch dem Linker die OpenCV-Bibliotheken "cv.lib" bekannt gemacht werden indem sie mit einem Leerzeichen getrennt in der Textzeile "Objekt-/Bibliothekmodule" angehängt wird. Danach kann man sich der Programmierarbeit widmen.

Header-Datei

Zu Beginn der Header-Datei wird zunächst die OpenCV-Datei "cv.h" eingebunden.

#include "cv.h"

Unterhalb der include-Dateien werdem Konstanten definiert. Die ersten beiden dienen zur Definition des Schachbrettmuster. Im Beispiel hat das Schachbrettmuster 9 x 7 Felder.

const int NUM_H_CORNERS = 8; // horizontale, innere Eckpunkte der Schachbrettvorlage (== horizontale Felder -1)
const int NUM_V_CORNERS = 6; // vertikale, innere Eckpunkte der Schachbrettvorlage (== vertikale Felder -1)
const int COUNTDOWN = 5; // Zeit in Sekunden (bei einer frame rate von 25), nach denen jeweils ein Bild zur Kalibrierung herangezogen wird
const int START_CALIBRATION = 3; // Anzahl korrekt erkannter Bilder, nach der die Kalibrierung durchgeführt werden soll

Anschließend werden eine Variablen im private-Teil definiert. imageCounter zählt lediglich alle eingehenden Kamerabilder. Dies ist nötig, da der Countdown an die Frame-Rate gekoppelt ist, um nicht den Fehler zu provozieren, dass im Moment des Countdown-Endes kein (vollständiges) Kamerabild zur Verfügung steht. calibrateCountdown ist schlicht der aktuelle Stand des Countdowns, wie er später auch ausgegeben wird.

int imageCounter; // zählt aufgenommene Bilder, um den Countdown in frame-Intervall umzurechnen
int calibrateCountdown; // aktueller Stand des Countdowns, der vom Block ausgegeben wird

Folgend werden Zeiger auf Matrizen vom OpenCV-Datentyp CvMat definiert. Im Block wird eine Entzerrungskarte errechnet, in der angegeben ist, welcher Bildpunkt im entzerrten Bild wohin gehört. Die Daten dieser Entzerrungskarte werden in mapx und mapy gesichert. Für das Erstellen der Karte müssen zuvor die intrinsischen Parameter der Kamera (unter anderem Brennweite und Versatz des Bildhauptmittelpunktes) bestimmt werden. Aus diesen werden die Verzerrungskoeffizienten der Verzerrungsgleichung errechnet, die lautet: Rn = a + bRa + cRa2 + dRa3 + eRa4 (mit Rn als Entfernung eines Punktes zum Zentrum der Verzerrung im unverzerrten Bild und Ra im verzerrten Bild; sowie den Konstanten a, b, c, d, e). Die durch die von den intrinsischen Parametern abgeleiteten Konstanten werden in distortion_coeffs festgehalten.

CvMat* mapx; // x-Koorinaten der Entzerrungskarte
CvMat* mapy; // y-Koordinaten der Entzerrungskarte
CvMat* intrinsic_matrix; // intrinsische Parameter der Kamera
CvMat* distortion_coeffs; // Koeffizienten der Verzerrung

Nun müssen noch die erkannten Bilder und die daraus analysierten Koordinaten der Schachbrettmusterecken in Vektoren festgehalten werden. m_images ist ein Vektor, der Zeiger auf Bilder vom OpenCV-Datentyp m_images enthält. Die Koordinaten werden über Zeiger auf den den Datentyp CvPoint2D32f festgehalten. Des weiteren wird aus Konsistenzgründen noch ein Vektor erzeugt, um die Anzahl der gefunden Ecken pro Bild zu sichern.

std::vector<IplImage*> m_images; // erkannte Bilder
std::vector<CvPoint2D32f*> m_corners; // Koordinaten gefundener Ecken pro erkanntem Bild
std::vector<int> m_cornerCount; // Anzahl gefundener Ecken pro erkanntem Bild

Nun wird noch eine boolsche Variable benötigt, die speichert, ob die Kalibrierung abgeschlossen ist.

bool calibrated; // Kalibrierung abgeschlossen

Schließlich fehlt noch ein temporäres Bild, in dem das auszugebende, entzerrte Bild festgehalten wird.

IplImage* outTmp; // temporäres, entzerrtes Bild

Die Editierung der Header-Datei ist damit abgeschlossen.

Konstruktor

In der Body-Datei werden die eben definierten Variablen initialisiert. Für outTmp wird mit cvCreateImage() ein Dummy-Bild erzeugt, da das Bildarray von outTmp später bei jedem eintreffen eines neuen Kamerabildes erst geleert wird und dies beim ersten Durchlauf ohne initialisiertes Bild zu einem Fehler führen würde.

imageCounter = 0;
calibrateCountdown = COUNTDOWN;
 
mapx = NULL;
mapy = NULL;
intrinsic_matrix = NULL;
distortion_coeffs = NULL;
 
calibrated = false;
 
outTmp = cvCreateImage(cvSize(10,10), 8, 3);

Execute()

Anschließend wird die eigentliche Kernfunktionalität des Blocks bearbeitet. Diese wird in der noch Methode Execute() festgelegt (einfach zu finden über den Kommentar "//TODO: Add your implementation code here"). Da es sich bei dem Block um einen passiven Block handelt, wird jedes Mal, wenn ein neues Eingangssignal (also in diesem Fall eines neues Bild) eintrifft die Methode Execute() aufgerufen.

Als erstes werden die Ein- und Ausgangsschnittstellen gesperrt, damit deren Daten bearbeitet werden können.

IDTImage* imageIn = (IDTImage*) LockInput(EYWIN_IMAGEIN);
IDTImage* imageOut = (IDTImage*) LockOutput(EYWOUT_IMAGEOUT);
IDTScalar* calibrateCountdownOut = (IDTScalar*) LockOutput(EYWOUT_CALIBRATECOUNTDOWN);

Vom Signalgang des eingehenden Bildes (imageIn) wird ein Zeiger auf ein IplImage initialisiert. Falls dies misslingt, soll die Methode einen Fehler zurückmelden.

IplImage* imgIn = NULL;
imageIn->GetIplImage((void**) &imgIn);
if(imgIn == NULL) return E_FAIL;

Nun geht es darum die Kalibrierung durchzuführen. Die Klassenvariable calibrated wird abgefragt.

if (!calibrated) { // Kalibrierung durchführen

Anschließend wird abgefragt, ob imageCounter, die jedes eingehend Bild mitzählt durch die Frame-Rate 25 ohne Rest teilbar ist. In diesem Fall wird der Countdown heruntergesetzt.

  if (imageCounter%25 == 0) calibrateCountdown--; // nach 25 Bildern (1 Sekunde bzw. Counter-Einheit) Countdown weiter runterzählen

Ist der Countdown bei 0 angekommen wird zuerst imageCounter auf 0 zurückgesetzt und auch der Countdown wieder auf den Startwert gesetzt.

  if (calibrateCountdown == 0) {
    imageCounter = 0;
    calibrateCountdown = COUNTDOWN;

Danach wird gemäß der Anzahl der inneren Schachbrettecken ein Array corners angelegt. cornerCount wird mit der Anzahl der zu findenden Ecken initialisiert.

    CvPoint2D32f corners[NUM_H_CORNERS * NUM_V_CORNERS]; // Array mit Koordinaten der gefunden Ecken
    int cornerCount = NUM_H_CORNERS * NUM_V_CORNERS; // Anzahl zu findender Ecken im aktuellen Bild

Nun wird die Funktion cvFindChessboardCorners() ausgeführt. Diese erhält das aktuelle Kamerabild zur Analyse, den mit cvSize() definierten Aufbau des Schachbrettmusters, das eben initialisierte Array corners, in dem die Koordinaten der gefunden Ecken gespeichert werden und eben schon angesprochenes cornerCount. cvFindChessboardCorners() wird cornerCount mit der Anzahl Eckpunkte überschreiben, die tatsächlich gefunden wurden.

    cvFindChessboardCorners(imgIn, cvSize(NUM_H_CORNERS, NUM_V_CORNERS), corners, &cornerCount, 0); // Eckpunkte suchen

Im Anschluss wird abgefragt, ob die detektierte Anzahl Ecken auf wirklich mit der definierten Anzahl übereinstimmt.

    if (cornerCount == NUM_H_CORNERS * NUM_V_CORNERS) { // gefunden Eckzahl == gesuchte Eckzahl => aktuelles Bild mit Daten festhalten

Das Einzelbild der Kamera, in dem das Schachbrettmuster korrekt detektiert wurde und dessen Daten sollen nun festgehalten werden, um es später für die Entzerrungsprozedur heranziehen zu können. Um die Daten der Punkte in den Zeigervektor m_corners zu schreiben, müssen die Daten aus corners umgeschrieben werden. Anschließend wird m_cornerCount mit der Anzahl der detektierten Ecken des Bildes, m_corners mit den Koordinaten der Eckpunkte und m_images mit dem Bild selbst erweitert. Auf der EyesWeb-Konsole wird eine Erfolgsmeldungs ausgegeben.

      CvPoint2D32f* newPoints = new CvPoint2D32f[cornerCount];
      for (int i = 0; i < cornerCount; i++) newPoints[i] = corners[i];
 
      m_cornerCount.push_back(cornerCount);
      m_corners.push_back(newPoints);
      m_images.push_back(imgIn);
 
      Message("Muster erkannt.\n");

Falls die Anzahl detektierter Bilder nun die festgelegte Zahl Bilder, die zur Entzerrung herangezogen werden sollen, erreicht hat, wird der eigentliche Kalibrierungsvorgang gestartet. Sogleich werden drei Hilfsvariablen für durchzuführende for-Schleifen definiert.

      int numImages = (int) m_images.size();
 
      if (numImages == START_CALIBRATION) {
        int i, x, y;

Die Entzerrungskarte bestehend aus den Matrizen mapx und mapy wird initialisiert.

        // Entzerrungskarte initialisieren
        mapx = cvCreateMat(imgIn->height, imgIn->width, CV_32FC1);
        mapy = cvCreateMat(imgIn->height, imgIn->width, CV_32FC1);

Im Anschluss werden weitere Matrizen definiert. object_points enthält die dreidimensionalen Positionen des Schachbrettmusters in allen Bildern in Form von x-, y- und z-Koordinaten. Dies wird für die später angewendete Kalibrierungsfunktion cvCalibrateCamera2() benötigt. Nun ist es allerdings schwer zu bestimmen, inwieweit das Schachbrettmuster in der Tiefe des Raumes liegt. Insofern muss das Schachbrettmuster bei der Kalibrierung immer parallel zur Kamera präsentiert werden, da somit die z-Koordinate vernachlässigt und durchgehend auf 0 gesetzt werden kann. image_points enthält die Eckpunktkoordinaten, wie sie im verzerrten, zweidimensionalen Kamerabild vorliegen. point_counts nimmt noch die Anzahl Punkte pro Bild auf. Da die Abmessungen der zu behandelnden Bilder auch ein Parameter von cvCalibrateCamer2() sind, werden diese Daten des Eingangsbildes hier auch gleich mit CvSize() festgehalten.

        CvMat* object_points = cvCreateMat(numImages * NUM_H_CORNERS * NUM_V_CORNERS, 3, CV_32FC1); // 3D-Koordinaten der (verzerrten) Eckpunkte des Schachbrettmusters
        CvMat* image_points = cvCreateMat(numImages * NUM_H_CORNERS * NUM_V_CORNERS, 2, CV_32FC1); // 2D-Koordinaten der (verzerrten) Eckpunkte im Kamerabild
        CvMat* point_counts = cvCreateMat(numImages, 1, CV_32SC1); // Anzahl Eckpunkte in den Bildern
        CvSize image_size = cvSize(imgIn->width, imgIn->height);

Es wird Zeit, nun auch die Klassenvariablen intrinsic_matrix und distortion_coeffs zu initialisieren. Gemäß der später von cvCalibrateCamera2() zurückgegebenen Werte erhält intrinsic_matrix eine 3 x 3 Matrix und distortion_coeffs eine 4 x 1 Matrix. Außerdem werden noch zwei weitere, allerdings temporäre Matrizen benötigt, die lediglich dazu dienen, die erforderlichen Parameter von cvCalibrateCamera2() zu vervollständigen. Sie erhalten von cvCalibrateCamera2() Drehung und Verschiebung jedes einzelnen Bildes zugewiesen. Außerdem wird hier noch eine Variable flags definiert, mit der cvCalibrateCamera2() eventuelle Zusatzoptionen zugewiesen werden könnten.

        intrinsic_matrix = cvCreateMat(3, 3, CV_32FC1);
        distortion_coeffs = cvCreateMat(4, 1, CV_32FC1);
        CvMat* rotation_vectors = NULL;
        CvMat* translation_vectors = NULL;
        int flags = 0;

Bevor cvCalibrateCamera2() nun endlich angewendet werden kann, müssen die cvCalibrateCamera2()-gerechten Matrizen object_points und image_points sowie point_counts mit entsprechenden Werten gefüllt werden. Dies geschieht in drei geschachtelten for-Schleifen. Für jedes Bild wird point_counts die Anzahl gefundener Schachbrettmusterecken zugewiesen.
Anschließend werden alle einzelnen Punkte jedes Bildes durchlaufen. Mit cvSetReal2D() werden den Matrizen praktisch dieselben Werte zugewiesen. Für die dritte Spalte, die Positionen der Eckpunkte auf der z-Achse, erhält object_points allerdings immer den Wert 0 (wurde weier oben bei der definition von object_points ansgesprochen).

        for (i = 0; i < numImages; i++) { // erkannte Bilder durchlaufen
          cvSetReal2D(point_counts, i, 0, (double)(m_cornerCount[i])); // Anzahl gefunder Eckpunkte des Bildes in Gesamtarray überführen
          for (y = 0; y < NUM_V_CORNERS; y++) { // einzelne Eckpunkte des Bildes durchlaufen
            for (x = 0; x < NUM_H_CORNERS; x++) {
              // gefundene x- und y-Werte in Gesamtarray überführen
              cvSetReal2D(image_points, x + y * NUM_H_CORNERS + i * NUM_H_CORNERS * NUM_V_CORNERS, 0, (double)(m_corners[i][x + y * NUM_H_CORNERS].x));
              cvSetReal2D(image_points, x + y * NUM_H_CORNERS + i * NUM_H_CORNERS * NUM_V_CORNERS, 1, (double)(m_corners[i][x + y * NUM_H_CORNERS].y));
 
              // Koordinaten der Punkte des Schachbrettmusters in Gesamtarray überführen (z-Achse == 0)
              cvSetReal2D(object_points, x + y * NUM_H_CORNERS + i * NUM_H_CORNERS * NUM_V_CORNERS, 0, (double)(m_corners[i][x + y * NUM_H_CORNERS].x));
              cvSetReal2D(object_points, x + y * NUM_H_CORNERS + i * NUM_H_CORNERS * NUM_V_CORNERS, 1, (double)(m_corners[i][x + y * NUM_H_CORNERS].y));
              cvSetReal2D(object_points, x + y * NUM_H_CORNERS + i * NUM_H_CORNERS * NUM_V_CORNERS, 2, (double)(0));
            }
          }
        }

Nun ist es endlich soweit, dass die Kalibrierungsfunktion cvCalibrateCamera2() angewendet werden kann. Sie erhält eine ganze Latte Parameter, wobei intrinsic_matrix, distortion_coeffs, rotation_vectors und translation_vectors wie bereits erwähnt Rückgabeparameter darstellen.

        cvCalibrateCamera2(object_points, image_points,
          point_counts, image_size,
          intrinsic_matrix, distortion_coeffs,
          rotation_vectors, translation_vectors,
          flags);

Mit den zurückgegebenen Werten stehen nun im Prinzip die Eigenschaften der Kamera zur Verfügung. Mit cvUndistortMap() erzeugen wir direkt die bereits erwähnte Entzerrungskarte. In den Klassenvariablen mapx und mapy werden die korrigierten Koordinaten der Kamerbildpixel festgehalten.

        cvInitUndistortMap(intrinsic_matrix, distortion_coeffs, mapx, mapy);

Zum Abschluss der Kalibrierung werden die nicht mehr benötigten temporären Matrizen noch entfernt. Außerdem wird als zusätzliches Zeichen zur Erfolgsmeldung auf der EyesWeb-Konsole, der auszugebende Countdown auf –1 gesetzt.

        object_points = NULL;
        image_points = NULL;
        point_counts = NULL;
 
        calibrated = true;
        calibrateCountdown = -1;
 
        Message("Kalibrierung erfolgreich. Supi!\n");
      }

An dieser Stelle wird die if-Abfrage geschlossen, die prüft, ob die gezählten inneren Eckpunkte eines Schachbrettmusters der vordefinierten Anzahl entsprechen. Hier ist es also sinnvoll, mit einer else-Anweisung auf den Fehler hinzuweisen, sollte das Schachbrettmuster nicht korrekt erkannt werden.

    } else  {
      Message("Muster nicht erkannt. DEPP!\n");
    }

Jetzt muss auch noch der if-Block geschlossen werden, der durchlaufen wird, wenn der Countdown bei 0 angekommen ist. Hiernach folgt die letzte Anweisung während der Kalibrierungsprozedur. Der Zähler, der alle Einzelbilder zählt und anhand dessen ja wiederum Countdown heruntergezählt wird, wird inkrementiert.

  }
  imageCounter++;

Es stellt sich schließlich noch die Frage, wie verfahren wird, sollte die Kalibrierung abgeschlossen sein.
Als erstes geben wir den Speicher des temporären Bildes outTmp inklusive dessen Header mit cvReleaseImage() frei. Denn outTemp wird nach Abschluss der Kalibrierung bei jedem "Anstoßen" des Blocks durch ein eingehendes Kamerabild mit dem anschließenden cvCreateImage() immer wieder neu gesetzt.

} else {
  cvReleaseImage(&outTmp); // Bild-Header schreiben und anschließend leeres Bild gemäß Eingangsbild erzeugen
  outTmp = cvCreateImage(cvSize(imgIn->width,imgIn->height), imgIn->depth, imgIn->nChannels); // temporäres Entzerrungsbild
  cvSetZero(outTmp); // Werte auf 0 (also schwarz) setzen

Nun wendet die Funktion cvRemap() die Entzerrungskarte in Form von mapx und mapy auf das aktuelle Eingangsbild imgIn an. Als Ausgabebildarray darf nicht das Eingangsarray verwendet werden. Dies ist der einzige Grund, warum outTmp benötigt wird. Denn anschließend muss mit cvCopy() das Bildarry von outTmp wieder nach imgIn kopiert werden, da bei diesem Block ja das Bildarray des Eingangsbildes zugleich als Ausgabebildarray herangezogen wird.
Damit wäre die Entzerrung des Kamerabildes abgeschlossen.

  // Entzerrung durchführen (Wäre zwar im Zuge von EyesWeb simpler, imgIn auch gleich als Output der Funktion zu übergeben,
  // da spielt die Funktion allerdings nicht mit!)
  cvRemap(imgIn, outTmp, mapx, mapy, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS, cvScalarAll(0)); // Entzerrung durchführen
 
  cvCopy(outTmp, imgIn); // da für EyesWeb Output überbrückter Input ist, Inputbild mit entzerrtem Bild überschreiben
}

Zum Abschluss der Methode Execute() folgen nun noch Anweisungen, die bei jedem eingehenden Kamerabild ausgeführt werden sollen. Zum einen muss der Countdown noch aktualisiert werden, da dessen Wert sich unter Umständen geändert hat. Schließlich werden Ein- und Ausgangsschnittstellen freigegeben.

calibrateCountdownOut->SetIntValue(calibrateCountdown);
 
UnlockInput(EYWIN_IMAGEIN);
UnlockOutput(EYWOUT_IMAGEOUT);
UnlockOutput(EYWOUT_CALIBRATECOUNTDOWN);
 
return S_OK;

Damit wäre der Kern des Blocks fertig.

SetParam()

Nun fehlt noch die Behandlung des Parameters "Reset". Hierzu muss noch die Methode SetParam() angepasst werden. (Spätestens hier kommt die nicht unwesentliche Unkenntnis des Autors von C++ zum tragen.) Noch vor der Switch-Anweisung werden zwei leere Vektoren definiert, die im Folgenden dafür sorgen, dass nach dem Betätigen von "Reset" keine bereits erfassten Bilder und deren erfassten Schachbrettmusterkoordinaten in m_images und m_corners verbleiben.

std::vector<IplImage*> dropImages; // leeres Array, um Bilddaten zu löschen
std::vector<CvPoint2D32f*> dropCorners; // leeres Array, um Eckdaten zu löschen

Unter "// Add command handling code here" werden sodann alle Aktionen eingebunden, die beim Betätigen von "Reset" ausgeführt werden sollen. Alle Werte werden also nach Möglichkeit und Notwendigkeit zurückgesetzt. Aber wahrscheinlich nicht nur das könnte man sicherlich besser lösen.

imageCounter = 0;
calibrateCountdown = COUNTDOWN;
 
mapx = NULL;
mapy = NULL;
intrinsic_matrix = NULL;
distortion_coeffs = NULL;
 
for (int i; i < m_images.size(); i++) {
  cvReleaseImage(&m_images[i]);
  delete [] m_images[i];
  delete [] m_corners[i];
  m_cornerCount[i] = NULL;
}
m_images = dropImages;
m_corners = dropCorners;
 
calibrated = false;
m_init = false;

Damit ist die Programmierung des Blocks abgeschlossen. Die dll-Datei kann nun erstellt werden und nachdem man diese registriert hat steht der Block in EyesWeb zur Verfügung.

Die Software zu diesen Blöcken ist im internen Bereich als Datei manualundistort.zip bzw. undistort.zip abgelegt.