通过迭代自适应阈值处理和形状分析来检测圆形对象的簇

时间:2012-07-24 12:47:20

标签: c++ algorithm image-processing opencv openmp

我一直在开发一个应用程序来计算圆形物体,例如图片中的细菌菌落。

事实上,对象通常与背景截然不同。

然而,很少有困难使分析变得棘手:

  1. 背景将呈现渐变和快速的强度变化。
  2. 在容器的边缘,对象将是椭圆形而不是圆形。
  3. 对象的边缘有时相当模糊。
  4. 对象将聚集。
  5. 物体可以非常小(直径6px)
  6. 最终,算法将由对图像分析没有深刻理解的人使用(通过GUI),因此参数必须直观且极少。
  7. 这个问题已在科学文献中多次提及并“解决”,例如,使用循环Hough变换或分水岭方法,但我从未对结果感到满意。

    描述的一个简单方法是通过自适应阈值处理和分割(如我在this post中所描述的)使用距离变换分割聚类对象来获得前景。

    我已经成功实施了这种方法,但它并不总能处理突然的强度变化。此外,我被同行们要求提出更“新颖”的方法。

    因此,我正在寻找一种新方法来提取前景。

    因此,我研究了其他阈值/斑点检测方法。 我尝试了MSER,但发现它们不是很强大,而且在我的情况下非常慢。

    我最终推出了一种算法,到目前为止,它给了我很好的结果:

    1. 我分割了图像的三个通道并降低了噪点(模糊/中位模糊)。对于每个频道:
    2. 我通过计算原始通道和卷积(通过大内核模糊)之间的绝对差异来应用自适应阈值处理的第一步的手动实现。然后,对于所有相关的阈值值:
    3. 我对2)
    4. 的结果应用了一个阈值
    5. 查找轮廓
    6. 在授予其形状(大小,面积,凸度......)时验证或使轮廓无效
    7. 然后只在累加器中重绘有效的连续区域(由轮廓分隔)(每个通道1个累加器)。
    8. 在超过阈值的值累积连续区域后,我最终得到“地区分数”的映射。强度最高的区域是最常满足形态过滤标准的区域。
    9. 然后将三个地图(每个通道一个)转换为灰度和阈值(阈值由用户控制)
    10. 只是为了向您展示我必须使用的图像: enter image description here 此图片代表顶部3个样本图像的一部分,以及底部各个部分的算法结果(蓝色=前景)。

      这是我的C ++实现: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. 这种前景提取方法的名称是什么?你认为在这种情况下使用它的任何原因是不正确的吗?

      2. 由于递归查找和绘制轮廓是非常密集的,我想让我的算法更快。你能指出我实现这一目标的任何方法吗?

      3. 非常感谢你的帮助,

1 个答案:

答案 0 :(得分:2)

几年前,我写了一个检测显微镜图像中细胞的应用程序。代码是用Matlab编写的,我认为现在它比它应该更复杂(这是我的第一个CV项目),所以我只会概述那些对你有帮助的技巧。顺便说一句,这是致命的慢,但它真的很好地分离了大量的双胞胎。

我定义了一个度量标准,通过该度量标准来评估给定点是单元格中心的可能性:   - 亮度在其周围呈圆形图案减少   - 纹理亮度的方差遵循给定的模式   - 单元格不会覆盖相邻单元格的%以上

有了它,我开始迭代地找到最好的单元格,将其标记为找到,然后寻找下一个单元格。因为这样的搜索很昂贵,我使用遗传算法在我的特征空间中搜索得更快。

下面给出了一些结果:

Cells 2 Cells