[Разработка мобильных приложений, Разработка под Android, Обработка изображений] Рисуем светом: длинная выдержка на Android
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет, меня зовут Дмитрий, и я Android-разработчик в компании «MEL Science». Сегодня я хочу рассказать, как можно реализовать поддержку длинной выдержки на смартфонах, да так, чтобы получающуюся картинку можно было наблюдать прямо в процессе создания. А для заинтересовавшихся в конце статьи я подготовил ссылку на тестовое приложение - чтобы вы могли сами сделать крутое фото с длинной выдержкой.
Длинная выдержкаВыдержка - термин из мира фотографии, который определяет время открытия затвора при съемке. Чем дольше открыт затвор, тем дольше свет экспонирует светочувствительную матрицу. Проще говоря, делает фотографию более яркой. Современные фотоаппараты используют выдержки длинной в 1/2000 cекунды, что позволяет получить освещенную, но при этом не пересвеченную фотографию. Длинная выдержка подразумевает открытие затвора на секунду и больше. При верно выбранной сцене это позволяет получать фантастические фотографии, способные запечатлеть движение света в объективе камеры. Причем фотографировать можно что угодно: ночные улицы с мчащимися машинами или маятник, с укрепленным на нем фонариком, позволяющим выписывать фигуры Лиссажу. А можно вообще рисовать светом самому и получать целые картины-фотографии.
Улицы города, сфотографированные с использованием длинной выдержкиТеорияДля создания эффекта длинной выдержки можно использовать два подхода:
- аппаратный - состоит в управлении физическим открытием и закрытием затвора
- программный - эмулирует длинную выдержку за счет объединения большого числа обычных кадров
Главным недостатком аппаратного подхода является отсутствие возможности наблюдать за процессом появления фотографии онлайн - результат будет виден лишь после закрытия затвора и формирования изображения. Нарисовать что-либо светом человеку без опыта в таком режиме вряд ли удастся. Еще одним недостатком становится ограничение смартфонов на максимальное время выдержки - на Android оно составляет 30 секунд.Программная эмуляция позволяет избавиться от всех недостатков ценой усложнения кода.ПрактикаДля реализации работы с камерой смартфона будем использовать API CameraX. Это обусловлено ее гибкостью и лаконичностью. Также для программного подхода нам потребуется OpenGL ES для работы с изображениями. Данный выбор был сделан так как, это позволит работать напрямую с изображениями в видео памяти и обеспечить минимальную задержку при записи, так как вся обработка изображений происходит в реальном времени.Аппаратный подходДля реализации длинной выдержки средствами камеры, необходимо лишь правильно сконфигурировать usecase фотосъемки. Делается это всего в пару строк, а на выходе мы получаем фотографию с длинной выдержкой.Для тонкой настройки камеры, используем Camera2Interop который позволяет устанавливать флаги настройки камеры вручную, как в Camera2API. Для активации длинной выдержки помимо установки времени выдержки необходимо также отключить автоэкспозицию, т.к. иначе выдержка будет управляться камерой самостоятельно.
val imageCaptureBuilder = ImageCapture.Builder()
Camera2Interop.Extender(imageCaptureBuilder).apply {
setCaptureRequestOption(
CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_OFF
)
setCaptureRequestOption(
CaptureRequest.SENSOR_EXPOSURE_TIME,
EXPOSURE_TIME_SEC * NANO_IN_SEC
)
}
Кстати, для каждой камеры диапазон допустимого интервала выдержки может быть разным, чтобы его узнать необходимо запросить cameraCharacteristics
val manager = getSystemService(CAMERA_SERVICE) as CameraManager
for (cameraId in manager.cameraIdList) {
val chars = manager.getCameraCharacteristics(cameraId)
val range = chars.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE)
Log.e("CameraCharacteristics", "Camera $cameraId range: ${range.toString()}")
}
Программный подходДля начала определимся с общей идеей нашей реализации.
- Нам потребуется буффер для хранения формирующегося изображения, а также регулярно обновляемое изображение с камеры.
- Каждый новый кадр будем объединять с хранящимся в буффере, обрабатывая попиксельно и оставляя в буфере пиксель с наибольшей яркостью.
- Для того чтобы придать нашим картинкам эффект постепенного исчезновения света можем долго неизменяемые пиксели постепенно затемнять.
Я не буду приводить здесь весь код рендеринга, так как это займет слишком много места. Для желающих код можно найти здесь. Представлю лишь блок схему, объясняющую последовательность действий для получения очередного кадра на экране.
Как видно из схемы, основная магия происходит при объединении 2х кадров: сохраненного в фреймбуфере и полученного с камеры. Рассмотрим шейдер для этой задачи подробнее.
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform mat4 stMatrix;
uniform texType0 tex_sampler;
uniform texType1 old_tex_sampler;
varying vec2 v_texcoord;
void main() {
vec4 color = texture2D(tex_sampler, (stMatrix * vec4(v_texcoord.xy, 0, 1)).xy);
vec4 oldColor = texture2D(old_tex_sampler, v_texcoord);
float oldBrightness = oldColor.r * 0.2126 + oldColor.g * 0.7152 + oldColor.b * 0.0722 + oldColor.a;
float newBrightness = color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722 + color.a;
// объединяем пиксели
}
Работа шейдера состоит из нескольких этапов:
- К текстуре камеры мы применяем матрицу для получения верной ориентации изображения.
- Затем вычисляем яркость пикселя на обоих кадрах
- Объединяем пиксели
Для того чтобы понять, как правильно объединить пиксели, необходимо вспомнить принцип работы длинной выдержки - “длинная выдержка позволяет свету попадать на матрицу камеры длительное время и сохраняться до конца съемки”. То есть, если в течение процесса фотографии, в разных местах кадра будет появляться свет, то он должен оставаться в кадре до конца. При этом, длительность освещения пикселя не важна, т.к. однажды попавший в кадр свет сохранится до завершения съемки, даже если все остальное время свет на этот пиксель попадать не будет. Очевидным в таком случае является накопление света за счет сложения значений с последовательных кадров. Однако такое решение позволит снимать лишь в условиях минимальной освещенности и будет приводить к быстрому засвету пикселя. Во избежание данных проблем в качестве способа объединения пикселей была выбрана функция максимума. Она позволяет обновить значение пикселя только, если новый кадр содержит более освещенный пиксель в данной позиции.Тогда объединение пикселей будет выглядеть вот так:
if (newBrightness > oldBrightness) {
gl_FragColor = color;
} else {
gl_FragColor = oldColor;
}
Попробуем запустить такой код и обнаружим, что эффект длинной выдержки действительно достигается. Вот что получилось.
Длинная выдержкаОднако любая ошибка при рисовании требует перезапуска камеры, т.к. один раз попавший на нее свет уже нельзя стереть! Такое поведение приемлемо при выполнении каких-то заранее спланированных фотографий, но что если мы хотим просто рисовать светом и сохранять изображение, лишь когда нам действительно понравился результат? Перезапускать постоянно камеру совсем неудобно. Значит свет все-таки должен пропадать через какое то время. Этого можно добиться с помощью постепенного затухания ярких пикселей. Чтобы добиться такого эффекта достаточно просто на каждом новом шаге добавлять к каждому пикселю немного черного цвета (чтобы сохранять корректность картинки мы будем добавлять не черный цвет, а просто более темный пиксель из доступных - это позволит и эффект угасания получить и сохранить гамму цветов). Тогда объединение пикселей будет выглядеть следующим образом
if (newBrightness > oldBrightness) {
gl_FragColor = mix(color, oldColor, 0.01);
} else {
gl_FragColor = mix(oldColor, color, 0.01);
}
Вот несколько примеров с разными коэффициентами и временем затухания света.
Коэффициент 0.001
Коэффициент 0.01
Коэффициент 0.5Заключение
Вот несколько примеров, что умеет получившееся приложение.
Все-таки я не художник :( А что нарисуете вы?На этом на сегодня все. Для желающих попробовать самому полный код приложения и apk можно найти здесь.
===========
Источник:
habr.com
===========
Похожие новости:
- [Обработка изображений, Разработка под MacOS, Настольные компьютеры] Анимированное изображение в формате GIF на x86 занимает 360 МБ ОЗУ, на Apple Silicon — почему-то 35.51 ГБ со свапом
- [Google Chrome, Разработка под Android, Управление медиа] Google вернет поддержку RSS в Chrome для Android
- [Разработка мобильных приложений, Разработка под Android, Дизайн мобильных приложений, Монетизация мобильных приложений] Google I/O 2021: что нового для Android-разработчиков
- [Java, Разработка мобильных приложений, Kotlin] «Почему Kotlin хуже, чем Java?» (перевод)
- [Разработка под Android] Пример модульного андроид приложения с помощью Navigation component и Koin (DI)
- [Программирование, Разработка мобильных приложений, Разработка под Android] Google I/O: что нового представили Android-разработчикам (перевод)
- [Разработка под Android, Kotlin, Искусственный интеллект, Голосовые интерфейсы] На продуктах Just AI будут обучать разговорному искусственному интеллекту
- [Разработка под iOS, Разработка мобильных приложений, Разработка под MacOS, IT-компании] Разработчик рассказал, сколько ему принесло место в рейтинге приложений магазинов iOS и Mac: около 60$
- Началось бета-тестирование мобильной платформы Android 12
- [Программирование, Разработка мобильных приложений, Flutter] Flutter: флип-анимация (перевод)
Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_obrabotka_izobrazhenij (Обработка изображений), #_dlinnaja_vyderzhka (длинная выдержка), #_long_exposure, #_android, #_open_gl, #_mobilnaja_razrabotka (мобильная разработка), #_rabota_s_kameroj_v_android (работа с камерой в android), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_razrabotka_pod_android (
Разработка под Android
), #_obrabotka_izobrazhenij (
Обработка изображений
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:41
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет, меня зовут Дмитрий, и я Android-разработчик в компании «MEL Science». Сегодня я хочу рассказать, как можно реализовать поддержку длинной выдержки на смартфонах, да так, чтобы получающуюся картинку можно было наблюдать прямо в процессе создания. А для заинтересовавшихся в конце статьи я подготовил ссылку на тестовое приложение - чтобы вы могли сами сделать крутое фото с длинной выдержкой. Длинная выдержкаВыдержка - термин из мира фотографии, который определяет время открытия затвора при съемке. Чем дольше открыт затвор, тем дольше свет экспонирует светочувствительную матрицу. Проще говоря, делает фотографию более яркой. Современные фотоаппараты используют выдержки длинной в 1/2000 cекунды, что позволяет получить освещенную, но при этом не пересвеченную фотографию. Длинная выдержка подразумевает открытие затвора на секунду и больше. При верно выбранной сцене это позволяет получать фантастические фотографии, способные запечатлеть движение света в объективе камеры. Причем фотографировать можно что угодно: ночные улицы с мчащимися машинами или маятник, с укрепленным на нем фонариком, позволяющим выписывать фигуры Лиссажу. А можно вообще рисовать светом самому и получать целые картины-фотографии. Улицы города, сфотографированные с использованием длинной выдержкиТеорияДля создания эффекта длинной выдержки можно использовать два подхода:
val imageCaptureBuilder = ImageCapture.Builder()
Camera2Interop.Extender(imageCaptureBuilder).apply { setCaptureRequestOption( CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF ) setCaptureRequestOption( CaptureRequest.SENSOR_EXPOSURE_TIME, EXPOSURE_TIME_SEC * NANO_IN_SEC ) } val manager = getSystemService(CAMERA_SERVICE) as CameraManager
for (cameraId in manager.cameraIdList) { val chars = manager.getCameraCharacteristics(cameraId) val range = chars.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE) Log.e("CameraCharacteristics", "Camera $cameraId range: ${range.toString()}") }
Как видно из схемы, основная магия происходит при объединении 2х кадров: сохраненного в фреймбуфере и полученного с камеры. Рассмотрим шейдер для этой задачи подробнее. #extension GL_OES_EGL_image_external : require
precision mediump float; uniform mat4 stMatrix; uniform texType0 tex_sampler; uniform texType1 old_tex_sampler; varying vec2 v_texcoord; void main() { vec4 color = texture2D(tex_sampler, (stMatrix * vec4(v_texcoord.xy, 0, 1)).xy); vec4 oldColor = texture2D(old_tex_sampler, v_texcoord); float oldBrightness = oldColor.r * 0.2126 + oldColor.g * 0.7152 + oldColor.b * 0.0722 + oldColor.a; float newBrightness = color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722 + color.a; // объединяем пиксели }
if (newBrightness > oldBrightness) {
gl_FragColor = color; } else { gl_FragColor = oldColor; } Длинная выдержкаОднако любая ошибка при рисовании требует перезапуска камеры, т.к. один раз попавший на нее свет уже нельзя стереть! Такое поведение приемлемо при выполнении каких-то заранее спланированных фотографий, но что если мы хотим просто рисовать светом и сохранять изображение, лишь когда нам действительно понравился результат? Перезапускать постоянно камеру совсем неудобно. Значит свет все-таки должен пропадать через какое то время. Этого можно добиться с помощью постепенного затухания ярких пикселей. Чтобы добиться такого эффекта достаточно просто на каждом новом шаге добавлять к каждому пикселю немного черного цвета (чтобы сохранять корректность картинки мы будем добавлять не черный цвет, а просто более темный пиксель из доступных - это позволит и эффект угасания получить и сохранить гамму цветов). Тогда объединение пикселей будет выглядеть следующим образом if (newBrightness > oldBrightness) {
gl_FragColor = mix(color, oldColor, 0.01); } else { gl_FragColor = mix(oldColor, color, 0.01); } Коэффициент 0.001 Коэффициент 0.01 Коэффициент 0.5Заключение Вот несколько примеров, что умеет получившееся приложение. Все-таки я не художник :( А что нарисуете вы?На этом на сегодня все. Для желающих попробовать самому полный код приложения и apk можно найти здесь. =========== Источник: habr.com =========== Похожие новости:
Разработка мобильных приложений ), #_razrabotka_pod_android ( Разработка под Android ), #_obrabotka_izobrazhenij ( Обработка изображений ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:41
Часовой пояс: UTC + 5