Как я могу найти самую яркую точку в CIImage (возможно, в Metal)?

Я создал собственный CIKernel в Metal. Это полезно, потому что оно приближено к реальному времени. Я избегаю любых cgcontext или cicontext, которые могут отставать в реальном времени. Мое ядро, по сути, выполняет преобразование Хафа, но я не могу понять, как читать белые точки из буфера изображений.

Вот kernel.metal:

#include <CoreImage/CoreImage.h>

extern "C" {
    namespace coreimage {

        float4 hough(sampler src) {

            // Math

            // More Math

            // eventually:

            if (luminance > 0.8) {
                uint2 position = src.coord()
                // Somehow add this to an array because I need to know the x,y pair
            }

            return float4(luminance, luminance, luminance, 1.0);
        }
    }
}

Я не возражаю, если эту часть можно извлечь в другое ядро ​​или функцию. Предостережение для CIKernel заключается в том, что его тип возвращаемого значения - float4, представляющий новый цвет пикселя. В идеале вместо image -> image фильтра я хотел бы сделать image -> array вид сделки. Например. уменьшить вместо карты. У меня плохое предчувствие, что мне потребуется отрендерить его и обработать на CPU.

В конечном итоге я хочу получить соответствующие координаты (которых может быть несколько на изображение) обратно в моей быстрой функции.

ОКОНЧАТЕЛЬНОЕ РЕШЕНИЕ:

Согласно предложениям ответа, я выполняю большие попиксельные вычисления на графическом процессоре и некоторую математику на процессоре. Я разработал 2 дополнительных ядра, которые работают как встроенные ядра сокращения. Одно ядро ​​возвращает изображение высотой 1 пиксель для наивысших значений в каждом столбце, а другое ядро ​​возвращает изображение высотой 1 пиксель нормализованной координаты y наивысшего значения:

    /// Returns the maximum value in each column.
    ///
    /// - Parameter src: a sampler for the input texture
    /// - Returns: maximum value in for column
    float4 maxValueForColumn(sampler src) {

        const float2 size = float2(src.extent().z, src.extent().w);

        /// Destination pixel coordinate, normalized
        const float2 pos = src.coord();

        float maxV = 0;

        for (float y = 0; y < size.y; y++) {
            float v = src.sample(float2(pos.x, y / size.y)).x;
            if (v > maxV) {
                maxV = v;
            }
        }

        return float4(maxV, maxV, maxV, 1.0);
    }

    /// Returns the normalized coordinate of the maximum value in each column.
    ///
    /// - Parameter src: a sampler for the input texture
    /// - Returns: normalized y-coordinate of the maximum value in for column
    float4 maxCoordForColumn(sampler src) {

        const float2 size = float2(src.extent().z, src.extent().w);

        /// Destination pixel coordinate, normalized
        const float2 pos = src.coord();

        float maxV = 0;
        float maxY = 0;

        for (float y = 0; y < size.y; y++) {
            float v = src.sample(float2(pos.x, y / size.y)).x;
            if (v > maxV) {
                maxY = y / size.y;
                maxV = v;
            }
        }

        return float4(maxY, maxY, maxY, 1.0);
    }

Это не даст каждый пиксель, где яркость больше 0,8, но для моих целей он возвращает достаточно: максимальное значение в каждом столбце и его местоположение.

Плюс: копирование в ЦП только байтов (2 * ширины изображения) вместо каждого пикселя экономит ТОННУ времени (несколько мс).

Против: если у вас есть две основные белые точки в одном столбце, вы никогда не узнаете. Возможно, вам придется изменить это и выполнять вычисления по строкам, а не по столбцам, если это соответствует вашему варианту использования.

ПОСЛЕДУЮЩИЕ ДЕЙСТВИЯ:

Кажется, возникла проблема с отображением выходных данных. Значения Float, возвращаемые в metal, не коррелируют со значениями UInt8, которые я получаю быстро.

Этот вопрос без ответа описывает проблему.

Изменить: Ответ на этот вопрос предоставляет очень удобная металлическая функция. Когда вы вызываете его на металлическом значении (например, 0,5) и возвращаете его, вы получите правильное значение (например, 128) на ЦП.


person Michael Austin    schedule 12.06.2019    source источник


Ответы (1)


Проверьте фильтры в CICategoryReduction (например, CIAreaAverage). Они возвращают изображения высотой всего в несколько пикселей, содержащие результат уменьшения. Но вам все равно нужно отрендерить их, чтобы иметь возможность читать значения в вашей функции Swift.

Проблема использования этого подхода для вашей проблемы заключается в том, что вы заранее не знаете количество возвращаемых координат. Однако Core Image должен знать объем вывода, когда он вызывает ваше ядро. Вы можете просто принять статическое максимальное количество координат, но все это звучит утомительно.

Я думаю, вам лучше использовать API-интерфейсы Accelerate для итерации пикселей вашего изображения (распараллеливание, супер эффективно) на ЦП, чтобы найти соответствующие координаты.

Вы можете использовать гибридный подход, при котором вы выполняете тяжелые вычисления на каждый пиксель на графическом процессоре с помощью Core Image, а затем выполняете анализ на процессоре с помощью Accelerate. Вы даже можете интегрировать часть ЦП в конвейер Core Image с помощью CIImageProcessorKernel.

person Frank Schlegel    schedule 13.06.2019
comment
Спасибо! Я пробовал как Accelerate, так и CIImageProcessorKernel, и это включало в себя копирование пикселей в память, в результате чего мой канал камеры упал примерно до 11 кадров в секунду (я амбициозно стремлюсь к минимум 30). Мне любопытно, как выглядят основные ядра для CICategoryReduction. Не уверен, подойдут ли встроенные модули для моих нужд, но, возможно, я смогу их создать. - person Michael Austin; 13.06.2019
comment
Нет необходимости копировать данные, поскольку память графического процессора и процессора совместно используется в iOS. Из CIImageProcessorInput CIImageProcessorKernel вы можете получить baseAddress базового буфера данных входного изображения и работать с ним напрямую, используя Accelerate. - person Frank Schlegel; 14.06.2019
comment
не могли бы вы подробнее объяснить, как читать байты в CPU? Я использовал .getBytes(), и это немного замедлило работу. В документации Core Image объединяет фильтры в сети в как можно меньшее количество ядер, избегая создания промежуточных буферов. Однако это невозможно сделать с ядрами процессора изображений. Думаю, это тоже может быть проблемой - person Michael Austin; 14.06.2019
comment
Подумав об этом, я полагаю, что вы тоже не можете использовать CIImageProcessorKernel, так как вы не знаете количество результатов. Каким-то образом вам нужно отрендерить ваш последний CIImage в некоторый буфер данных, к которому у вас есть доступ с помощью Accelerate (например, с помощью CIContext.render(CIImage, toBitmap: UnsafeMutableRawPointer, rowBytes: Int, bounds: CGRect, format: CIFormat, colorSpace: CGColorSpace?)). Можете ли вы сказать, откуда изначально берутся ваши изображения и куда вам нужен результат (то есть, нужен ли он дальше по конвейеру)? - person Frank Schlegel; 16.06.2019
comment
есть ли у вас какое-либо представление о этом вопросе о srgba to linear, потому что я читаю другие значения, чем возвращает мое ядро - person Michael Austin; 21.06.2019
comment
@MichaelAustin Да, скорее всего, из-за автоматического подбора цветов. См. Мой ответ на связанный вопрос. - person Frank Schlegel; 22.06.2019