Обработка изображений iPhone с помощью Accelerate Framework и vDSP
ОБНОВЛЕНИЕ: см. Дополнительный вопрос ниже с большим количеством кода;
Я пытаюсь кодировать категорию для размытия изображения. Моя отправная точка - образец Джеффа Ламарша здесь. Хотя это (после исправлений, предложенных другими) работает нормально, это на порядок медленнее для моих требований - в 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? Документация, которую я могу найти, ориентирована на справочную информацию и не очень удобна для новичков, когда дело доходит до такого рода вещей.
Если у кого-то есть ссылка на действительно быстрое размытие (и высокое качество, а не уменьшенное разрешение, а затем масштабируемый материал, который я видел, и смотрит штаны), это было бы великолепно!
РЕДАКТИРОВАТЬ:
Спасибо @ Джейсон. Я сделал это, и это почти работает, но теперь моя проблема в том, что, хотя изображение размыто, при каждом вызове оно сдвигается влево на 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 * B и т. Д.), Это объясняет, почему я думаю, что получаю черно-белый результат, но не сдвиг. Опять же, я думаю, что здесь может быть что-то большее, чем моя наивная версия здесь...
ЗАКЛЮЧИТЕЛЬНАЯ МЫСЛЬ: Я думаю, что смещение влево является естественным результатом фильтра, и мне нужно посмотреть на размеры изображения и, возможно, дополнить его... так что я думаю, что код на самом деле работает нормально, учитывая то, что я его выдал.
5 ответов
Вы определенно хотите конвертировать в float
выполнять фильтрацию, так как это то, что берут ускоренные функции, плюс это гораздо более гибко, если вы хотите выполнить дополнительную обработку. Время вычисления двумерной свертки (фильтра), скорее всего, будет меньше, чем время, потраченное на преобразование. Посмотрите на функцию vDSP_vfltu8()
который быстро преобразует данные uint8 во всплывающее. vDSP_vfixu8()
преобразует его обратно в uint8.
Чтобы выполнить размытие, вам, вероятно, понадобится большее ядро свертки, чем 3x3, поэтому я бы предложил использовать функцию vDSP_imgfir()
который займет любой размер ядра.
Ответ для редактирования:
Несколько вещей:
Вам необходимо выполнить фильтрацию для каждого цветового канала независимо. То есть вам нужно разделить компоненты R, G и B на их собственные изображения (типа float), отфильтровать их, а затем повторно мультиплексировать их в изображение ARGB.
vDSP_conv
вычисляет 1-мерную свертку, но для размытия изображения вам действительно нужна 2-мерная свертка.vDSP_imgfir
по существу, вычисляет двумерную свертку. Для этого вам понадобится и двумерное ядро. Вы можете найти формулу для двумерной гауссовской функции для создания ядра.
Примечание: вы на самом деле можете выполнить 2-D свертку, используя 1-D свертки, если ваше ядро отделимо (то есть гауссово). Я не буду вдаваться в то, что это значит, но по сути вам нужно выполнить 1-D свертку по столбцам, а затем выполнить 1-D свертку по полученным строкам. Я бы не пошел по этому пути, если вы не знаете, что делаете.
Несмотря на то, что инфраструктура 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 не приближается к таким скоростям, особенно когда речь идет о фильтрации живого видео.
Для дальнейшего использования, если вы планируете реализовать это НЕ: я сделал это для вас!
см.: https://github.com/gdawg/uiimage-dsp
для категории UIImage, которая добавляет Gaussian/Box Blur/Sharpen с использованием vDSP и инфраструктуры ускорения.
Поэтому, отвечая на мой собственный вопрос с превосходной помощью Джейсона, окончательный фрагмент рабочего кода предоставлен здесь для справки на случай, если он поможет кому-то еще. Как видите, стратегия состоит в том, чтобы разделить исходный 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);
Почему вы используете vDSP для фильтрации изображений? Попробуйте vImageConvolve_ARGB8888(). vImage является компонентом обработки изображений Accelerate.framework.