Обнаружение кластеров круговых объектов путем итеративного адаптивного определения порога и формы
Я разрабатываю приложение для подсчета круговых объектов, таких как колонии бактерий, из изображений.
Что облегчает тот факт, что объекты обычно отлично от фона.
Однако несколько трудностей затрудняют анализ:
- Фон будет представлять собой постепенное, а также быстрое изменение интенсивности.
- В краях контейнера объект будет скорее эллиптическим, чем круговым.
- Края объектов иногда довольно нечеткие.
- Объекты будут группироваться.
- Объект может быть очень маленьким (6 пикселей диаметра)
- В конечном итоге алгоритмы будут использоваться (через графический интерфейс) людьми, которые не имеют глубокого понимания анализа изображений, поэтому параметры должны быть интуитивно понятными и очень немногими.
Проблема неоднократно упоминалась в научной литературе и "решалась", например, с использованием круговых преобразований Хью или водоразделов, но я никогда не был удовлетворен результатами.
Один простой подход, который был описан, заключается в том, чтобы получить передний план путем адаптивного порогового значения и разделения (как я описал в этом сообщении) кластеризованные объекты, использующие дистанционное преобразование.
Я успешно реализовал этот метод, но он не всегда мог справиться с внезапным изменением интенсивности. Кроме того, меня попросили сверстники выступить с более "новым" подходом.
Поэтому я искал новый метод для извлечения переднего плана.
Поэтому я исследовал другие методы обнаружения порога /blob.
Я пробовал MSER, но узнал, что они не очень надежны и довольно медленны в моем случае.
В конце концов я получил алгоритм, который до сих пор дает отличные результаты:
- Я разделил три канала своего изображения и уменьшил их шум (размытие/медианное размытие). Для каждого канала:
- Я применяю ручную реализацию первого шага адаптивного порога, вычисляя абсолютную разницу между исходным каналом и свернутым (большим размытием ядра). Затем для всех соответствующих значений порога:
- Я применяю порог к результату 2)
- найти контуры
- проверить или аннулировать контуры при предоставлении формы (размер, площадь, выпуклость...)
- только действительные непрерывные области (т.е. ограниченные контурами) затем перерисовываются в аккумуляторе (1 аккумулятор на канал).
- После накопления непрерывных областей над значениями порога я получаю карту "десятков регионов". Наиболее интенсивными областями являются те, которые наиболее часто выполняли критерии фильтра морфологии.
- Три карты (по одному на канал) затем преобразуются в шкалу серого и пороговое значение (порог контролируется пользователем)
Просто чтобы показать вам образ, с которым я должен работать:
Это изображение представляет собой часть трех образцовых изображений в верхней части и результат моего алгоритма (синий = передний план) соответствующих частей внизу.
Вот моя реализация на С++: 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);
У меня есть два вопроса:
-
Как называется такой подход для выделения переднего плана, и видите ли вы какую-либо причину, по которой было бы невозможно использовать его в этом случае?
-
Поскольку рекурсивно поиск и рисование контуров довольно интенсивно, я хотел бы сделать мой алгоритм быстрее. Можете ли вы указать мне какой-либо способ достичь этой цели?
Большое вам спасибо за помощь,
Ответы
Ответ 1
Несколько лет назад я написал приложение, которое обнаруживает ячейки в изображении микроскопа. Код написан в Matlab, и теперь я думаю, что это сложнее, чем должно быть (это был мой первый проект CV), поэтому я только опишу трюки, которые действительно будут полезны для вас. Кстати, это было смертельно медленным, но было действительно хорошо разделять большие группы двойных клеток.
Я определил метрику, с помощью которой можно оценить вероятность того, что данная точка является центром ячейки:
- Светимость уменьшается по кругу вокруг него
- Отклонение текстурной светимости следует за заданным шаблоном
- ячейка не будет охватывать более чем% соседней ячейки
С этим я начал итеративно находить лучшую ячейку, отмечать ее как найденную, а затем искать следующую. Поскольку такой поиск дорог, я использовал генетические алгоритмы для поиска быстрее в моем пространстве объектов.
Некоторые результаты приведены ниже:
![Cells 2]()
![Cells]()