Opracowuję aplikację do liczenia okrągłych obiektów, takich jak kolonie bakteryjne ze zdjęć.Wykrywanie skupisk okrągłych obiektów za pomocą iteratywnego progowania adaptacyjnego i analizy kształtu
To, co ułatwia sprawę, to fakt, że obiekty są zasadniczo dobrze odróżnione od tła.
jednak kilka trudności dokonać analizy trudne:
- W tle zaprezentuje stopniowe, a także szybką zmianę intensywności.
- na krawędziach kontenera, obiekt będzie eliptyczny zamiast okrągłego.
- Krawędzie przedmioty są czasami raczej rozmytej.
- Obiekty zostaną zgrupowane.
- Obiekt może być bardzo mały (6px średnicy)
- Docelowo algorytmy będą używane (przez GUI) przez osoby, które nie mają głębokiego zrozumienia analizy obrazu, więc parametry muszą być intuicyjne i bardzo nieliczne.
Problem został rozwiązany wiele razy w literaturze naukowej i "rozwiązany", na przykład przy użyciu okrągłych transformacji Hough lub działów przełomowych, ale nigdy nie byłem zadowolony z wyników.
Jednym z prostych opisów jest uzyskanie pierwszego planu za pomocą progu adaptacyjnego i podzielenie (jak opisałem w this post) obiektów klastrowych za pomocą transformacji odległości.
Z powodzeniem wdrożyłem tę metodę, ale nie zawsze mogłem poradzić sobie z nagłą zmianą intensywności. Ponadto zostaliśmy poproszeni przez rówieśników o bardziej "nowatorskie" podejście.
Dlatego szukałem nowej metody ekstrakcji pierwszego planu.
W związku z tym zbadałem inne metody wykrywania progów/obiektów typu blob. Próbowałem MSER, ale okazało się, że nie były one bardzo wytrzymałe i dość powolne w moim przypadku.
I w końcu wyszedł z algorytmu, który, jak dotąd, daje mi doskonałe rezultaty:
- podzielić trzy kanały mojego obrazu i zmniejszenie hałasu (rozmycie/mediana blur). Dla każdego kanału:
- Stosuję ręczną implementację pierwszego kroku adaptacyjnego progowania, obliczając bezwzględną różnicę między oryginalnym kanałem a splotem (przez duże rozmycie jądra). Następnie, dla wszystkich istotnych wartości progowej:
- I stosuje próg od wyniku 2)
- znaleźć kontury
- validate lub unieważnić kontury na przyznanie ich kształtu (wielkość, obszar, wypukłość ...)
- tylko prawidłowe ciągłe obszary (, tj. ograniczone przez kontury) są następnie przerysowywane w akumulatorze (1 akumulator na kanał).
- Po zgromadzeniu ciągłe regiony ponad wartości progowej, kończę-up z mapą „dziesiątki” regionów.Regiony o największym natężeniu są tymi, które najczęściej spełniają kryteria filtra morfologicznego.
- trzy mapy (jeden na kanał) są następnie konwertowane do skali szarości i progowaniu (próg jest kontrolowana przez użytkownika)
Wystarczy pokazać rodzaj obrazu muszę pracować z: To zdjęcie przedstawia część 3 przykładowych obrazów na górze i wynik mojego algorytmu (niebieski = pierwszy plan) odpowiednich części na dole.
Oto moja implementacja C++ od: 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);
Mam dwa pytania:
Jaka jest nazwa takiego podejścia planie ekstrakcji i widzisz żadnego powodu, dla którego może być niewłaściwe używać go w tym przypadku?
Ponieważ rekursywne wyszukiwanie i rysowanie konturów jest dość intensywne, chciałbym uczynić mój algorytm szybszym. Czy możesz wskazać mi jakiś sposób na osiągnięcie tego celu?
Dziękuję bardzo za pomóc,
Próbowałaś [Metoda Otsu] (http://en.wikipedia.org/wiki/Otsu%27s_method) zamiast krokach 2 i 3 ? Jest to niezwykle szybki próg adaptacyjny, który można łatwo obliczyć z histogramu. Zwykle działa całkiem dobrze dla tłumienia tła. Składnia to np. 'cvThreshold (img_src, img_dest, 128, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); '. – smocking
@ SSocking Dziękuję bardzo, ale 1) Stwierdziłem, że nie działa tak dobrze jak próg adaptacyjny dla wszystkich moich próbek. I 2), produkty z etapu 2 i 3 są stosunkowo szybkie (w porównaniu do 3-7), aby uzyskać obraz w skali szarości potrzebny później ... –
OK brzmi dobrze; A co z wykorzystaniem [połączonej analizy komponentów] (http://en.wikipedia.org/wiki/Connected_component_labeling), aby znaleźć ciągłe kształty? Niestety nie ma go w podstawowej bibliotece OpenCV, ale istnieje biblioteka o nazwie [cvBlobsLib] (http://opencv.willowgarage.com/wiki/cvBlobsLib/), która to zrobi. – smocking