2016-01-12 14 views
17

Stiamo attualmente lavorando a un progetto di analisi delle immagini in cui è necessario identificare gli oggetti scomparsi/comparsi in una scena. Qui ci sono 2 immagini, una catturata prima che un'azione sia stata fatta dal chirurgo e l'altra in seguito.Il flusso ottico ignora i movimenti sparsi

PRIMA: enter image description here DOPO: enter image description here

In primo luogo, abbiamo appena calcolato la differenza tra le 2 immagini ed ecco il risultato (Si noti che ho aggiunto 128 al risultato Mat solo per avere un immagine più bella) :

(DOPO - PRIMA) + 128 enter image description here

l'obiettivo è quello di rilevare che la coppa (freccia rossa) è scomparso dal sce ne e la siringa (freccia nera) è entrata nella scena, in altre parole dovremmo rilevare SOLO le regioni che corrispondono agli oggetti lasciati/inseriti nella scena. Inoltre, è ovvio che gli oggetti in alto a sinistra della scena si sono spostati un po 'dalla loro posizione iniziale. Ho pensato a Optical flow così ho usato OpenCV C++ per calcolare quello del Farneback per vedere se è abbastanza per il nostro caso, ed è qui il risultato che abbiamo ottenuto, seguito dal codice che abbiamo scritto:

FLOW: enter image description here

void drawOptFlowMap(const Mat& flow, Mat& cflowmap, int step, double, const Scalar& color) 
{ 
    cout << flow.channels() << "/" << flow.rows << "/" << flow.cols << endl; 
    for(int y = 0; y < cflowmap.rows; y += step) 
     for(int x = 0; x < cflowmap.cols; x += step) 
     { 
      const Point2f& fxy = flow.at<Point2f>(y, x); 
      line(cflowmap, Point(x,y), Point(cvRound(x+fxy.x), cvRound(y+fxy.y)), color); 
      circle(cflowmap, Point(x,y), 1, color, -1); 
     } 
} 

void MainProcessorTrackingObjects::diffBetweenImagesToTestTrackObject(string pathOfImageCaptured, string pathOfImagesAfterOneAction, string pathOfResultsFolder) 
{ 
    //Preprocessing step... 

    string pathOfImageBefore = StringUtils::concat(pathOfImageCaptured, imageCapturedFileName); 
    string pathOfImageAfter = StringUtils::concat(pathOfImagesAfterOneAction, *it); 

    Mat imageBefore = imread(pathOfImageBefore); 
    Mat imageAfter = imread(pathOfImageAfter); 

    Mat imageResult = (imageAfter - imageBefore) + 128; 
    //   absdiff(imageAfter, imageBefore, imageResult); 
    string imageResultPath = StringUtils::stringFormat("%s%s-color.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str()); 
    imwrite(imageResultPath, imageResult); 

    Mat imageBeforeGray, imageAfterGray; 
    cvtColor(imageBefore, imageBeforeGray, CV_RGB2GRAY); 
    cvtColor(imageAfter, imageAfterGray, CV_RGB2GRAY); 

    Mat imageResultGray = (imageAfterGray - imageBeforeGray) + 128; 
    //   absdiff(imageAfterGray, imageBeforeGray, imageResultGray); 
    string imageResultGrayPath = StringUtils::stringFormat("%s%s-gray.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str()); 
    imwrite(imageResultGrayPath, imageResultGray); 


    //*** Compute FarneBack optical flow 
    Mat opticalFlow; 
    calcOpticalFlowFarneback(imageBeforeGray, imageAfterGray, opticalFlow, 0.5, 3, 15, 3, 5, 1.2, 0); 

    drawOptFlowMap(opticalFlow, imageBefore, 5, 1.5, Scalar(0, 255, 255)); 
    string flowPath = StringUtils::stringFormat("%s%s-flow.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str()); 
    imwrite(flowPath, imageBefore); 

    break; 
} 

E per sapere quanto accurata questo flusso ottico è, ho scritto questo piccolo pezzo di codice che calcola (IMAGEAFTER + fLOW) - IMAGEBEFORE:

//Reference method just to see the accuracy of the optical flow calculation 
Mat accuracy = Mat::zeros(imageBeforeGray.rows, imageBeforeGray.cols, imageBeforeGray.type()); 

strinfor(int y = 0; y < imageAfter.rows; y ++) 
for(int x = 0; x < imageAfter.cols; x ++) 
{ 
    Point2f& fxy = opticalFlow.at<Point2f>(y, x); 
    uchar intensityPointCalculated = imageAfterGray.at<uchar>(cvRound(y+fxy.y), cvRound(x+fxy.x)); 
    uchar intensityPointBefore = imageBeforeGray.at<uchar>(y,x); 
    uchar intensityResult = ((intensityPointCalculated - intensityPointBefore)/2) + 128; 
    accuracy.at<uchar>(y, x) = intensityResult; 
} 
validationPixelBased = StringUtils::stringFormat("%s%s-validationPixelBased.png",pathOfResultsFolder.c_str(), fileNameWithoutFrameIndex.c_str()); 
imwrite(validationPixelBased, accuracy); 

L'intento di avere t il suo ((intensityPointCalculated - intensityPointBefore)/2) + 128; è solo per avere un'immagine comprensibile.

Risultato Immagine:

enter image description here

Dal momento che rileva tutte le regioni che sono state spostate/entrato/uscito di scena, pensiamo che il OpticalFlow non è sufficiente per rilevare solo le regioni che rappresentano gli oggetti scomparsi/apparso nella scena. C'è un modo per ignorare i movimenti sparsi rilevati da opticalFlow? O esiste un modo alternativo per rilevare ciò di cui abbiamo bisogno?

risposta

9

Diciamo che l'obiettivo qui è identificare le regioni con oggetti appariti/scomparsi, ma non quelli che sono presenti in entrambe le immagini ma solo posizioni spostate.

Il flusso ottico dovrebbe essere una buona strada da percorrere, come avete già fatto. Tuttavia il problema è come viene valutato il risultato. Contrariamente al diff tra pixel e pixel che mostra non ha tolleranza alle variazioni di rotazione/ridimensionamento, è possibile eseguire una corrispondenza delle caratteristiche (SIFT ecc. Check out here for what you can use with opencv)

Ecco cosa ho ottenuto con le funzioni buone per tenere traccia della tua immagine prima.

GoodFeaturesToTrackDetector detector; 
vector<KeyPoint> keyPoints; 
vector<Point2f> kpBefore, kpAfter; 
detector.detect(imageBefore, keyPoints); 

enter image description here

Invece di flusso ottico denso, è possibile utilizzare un flusso sparse e tenere traccia solo le funzionalità,

vector<uchar> featuresFound; 
vector<float> err; 
calcOpticalFlowPyrLK(imageBeforeGray, imageAfterGray, keyPointsBefore, keyPointsAfter, featuresFound, err, Size(PATCH_SIZE , PATCH_SIZE)); 

output include valori FeaturesFound e di errore. Ho semplicemente usato una soglia qui per distinguere le caratteristiche mosse e quelle scomparse ineguagliabili.

vector<KeyPoint> kpNotMatched; 
for (int i = 0; i < kpBefore.size(); i++) { 
    if (!featuresFound[i] || err[i] > ERROR_THRESHOLD) { 
     kpNotMatched.push_back(KeyPoint(kpBefore[i], 1)); 
    } 
} 
Mat output; 
drawKeypoints(imageBefore, kpNotMatched, output, Scalar(0, 0, 255)); 

enter image description here

Le restanti caratteristiche non correttamente appaiati possono essere filtrati. Qui ho usato il filtro medio semplice più soglia per ottenere la maschera della regione appena apparso.

Mat mask = Mat::zeros(imageBefore.rows, imageBefore.cols, CV_8UC1); 
for (int i = 0; i < kpNotMatched.size(); i++) { 
    mask.at<uchar>(kpNotMatched[i].pt) = 255; 
} 
blur(mask, mask, Size(BLUR_SIZE, BLUR_SIZE)); 
threshold(mask, mask, MASK_THRESHOLD, 255, THRESH_BINARY); 

enter image description here

e poi trovare la sua convesso per mostrare la regione nell'immagine originale (in giallo).

vector<vector<Point> > contours; 
vector<Vec4i> hierarchy; 
findContours(mask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); 

vector<vector<Point> >hull(contours.size()); 
for(int i = 0; i < contours.size(); i++) { 
    convexHull(Mat(contours[i]), hull[i], false); 
} 
for(int i = 0; i < contours.size(); i++) { 
    drawContours(output, hull, i, Scalar(0, 255, 255), 3, 8, vector<Vec4i>(), 0, Point()); 
} 

enter image description here

E semplicemente farlo nel modo inverso (corrispondenza da imageAfter a imageBefore) per ottenere le regioni apparso. :)

+0

io non sono in grado di riprodurre gli stessi risultati .. puoi per favore dirmi i valori che stai utilizzando per queste costanti: BLUR_SIZE, ERROR_THRESHOLD, MASK_THRESHOLD – Maystro

+0

In base a l'immagine di input 960 x 540, avevo BLUR_SIZE = 35, ERROR_THRESHOLD = 30, MASK_THRESHOLD = 1.5. Potresti anche voler modificare altri parametri come i livelli sparsi del piramide del flusso ottico, le dimensioni delle patch, ecc. Tuttavia, la semplice soglia costante potrebbe non funzionare bene in tutte le situazioni e potresti voler applicare strategie più sofisticate basate sui tuoi casi d'uso. – myin528

+0

Grazie per il vostro supporto. La tua risposta non copre la maggior parte dei miei casi, ma ti darò la taglia poiché è abbastanza vicina. – Maystro

0

Si potrebbe provare un approccio a due punte - L'uso del metodo di differenza dell'immagine è ottimo per rilevare oggetti che entrano ed escono dalla scena, a condizione che il colore dell'oggetto sia diverso dal colore dello sfondo. Ciò che mi colpisce è che sarebbe notevolmente migliorato se fosse possibile rimuovere gli oggetti che sono stati spostati prima di utilizzare il metodo.

C'è un ottimo metodo OpenCV per il rilevamento di oggetti here che trova i punti di interesse in un'immagine per rilevare la traduzione di un oggetto.Penso che si potrebbe ottenere quello che vuoi con il seguente metodo -

1 confrontare le immagini con il codice OpenCV ed evidenziare gli oggetti in movimento in entrambe le immagini

2 Color in oggetti rilevati con sfondo l'altra immagine allo stesso insieme di pixel (o qualcosa di simile) per ridurre la differenza di immagini che è causato da immagini in movimento

3 Trova la differenza immagine che ora dovrebbe avere grandi oggetti principali e manufatti minori lasciati dalle immagini in movimento

4 soglia per una certa dimensione dell'oggetto rilevato in immagine differenza

5 redigere un elenco di probabili candidati

ci sono altre alternative per il monitoraggio oggetto, quindi non ci può essere il codice che ti piace di più, ma il processo dovrebbe essere a posto per quello che state facendo, credo.

1

Ecco cosa ho provato;

  • Rileva le regioni che hanno subito una modifica.Per questo uso semplici differenze di frame, soglie, operazioni morfologiche e convessità.
  • Trova punti funzione di queste regioni in entrambe le immagini e verifica se corrispondono. Una buona corrispondenza in una regione indica che non ha subito un cambiamento significativo. Cattiva corrispondenza significa che le due regioni sono ora diverse. Per questo uso la distanza BOW e Bhattacharyya.

I parametri potrebbero richiedere la sintonizzazione. Ho usato valori che hanno funzionato solo per le due immagini di esempio. Come rivelatore di caratteristiche/descrittore ho usato SIFT (non libero). Puoi provare altri rilevatori e descrittori.

diference Image: diff

Regioni: regions

Changes (Rosso: inserimento/rimozione, gialli: movimento sparso): changes

// for non-free modules SIFT/SURF 
cv::initModule_nonfree(); 

Mat im1 = imread("1.png"); 
Mat im2 = imread("2.png"); 

// downsample 
/*pyrDown(im1, im1); 
pyrDown(im2, im2);*/ 

Mat disp = im1.clone() * .5 + im2.clone() * .5; 
Mat regions = Mat::zeros(im1.rows, im1.cols, CV_8U); 

// gray scale 
Mat gr1, gr2; 
cvtColor(im1, gr1, CV_BGR2GRAY); 
cvtColor(im2, gr2, CV_BGR2GRAY); 
// simple frame differencing 
Mat diff; 
absdiff(gr1, gr2, diff); 
// threshold the difference to obtain the regions having a change 
Mat bw; 
adaptiveThreshold(diff, bw, 255, CV_ADAPTIVE_THRESH_GAUSSIAN_C, CV_THRESH_BINARY_INV, 15, 5); 
// some post processing 
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3)); 
morphologyEx(bw, bw, MORPH_CLOSE, kernel, Point(-1, -1), 4); 
// find contours in the change image 
Mat cont = bw.clone(); 
vector<vector<Point> > contours; 
vector<Vec4i> hierarchy; 
findContours(cont, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE, Point(0, 0)); 
// feature detector, descriptor and matcher 
Ptr<FeatureDetector> featureDetector = FeatureDetector::create("SIFT"); 
Ptr<DescriptorExtractor> descExtractor = DescriptorExtractor::create("SIFT"); 
Ptr<DescriptorMatcher> descMatcher = DescriptorMatcher::create("FlannBased"); 

if(featureDetector.empty() || descExtractor.empty() || descMatcher.empty()) 
{ 
    cout << "featureDetector or descExtractor or descMatcher was not created" << endl; 
    exit(0); 
} 
// BOW 
Ptr<BOWImgDescriptorExtractor> bowExtractor = new BOWImgDescriptorExtractor(descExtractor, descMatcher); 

int vocabSize = 10; 
TermCriteria terminate_criterion; 
terminate_criterion.epsilon = FLT_EPSILON; 
BOWKMeansTrainer bowTrainer(vocabSize, terminate_criterion, 3, KMEANS_PP_CENTERS); 

Mat mask(bw.rows, bw.cols, CV_8U); 
for(size_t j = 0; j < contours.size(); j++) 
{ 
    // discard regions that a below a specific threshold 
    Rect rect = boundingRect(contours[j]); 
    if ((double)(rect.width * rect.height)/(bw.rows * bw.cols) < .01) 
    { 
     continue; // skip this region as it's too small 
    } 
    // prepare a mask for each region 
    mask.setTo(0); 
    vector<Point> hull; 
    convexHull(contours[j], hull); 
    fillConvexPoly(mask, hull, Scalar::all(255), 8, 0); 

    fillConvexPoly(regions, hull, Scalar::all(255), 8, 0); 

    // extract keypoints from the region 
    vector<KeyPoint> im1Keypoints, im2Keypoints; 
    featureDetector->detect(im1, im1Keypoints, mask); 
    featureDetector->detect(im2, im2Keypoints, mask); 
    // get their descriptors 
    Mat im1Descriptors, im2Descriptors; 
    descExtractor->compute(im1, im1Keypoints, im1Descriptors); 
    descExtractor->compute(im2, im2Keypoints, im2Descriptors); 

    if ((0 == im1Keypoints.size()) || (0 == im2Keypoints.size())) 
    { 
     // mark this contour as object arrival/removal region 
     drawContours(disp, contours, j, Scalar(0, 0, 255), 2); 
     continue; 
    } 

    // bag-of-visual-words 
    Mat vocabulary = bowTrainer.cluster(im1Descriptors); 
    bowExtractor->setVocabulary(vocabulary); 
    // get the distribution of visual words in the region for both images 
    vector<vector<int>> idx1, idx2; 
    bowExtractor->compute(im1, im1Keypoints, im1Descriptors, &idx1); 
    bowExtractor->compute(im2, im2Keypoints, im2Descriptors, &idx2); 
    // compare the distributions 
    Mat hist1 = Mat::zeros(vocabSize, 1, CV_32F); 
    Mat hist2 = Mat::zeros(vocabSize, 1, CV_32F); 

    for (int i = 0; i < vocabSize; i++) 
    { 
     hist1.at<float>(i) = (float)idx1[i].size(); 
     hist2.at<float>(i) = (float)idx2[i].size(); 
    } 
    normalize(hist1, hist1); 
    normalize(hist2, hist2); 
    double comp = compareHist(hist1, hist2, CV_COMP_BHATTACHARYYA); 

    cout << comp << endl; 
    // low BHATTACHARYYA distance means a good match of features in the two regions 
    if (comp < .2) 
    { 
     // mark this contour as a region having sparse motion 
     drawContours(disp, contours, j, Scalar(0, 255, 255), 2); 
    } 
    else 
    { 
     // mark this contour as object arrival/removal region 
     drawContours(disp, contours, j, Scalar(0, 0, 255), 2); 
    } 
} 
+0

Ho dovuto aggiungere if ((im1Keypoints.size() Maystro

Problemi correlati