Ho sviluppato un'applicazione per contare oggetti circolari come colonie batteriche dalle immagini.Rileva cluster di oggetti circolari mediante soglie adattative e analisi di forma iterative
Ciò che rende semplice il fatto che gli oggetti sono generalmente ben distinti dallo sfondo.
Tuttavia, poche difficoltà effettuare l'analisi ingannevole:
- Lo sfondo presenterà graduale e rapido cambiamento di intensità.
- Nei bordi del contenitore, l'oggetto sarà ellittico anziché circolare.
- I bordi degli oggetti a volte sono piuttosto sfocati.
- Gli oggetti verranno raggruppati.
- L'oggetto può essere molto piccolo (6px di diametro)
- In definitiva, gli algoritmi verranno utilizzati (tramite GUI) da persone che non hanno una profonda comprensione dell'analisi delle immagini, quindi i parametri devono essere intuitivi e molto pochi.
Il problema è stato affrontato molte volte nella letteratura scientifica e "risolto", ad esempio utilizzando la trasformazione circolare Hough o gli approcci spartiacque, ma non sono mai stato soddisfatto dai risultati.
Un approccio semplice che è stato descritto è quello di ottenere il primo piano mediante soglie adattive e divisione (come ho descritto in this post) gli oggetti in cluster utilizzando la trasformazione a distanza.
Ho implementato con successo questo metodo, ma non è sempre stato possibile gestire cambiamenti improvvisi di intensità. Inoltre, mi è stato chiesto dai colleghi di uscire con un approccio più "nuovo".
Ho quindi cercato un nuovo metodo per estrarre in primo piano.
Ho quindi studiato altri metodi di rilevamento soglia/blob. Ho provato MSER ma ho scoperto che non erano molto robusti e piuttosto lenti nel mio caso.
io alla fine è venuto fuori con un algoritmo che, finora, mi dà ottimi risultati:
- ho diviso i tre canali della mia immagine e ridurre il loro rumore (sfocatura/sfocatura mediana). Per ogni canale:
- Applico un'implementazione manuale del primo passaggio della soglia adattativa calcolando la differenza assoluta tra il canale originale e uno sfocato (con una sfocatura del kernel di grandi dimensioni). Quindi, per tutti i valori rilevanti di soglia:
- applico una soglia sul risultato di 2)
- trovare contorni
- convalidare o contorni sulla concessione della loro forma (dimensioni, settore, convessità ...
- solo le regioni continue valide (ovvero delimitate da contorni) vengono quindi ridisegnate in un accumulatore (1 accumulatore per canale).
- Dopo aver accumulato regioni continue su valori di soglia, ho terminato con una mappa di "punteggi di regioni".Le regioni con l'intensità più alta sono quelle che soddisfano più spesso i criteri del filtro morfologico.
- Le tre mappe (uno per canale) vengono poi convertiti in scala di grigi e thresholded (la soglia è controllato dall'utente)
solo per mostrarvi il tipo di immagine che ho di lavorare con: Questa immagine rappresenta parte di 3 immagini campione nella parte superiore e il risultato del mio algoritmo (blu = primo piano) delle rispettive parti in basso.
Ecco la mia applicazione C++ di: 3-7
/*
* cv::Mat dst[3] is the result of the absolute difference between original and convolved channel.
* MCF(std::vector<cv::Point>, int, int) is a filter function that returns an positive int only if the input contour is valid.
*/
/* Allocate 3 matrices (1 per channel)*/
cv::Mat accu[3];
/* We define the maximal threshold to be tried as half of the absolute maximal value in each channel*/
int maxBGR[3];
for(unsigned int i=0; i<3;i++){
double min, max;
cv::minMaxLoc(dst[i],&min,&max);
maxBGR[i] = max/2;
/* In addition, we fill accumulators by zeros*/
accu[i]=cv::Mat(compos[0].rows,compos[0].cols,CV_8U,cv::Scalar(0));
}
/* This loops are intended to be multithreaded using
#pragma omp parallel for collapse(2) schedule(dynamic)
For each channel */
for(unsigned int i=0; i<3;i++){
/* For each value of threshold (m_step can be > 1 in order to save time)*/
for(int j=0;j<maxBGR[i] ;j += m_step){
/* Temporary matrix*/
cv::Mat tmp;
std::vector<std::vector<cv::Point> > contours;
/* Thresholds dst by j*/
cv::threshold(dst[i],tmp, j, 255, cv::THRESH_BINARY);
/* Finds continous regions*/
cv::findContours(tmp, contours, CV_RETR_LIST, CV_CHAIN_APPROX_TC89_L1);
if(contours.size() > 0){
/* Tests each contours*/
for(unsigned int k=0;k<contours.size();k++){
int valid = MCF(contours[k],m_minRad,m_maxRad);
if(valid>0){
/* I found that redrawing was very much faster if the given contour was copied in a smaller container.
* I do not really understand why though. For instance,
cv::drawContours(miniTmp,contours,k,cv::Scalar(1),-1,8,cv::noArray(), INT_MAX, cv::Point(-rect.x,-rect.y));
is slower especially if contours is very long.
*/
std::vector<std::vector<cv::Point> > tpv(1);
std::copy(contours.begin()+k, contours.begin()+k+1, tpv.begin());
/* We make a Roi here*/
cv::Rect rect = cv::boundingRect(tpv[0]);
cv::Mat miniTmp(rect.height,rect.width,CV_8U,cv::Scalar(0));
cv::drawContours(miniTmp,tpv,0,cv::Scalar(1),-1,8,cv::noArray(), INT_MAX, cv::Point(-rect.x,-rect.y));
accu[i](rect) = miniTmp + accu[i](rect);
}
}
}
}
}
/* Make the global scoreMap*/
cv::merge(accu,3,scoreMap);
/* Conditional noise removal*/
if(m_minRad>2)
cv::medianBlur(scoreMap,scoreMap,3);
cvtColor(scoreMap,scoreMap,CV_BGR2GRAY);
Ho due domande:
Qual è il nome di tale approccio estrazione di primo piano e vedete alcuna ragione per la quale potrebbe essere improprio usarlo in questo caso?
Poiché la ricerca e il disegno di contorni ricorsivi è piuttosto intensivo, vorrei rendere più veloce il mio algoritmo. Puoi indicarmi un modo per raggiungere questo obiettivo?
La ringrazio molto per l'aiuto,
Hai provato [Metodo Otsu] (http://en.wikipedia.org/wiki/Otsu%27s_method), invece di punti 2 e 3 ? È una soglia adattiva estremamente veloce che può essere facilmente calcolata dall'istogramma. Di solito funziona abbastanza bene per la soppressione dello sfondo. La sintassi è ad es. 'cvThreshold (img_src, img_dest, 128, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); '. – smocking
@smocking La ringrazio molto, l'ho fatto, ma 1) ho trovato che non funziona bene come soglia adattiva per tutti i miei campioni. E 2), i passaggi 2 e 3 sono relativamente veloci (rispetto a 3-7) per produrre l'immagine in scala di grigi necessaria per il secondo ... –
OK suona bene; che dire dell'utilizzo di [analisi dei componenti connessi] (http://en.wikipedia.org/wiki/Connected_component_labeling) per trovare le forme contigue? Sfortunatamente non è nella libreria OpenCV di base, ma c'è una libreria chiamata [cvBlobsLib] (http://opencv.willowgarage.com/wiki/cvBlobsLib/) che lo farà. – smocking