Обработка изображений iPhone с помощью Accelerate Framework и vDSP - PullRequest
8 голосов
/ 09 мая 2011

ОБНОВЛЕНИЕ: см. Дополнительный вопрос ниже с дополнительным кодом;

Я пытаюсь кодировать категорию для размытия изображения. Моя отправная точка - образец Джеффа Ламарша здесь . Хотя это (после исправлений, предложенных другими) работает нормально, для моих требований это на порядок медленнее - на 3GS для приличного размытия может потребоваться, возможно, 3 секунды, и я бы хотел уменьшить это значение до 0,5 сек для полного экрана (чем быстрее, тем лучше).

Он упоминает инфраструктуру Accelerate как повышение производительности, поэтому я провел последний день, рассматривая это, и в частности vDSP_f3x3, который согласно документации Apple

Фильтрует изображение, выполняя двумерная свертка с 3x3 ядро; одинарная точность.

Отлично - у меня есть подходящая матрица фильтров, и у меня есть изображение ... но это то место, где я в тупике.

vDSP_f3x3 предполагает, что данные изображения (float *), но мое изображение взято;

srcData = (unsigned char *)CGBitmapContextGetData (context);

и контекст исходит из CGBitmapContextCreate с kCGImageAlphaPremultipliedFirst, поэтому мой srcData действительно ARGB с 8 битами на компонент.

Я подозреваю, что мне действительно нужен контекст с плавающими компонентами, но в соответствии с документацией Quartz здесь , kCGBitMapFloatComponents доступен только в Mac OS, а не в iOS: - (

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

Может, у меня неправильный подход?

У кого-нибудь есть какие-нибудь советы для меня, которые выполнили некоторую обработку изображений на iphone с использованием vDSP? Документация, которую я могу найти, ориентирована на справочную информацию и не очень удобна для новичков, когда дело доходит до такого рода вещей.

Если у кого-то есть ссылка на действительно быстрое размытие (и высокое качество, а не уменьшенное разрешение, а затем масштабируемый материал, который я видел, и смотрит штаны), это было бы потрясающе!

EDIT:

Спасибо, @ Джейсон. Я сделал это, и это почти работает, но теперь моя проблема заключается в том, что, хотя изображение размыто, при каждом вызове оно сдвигается влево на 1 пиксель. Также кажется, что изображение черно-белое, но это может быть что-то еще.

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

CGImageRef CreateCGImageByBlurringImage(CGImageRef inImage, NSUInteger pixelRadius, NSUInteger gaussFactor)
{
unsigned char *srcData, *finalData;

CGContextRef context = CreateARGBBitmapContext(inImage);
if (context == NULL) 
    return NULL;

size_t width = CGBitmapContextGetWidth(context);
size_t height = CGBitmapContextGetHeight(context);
size_t bpr = CGBitmapContextGetBytesPerRow(context);

int componentsPerPixel = 4; // ARGB

CGRect rect = {{0,0},{width,height}}; 
CGContextDrawImage(context, rect, inImage); 

// Now we can get a pointer to the image data associated with the bitmap
// context.

srcData = (unsigned char *)CGBitmapContextGetData (context);

if (srcData != NULL)
{

    size_t dataSize = bpr * height;
    finalData = malloc(dataSize);
    memcpy(finalData, srcData, dataSize);

    //Generate Gaussian kernel

    float *kernel;  

    // Limit the pixelRadius

    pixelRadius = MIN(MAX(1,pixelRadius), 248);
    int kernelSize = pixelRadius * 2 + 1;

    kernel = malloc(kernelSize * sizeof *kernel);

    int gauss_sum =0;

    for (int i = 0; i < pixelRadius; i++)
    {
        kernel[i] = 1 + (gaussFactor*i);
        kernel[kernelSize - (i + 1)] = 1 + (gaussFactor * i);
        gauss_sum += (kernel[i] + kernel[kernelSize - (i + 1)]);
    }

    kernel[(kernelSize - 1)/2] = 1 + (gaussFactor*pixelRadius);

    gauss_sum += kernel[(kernelSize-1)/2];

    // Scale the kernel

    for (int i=0; i<kernelSize; ++i) {
        kernel[i] = kernel[i]/gauss_sum;
    }

    float * srcAsFloat,* resultAsFloat;

    srcAsFloat = malloc(width*height*sizeof(float)*componentsPerPixel);
    resultAsFloat = malloc(width*height*sizeof(float)*componentsPerPixel);

   // Convert uint source ARGB to floats

    vDSP_vfltu8(srcData,1,srcAsFloat,1,width*height*componentsPerPixel);

    // Convolve (hence the -1) with the kernel

    vDSP_conv(srcAsFloat, 1, &kernel[kernelSize-1],-1, resultAsFloat, 1, width*height*componentsPerPixel, kernelSize);

    // Copy the floats back to ints

    vDSP_vfixu8(resultAsFloat, 1, finalData, 1, width*height*componentsPerPixel);

    free(resultAsFloat);
    free(srcAsFloat);

}

size_t bitmapByteCount = bpr * height;

CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, finalData, bitmapByteCount, &providerRelease);

CGImageRef cgImage = CGImageCreate(width, height, CGBitmapContextGetBitsPerComponent(context),
                                   CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), CGBitmapContextGetColorSpace(context), CGBitmapContextGetBitmapInfo(context), 
                                   dataProvider, NULL, true, kCGRenderingIntentDefault);

CGDataProviderRelease(dataProvider);
CGContextRelease(context); 


return cgImage;
}

Я должен добавить это, если я закомментирую строку vDSP_conv и изменим следующую строку на;

       vDSP_vfixu8(srcAsFloat, 1, finalData, 1, width*height*componentsPerPixel);

Тогда, как и ожидалось, мой результат - клон оригинального источника. По цвету и не смещено влево. Это означает для меня, что это Свертка, которая идет не так, но я не вижу, где: - (

МЫСЛЬ: На самом деле, думая об этом, мне кажется, что сверточник должен знать, что входные пиксели находятся в формате ARGB, так как в противном случае свертка будет умножать значения вместе без знания их значения (т. Е. Оно будет кратно R * Б и т. Д.). Это объясняет, почему я думаю, что получаю черно-белый результат, но не сдвиг. Опять же, я думаю, что здесь может быть что-то большее, чем моя наивная версия ...

ЗАКЛЮЧИТЕЛЬНАЯ МЫСЛЬ: Я думаю, что сдвиг влево - это естественный результат фильтра, и мне нужно посмотреть на размеры изображения и, возможно, дополнить его ... поэтому я думаю, что код на самом деле работает нормально, учитывая то, что я передал это.

Ответы [ 5 ]

12 голосов
/ 10 мая 2011

Несмотря на то, что инфраструктура Accelerate будет работать быстрее, чем простой последовательный код, вы, вероятно, никогда не увидите наивысшую производительность, размыв изображение с ее помощью.

Я бы предложил использовать шейдер OpenGL ES 2.0 (для устройств, поддерживающих этот API), чтобы сделать размытие в два прохода. Исходя из моих тестов, графический процессор может обрабатывать подобные операции с изображениями со скоростью 14–28 раз выше, чем у процессора на iPhone 4, по сравнению с, возможно, 4,5 раза, который Apple сообщает для платформы Accelerate в лучших случаях.

Некоторый код для этого описан в этом вопросе , а также в главе «Эффекты постобработки на мобильных устройствах» в книге GPU Pro 2 (для которой Пример кода можно найти здесь ). Поместив изображение в текстуру, затем считав значения между пикселями, билинейная фильтрация на графическом процессоре дает вам некоторое размытие бесплатно, которое затем можно комбинировать с несколькими быстрыми поисками и операциями усреднения.

Если вам нужен стартовый проект для подачи изображений в графический процессор для обработки, вы можете использовать мой пример приложения из статьи здесь . Этот пример приложения передает видеокадры AVFoundation в качестве текстур в шейдер обработки, но вы можете изменить его, чтобы отправлять данные вашего конкретного изображения и выполнять операцию размытия. Вы сможете использовать мой код glReadPixels(), чтобы затем получить размытое изображение для дальнейшего использования.

С тех пор как я написал этот ответ, я создал инфраструктуру обработки изображений и видео с открытым исходным кодом для выполнения подобных операций на графическом процессоре. Фреймворк имеет несколько различных типов размытия, которые можно очень быстро применить к изображениям или живому видео. GPUImageGaussianBlurFilter, который применяет стандартное размытие по Гауссу с 9 ударами, выполняется в 16 мс для кадра видео 640x480 на iPhone 4. GPUImageFastBlurFilter - это модифицированный размытие по Гауссу с 9 ударами, которое использует аппаратную фильтрацию, и он работает за 2,0 мс тот самый видеокадр. Аналогично, есть GPUImageBoxBlurFilter, который использует 5-пиксельное поле и работает в 1,9 мс для того же изображения на том же оборудовании. У меня также есть медианные и двусторонние фильтры размытия, хотя они требуют небольшой настройки производительности.

В моих тестах Accelerate не приближается к таким скоростям, особенно когда речь идет о фильтрации живого видео.

9 голосов
/ 09 мая 2011

Вы определенно хотите преобразовать в float, чтобы выполнить фильтрацию, поскольку это то, что принимают ускоренные функции, плюс она намного более гибкая, если вы хотите выполнить какую-либо дополнительную обработку.Время вычисления двумерной свертки (фильтра), скорее всего, будет меньше, чем время, потраченное на преобразование.Взгляните на функцию vDSP_vfltu8(), которая быстро преобразует данные uint8 в плавающее.vDSP_vfixu8() преобразует его обратно в uint8.

Чтобы выполнить размытие, вам, вероятно, понадобится большее сверточное ядро, чем 3x3, поэтому я бы предложил использовать функцию vDSP_imgfir(), которая будет принимать ядро ​​любого размера.

Ответ на редактирование:

Несколько вещей:

  1. Вам необходимо выполнить фильтрацию для каждого цветового канала независимо.То есть вам нужно разделить компоненты R, G и B на их собственные изображения (типа float), отфильтровать их, а затем повторно мультиплексировать их в изображение ARGB.

  2. vDSP_conv вычисляет 1-мерную свертку, но для размытия изображения вам действительно нужна 2-мерная свертка.vDSP_imgfir по существу вычисляет 2-мерную свертку.Для этого вам понадобится и двумерное ядро.Вы можете найти формулу для двумерной гауссовой функции для создания ядра.
    Примечание: На самом деле вы можете выполнить 2-D свертку, используя 1-D свертки, если ваше ядро ​​отделимо (котороеГауссов есть).Я не буду вдаваться в то, что это значит, но по сути вам нужно выполнить 1-D свертку по столбцам, а затем выполнить 1-D свертку по полученным строкам.Я бы не пошел по этому пути, если вы не знаете, что делаете.

3 голосов
/ 18 мая 2011

Для дальнейшего использования, если вы планируете реализовать это НЕ: Я сделал это для вас!

см: https://github.com/gdawg/uiimage-dsp

для категории UIImage, которая добавляет Gaussian / Box Blur / Sharpen с использованием vDSP и инфраструктуры ускорения.

3 голосов
/ 10 мая 2011

Итак, отвечая на мой собственный вопрос с превосходной помощью Джейсона, здесь приводится окончательный фрагмент рабочего кода для справки на случай, если он кому-нибудь поможет.Как видите, стратегия состоит в том, чтобы разделить исходный ARGB (я игнорирую A для производительности и предполагаю, что данные XRGB) на 3 плавающих массива, применить фильтр и затем повторно мультиплексировать результат.Это работает удовольствие - но это мучительно медленно.Я использую большое ядро ​​16x16, чтобы получить сильное размытие, и на моем 3GS требуется около 5 секунд для полноэкранного изображения, так что это не будет жизнеспособным решением.

Следующим шагом будет поиск альтернатив ... но спасибо, что помогли мне начать работу.

    vDSP_vfltu8(srcData+1,4,srcAsFloatR,1,pixels);
    vDSP_vfltu8(srcData+2,4,srcAsFloatG,1,pixels);
    vDSP_vfltu8(srcData+3,4,srcAsFloatB,1,pixels);

    // Now apply the filter to each of the components. For a gaussian blur with a 16x16 kernel
    // this turns out to be really slow!

    vDSP_imgfir (srcAsFloatR, height, width, kernel,resultAsFloatR, frows, fcols);
    vDSP_imgfir (srcAsFloatG, height, width, kernel,resultAsFloatG, frows, fcols);
    vDSP_imgfir (srcAsFloatB, height, width, kernel,resultAsFloatB, frows, fcols);

    // Now re-multiplex the final image from the processed float data

    vDSP_vfixu8(resultAsFloatR, 1, finalData+1, 4, pixels);
    vDSP_vfixu8(resultAsFloatG, 1, finalData+2, 4, pixels);
    vDSP_vfixu8(resultAsFloatB, 1, finalData+3, 4, pixels);
2 голосов
/ 21 января 2014

Почему вы используете vDSP для фильтрации изображений? Попробуйте vImageConvolve_ARGB8888 (). vImage является компонентом обработки изображений Accelerate.framework.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...