【问题标题】:Find local maxima in grayscale image using OpenCV使用 OpenCV 在灰度图像中查找局部最大值
【发布时间】:2011-07-29 20:21:27
【问题描述】:

有人知道如何使用 OpenCV 在灰度 IPL_DEPTH_8U 图像中找到局部最大值吗? HarrisCorner 提到了类似的东西,但我实际上对角落不感兴趣...... 谢谢!

【问题讨论】:

  • OpenCV 中的形态膨胀操作不是找出 3x3 或用户定义的内核中的局部最大值并将像素设置为这个最大值吗?所以这可以根据您的目的进行修改。

标签: image image-processing opencv mathematical-optimization


【解决方案1】:

要查找的不仅仅是全局最小值和最大值,请尝试使用skimage 中的此函数:

http://scikit-image.org/docs/dev/api/skimage.feature.html#skimage.feature.peak_local_max

您也可以参数化峰值之间的最小距离。和更多。要找到最小值,请使用否定值(不过请注意数组类型,255-image 可以解决问题)。

【讨论】:

    【解决方案2】:

    这是一个简单的技巧。这个想法是用一个在中心包含一个洞的内核来扩张。在扩张操作之后,每个像素被替换为其最大的邻居(在本例中使用 5 x 5 邻域),排除原始像素。

    Mat1b kernelLM(Size(5, 5), 1u);
    kernelLM.at<uchar>(2, 2) = 0u;
    Mat imageLM;
    dilate(image, imageLM, kernelLM);
    Mat1b localMaxima = (image > imageLM);
    

    【讨论】:

      【解决方案3】:

      找到了一个简单的解决方案。

      在此示例中,如果您尝试查找 matchTemplate 函数的 2 个结果,并且彼此之间的距离最小。

          cv::Mat result;
          matchTemplate(search, target, result, CV_TM_SQDIFF_NORMED);
          float score1;
          cv::Point displacement1 = MinMax(result, score1);
          cv::circle(result, cv::Point(displacement1.x+result.cols/2 , displacement1.y+result.rows/2), 10, cv::Scalar(0), CV_FILLED, 8, 0);
          float score2;
          cv::Point displacement2 = MinMax(result, score2);
      

      在哪里

      cv::Point MinMax(cv::Mat &result, float &score)
      {
          double minVal, maxVal;
          cv::Point  minLoc, maxLoc, matchLoc;
      
          minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
          matchLoc.x = minLoc.x - result.cols/2;
          matchLoc.y = minLoc.y - result.rows/2;
          return minVal;
      }
      

      流程是:

      1. 使用 minMaxLoc 查找全局最小值
      2. 使用最小值之间的最小距离作为半径在全局最小值周围绘制一个实心白色圆圈
      3. 找到另一个最小值

      可以将分数相互比较以确定例如匹配的确定性,

      【讨论】:

        【解决方案4】:

        您可以检查每个像素并测试它是否是局部最大值。这是我将如何做到的。 假设输入为 CV_32FC1 类型

        #include <vector>//std::vector
        #include <algorithm>//std::sort
        #include "opencv2/imgproc/imgproc.hpp"
        #include "opencv2/core/core.hpp"
        
        //structure for maximal values including position
        struct SRegionalMaxPoint
        {
            SRegionalMaxPoint():
                values(-FLT_MAX),
                row(-1),
                col(-1)
            {}
            float values;
            int row;
            int col;
            //ascending order
            bool operator()(const SRegionalMaxPoint& a, const SRegionalMaxPoint& b)
            {   
                return a.values < b.values;
            }   
        };
        
        //checks if pixel is local max
        bool isRegionalMax(const float* im_ptr, const int& cols )
        {
            float center = *im_ptr;
            bool is_regional_max = true;
            im_ptr -= (cols + 1);
            for (int ii = 0; ii < 3; ++ii, im_ptr+= (cols-3))
            {
                for (int jj = 0; jj < 3; ++jj, im_ptr++)
                {
                    if (ii != 1 || jj != 1)
                    {
                        is_regional_max &= (center > *im_ptr);
                    }
                }
            }
            return is_regional_max;
        }
        
        void imregionalmax(
            const cv::Mat& input, 
            std::vector<SRegionalMaxPoint>& buffer)
        {
            //find local max - top maxima
            static const int margin = 1;
            const int rows = input.rows;
            const int cols = input.cols;
            for (int i = margin; i < rows - margin; ++i)
            {
                const float* im_ptr = input.ptr<float>(i, margin);
                for (int j = margin; j < cols - margin; ++j, im_ptr++)
                {
                    //Check if pixel is local maximum
                    if ( isRegionalMax(im_ptr, cols ) )
                    {
                        cv::Rect roi = cv::Rect(j - margin, i - margin, 3, 3);
                        cv::Mat subMat = input(roi);
        
                        float val = *im_ptr;
                        //replace smallest value in buffer
                        if ( val > buffer[0].values )
                        {
                            buffer[0].values = val;
                            buffer[0].row    = i;
                            buffer[0].col    = j;
                            std::sort(buffer.begin(), buffer.end(), SRegionalMaxPoint());
                        }
        
                    }
                }
            }
        
        }
        

        为了测试代码,你可以试试这个:

        cv::Mat temp = cv::Mat::zeros(15, 15, CV_32FC1);
        temp.at<float>(7, 7) = 1;
        temp.at<float>(3, 5) = 6;
        temp.at<float>(8, 10) = 4;
        temp.at<float>(11, 13) = 7;
        temp.at<float>(10, 3) = 8;
        temp.at<float>(7, 13) = 3;
        
        vector<SRegionalMaxPoint> buffer_(5);
        imregionalmax(temp, buffer_);
        
        cv::Mat debug;
        cv::cvtColor(temp, debug, cv::COLOR_GRAY2BGR);
        for (auto it = buffer_.begin(); it != buffer_.end(); ++it)
        {
            circle(debug, cv::Point(it->col, it->row), 1, cv::Scalar(0, 255, 0));
        }
        

        这个解决方案没有考虑高原,所以它与matlab的imregionalmax()并不完全相同

        【讨论】:

          【解决方案5】:

          下面的清单是一个类似于 Matlab 的“imregionalmax”的函数。它最多在 threshold 之上寻找 nLocMax 个局部最大值,其中找到的局部最大值相距至少 minDistBtwLocMax 个像素。它返回找到的局部最大值的实际数量。请注意,它使用 OpenCV 的 minMaxLoc 来查找全局最大值。它是“opencv-self-contained”的,除了(易于实现)函数vdist,它计算点(r,c)和(row,col)之间的(欧几里德)距离。

          input 是单通道 CV_32F 矩阵,locations 是 nLocMax(行)乘 2(列)CV_32S 矩阵。

          int imregionalmax(Mat input, int nLocMax, float threshold, float minDistBtwLocMax, Mat locations)
          {
              Mat scratch = input.clone();
              int nFoundLocMax = 0;
              for (int i = 0; i < nLocMax; i++) {
                  Point location;
                  double maxVal;
                  minMaxLoc(scratch, NULL, &maxVal, NULL, &location);
                  if (maxVal > threshold) {
                      nFoundLocMax += 1;
                      int row = location.y;
                      int col = location.x;
                      locations.at<int>(i,0) = row;
                      locations.at<int>(i,1) = col;
                      int r0 = (row-minDistBtwLocMax > -1 ? row-minDistBtwLocMax : 0);
                      int r1 = (row+minDistBtwLocMax < scratch.rows ? row+minDistBtwLocMax : scratch.rows-1);
                      int c0 = (col-minDistBtwLocMax > -1 ? col-minDistBtwLocMax : 0);
                      int c1 = (col+minDistBtwLocMax < scratch.cols ? col+minDistBtwLocMax : scratch.cols-1);
                      for (int r = r0; r <= r1; r++) {
                          for (int c = c0; c <= c1; c++) {
                              if (vdist(Point2DMake(r, c),Point2DMake(row, col)) <= minDistBtwLocMax) {
                                  scratch.at<float>(r,c) = 0.0;
                              }
                          }
                      }
                  } else {
                      break;
                  }
              }
              return nFoundLocMax;
          }

          【讨论】:

          • +1 用于制作具有所有可能选项的函数,例如距离 btw max,否。最大值,阈值等。尚未研究计算效率。如果有任何相关问题,会告诉你。
          • “vdist”和“Point2DMake”来自哪里?
          • 你必须自己实现这些。
          【解决方案6】:

          如果像素等于“局部”邻域中的最大值,则将其视为局部最大值。下面的函数在两行代码中捕获了这个属性。

          要处理“高原”上的像素(值等于它们的邻域),可以使用局部最小值属性,因为高原像素等于它们的局部最小值。其余代码过滤掉这些像素。

          void non_maxima_suppression(const cv::Mat& image, cv::Mat& mask, bool remove_plateaus) {
              // find pixels that are equal to the local neighborhood not maximum (including 'plateaus')
              cv::dilate(image, mask, cv::Mat());
              cv::compare(image, mask, mask, cv::CMP_GE);
          
              // optionally filter out pixels that are equal to the local minimum ('plateaus')
              if (remove_plateaus) {
                  cv::Mat non_plateau_mask;
                  cv::erode(image, non_plateau_mask, cv::Mat());
                  cv::compare(image, non_plateau_mask, non_plateau_mask, cv::CMP_GT);
                  cv::bitwise_and(mask, non_plateau_mask, mask);
              }
          }
          

          【讨论】:

          • 这段代码给出了“连接”的结果,这很奇怪,因为我们正在寻找局部最大值;如果你问我,两个最大值无法连接。即使我增加内核大小。
          【解决方案7】:

          这是一种非常快速的方法。它将建立的最大值存储在一个向量中 积分。

          vector <Point> GetLocalMaxima(const cv::Mat Src,int MatchingSize, int Threshold, int GaussKernel  )
          {  
            vector <Point> vMaxLoc(0); 
          
            if ((MatchingSize % 2 == 0) || (GaussKernel % 2 == 0)) // MatchingSize and GaussKernel have to be "odd" and > 0
            {
              return vMaxLoc;
            }
          
            vMaxLoc.reserve(100); // Reserve place for fast access 
            Mat ProcessImg = Src.clone();
            int W = Src.cols;
            int H = Src.rows;
            int SearchWidth  = W - MatchingSize;
            int SearchHeight = H - MatchingSize;
            int MatchingSquareCenter = MatchingSize/2;
          
            if(GaussKernel > 1) // If You need a smoothing
            {
              GaussianBlur(ProcessImg,ProcessImg,Size(GaussKernel,GaussKernel),0,0,4);
            }
            uchar* pProcess = (uchar *) ProcessImg.data; // The pointer to image Data 
          
            int Shift = MatchingSquareCenter * ( W + 1);
            int k = 0;
          
            for(int y=0; y < SearchHeight; ++y)
            { 
              int m = k + Shift;
              for(int x=0;x < SearchWidth ; ++x)
              {
                if (pProcess[m++] >= Threshold)
                {
                  Point LocMax;
                  Mat mROI(ProcessImg, Rect(x,y,MatchingSize,MatchingSize));
                  minMaxLoc(mROI,NULL,NULL,NULL,&LocMax);
                  if (LocMax.x == MatchingSquareCenter && LocMax.y == MatchingSquareCenter)
                  { 
                    vMaxLoc.push_back(Point( x+LocMax.x,y + LocMax.y )); 
                    // imshow("W1",mROI);cvWaitKey(0); //For gebug              
                  }
                }
              }
              k += W;
            }
            return vMaxLoc; 
          }
          

          【讨论】:

          • 它只给出负的 X 和 Y
          • 虽然这个想法既好又快,但您的代码不适用于位于图像底部和右侧边缘的特征。这可能是因为您的 pProcess[m++] 不会到达底部和右侧边缘,并且如果尚未触发阈值,它将不会给出结果,而在这些边缘附近,阈值可能确实更高,但它永远不会扳机。这里的解决方案是增加 SearchWidth 和 searchHeight,然后在裁剪矩形上进行一些卫生处理。
          【解决方案8】:

          实际上,在我发布上面的代码之后,我写了一个更好、更快的代码.. 即使是 640x480 的图片,上面的代码也会受到影响。 我对其进行了优化,现在即使是 1600x1200 的图片也非常快。 这是代码:

          void localMaxima(cv::Mat src,cv::Mat &dst,int squareSize)
          {
          if (squareSize==0)
          {
              dst = src.clone();
              return;
          }
          
          Mat m0;
          dst = src.clone();
          Point maxLoc(0,0);
          
          //1.Be sure to have at least 3x3 for at least looking at 1 pixel close neighbours
          //  Also the window must be <odd>x<odd>
          SANITYCHECK(squareSize,3,1);
          int sqrCenter = (squareSize-1)/2;
          
          //2.Create the localWindow mask to get things done faster
          //  When we find a local maxima we will multiply the subwindow with this MASK
          //  So that we will not search for those 0 values again and again
          Mat localWindowMask = Mat::zeros(Size(squareSize,squareSize),CV_8U);//boolean
          localWindowMask.at<unsigned char>(sqrCenter,sqrCenter)=1;
          
          //3.Find the threshold value to threshold the image
              //this function here returns the peak of histogram of picture
              //the picture is a thresholded picture it will have a lot of zero values in it
              //so that the second boolean variable says :
              //  (boolean) ? "return peak even if it is at 0" : "return peak discarding 0"
          int thrshld =  maxUsedValInHistogramData(dst,false);
          threshold(dst,m0,thrshld,1,THRESH_BINARY);
          
          //4.Now delete all thresholded values from picture
          dst = dst.mul(m0);
          
          //put the src in the middle of the big array
          for (int row=sqrCenter;row<dst.size().height-sqrCenter;row++)
              for (int col=sqrCenter;col<dst.size().width-sqrCenter;col++)
              {
                  //1.if the value is zero it can not be a local maxima
                  if (dst.at<unsigned char>(row,col)==0)
                      continue;
                  //2.the value at (row,col) is not 0 so it can be a local maxima point
                  m0 =  dst.colRange(col-sqrCenter,col+sqrCenter+1).rowRange(row-sqrCenter,row+sqrCenter+1);
                  minMaxLoc(m0,NULL,NULL,NULL,&maxLoc);
                  //if the maximum location of this subWindow is at center
                  //it means we found the local maxima
                  //so we should delete the surrounding values which lies in the subWindow area
                  //hence we will not try to find if a point is at localMaxima when already found a neighbour was
                  if ((maxLoc.x==sqrCenter)&&(maxLoc.y==sqrCenter))
                  {
                      m0 = m0.mul(localWindowMask);
                                      //we can skip the values that we already made 0 by the above function
                      col+=sqrCenter;
                  }
              }
          }
          

          【讨论】:

          • 您可以随时编辑自己的帖子,即使您有两个答案,也可以分别发布。
          • 我才意识到 :) 感谢您的提示.. 但是可以删除上面的那个吗?
          • 版主可以删除它。您可以标记它以供版主考虑。
          • 太棒了.. 非常感谢.. 但是虽然我可以标记这个,但其他帖子在下面没有标记选项:(
          • @DogaSiyli SANITYCHECK 是什么?
          【解决方案9】:

          我想你想使用

          MinMaxLoc(arr, mask=NULL)-> (minVal, maxVal, minLoc, maxLoc)
          Finds global minimum and maximum in array or subarray
          

          作用于你的图像

          【讨论】:

          • 其实就是寻找全局。我更喜欢像 Matlab 的 imregionmax() 函数这样的本地(区域)函数。
          • 您可以使用 cvSetImageROI 和 cvResetImageROI 调用来定义您正在搜索的子区域。那么,fabrizio 的建议就可以了。
          • 在运行此函数之前,如果您想获得局部峰值,请考虑对图像进行模糊处理。
          • @peakxu 使用全局 MinMaxLoc 函数结合一些 ROI 找到局部极值实际上并不容易。你如何设置子区域?以滑动窗口的方式? MinMaxLoc 将始终返回最小值和最大值。 ROI 中的边界像素可能是 ROI 内的全局最大值,但 ROI 外的下一个像素可能具有更大的值。
          【解决方案10】:

          首先要回答的问题是您认为什么是“本地”。答案很可能是一个方形窗口(比如 3x3 或 5x5)或一定半径的圆形窗口。然后,您可以扫描整个图像,窗口以每个像素为中心,并选择窗口中的最大值。

          请参阅this,了解如何在 OpenCV 中访问像素值。

          【讨论】:

          • 在接受的答案中查看我的评论。这种方法不起作用。考虑灰度值(x,y)= x+y 的 100x100 图像。在 [99,99] 处只有一个最大值。滑动窗口总是会在右下角找到一个局部最大值。您的方法基本上会将几乎每个像素都返回为局部最大值。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-12
          • 2014-04-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多