2015-04-29 8 views
9

Ich habe eine enge Schleife, in der ich ein Kamerabild bekomme, es nicht entziffern und es auch entsprechend einer Transformation (z. B. einer perspektivischen Transformation) transformiere. Ich habe bereits herausgefunden, cv::remap(...) für jede Operation zu verwenden, die bereits viel effizienter ist als mit einfachen Matrix-Operationen.Wie kombiniert man zwei Remap() Operationen in einem?

In meinem Verständnis soll es möglich sein, die Lookup-Karten zu einem kombinieren und nur einmal in jedem Schleifendurchlauf neu zuordnen nennen. Gibt es einen kanonischen Weg, dies zu tun? Ich würde es vorziehen, nicht alles Interpolationsmaterial selbst zu implementieren.

Hinweis: Das Verfahren sollte mit Karten unterschiedlicher Größe funktionieren. In meinem speziellen Fall behält die Unverzerrung die Bilddimensionen bei, während die andere Transformation das Bild auf eine andere Größe skaliert.

-Code zur Erläuterung:

// input arguments 
const cv::Mat_<math::flt> intrinsic = getIntrinsic(); 
const cv::Mat_<math::flt> distortion = getDistortion(); 
const cv::Mat mNewCameraMatrix = cv::getOptimalNewCameraMatrix(intrinsic, distortion, myImageSize, 0); 

// output arguments 
cv::Mat undistortMapX; 
cv::Mat undistortMapY; 

// computes undistortion maps 
cv::initUndistortRectifyMap(intrinsic, distortion, cv::Mat(), 
          newCameraMatrix, myImageSize, CV_16SC2, 
          undistortMapX, undistortMapY); 

// computes undistortion maps 
// ...computation of mapX and mapY omitted 
cv::convertMaps(mapX, mapY, skewMapX, skewMapY, CV_16SC2); 

for(;;) { 
    cv::Mat originalImage = getNewImage(); 

    cv::Mat undistortedImage; 
    cv::remap(originalImage, undistortedImage, undistortMapX, undistortMapY, cv::INTER_LINEAR); 

    cv::Mat skewedImage; 
    cv::remap(undistortedImage, skewedImage, skewMapX, skewMapY, cv::INTER_LINEAR); 

    outputImage(skewedImage); 
} 

Antwort

2

Im Fall von zwei allgemeinen Zuordnungen gibt es keine andere Wahl, als den Ansatz von @MichaelBurdinov vorgeschlagen zu verwenden.

Jedoch ist in dem speziellen Fall von zwei Zuordnungen mit bekannten inversen Zuordnungen ein alternativer Ansatz, die Zuordnungen manuell zu berechnen. Diese manuelle Vorgehensweise ist genauer als die doppelte Neuzuordnung, da keine Interpolation von Koordinatenkarten erforderlich ist.

In der Praxis entsprechen die meisten interessanten Anwendungen diesem speziellen Fall. In Ihrem Fall auch, weil Ihre erste Karte der Bildverzerrung entspricht (deren umgekehrte Operation die Bildverzerrung ist, die mit einem bekannten analytischen Modell verbunden ist) und Ihre zweite Karte einer perspektivischen Transformation entspricht (deren Inverse sich analytisch ausdrücken lässt).

Die Karten manuell zu berechnen ist eigentlich ziemlich einfach. Wie in der Dokumentation (link) angegeben, enthalten diese Karten für jedes Pixel im Zielbild die (x, y) -Koordinaten, an denen die geeignete Intensität im Quellbild zu finden ist. Der folgende Codeausschnitt zeigt, wie die Karten manuell in Ihrem Fall berechnen:

int dst_width=...,dst_height=...;   // Initialize the size of the output image 
cv::Mat Hinv=H.inv(), Kinv=K.inv();   // Precompute the inverse perspective matrix and the inverse camera matrix 
cv::Mat map_undist_warped_x32f(dst_height,dst_width,CV_32F); // Allocate the x map to the correct size (n.b. the data type used is float) 
cv::Mat map_undist_warped_y32f(dst_height,dst_width,CV_32F); // Allocate the y map to the correct size (n.b. the data type used is float) 
// Loop on the rows of the output image 
for(int y=0; y<dst_height; ++y) { 
    std::vector<cv::Point3f> pts_undist_norm(dst_width); 
    // For each pixel on the current row, first use the inverse perspective mapping, then multiply by the 
    // inverse camera matrix (i.e. map from pixels to normalized coordinates to prepare use of projectPoints function) 
    for(int x=0; x<dst_width; ++x) { 
     cv::Mat_<float> pt(3,1); pt << x,y,1; 
     pt = Kinv*Hinv*pt; 
     pts_undist_norm[x].x = pt(0)/pt(2); 
     pts_undist_norm[x].y = pt(1)/pt(2); 
     pts_undist_norm[x].z = 1; 
    } 
    // For each pixel on the current row, compose with the inverse undistortion mapping (i.e. the distortion 
    // mapping) using projectPoints function 
    std::vector<cv::Point2f> pts_dist; 
    cv::projectPoints(pts_undist_norm,cv::Mat::zeros(3,1,CV_32F),cv::Mat::zeros(3,1,CV_32F),intrinsic,distortion,pts_dist); 
    // Store the result in the appropriate pixel of the output maps 
    for(int x=0; x<dst_width; ++x) { 
     map_undist_warped_x32f.at<float>(y,x) = pts_dist[x].x; 
     map_undist_warped_y32f.at<float>(y,x) = pts_dist[x].y; 
    } 
} 
// Finally, convert the float maps to signed-integer maps for best efficiency of the remap function 
cv::Mat map_undist_warped_x16s,map_undist_warped_y16s; 
cv::convertMaps(map_undist_warped_x32f,map_undist_warped_y32f,map_undist_warped_x16s,map_undist_warped_y16s,CV_16SC2); 

Hinweis: H oben ist Ihre Perspektive zu verwandeln, während K die Kameramatrix mit dem unverzerrtes Bild zugeordnet sein sollte, so sollte es sein, was in Ihrem Code heißt newCameraMatrix (BTW ist kein Ausgabeargument von initUndistortRectifyMap). Abhängig von Ihren spezifischen Daten können auch einige zusätzliche Fälle zu behandeln sein (z. B. Division durch pt(2), wenn diese Null sein könnte, usw.).

+0

Große Antwort und der Algorithmus funktioniert wie gewünscht für mich. 'img2.cols' sollte wahrscheinlich' dst_width' sagen. Ich werde auch mein Code-Snippet korrigieren, um den Ursprung von 'newCameraMatrix' zu reflektieren. –

+0

@DimitriSchachmann Danke, der Tippfehler wurde korrigiert. – AldurDisciple

4

Sie können remap auf undistortMapX und undistortMapY anzuwenden.

cv::remap(undistortMapX, undistrtSkewX, skewMapX, skewMapY, cv::INTER_LINEAR); 
cv::remap(undistortMapY, undistrtSkewY, skewMapX, skewMapY, cv::INTER_LINEAR); 

als Sie verwenden können:

cv::remap(originalImage , skewedImage, undistrtSkewX, undistrtSkewY, cv::INTER_LINEAR); 

Es funktioniert, weil skewMaps und undistortMaps Arrays von Koordinaten in Bild sind, so sollte es zu nehmen Lage Lage ähnlich sein ...

edit (Antwort auf Kommentare):

ich glaube, ich muss eine Klarstellung machen. Die Funktion remap() berechnet Pixel im neuen Bild aus Pixeln des alten Bildes. Im Fall einer linearen Interpolation ist jedes Pixel in einem neuen Bild ein gewichteter Durchschnitt von 4 Pixeln von dem alten Bild. Die Gewichte unterscheiden sich von Pixel zu Pixel entsprechend den Werten der bereitgestellten Karten. Wenn der Wert mehr oder weniger ganzzahlig ist, wird das meiste Gewicht von einem einzelnen Pixel genommen. Als Ergebnis wird ein neues Bild so scharf wie das Originalbild sein. Wenn andererseits der Wert weit davon entfernt ist, eine ganze Zahl zu sein (d. H. Eine ganze Zahl + 0,5), dann sind die Gewichte ähnlich. Dies erzeugt einen glättenden Effekt. Um ein Gefühl dafür zu bekommen, worüber ich rede, schauen Sie sich das unverzerrte Bild an. Sie werden sehen, dass einige Teile des Bildes schärfer/glatter sind als andere Teile.

Nun zurück zur Erklärung über das, was passiert, wenn Sie zwei in einer Remap-Operationen kombiniert. Die Koordinaten in kombinierten Karten sind korrekt, d. H. Pixel in skewedImage wird aus korrekten 4 Pixeln von originalImage mit korrekten Gewichten berechnet. Aber es ist nicht identisch mit dem Ergebnis von zwei Remap-Operationen. Jedes Pixel in unverzerrtem Bild ist ein gewichteter Durchschnitt von 4 Pixeln von originalImage. Dies bedeutet, dass jedes Pixel von skewedImage ein gewichteter Durchschnitt von 9-16 Pixel von orginalImage wäre. Fazit: mit Single Remap() kann NICHT geben Sie möglicherweise Ergebnis, das mit zwei Verwendungen von remap() identisch ist.

Diskussion darüber, welche der beiden möglichen Bilder (Einzel remap() vs Doppel remap()) besser ist, ist ziemlich kompliziert. Normalerweise ist es gut, so wenig Interpolationen wie möglich zu machen, da jede Interpolation verschiedene Artefakte einführt. Vor allem, wenn die Artefakte im Bild nicht einheitlich sind (einige Bereiche wurden weicher als andere). In einigen Fällen können diese Artefakte eine gute visuelle Wirkung auf das Bild haben - wie zum Beispiel die Reduzierung des Jitters. Aber wenn Sie das möchten, können Sie dies auf günstigere und konsistentere Weise erreichen. Zum Beispiel durch Glätten des Originalbildes vor der Neubelegung.

+0

Vielen Dank! Ich habe es getestet und die allgemeine Unverzerrung und Bildverzerrung sieht gut aus, aber die Interpolation scheint nicht richtig zu funktionieren. Während ich zuvor glatte diagonale Linien hatte, sind sie jetzt auf einer kleinen Ebene gezackt. –

+0

Gern geschehen. Ein Teil des ursprünglichen Bildes wurde geglättet, indem Sie es zuerst neu zuordnen (nicht verzerren). Im Falle einer Verzerrungskorrektur sieht dies normalerweise wie Streifen von geglätteten und nicht geglätteten Bereichen aus. Wenn eine zweite Neuabbildung (Skew) Pixel aus geglätteten Bereichen annimmt, sollte dies zu relativ glatten diagonalen Linien führen. Wenn es Pixel aus den Regionen genommen hat, die nicht zu glatt sind, werden die resultierenden Zeilen ein wenig zackig sein.Wenn diese gezackten Zeilen ein Problem darstellen, sollten Sie Original-Image vor der Neu-Zuordnung auf jeden Fall glätten. –

+0

Eigentlich bin ich mir nicht sicher, was Sie mit 'geglättet durch erste Neuabbildung' meinen. Was ich sehe, ist, dass das resultierende Bild sich von meinem ursprünglichen Ansatz mit den zwei Remaps unterscheidet. Es ist dasselbe im großen Maßstab, aber nervös, wenn man genau hinsieht. Wenn Sie möchten, kann ich später einige Screenshots veröffentlichen. Was ich unbedingt brauche, ist, dass die Ergebnisse identisch sind mit der Verwendung von zwei 'Remap's in jeder Schleifeniteration. –

0

Ich stieß auf das gleiche Problem. Ich versuchte AldurDisciples Antwort zu implementieren. Anstatt die Transformation in einer Schleife zu berechnen. Ich habe eine Matte mit mat.at <Vec2f> (x, y) = Vec2f (x, y) und Anwendung perspectiveTransform auf diese Matte. Fügen Sie der Ergebnismatte einen 3. Kanal von "1" hinzu und wenden Sie projectPoints an. Hier ist mein Code

Mat xy(2000, 2500, CV_32FC2); 
float *pxy = (float*)xy.data; 
for (int y = 0; y < 2000; y++) 
    for (int x = 0; x < 2500; x++) 
    { 
     *pxy++ = x; 
     *pxy++ = y; 
    } 

// perspective transformation of coordinates of destination image, 
// which generates the map from destination image to norm points 
Mat pts_undist_norm(2000, 2500, CV_32FC2); 
Mat matPerspective =transRot3x3; 
perspectiveTransform(xy, pts_undist_norm, matPerspective); 

//add 3rd channel of 1 
vector<Mat> channels; 
split(pts_undist_norm, channels); 
Mat channel3(2000, 2500, CV_32FC1, cv::Scalar(float(1.0))); 
channels.push_back(channel3); 
Mat pts_undist_norm_3D(2000, 2500, CV_32FC3); 
merge(channels, pts_undist_norm_3D); 

//projectPoints to extend the map from norm points back to the original captured image 
pts_undist_norm_3D = pts_undist_norm_3D.reshape(0, 5000000); 
Mat pts_dist(5000000, 1, CV_32FC2); 
projectPoints(pts_undist_norm_3D, Mat::zeros(3, 1, CV_64F), Mat::zeros(3, 1, CV_64F), intrinsic, distCoeffs, pts_dist); 
Mat maps[2]; 
pts_dist = pts_dist.reshape(0, 2000); 
split(pts_dist, maps); 

// apply map 
remap(originalImage, skewedImage, maps[0], maps[1], INTER_LINEAR); 

Die Transformationsmatrix zur Norm Punkte verwendet wird, von dem ein Bit in AldurDisciple Antwort verwendet, um verschiedene abzubilden. transRot3x3 besteht aus TVEC und rvec erzeugt durch calibrateCamera.

double transData[] = { 0, 0, tvecs[0].at<double>(0), 0, 0, 
tvecs[0].at<double>(1), 0, 0, tvecs[0].at<double>(2) }; 
Mat translate3x3(3, 3, CV_64F, transData); 
Mat rotation3x3; 
Rodrigues(rvecs[0], rotation3x3); 

Mat transRot3x3(3, 3, CV_64F); 
rotation3x3.col(0).copyTo(transRot3x3.col(0)); 
rotation3x3.col(1).copyTo(transRot3x3.col(1)); 
translate3x3.col(2).copyTo(transRot3x3.col(2)); 

Hinzugefügt:

erkennen ich, wenn die einzige benötigte Karte die letzte Karte ist, warum gerade projectPoints nicht mit mat.at zu einer Matte verwenden (x, y) = Vec2f (x , y, 0).

//generate a 3-channel mat with each entry containing it's own coordinates 
Mat xyz(2000, 2500, CV_32FC3); 
float *pxyz = (float*)xyz.data; 
for (int y = 0; y < 2000; y++) 
    for (int x = 0; x < 2500; x++) 
    { 
     *pxyz++ = x; 
     *pxyz++ = y; 
     *pxyz++ = 0; 
    } 

// project coordinates of destination image, 
// which generates the map from destination image to source image directly 
xyz=xyz.reshape(0, 5000000); 
Mat pts_dist(5000000, 1, CV_32FC2); 
projectPoints(xyz, rvecs[0], tvecs[0], intrinsic, distCoeffs, pts_dist); 
Mat maps[2]; 
pts_dist = pts_dist.reshape(0, 2000); 
split(pts_dist, maps); 

//apply map 
remap(originalImage, skewedImage, maps[0], maps[1], INTER_LINEAR); 
Verwandte Themen