JONLIN Ответов: 3

Найти локальные экстремумы 3D массива


Название вопроса говорит само за себя.

Дан массив
float[][][] arr;
с размерами width=w, height=h и depth=d найдите все локальные максимумы и минимумы. (индексация должна быть выполнена arr[глубина][высота][ширина])

Локальный экстремум обнаруживается, когда он больше/меньше, чем все его 26 соседей.

Конечная цель этого метода заключается в том, чтобы стать строительным блоком для реализации SIFT (вот ссылка на оригинальную статью, если вы хотите увидеть, откуда берется этот массив)

Что я уже пробовал:

Все примеры кода, представленные здесь,сделаны на Java, однако если вы более комфортно работаете с такими языками, как c, c++ и c#, вы можете отправить ответы, используя их вместо этого.

Я попробовал 2 разных решения, оба дающих совершенно разные ответы, и на поверхностном уровне ни одно из них, похоже, не дает мне правильного ответа...

Ответы,которые я здесь получаю,предполагают, что точка (x, y, z) задана в этом 3D-массиве. где
Image adj[3] = {arr[z-1],arr[z],arr[z+1]};

Функция getPixel просто возвращает значение пикселя, расположенное в точке (x,y) внутри 2D-среза 3D-массива.


private boolean isExtrema(int x, int y, BufferedImage low, BufferedImage curr, BufferedImage high) {
BufferedImage[] adj = {low,curr,high};

float value = adj[1].getPixel(x, y);

// Since all neighbors all need to be on the same 'side' for an
// extremum we can just take an arbitrary neighbor to determine
// if we might face a minimum or a maximum.
float sign = Math.signum(value - adj[0].getPixel(x,y));
value *= sign;
boolean isExtrema = true;

isExtrema &= adj[0].getPixel(x - 1, y - 1) * sign < value;
isExtrema &= adj[0].getPixel(x - 1, y) * sign < value;
isExtrema &= adj[0].getPixel(x - 1, y + 1) * sign < value;
isExtrema &= adj[0].getPixel(x, y - 1) * sign < value;
isExtrema &= adj[0].getPixel(x, y) * sign < value;
isExtrema &= adj[0].getPixel(x,y + 1) * sign < value;
isExtrema &= adj[0].getPixel(x + 1, y - 1) * sign < value;
isExtrema &= adj[0].getPixel(x + 1, y) * sign < value;
isExtrema &= adj[0].getPixel(x + 1, y + 1) * sign < value;

isExtrema &= adj[1].getPixel(x - 1, y - 1) * sign < value;
isExtrema &= adj[1].getPixel(x - 1, y) * sign < value;
isExtrema &= adj[1].getPixel(x - 1, y + 1) * sign < value;
isExtrema &= adj[1].getPixel(x, y - 1) * sign < value;
isExtrema &= adj[1].getPixel(x, y + 1) * sign < value;
isExtrema &= adj[1].getPixel(x + 1, y - 1) * sign < value;
isExtrema &= adj[1].getPixel(x + 1, y) * sign < value;
isExtrema &= adj[1].getPixel(x + 1, y + 1) * sign < value;

isExtrema &= adj[2].getPixel(x - 1, y - 1) * sign < value;
isExtrema &= adj[2].getPixel(x - 1, y) * sign < value;
isExtrema &= adj[2].getPixel(x - 1, y + 1) * sign < value;
isExtrema &= adj[2].getPixel(x, y - 1) * sign < value;
isExtrema &= adj[2].getPixel(x, y) * sign < value;
isExtrema &= adj[2].getPixel(x, y + 1) * sign < value;
isExtrema &= adj[2].getPixel(x + 1, y - 1) * sign < value;
isExtrema &= adj[2].getPixel(x + 1, y) * sign < value;
isExtrema &= adj[2].getPixel(x + 1, y + 1) * sign < value;

return isExtrema;
}


Второе решение, которое я пробовал
private boolean isExtrema(BufferedImage low, BufferedImage curr, BufferedImage high, int x, int y) {
BufferedImage[] adj = {low,curr,high};

float cv = adj[1].getPixel(x, y);
boolean isMax = true;
MAX:for(int a = 0; a < 3; a++) {
    for (int j = -1; j < 1; j++) {
        for (int i = -1; i < 1; i++) {
            if (adj[a].getPixel(x + i, y + j) >= cv && !(i == 0 && j == 0 && a == 1)) {
                isMax = false;
                break MAX;
            }
        }
    }
}
if(isMax)
    return true;

boolean isMin = true;
MIN:for(int a = 0; a < 3; a++) {
    for (int j = -1; j < 1; j++) {
        for (int i = -1; i < 1; i++) {
            if (adj[a].getPixel(x + i, y + j) <= cv && !(i == 0 && j == 0 && a == 1)) {
                isMin = false;
                break MIN;
            }
        }
    }
}
return isMin;
}


Учитывая выборку массива, первый метод дал 24000 экстремумов, тогда как второй дал более 200000 тысяч...

EDIT: контекст для приведенных примеров кода
public void findExtrema(BufferedImage[] pyramid) {
    for(int i = 1; i < pyramid.length - 1; i++) {

        final BufferedImage prev = dogpyr[i-1];
        final BufferedImage img = dogpyr[i];
        final BufferedImage next = dogpyr[i+1];

        final int IMG_BORDER = 5;

        for(int row = IMG_BORDER; row < img.getHeight()-IMG_BORDER; row++) {
            for(int col = IMG_BORDER; col < img.getWidth()-IMG_BORDER; col++) {
                if(isExtrema(col,row,prev,img,next)) {
                  // Further processing
                }
            }
        }
    }
}

0x01AA

Вопрос не так прост. Давайте посмотрим на два измерения, и мы находимся на максимуме y= f(x). Теперь, если вы идете "налево", предположим, что y= 2. Если вы идете "правой" стороной y =1. Оба минимальны, но имеет ли меньший минимум большее значение? И если да, то почему? А для N измерений?

JONLIN

Определение экстремума здесь заключается в том, что данный пиксель больше или меньше, чем все его 26 соседей.
Таким образом, можно иметь 2 соседних экстремума, где один является максимумом, а другой-минимумом.
Нет никакого взвешивания, привязанного к экстремумам (пока, см. Главу 4.) так что все экстремумы считаются одинаковыми.

3 Ответов

Рейтинг:
28

JONLIN

Я включаю этот ответ для полноты картины.
В то время как г-н Томас такак отвечает, является ли данный пиксель минимумом или нет, он не отвечает на вопрос, Является ли он экстремумом (он легко модифицируется, поэтому я отмечу его как ответ).

Я провел некоторые дополнительные исследования по реализации документ ссылаться и я наткнулся на него. этот который отвечает на данный вопрос с помощью эта реализация.

(Реализация, c++, скопирована здесь для полноты картины)
highData является Z+1 кусочек 3D массив, currData является Z нарезать и lowData является Z-1 ломтик. индекс-это индекс в срезе 2D-изображения до точки (x,y), где w-ширина изображения. Параметр |val| >= contr_thr-это проверка контраста, относящаяся только к данному варианту использования, и ее следует опустить, если она используется в другом месте (не делайте этого, этот код заслуживает того, чтобы быть представленным в r/programminghorror).

bool bExtrema =
    (val >= contr_thr && val > highData[index - w - 1] &&
    val > highData[index - w] &&
    val > highData[index - w + 1] &&
    val > highData[index - 1] && val > highData[index] &&
    val > highData[index + 1] &&
    val > highData[index + w - 1] &&
    val > highData[index + w] &&
    val > highData[index + w + 1] &&
    val > currData[index - w - 1] &&
    val > currData[index - w] &&
    val > currData[index - w + 1] &&
    val > currData[index - 1] &&
    val > currData[index + 1] &&
    val > currData[index + w - 1] &&
    val > currData[index + w] &&
    val > currData[index + w + 1] &&
    val > lowData[index - w - 1] &&
    val > lowData[index - w] &&
    val > lowData[index - w + 1] &&
    val > lowData[index - 1] && val > lowData[index] &&
    val > lowData[index + 1] &&
    val > lowData[index + w - 1] &&
    val > lowData[index + w] &&
    val > lowData[index + w + 1]) || // Local min
    (val <= -contr_thr && val < highData[index - w - 1] &&
    val < highData[index - w] &&
    val < highData[index - w + 1] &&
    val < highData[index - 1] && val < highData[index] &&
    val < highData[index + 1] &&
    val < highData[index + w - 1] &&
    val < highData[index + w] &&
    val < highData[index + w + 1] &&
    val < currData[index - w - 1] &&
    val < currData[index - w] &&
    val < currData[index - w + 1] &&
    val < currData[index - 1] &&
    val < currData[index + 1] &&
    val < currData[index + w - 1] &&
    val < currData[index + w] &&
    val < currData[index + w + 1] &&
    val < lowData[index - w - 1] &&
    val < lowData[index - w] &&
    val < lowData[index - w + 1] &&
    val < lowData[index - 1] && val < lowData[index] &&
    val < lowData[index + 1] &&
    val < lowData[index + w - 1] &&
    val < lowData[index + w] &&
    val < lowData[index + w + 1]);


Tomas Takac

Точка зрения принята. Я только показал, как искать минимум, потому что думал, что изменение его для Макса тривиально. Я должен был заявить об этом прямо.

Рейтинг:
2

Patrice T

Ваш код настолько урезан, что невозможно догадаться, как вы его используете.
Все, что я могу сказать, это то, что я не вижу ничего, чтобы справиться с точкой на стороне или углу 3D-массива, и это плохой знак. Вероятно, пришло время изучить отладчик.
Мой алгоритм был бы таким грубую силу с 6 вложенность циклов.

Цитата:
Я попробовал 2 разных решения, оба дающих совершенно разные ответы, и на поверхностном уровне ни одно из них, похоже, не дает мне правильного ответа...

Ваш код ведет себя не так, как вы ожидаете, или вы не понимаете, почему !

Существует почти универсальное решение: запускайте свой код на отладчике шаг за шагом, проверяйте переменные.
Отладчик здесь, чтобы показать вам, что делает ваш код, и ваша задача-сравнить с тем, что он должен делать.
В отладчике нет никакой магии, он не знает, что должен делать ваш код, он не находит ошибок, он просто помогает вам, показывая, что происходит. Когда код не делает того, что ожидается, вы близки к ошибке.
Чтобы увидеть, что делает ваш код: просто установите точку останова и посмотрите, как работает ваш код, отладчик позволит вам выполнять строки 1 на 1 и проверять переменные по мере их выполнения.

Отладчик - Википедия, свободная энциклопедия[^]

Освоение отладки в Visual Studio 2010 - руководство для начинающих[^]
Базовая отладка с помощью Visual Studio 2010 - YouTube[^]

http://docs.oracle.com/javase/7/docs/technotes/tools/windows/jdb.html[^]
https://www.jetbrains.com/idea/help/debugging-your-first-java-application.html[^]

Отладчик здесь только для того, чтобы показать вам, что делает ваш код, и ваша задача-сравнить его с тем, что он должен делать.


JONLIN

Я посчитал, что полный код будет очень длинным, но очень хорошо, я обновлю свой вопрос.

JONLIN

Хотя да, предоставленный код даже не будет компилироваться, не говоря уже о запуске. Но я повторю это еще раз
"
Учитывая массив с размерами width=w, height=h и depth=d, найдите все локальные максимумы и минимумы. [...]

Локальный экстремум обнаруживается, когда он больше/меньше, чем все его 26 соседей.
"
Поэтому для каждого измерения требуется 3 петли. 26 точек-это окружение, необходимое для определения экстремума, прямоугольника 3x3x3.
Это окружение, однако, не определено для ребер и углов, поэтому экстремумы не могут существовать на ребре или углу.
таким образом, требуется 3 петли

for(int x = 1; x < w-1; x++) {
for(int y = 1; y < h-1; y++) {
for(int z = 1; z < d-1; z++) {
if(isExtrema(x,y,z)) {
//...
}}}}

Я виноват в том, что предположил это, я понимаю это, но я надеюсь, что это прояснит ваши опасения.

Рейтинг:
12

Tomas Takac

Моя реализация находится в C#. Я полагаю arr это массив объектов, которые имеют getPixel(x, y) метод. Я создал класс Layer для этой цели.

Чтобы проверить минимум для заданного набора координат:

bool IsMinimum(Layer[] arr, int x, int y, int z)
{
    double myValue = arr[z].getPixel(x, y);
    int xlo = Math.Max(0, x - 1);
    int xhi = Math.Min(MaxX, x + 2);
    int ylo = Math.Max(0, y - 1);
    int yhi = Math.Min(MaxY, y + 2);
    int zlo = Math.Max(0, z - 1);
    int zhi = Math.Min(MaxZ, z + 2);
    
    bool isMinimum = true;
    for (int zi = zlo; zi < zhi; zi++)
        for (int yi = ylo; yi < yhi; yi++)
            for (int xi = xlo; xi < xhi; xi++)
                if (xi != x || yi != y || zi != z)              
                {
                    double neighborValue = arr[zi].getPixel(xi, yi);
                    isMinimum = isMinimum && (myValue < neighborValue);
                }
            
    return isMinimum;
}

Вы просто называете это для всех пикселей:
for (int z = 0; z < MaxZ; z++)
    for (int y = 0; y < MaxY; y++)
        for (int x = 0; x < MaxX; x++)
            if (IsMinimum(arr, x, y, z))
                Console.WriteLine("x={0}, y={1}, z={2}, value={3}", x, y, z, arr[z].getPixel(x, y));

Это подход грубой силы. Скорее всего, есть лучший способ сделать это, поскольку мы читаем одни и те же значения несколько раз.


JONLIN

Мне действительно интересно, можно ли вообще найти решение, которое не делает это "грубой силой"? Будет ли такое решение найдено, если подмножество 3D-изображения содержит экстремум?

Tomas Takac

Это не тривиальный вопрос. Только подход грубой силы будет гарантированно находить все экстремумы. Ограничение, конечно, заключается в том, что вы можете искать весь набор в разумные сроки. Есть методы, чтобы найти глобальный мин/макс, как Имитация отжига[^Однако я не помню никакой техники, позволяющей найти все локальные экстремумы.