2014-10-17 17 views
7

Quello che sto cercando di fare è misurare lo spessore dei telai degli occhiali. Ho avuto l'idea di misurare lo spessore dei contorni del telaio (potrebbe essere un modo migliore?). Finora ho delineato la cornice degli occhiali, ma ci sono degli spazi vuoti in cui le linee non si incontrano. Ho pensato di usare HoughLinesP, ma non sono sicuro se questo è ciò di cui ho bisogno.Rilevazione degli occhiali

Finora ho condotto le seguenti operazioni: immagine

  • Converti in scala di grigi
  • Crea ROI intorno alla zona degli occhi/occhiali
  • sfocatura dell'immagine
  • Dilatare l'immagine (hanno fatto questo per rimuovere eventuali occhiali con cornice sottile)
  • Comportamento Rilevamento bordo Canny
  • Contorni trovati

Questi sono i risultati:

Questo è il mio codice finora:

//convert to grayscale 
cv::Mat grayscaleImg; 
cv::cvtColor(img, grayscaleImg, CV_BGR2GRAY); 

//create ROI 
cv::Mat eyeAreaROI(grayscaleImg, centreEyesRect); 
cv::imshow("roi", eyeAreaROI); 

//blur 
cv::Mat blurredROI; 
cv::blur(eyeAreaROI, blurredROI, Size(3,3)); 
cv::imshow("blurred", blurredROI); 

//dilate thin lines 
cv::Mat dilated_dst; 
int dilate_elem = 0; 
int dilate_size = 1; 
int dilate_type = MORPH_RECT; 

cv::Mat element = getStructuringElement(dilate_type, 
    cv::Size(2*dilate_size + 1, 2*dilate_size+1), 
    cv::Point(dilate_size, dilate_size)); 

cv::dilate(blurredROI, dilated_dst, element); 
cv::imshow("dilate", dilated_dst); 

//edge detection 
int lowThreshold = 100; 
int ratio = 3; 
int kernel_size = 3;  

cv::Canny(dilated_dst, dilated_dst, lowThreshold, lowThreshold*ratio, kernel_size); 

//create matrix of the same type and size as ROI 
Mat dst; 
dst.create(eyeAreaROI.size(), dilated_dst.type()); 
dst = Scalar::all(0); 

dilated_dst.copyTo(dst, dilated_dst); 
cv::imshow("edges", dst); 

//join the lines and fill in 
vector<Vec4i> hierarchy; 
vector<vector<Point>> contours; 

cv::findContours(dilated_dst, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); 
cv::imshow("contours", dilated_dst); 

Non sono del tutto sicuro di quello che sarebbero i prossimi passi o come ho detto sopra, se dovessi usare HoughLinesP e come implementarlo. Qualsiasi aiuto è molto apprezzato!

+1

Hai considerato la segmentazione? Con ogni mezzo necessario, separa i tuoi pixel in due gruppi: (1) gli occhiali appartenenti ai pixel (2) non appartenenti ai pixel degli occhiali. Usa la nozione di super pixel: ogni pixel dovrebbe avere varie caratteristiche: colore, posizione, se appartengono a qualsiasi contorno che hai già trovato, se sono sui bordi, ecc. – William

+1

penso che i tuoi contorni siano buoni perché ci sono alcune lacune. Prova a dilatare i tuoi abili risultati prima dell'estrazione del contorno e verifica i contorni disegnandoli con una nuova immagine. Se i contorni vengono estratti correttamente, è possibile calcolare la trasformazione della distanza dal contorno pieno invertito. lo spessore del telaio potrebbe essere approssimato dalla distanza massima trovata * 2. – Micka

+1

Ciao @William, grazie per la risposta! Ho pensato di eseguire il rilevamento della pelle e la segmentazione da lì. Inoltre ho esaminato il posizionamento probabile e simili. Non sono sicuro di come rilevare i pixel che appartengono a cosa, ma esamineremo il problema. – LKB

risposta

3

Penso che ci siano 2 problemi principali.

  1. segmento della struttura di vetro

  2. trovare lo spessore del telaio segmentato

io ora postare un modo di segmentare gli occhiali della vostra immagine del campione. Forse questo metodo funzionerà anche per immagini diverse, ma probabilmente dovrai aggiustare i parametri, o potresti essere in grado di usare le idee principali.

L'idea principale è: Innanzitutto, trova il contorno più grande nell'immagine, che dovrebbe essere gli occhiali. In secondo luogo, trova i due più grandi contorni all'interno del precedente più grande contorno, che dovrebbero essere gli occhiali all'interno della cornice!

Io uso questa immagine come input (che dovrebbe essere la vostra immagine sfocata ma non dilatato):

enter image description here

// this functions finds the biggest X contours. Probably there are faster ways, but it should work... 
std::vector<std::vector<cv::Point>> findBiggestContours(std::vector<std::vector<cv::Point>> contours, int amount) 
{ 
    std::vector<std::vector<cv::Point>> sortedContours; 

    if(amount <= 0) amount = contours.size(); 
    if(amount > contours.size()) amount = contours.size(); 

    for(int chosen = 0; chosen < amount;) 
    { 
     double biggestContourArea = 0; 
     int biggestContourID = -1; 
     for(unsigned int i=0; i<contours.size() && contours.size(); ++i) 
     { 
      double tmpArea = cv::contourArea(contours[i]); 
      if(tmpArea > biggestContourArea) 
      { 
       biggestContourArea = tmpArea; 
       biggestContourID = i; 
      } 
     } 

     if(biggestContourID >= 0) 
     { 
      //std::cout << "found area: " << biggestContourArea << std::endl; 
      // found biggest contour 
      // add contour to sorted contours vector: 
      sortedContours.push_back(contours[biggestContourID]); 
      chosen++; 
      // remove biggest contour from original vector: 
      contours[biggestContourID] = contours.back(); 
      contours.pop_back(); 
     } 
     else 
     { 
      // should never happen except for broken contours with size 0?!? 
      return sortedContours; 
     } 

    } 

    return sortedContours; 
} 

int main() 
{ 
    cv::Mat input = cv::imread("../Data/glass2.png", CV_LOAD_IMAGE_GRAYSCALE); 
    cv::Mat inputColors = cv::imread("../Data/glass2.png"); // used for displaying later 
    cv::imshow("input", input); 

    //edge detection 
    int lowThreshold = 100; 
    int ratio = 3; 
    int kernel_size = 3;  

    cv::Mat canny; 
    cv::Canny(input, canny, lowThreshold, lowThreshold*ratio, kernel_size); 
    cv::imshow("canny", canny); 

    // close gaps with "close operator" 
    cv::Mat mask = canny.clone(); 
    cv::dilate(mask,mask,cv::Mat()); 
    cv::dilate(mask,mask,cv::Mat()); 
    cv::dilate(mask,mask,cv::Mat()); 
    cv::erode(mask,mask,cv::Mat()); 
    cv::erode(mask,mask,cv::Mat()); 
    cv::erode(mask,mask,cv::Mat()); 

    cv::imshow("closed mask",mask); 

    // extract outermost contour 
    std::vector<cv::Vec4i> hierarchy; 
    std::vector<std::vector<cv::Point>> contours; 
    //cv::findContours(mask, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); 
    cv::findContours(mask, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); 


    // find biggest contour which should be the outer contour of the frame 
    std::vector<std::vector<cv::Point>> biggestContour; 
    biggestContour = findBiggestContours(contours,1); // find the one biggest contour 
    if(biggestContour.size() < 1) 
    { 
     std::cout << "Error: no outer frame of glasses found" << std::endl; 
     return 1; 
    } 

    // draw contour on an empty image 
    cv::Mat outerFrame = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1); 
    cv::drawContours(outerFrame,biggestContour,0,cv::Scalar(255),-1); 
    cv::imshow("outer frame border", outerFrame); 

    // now find the glasses which should be the outer contours within the frame. therefore erode the outer border ;) 
    cv::Mat glassesMask = outerFrame.clone(); 
    cv::erode(glassesMask,glassesMask, cv::Mat()); 
    cv::imshow("eroded outer",glassesMask); 

    // after erosion if we dilate, it's an Open-Operator which can be used to clean the image. 
    cv::Mat cleanedOuter; 
    cv::dilate(glassesMask,cleanedOuter, cv::Mat()); 
    cv::imshow("cleaned outer",cleanedOuter); 


    // use the outer frame mask as a mask for copying canny edges. The result should be the inner edges inside the frame only 
    cv::Mat glassesInner; 
    canny.copyTo(glassesInner, glassesMask); 

    // there is small gap in the contour which unfortunately cant be closed with a closing operator... 
    cv::dilate(glassesInner, glassesInner, cv::Mat()); 
    //cv::erode(glassesInner, glassesInner, cv::Mat()); 
    // this part was cheated... in fact we would like to erode directly after dilation to not modify the thickness but just close small gaps. 
    cv::imshow("innerCanny", glassesInner); 


    // extract contours from within the frame 
    std::vector<cv::Vec4i> hierarchyInner; 
    std::vector<std::vector<cv::Point>> contoursInner; 
    //cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE); 
    cv::findContours(glassesInner, contoursInner, hierarchyInner, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); 

    // find the two biggest contours which should be the glasses within the frame 
    std::vector<std::vector<cv::Point>> biggestInnerContours; 
    biggestInnerContours = findBiggestContours(contoursInner,2); // find the one biggest contour 
    if(biggestInnerContours.size() < 1) 
    { 
     std::cout << "Error: no inner frames of glasses found" << std::endl; 
     return 1; 
    } 

    // draw the 2 biggest contours which should be the inner glasses 
    cv::Mat innerGlasses = cv::Mat::zeros(mask.rows, mask.cols, CV_8UC1); 
    for(unsigned int i=0; i<biggestInnerContours.size(); ++i) 
     cv::drawContours(innerGlasses,biggestInnerContours,i,cv::Scalar(255),-1); 

    cv::imshow("inner frame border", innerGlasses); 

    // since we dilated earlier and didnt erode quite afterwards, we have to erode here... this is a bit of cheating :-(
    cv::erode(innerGlasses,innerGlasses,cv::Mat()); 

    // remove the inner glasses from the frame mask 
    cv::Mat fullGlassesMask = cleanedOuter - innerGlasses; 
    cv::imshow("complete glasses mask", fullGlassesMask); 

    // color code the result to get an impression of segmentation quality 
    cv::Mat outputColors1 = inputColors.clone(); 
    cv::Mat outputColors2 = inputColors.clone(); 
    for(int y=0; y<fullGlassesMask.rows; ++y) 
     for(int x=0; x<fullGlassesMask.cols; ++x) 
     { 
      if(!fullGlassesMask.at<unsigned char>(y,x)) 
       outputColors1.at<cv::Vec3b>(y,x)[1] = 255; 
      else 
       outputColors2.at<cv::Vec3b>(y,x)[1] = 255; 

     } 

    cv::imshow("output", outputColors1); 

    /* 
    cv::imwrite("../Data/Output/face_colored.png", outputColors1); 
    cv::imwrite("../Data/Output/glasses_colored.png", outputColors2); 
    cv::imwrite("../Data/Output/glasses_fullMask.png", fullGlassesMask); 
    */ 

    cv::waitKey(-1); 
    return 0; 
} 

ottengo questo risultato per la segmentazione:

enter image description here

la sovrapposizione nell'immagine originale ti darà un'impressione di qualità:

enter image description here

e inversa:

enter image description here

Ci sono alcune parti difficili nel codice e non è riordinato ancora. Spero sia comprensibile.

Il passo successivo sarebbe quello di calcolare lo spessore del telaio segmentato. Il mio suggerimento è di calcolare la trasformazione a distanza della maschera inversa. Da questo si vorrà calcolare un rilevamento di cresta o scheletrizzare la maschera per trovare la cresta. Successivamente utilizzare il valore mediano delle distanze della cresta.

In ogni modo spero che questo intervento può aiutare un po ', anche se non è ancora una soluzione.

+0

Ciao Micka, grazie mille per aver trovato il tempo per aiutarmi.Ho eseguito il tuo codice e ho il seguente output: http://i.imgur.com/aNnXOlq.png È un po 'diverso dal tuo (come sarebbe successo?), Cioè uno dei contorni del vetro interno non ha chiuso. Qualche idea su come chiuderei questo? Daremo un'occhiata al Web e giocherò con il codice nel frattempo, vediamo se riesco a sistemarlo. – LKB

+1

Oops, hai dimenticato di sfocare prima l'immagine. :) – LKB

+1

Fai attenzione che potresti avere problemi simili per immagini diverse! – Micka

1

A seconda dell'illuminazione, del colore del telaio ecc. Questo può o non può funzionare, ma per quanto riguarda il semplice rilevamento del colore per separare il telaio? Il colore del telaio sarà solitamente molto più scuro della pelle umana. Finirai con un'immagine binaria (solo in bianco e nero) e calcolando il numero (area) di pixel neri otterrai l'area del fotogramma.

Un altro modo possibile è ottenere un migliore rilevamento dei bordi, regolando/dilatando/erodendo/entrambi fino ad ottenere contorni migliori. Sarà inoltre necessario differenziare il contorno dalle lenti e quindi applicare cvContourArea.

+0

Grazie per la tua risposta, Sonny! Non sono così sicuro del rilevamento dei colori, ma posso provarlo! Penso che il tuo ultimo suggerimento di mettere a punto il rilevamento dei contorni possa funzionare meglio, quindi vedrò anche in che modo. – LKB