Найдите локальные максимумы в изображении в градациях серого, используя OpenCV

Кто-нибудь знает, как найти локальные максимумы в изображении в оттенках серого IPL_DEPTH_8U с помощью OpenCV? HarrisCorner что-то подобное упоминает, но на самом деле меня углы не интересуют... Спасибо!


person Durin    schedule 05.04.2011    source источник
comment
Разве операция расширения морфологии в OpenCV не обнаруживает локальные максимумы в ядре 3x3 или определяемом пользователем ядре и не устанавливает для пикселей это максимальное значение? Так что это может быть изменено для вашей цели.   -  person AruniRC    schedule 06.04.2011


Ответы (10)


Я думаю, вы хотите использовать

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

функция на вашем изображении

person fabrizioM    schedule 05.04.2011
comment
собственно что ищет глобальный. Я бы предпочел локальную (региональную), например, функцию Matlab imregionmax(). - person Durin; 06.04.2011
comment
Вы можете использовать вызовы cvSetImageROI и cvResetImageROI для определения субрегиона, в котором вы ищете. Тогда предложение Фабрицио сработает просто отлично. - person peakxu; 06.04.2011
comment
Перед запуском этой функции рассмотрите возможность размытия изображения, если вы хотите получить локальные пики. - person TimZaman; 15.06.2014
comment
@peakxu На самом деле не так просто использовать глобальную функцию MinMaxLoc для поиска локальных экстремумов в сочетании с некоторым ROI. Как указать субрегион? В режиме скользящего окна? MinMaxLoc всегда будет возвращать минимум и максимум. Крайний пиксель в ROI может быть глобальным максимумом в ROI, но следующий пиксель за пределами ROI может иметь еще большее значение. - person Ben; 07.02.2018

Пиксель считается локальным максимумом, если он равен максимальному значению в «локальной» окрестности. Функция ниже фиксирует это свойство в двух строках кода.

Чтобы иметь дело с пикселями на «плато» (значение равно их окрестности), можно использовать свойство локального минимума, поскольку пиксели плато равны их локальному минимуму. Остальной код отфильтровывает эти пиксели.

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);
    }
}
person killogre    schedule 09.01.2014
comment
Этот код дает «связанные» результаты, что странно, поскольку мы ищем локальные максимумы; два максимума не могут быть связаны, если вы спросите меня. Даже если я увеличу размер ядра. - person TimZaman; 15.06.2014

Вот простой трюк. Идея состоит в том, чтобы расширить ядро ​​с отверстием в центре. После операции расширения каждый пиксель заменяется максимальным числом его соседей (в этом примере используется соседство 5 на 5), исключая исходный пиксель.

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

На самом деле, после того, как я разместил код выше, я написал его лучше и очень-очень быстрее. Код выше страдает даже для изображения 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;
        }
    }
}
person Doga Siyli    schedule 18.11.2012
comment
вы всегда можете отредактировать свой собственный пост, и даже если у вас два ответа, вы можете опубликовать их отдельно. - person Coding Mash; 18.11.2012
comment
Я только что это понял :) спасибо за совет .. но можно ли удалить тот, что выше? - person Doga Siyli; 18.11.2012
comment
Модератор может удалить. Вы можете пометить его модератором для рассмотрения. - person Coding Mash; 18.11.2012
comment
круто .. большое спасибо .. но хотя я могу отметить это, в другом посте нет опции флага ниже :( - person Doga Siyli; 18.11.2012
comment
@DogaSiyli, что такое SANITYCHECK? - person John Demetriou; 20.02.2013

Следующий листинг представляет собой функцию, аналогичную «imregionalmax» в Matlab. Он ищет не более nLocMax локальных максимумов выше порога, где найденные локальные максимумы находятся на расстоянии не менее minDistBtwLocMax пикселей друг от друга. Он возвращает фактическое количество найденных локальных максимумов. Обратите внимание, что он использует minMaxLoc OpenCV для поиска глобальных максимумов. Это «автономный opencv», за исключением (простой в реализации) функции vdist, которая вычисляет (евклидово) расстояние между точками (r, c) и (row, col).

input — это одноканальная матрица CV_32F, а местоположения — это 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;
}

person Marcelo    schedule 09.12.2014
comment
+1 за создание функции со всеми возможными параметрами, такими как расстояние между максимальным расстоянием, нет. максимальных, пороговых значений и т.д. Эффективность вычислений пока не изучал. Сообщит вам, если возникнут какие-либо проблемы, связанные с этим. - person G453; 20.02.2017
comment
Откуда взялись vdist и Point2DMake? - person ed22; 25.07.2019
comment
Вы должны реализовать их самостоятельно. - person Pieter Meiresone; 06.05.2021

Первый вопрос, на который нужно ответить, будет заключаться в том, что, по вашему мнению, является «местным». Ответом вполне может быть квадратное окно (скажем, 3х3 или 5х5) или круглое окно определенного радиуса. Затем вы можете сканировать все изображение с центром окна в каждом пикселе и выбирать самое высокое значение в окне.

См. это. как получить доступ к значениям пикселей в OpenCV.

person peakxu    schedule 05.04.2011
comment
см. мой комментарий в принятом ответе. Этот метод не работает. Рассмотрим изображение 100x100 с значением серого (x, y) = x + y. Имеется только один максимум на [99,99]. Скользящее окно всегда находит локальный максимум в правом нижнем углу. Ваш метод в основном возвращает почти каждый пиксель как локальный максимум. - person Ben; 07.02.2018

Это очень быстрый метод. Он хранит найденные максимумы в векторе точек.

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; 
}
person Dasdranagon    schedule 22.02.2013
comment
это дает только отрицательные X и Y - person John Demetriou; 28.02.2013
comment
Хотя идея хорошая и быстрая, ваш код не работает для элементов, расположенных в нижней и правой частях изображения. Возможно, это связано с тем, что ваш pProcess[m++] не доберется до нижнего и правого края, и если порог не сработал, то он не даст результата, а возле этих краев порог действительно может быть выше, но никогда не будет вызывать. Решение здесь состоит в том, чтобы увеличить SearchWidth и searchHeight, а затем провести некоторую санитарную обработку вашего прямоугольника обрезки. - person TimZaman; 15.06.2014

Нашел простое решение.

В этом примере, если вы пытаетесь найти 2 результата функции matchTemplate с минимальным расстоянием друг от друга.

    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. Найдите другой минимум

Оценки можно сравнивать друг с другом, чтобы определить, например, достоверность совпадения,

person Mich    schedule 23.07.2015

Вы можете просмотреть каждый пиксель и проверить, является ли он локальным максимумом. Вот как бы я это сделал. Предполагается, что ввод имеет тип 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));
}

Это решение не учитывает плато, поэтому оно не совсем совпадает с imregionalmax() в Matlab.

person Yonatan Simson    schedule 09.03.2015

Чтобы найти больше, чем просто глобальный минимум и максимум, попробуйте использовать эту функцию из skimage:

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

Вы также можете задать минимальное расстояние между пиками. И больше. Чтобы найти минимумы, используйте отрицательные значения (хотя позаботьтесь о типе массива, 255-image может помочь).

person DomTomCat    schedule 12.02.2019