我一直在开发一种应用程序,用于从图片中计数圆形物体,例如细菌菌落。

使对象变得容易的事实是,对象通常与背景完全不同。

但是,很少有困难使分析变得棘手:

  • 背景将呈现渐进的以及快速的强度变化。
  • 在容器的边缘,对象将是椭圆形而不是圆形。
  • 对象的边缘有时相当模糊。
  • 对象将群集。
  • 对象可以很小(直径为6px)
  • 最终,对图像分析不了解的人将使用该算法(通过GUI),因此参数必须直观且很少。

  • 这个问题已经在科学文献中多次讨论并“解决”,例如,使用圆形霍夫变换或分水岭方法,但结果使我从未满意。

    所描述的一种简单方法是通过自适应阈值获取前景并使用距离变换对聚类对象进行分割(如我在this post中所述)。

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

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

    因此,我研究了其他阈值/ Blob 检测方法。
    我尝试了MSER,但发现它们对我而言不是很可靠,而且速度很慢。

    最终,我提出了一种算法,迄今为止,该算法给了我出色的结果:
  • 我拆分了图像的三个 channel 并减少了它们的噪点(模糊/中位模糊)。对于每个 channel :
  • 我通过计算原始 channel 和卷积 channel (由较大的内核模糊)之间的绝对差值来应用自适应阈值第一步的手动实现。然后,对于阈值的所有相关值:
  • 我对2)
  • 的结果应用阈值
  • 查找轮廓
  • 根据轮廓的形状(大小,面积,凸度...)验证或使轮廓无效
    然后,在一个累加器(每个 channel 1个累加器)中仅重画有效的连续区域(即由轮廓界定)。
  • 在超过阈值的连续区域上累积后,我得到了“区域分数”图。强度最高的区域是最经常满足形态过滤条件的区域。
  • 然后将三个映射(每个 channel 一个)转换为灰度并设置阈值(阈值由用户控制)

  • 只是为了向您展示我必须使用的图像类型:

    此图片在顶部代表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);
    

    我有两个问题:
  • 这种前台提取方法的名称是什么,您是否看到这种情况下不适当使用的任何原因?
  • 由于以递归方式查找和绘制轮廓非常费力,因此我想使算法更快。你能告诉我实现这一目标的任何方法吗?

  • 非常感谢您的帮助,

    最佳答案

    几年前,我写了一篇论文,可检测显微镜图像中的细胞。该代码是用Matlab编写的,现在我认为它比应该的要复杂得多(这是我的第一个CV项目),因此我仅列出实际上对您有帮助的技巧。顺便说一句,这是致命的缓慢,但它确实擅长分离大批双胞胎。

    我定义了一个度量,用于评估给定点是单元格中心的机会:
    -亮度以圆形形式降低
    -纹理亮度的变化遵循给定的模式
    -一个小区的覆盖范围不会超过相邻小区的%

    有了它,我开始迭代找到最佳的单元,将其标记为已找到,然后寻找下一个。因为这样的搜索很昂贵,所以我采用了遗传算法来快速搜索特征空间。

    一些结果如下:

    10-07 19:01
    查看更多