Почему масштабирование UIImage из камеры такое медленное?

изменение размера UIImage камеры, возвращаемого UIImagePickerController, занимает до смешного много времени, если вы делаете это обычным способом, как в этот пост.

[обновление: здесь последний призыв к творческим идеям! я думаю, следующий вариант - спросить Apple.]

Да, это много пикселей, но графическое оборудование iPhone прекрасно способно рисовать много текстурированных квадратов 1024x1024 на экране за 1/60 секунды, так что действительно должен быть способ уменьшить размер изображения 2048x1536 до 640х480 намного меньше 1,5 секунд.

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

Я предполагаю, что его нужно преобразовать из RGBA в ABGR или что-то в этом роде; Может ли кто-нибудь придумать способ убедить систему предоставить мне данные быстро, даже если они в неправильном формате, и я сам займусь этим позже?

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

Итак, вопрос: есть ли какой-нибудь альтернативный метод рисования, помимо использования CGBitmapContextCreate и CGContextDrawImage, который использует больше преимуществ графического процессора?

Есть кое-что для исследования: если я начну с UIImage того же размера, который не из средства выбора изображений, будет ли он столь же медленным? Очевидно нет...

Обновление: Мэтт Лонг обнаружил, что для изменения размера изображения, которое вы получаете обратно из средства выбора в [info objectForKey:@"UIImagePickerControllerEditedImage"], требуется всего 30 мс, если вы включили кадрирование с помощью ручного управления камерой. Это бесполезно для случая, когда меня волнует, где я использую takePicture для программной съемки. Я вижу, что отредактированное изображение kCGImageAlphaPremultipliedFirst, но исходное изображение kCGImageAlphaNoneSkipFirst.

Дальнейшее обновление: Джейсон Кроуфорд предложил CGContextSetInterpolationQuality(context, kCGInterpolationLow), который на самом деле сокращает время примерно с 1,5 до 1,3 секунды за счет качества изображения - но это все еще далеко от скорости, на которую должен быть способен графический процессор!

Последнее обновление перед истечением недели: пользователь refulgentis провел некоторое профилирование, которое, похоже, указывает на то, что 1,5 секунды уходит на запись захваченного изображения с камеры на диск в формате JPEG, а затем его чтение обратно. Если это правда , очень странно.


person David Maymudes    schedule 05.12.2009    source источник
comment
Для всех, кто читал это в 2014 году (5 лет спустя!) ЕСЛИ вы используете UIImagePickerController, а затем takePicture. (т. е. более обычный процесс с настраиваемым оверлеем, позволяющим отключать редактирование и собственными кнопками). Любое разумное масштабирование / обрезка, которое вы напишете, займет около 0,15 на 5S. НО на самом деле глупая функция takePicture занимает 0,4 секунды (!) Это одна из самых глупых вещей в iOS. они должны добавить что-то, что обеспечивает меньшее изображение, не дожидаясь столетий, но при этом используя высокоуровневые библиотеки. Короче говоря, забудьте об оптимизации, а сам takePicture - бездельник! -2014   -  person Fattie    schedule 06.03.2014


Ответы (5)


Используйте Shark, профилируйте ее, выясните, что занимает так много времени.

Мне приходится много работать с MediaPlayer.framework, и когда вы получаете свойства для песен на iPod, первый запрос свойства безумно медленный по сравнению с последующими запросами, потому что в первом запросе свойства MobileMediaPlayer упаковывает словарь со всеми свойствами и передает это в мое приложение.

Готов поспорить, что здесь имеет место аналогичная ситуация.

РЕДАКТИРОВАТЬ: мне удалось создать временной профиль в Shark как для ситуации UIImagePickerControllerEditedImage Мэтта Лонга, так и для общей ситуации UIImagePickerControllerOriginalImage.

В обоих случаях большую часть времени занимает CGContextDrawImage. В случае Мэтта Лонга об этом позаботится UIImagePickerController в промежутке между захватом изображения пользователем и переходом изображения в режим редактирования.

При масштабировании процента времени до CGContextDrawImage = 100%, CGContextDelegateDrawImage затем занимает 100%, затем ripc_DrawImage (из libRIP.A.dylib) занимает 100%, а затем ripc_AcquireImage (который, похоже, распаковывает JPEG и занимает большую часть его time in _cg_jpeg_idct_islow, vec_ycc_bgrx_convert, decopress_onepass, sep_upsample) занимает 93% времени. На самом деле только 7% времени тратится на ripc_RenderImage, который, как я полагаю, и является фактическим рисунком.

person refulgentis    schedule 14.12.2009
comment
медленная часть - это единственный вызов CGContextDrawImage, поэтому я не думаю, что Shark может быть здесь очень полезен - сломает ли он то, что происходит в рамках одного вызова ОС? Я все равно попробую ... - person David Maymudes; 14.12.2009
comment
да, будет - я бы попробовал сделать это сам, но я не могу понять, какой именно код здесь используется: / - person refulgentis; 14.12.2009
comment
Ха, я использовал модифицированную версию образца кода Мэтта Лонга, чтобы воспроизвести проблему, но каждый раз, когда я пытаюсь профилировать ее с помощью Shark, Shark вылетает, очень сложно. Кроме того, моя теория о том, что он ведет себя аналогично MediaPlayer.framework, неверна, потому что с изображением можно делать все что угодно с нормальной скоростью, кроме CGContextDrawImage (); - person refulgentis; 14.12.2009
comment
Мне удалось создать временной профиль, в основном он медленный из-за распаковки JPEG. Прочтите мою правку к приведенному выше ответу, если вы хотите увидеть имеющиеся у меня временные профили. - person refulgentis; 14.12.2009
comment
все еще не уверен, о чем вы здесь говорите - вы говорите, что когда необработанное изображение поступает с камеры, оно сжато в формате JPEG? В это трудно поверить ... Я вижу bgrx_convert в вашем списке, из-за чего создается впечатление, что ОС переупорядочивает данные пикселей, и снова заставляет меня задаться вопросом, есть ли способ заставить систему немного выдать мне неупорядоченные данные быстрее, чтобы я мог работать над этим сам ... - person David Maymudes; 14.12.2009
comment
Как бы странно это ни звучало, очевидно, да. В профиле есть вызовы к [PLUICameraViewController cameraView: photoSaved:], затем к [PLNotifyImagePickerOImageAvailability], а затем к [ImagePickerThingViewController imagePickerController: didFinishPickingMediaWithInfo:], поэтому он должен отформатировать изображение, а затем сообщить об этом на диске в формате JPEG в формате JPEG. - person refulgentis; 14.12.2009
comment
это в высшей степени странно. очевидно, цель состоит в том, чтобы найти что-то, что я могу сделать, не копаясь в частных фреймворках ... - person David Maymudes; 14.12.2009
comment
ну, это все еще не тот ответ, на который я надеялся, но, возможно, самая новая информация из тех, кто исследовал. - person David Maymudes; 14.12.2009

Похоже, вы сделали здесь несколько предположений, которые могут быть, а могут и нет. Мой опыт отличается от вашего. Этот метод, кажется, занимает всего 20–30 мс на моих сетях 3G при масштабировании фотография сделана с камеры до 0,31 оригинального размера с обращением к:

CGImageRef scaled = CreateScaledCGImageFromCGImage([image CGImage], 0.31);

(Я получаю 0,31, принимая масштаб ширины, кстати, 640,0 / 2048,0)

Я проверил, что изображение того же размера, с которым вы работаете. Вот мой вывод NSLog:

2009-12-07 16:32:12.941 ImagePickerThing[8709:207] Info: {
    UIImagePickerControllerCropRect = NSRect: {{0, 0}, {2048, 1536}};
    UIImagePickerControllerEditedImage = <UIImage: 0x16c1e0>;
    UIImagePickerControllerMediaType = "public.image";
    UIImagePickerControllerOriginalImage = <UIImage: 0x184ca0>;
}

Я не уверен, почему разница, и я не могу ответить на ваш вопрос, поскольку он относится к графическому процессору, однако я считаю 1,5 секунды и 30 мс очень значительной разницей. Может быть, сравните код в этом сообщении в блоге с тем, что вы используете?

Наилучшие пожелания.

person Matt Long    schedule 07.12.2009
comment
ваш NSLog выводит изображение непосредственно с камеры или загруженное из камеры? у моих изображений нет ключа EditedImage или CropRect ... - person David Maymudes; 08.12.2009
comment
Что ж, я изменил свой код CGBitmapContextCreate, чтобы он больше соответствовал вашему, но я все еще вижу, что вызов CGContextDrawImage занимает 1,5 секунды. Возможно, разница в том, что сохраненное изображение отличается от изображения с камеры; Есть предположения? - person David Maymudes; 08.12.2009
comment
Я все еще получаю от 20 до 30 мс, даже если снимаю прямо с камеры. И да, у меня в этом режиме есть отредактированное изображение и клавиша кадрирования прямоугольника. Я не уверен, что могло вызвать разницу. Вызывает ли ваш контроллер представления viewDidUnload или didReceiveMemoryWarning, когда вы используете камеру? - person Matt Long; 08.12.2009
comment
Посмотрите, сколько времени вы получите с этим проектом: cimgf.com/files/ImagePickerThing.zip - person Matt Long; 08.12.2009
comment
Спасибо за размещение кода; Я вижу, что у вас есть [picker setAllowsImageEditing: YES], который, вероятно, объясняет дополнительные ключи. Я делаю снимки программно с помощью takePicture, поэтому я не знаю, как эти две вещи взаимодействуют друг с другом, но я дам вам знать. - person David Maymudes; 08.12.2009
comment
ОК: ваш код изменяет размер обрезанной версии изображения; если я изменю его на использование [info objectForKey: @UIImagePickerControllerOriginalImage], это займет 1,5 секунды. См. Обновление к вопросу выше. - person David Maymudes; 08.12.2009
comment
Я второй Мэтт. Изменение размера изображений с камеры не занимает много времени! - person lostInTransit; 13.12.2009
comment
lostInTransit - можете уточнить? Вы нашли способ делать снимки с камеры в реальном времени и делать с ними что-нибудь за ‹1 секунду? - person David Maymudes; 13.12.2009
comment
Причина, по которой этот подход «быстрее», заключается в том, что операционная система сама выполняет CGContextDrawImage между камерой, делающей снимок, и пользователем, которому предоставляется изображение для редактирования. Та же скорость, только другой порядок вызовов, что делает код программиста быстрее, потому что он получает результаты уже выполненной операции CGContextDrawImage. - person refulgentis; 14.12.2009

У меня была такая же проблема, и я долгое время бился о нее головой. Насколько я могу судить, первый раз, когда вы обращаетесь к UIImage, возвращаемому средством выбора изображений, это просто медленно. В качестве эксперимента попробуйте синхронизировать любые две операции с UIImage - например, ваше масштабирование, а затем UIImageJPEGRepresentation или что-то в этом роде. Затем поменяйте порядок. Когда я делал это раньше, первая операция получает штраф по времени. Моя лучшая гипотеза заключается в том, что память каким-то образом все еще находится на ПЗС-матрице, и передача ее в основную память для выполнения каких-либо действий с ней выполняется медленно.

Когда вы устанавливаете allowImageEditing = YES, изображение, которое вы возвращаете, изменяется и обрезается примерно до 320x320. Это делает его быстрее, но, вероятно, это не то, что вам нужно.

Лучшее ускорение, которое я нашел:

CGContextSetInterpolationQuality(context, kCGInterpolationLow)

в контексте, возвращаемом из CGBitmapContextCreate, прежде чем вы выполните CGContextDrawImage.

Проблема в том, что ваши уменьшенные изображения могут выглядеть не так хорошо. Однако, если вы уменьшаете масштаб на целочисленный коэффициент, например, с 1600x1200 до 800x600, то все выглядит нормально.

person jasoncrawford    schedule 08.12.2009
comment
Переход к низкому качеству, похоже, сокращает время примерно с 1,5 до 1,3 секунды, но, опять же, намного меньше, чем кажется возможным, поэтому я все еще надеюсь на лучший ответ. - person David Maymudes; 09.12.2009
comment
Удачи и дайте мне знать, если найдете. Я много боролся с этим, и я не уверен, что есть решение. Попробуйте опубликовать фотографию в Facebook или даже отправить фотографию из собственного фотоальбома по электронной почте. Все они имеют многосекундную задержку. - person jasoncrawford; 11.12.2009
comment
единственное, что дает мне надежду, - это то, что в этих случаях приложение для работы с фотографиями работает с изображением с полным разрешением, поэтому, возможно, есть более быстрый метод, если вам просто нужна версия с более низким разрешением и не нужно менять так много пикселей. - person David Maymudes; 12.12.2009

Вот проект git, который я использовал, и, похоже, он хорошо работает. Использование также довольно чистое - одна строка кода.

https://github.com/AliSoftware/UIImage-Resize

person jinglesthula    schedule 21.06.2011

НЕ ИСПОЛЬЗУЙТЕ CGBitmapImageContextCreate в этом случае! Я провел почти неделю в той же ситуации, в которой оказались вы. Производительность будет просто ужасной, и она будет съедать память как сумасшедшая. Вместо этого используйте UIGraphicsBeginImageContext:

// create a new CGImage of the desired size
UIGraphicsBeginImageContext(desiredImageSize);
CGContextRef c = UIGraphicsGetCurrentContext();

// clear the new image
CGContextClearRect(c, CGRectMake(0,0,desiredImageSize.width, desiredImageSize.height));

// draw in the image
CGContextDrawImage(c, rect, [image CGImage]);

// return the result to our parent controller
UIImage * result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

В приведенном выше примере (из моего собственного кода изменения размера изображения) "rect" значительно меньше изображения. Приведенный выше код работает очень быстро и должен делать именно то, что вам нужно.

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

person Ben Gotow    schedule 14.12.2009
comment
В качестве примечания: после того, как я обнаружил это, я просмотрел свой код и начал использовать вызовы UIGraphics ... для создания контекстов изображений во многих других местах. Возможно, вы сможете получить другие приросты скорости, используя этот подход в другом месте! - person Ben Gotow; 14.12.2009
comment
Мне трудно поверить, что это правильно - я почти уверен, что UIGraphicsBeginImageContext - это просто оболочка для CGBitmapImageContextCreate. Каков был источник изображения, которое вы используете с приведенным выше кодом и видите быстрое рисование? - person David Maymudes; 14.12.2009
comment
Я пробовал использовать это, это не дает скорости. Медлительность связана с декодированием изображения с камеры, она не имеет ничего общего с тем, как создается CGContext. - person refulgentis; 14.12.2009
comment
-1 за разумное звучание и хорошо отформатированный, но на самом деле неправильный. - person David Maymudes; 14.12.2009
comment
Также обратите внимание, что вы можете создавать такие CGContexts только в основном потоке. Если вы хотите сжать изображение в фоновом режиме (например, для отображения индикатора активности тем временем), вам необходимо использовать CGBitmapContextCreate. - person jasoncrawford; 15.12.2009