2015-04-29 11 views
9

Ho un circuito chiuso, dove ottengo un'immagine della telecamera, non la distorco e la trasformo anche in base a qualche trasformazione (ad esempio una trasformazione prospettica). Ho già capito di utilizzare cv::remap(...) per ogni operazione, che è già molto più efficiente rispetto all'utilizzo di operazioni con matrice semplice.Come combinare due operazioni rimappa() in una?

A mio parere, dovrebbe essere possibile combinare le mappe di ricerca in una e richiamare la correzione una sola volta in ogni ciclo iterativo. C'è un modo canonico per fare questo? Preferirei non implementare tutte le cose di interpolazione da solo.

Nota: la procedura dovrebbe funzionare con mappe di dimensioni diverse. Nel mio caso particolare la distorsione conserva le dimensioni dell'immagine, mentre l'altra trasformazione scala l'immagine in una dimensione diversa.

Codice per l'illustrazione:

// 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); 
} 

risposta

2

Nel caso di due mappature generali, non c'è altra scelta che utilizzare l'approccio suggerito da @MichaelBurdinov.

Tuttavia, nel caso speciale di due mapping con mappature inverse note, un approccio alternativo consiste nel calcolare manualmente le mappe. Questo approccio manuale è più accurato di quello del rimappa doppio, poiché non implica l'interpolazione delle mappe di coordinate.

In pratica, la maggior parte delle applicazioni interessanti corrispondono a questo caso speciale. Lo fa anche nel tuo caso perché la tua prima mappa corrisponde alla distorsione dell'immagine (la cui operazione inversa è la distorsione dell'immagine, che è associata a un modello analitico ben noto) e la tua seconda mappa corrisponde a una trasformazione prospettica (il cui inverso può essere espresso analiticamente).

Calcolare manualmente le mappe è in realtà abbastanza semplice. Come indicato nella documentazione (link) queste mappe contengono, per ogni pixel nell'immagine di destinazione, le coordinate (x, y) dove trovare l'intensità appropriata nell'immagine sorgente. Il frammento di codice seguente mostra come calcolare le mappe manualmente nel caso:

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); 

Nota: H sopra è la vostra prospettiva trasformazione mentre K dovrebbe essere la matrice telecamera associata all'immagine non distorta, quindi dovrebbe essere quello che nel il codice si chiama newCameraMatrix (che BTW non è un argomento di output di initUndistortRectifyMap). A seconda dei dati specifici, potrebbero anche esserci alcuni casi aggiuntivi da gestire (ad es. Divisione per pt(2) quando potrebbe essere zero, ecc.).

+0

Ottima risposta e l'algoritmo funziona come desiderato per me. 'img2.cols' probabilmente dovrebbe dire' dst_width'. Correggerò anche il mio snippet di codice per riflettere l'origine di 'newCameraMatrix'. –

+0

@DimitriSchachmann Grazie, l'errore di battitura è stato corretto. – AldurDisciple

4

È possibile applicare rimappare su undistortMapX e undistortMapY.

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

di quanto si può utilizzare:

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

Funziona perché skewMaps e undistortMaps sono array di coordinate in immagine, quindi dovrebbe essere simile a prendere posizione su posizione ...

Modifica (rispondi ai commenti):

Penso di aver bisogno di fare qualche chiarimento. la funzione rimappa() calcola i pixel nella nuova immagine dai pixel della vecchia immagine. In caso di interpolazione lineare, ogni pixel nella nuova immagine è una media ponderata di 4 pixel rispetto alla vecchia immagine. I pesi differiscono da pixel a pixel in base ai valori delle mappe fornite. Se il valore è più o meno intero, allora la maggior parte del peso viene preso da un singolo pixel. Di conseguenza, la nuova immagine risulterà nitida rispetto all'immagine originale. D'altra parte, se il valore è lontano dall'essere intero (cioè intero + 0,5), i pesi sono simili. Questo creerà un effetto levigante. Per avere un'idea di cosa sto parlando, guarda l'immagine non distorta. Vedrai che alcune parti dell'immagine sono più nitide/uniformi rispetto ad altre parti.

Ora torniamo alla spiegazione di cosa è successo quando hai combinato due operazioni di rimappatura in una. Le coordinate nelle mappe combinate sono corrette, vale a dire che pixel in skewedImage viene calcolato da 4 pixel corretti di originalImage con pesi corretti. Ma non è identico al risultato di due operazioni di rimappatura. Ogni pixel in un'immagine non distorta è una media ponderata di 4 pixel rispetto a originalImage. Ciò significa che ciascun pixel di skewedImage sarebbe una media ponderata di 9-16 pixel da orginalImage. Conclusione: l'utilizzo di single rimap() può NON possibilmente dare un risultato identico a due usi di rimappa().

La discussione su quale delle due immagini possibili (rimappatura singola() rispetto a rimappatura doppia()) è migliore è piuttosto complicata. Normalmente è bene fare il minor numero possibile di interpolazioni, perché ogni interpolazione introduce diversi artefatti. Soprattutto se gli artefatti non sono uniformi nell'immagine (alcune regioni sono diventate più lisce di altre). In alcuni casi questi artefatti possono avere un buon effetto visivo sull'immagine, ad esempio la riduzione di alcuni dei jitter. Ma se questo è ciò che vuoi, puoi ottenerlo in modi più economici e coerenti. Ad esempio, lisciando l'immagine originale prima di rimappare.

+0

Grazie! L'ho testato e la distorsione generale e l'inclinazione dell'immagine sembrano buone, ma l'interpolazione sembra non funzionare correttamente. Mentre in precedenza avevo linee diagonali lisce, ora sono un po 'frastagliate su un piccolo livello. –

+0

Siete i benvenuti. Una parte dell'immagine originale è stata lisciata dalla prima rimappatura (non distorsione) applicata. In caso di correzione della distorsione questo di solito appare come strisce di regioni levigate e non levigate. Se la seconda rimappatura (obliquità) richiede pixel dalle regioni levigate, dovrebbe risultare in linee diagonali relativamente regolari. Se ci sono voluti pixel dalle regioni che non sono troppo lisce, le linee risultanti saranno un po 'frastagliate.Se quelle righe seghettate sono un problema, in ogni caso dovresti lisciare originalImage prima di rimappare. –

+0

In realtà non sono sicuro di cosa intendi per 'attenuato dalla prima rimappatura'. Quello che vedo è che l'immagine risultante sembra diversa dal mio approccio originale con le due rimappature. È uguale su grande scala ma nervoso quando si guarda da vicino. Se lo desideri, posso postare alcuni screenshot in seguito. Ciò di cui ho assolutamente bisogno è che i risultati siano identici all'uso di due rimappature in ogni ciclo iterativo. –

0

Mi sono imbattuto nello stesso problema. Ho cercato di implementare la risposta di AldurDisciple. Invece di calcolare la trasformazione in un ciclo. Sto avendo un tappetino con mat.at <Vec2f> (x, y) = Vec2f (x, y) e l'applicazione perspectiveTransform a questo tappeto. Aggiungi un terzo canale di "1" al risultato mat e applica projectPoints. Ecco il mio codice

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); 

La matrice di trasformazione utilizzato per mappare i punti norma è un po 'diverso da quello utilizzato nella risposta di AldurDisciple. transRot3x3 è composto da TVEC e rvec generato da 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)); 

Aggiunto:

ho capito se l'unica mappa necessaria è la mappa finale perché non basta usare projectPoints ad una stuoia con mat.at (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);