По опыту, есть одна конкретная техническая проблема, которая чаще всего ставит людей в тупик. Нет, это не сложный теоретический вопрос или проблема с дорогими GPU. Дело в том, что почти все загружают в память изображения повёрнутыми, даже не подозревая об этом. А компьютеры не очень хорошо обнаруживают объекты или распознают лица в повёрнутых изображениях.
Как цифровые камеры автоматически поворачивают изображения
Когда вы делаете снимок, камера регистрирует положение телефона, так что в другой программе картинка отобразится в правильной ориентации:
Но камера фактически не поворачивает пиксельные данные внутри файла. Поскольку датчики изображения в цифровых камерах считываются построчно как непрерывный поток информации о пикселях, камере проще всегда сохранять данные о пикселях в одинаковом порядке, независимо от реального положения телефона.
Это уже забота программы для просмотра — правильно повернуть картинку, прежде чем отобразить её на экране. Наряду с данными самого изображения, камера также сохраняет метаданные — настройки объектива, данные о местоположении и, конечно, угол поворота камеры. Программа просмотра должна использовать эту информацию для правильного отображения.
Наиболее распространённый формат метаданных изображений называется EXIF (сокращение от Exchangeable Image File Format). Метаданные в формате EXIF внедряются в каждый файл jpeg. Вы не можете их видеть на экране, но они читаются любой программой, которая знает, где искать.
Вот метаданные EXIF внутри JPEG-изображения нашего гуся из инструмента exiftool:
Видите элемент ‘Orientation’? Он говорит программе просмотра, что перед отображением на экране картинку следует повернуть на 90 градусов по часовой стрелке. Если программа забудет это сделать, изображение будет на боку!
Почему это ломает так много приложений машинного зрения на Python?
Метаданные EXIF изначально не были частью формата JPEG. Их внедрили гораздо позже, позаимствовав идею из формата TIFF. Для обратной совместимости эти метаданные не обязательны, а некоторые программы не утруждают себя их разбором.
Большинство библиотек Python для работы с изображениями, такие как numpy, scipy, TensorFlow, Keras и т. д., считают себя научными инструментами для серьёзных людей, работающих с общими массивами данных. Они не заботятся о проблемах потребительского уровня, таких как автоматический поворот изображений, хотя это требуется практически для всех фотографий в мире, снятых с помощью современных камер.
Это означает, что при обработке изображения практически любой питоновской библиотекой вы получаете исходные данные изображения без поворота. И угадайте, что происходит, когда вы пытаетесь загрузить в модель обнаружения лиц или объектов фотографию на боку или вверх ногами? Детектор не срабатывает, потому что вы дали ему плохие данные.
Вы можете подумать, что проблемы возникают только в программах новичков и студентов, но это не так! Даже демо-версия флагманского Vision API от Google не обрабатывает правильно ориентацию EXIF:
Демонстрация Google Vision API не умеет поворачивать портретно-ориентированное изображение, снятое со стандартного мобильного телефона
И хотя Google Vision распознаёт каких-то животных на боку, но помечает их общей меткой «животное», потому что модели машинного зрения гораздо сложнее распознать гуся на боку, чем вертикального гуся. Вот результат, если правильно повернуть изображение перед подачей в модель:
При корректной ориентации Google обнаруживает птиц с более конкретной меткой «гусь» и более высоким показателем уверенности. Гораздо лучше!
Это суперочевидная проблема, когда вы явно видите, что изображение на боку, как в этой демонстрации. Но именно здесь все становится коварным — обычно вы этого не видите! Все нормальные программы на вашем компьютере отобразят изображение в правильной ориентации, а не как оно на самом деле хранится на диске. Поэтому, когда вы пытаетесь просмотреть изображение, чтобы увидеть, почему ваша модель не работает, оно будет отображаться правильно, и вы не поймёте, почему же модель не работает!
Finder на Mac всегда отображает фотографии с корректным поворотом из EXIF. Там никак не увидеть, чтобы изображение на самом деле хранится на боку
Это неизбежно приводит к массе открытых тикетов на Github: люди жалуются, что проекты с открытым исходным кодом сломаны, а модели не очень точны. Но проблема гораздо проще — они просто подают на вход повёрнутые или перевёрнутые фотографии!
Исправление
Решение заключается в том, что при каждой загрузке изображений в программы Python следует проверить метаданные ориентации EXIF и повернуть изображения, если это необходимо. Это довольно просто сделать, но в интернете удивительно трудно найти примеры кода, который правильно делает это для всех ориентаций.
Вот код для загрузки любого изображения в массив numpy с правильной ориентацией:
import PIL.Image
import PIL.ImageOps
import numpy as np
def exif_transpose(img):
if not img:
return img
exif_orientation_tag = 274
# Check for EXIF data (only present on some files)
if hasattr(img, "_getexif") and isinstance(img._getexif(), dict) and exif_orientation_tag in img._getexif():
exif_data = img._getexif()
orientation = exif_data[exif_orientation_tag]
# Handle EXIF Orientation
if orientation == 1:
# Normal image - nothing to do!
pass
elif orientation == 2:
# Mirrored left to right
img = img.transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 3:
# Rotated 180 degrees
img = img.rotate(180)
elif orientation == 4:
# Mirrored top to bottom
img = img.rotate(180).transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 5:
# Mirrored along top-left diagonal
img = img.rotate(-90, expand=True).transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 6:
# Rotated 90 degrees
img = img.rotate(-90, expand=True)
elif orientation == 7:
# Mirrored along top-right diagonal
img = img.rotate(90, expand=True).transpose(PIL.Image.FLIP_LEFT_RIGHT)
elif orientation == 8:
# Rotated 270 degrees
img = img.rotate(90, expand=True)
return img
def load_image_file(file, mode='RGB'):
# Load the image with PIL
img = PIL.Image.open(file)
if hasattr(PIL.ImageOps, 'exif_transpose'):
# Very recent versions of PIL can do exit transpose internally
img = PIL.ImageOps.exif_transpose(img)
else:
# Otherwise, do the exif transpose ourselves
img = exif_transpose(img)
img = img.convert(mode)
return np.array(img)
Отсюда вы можете передать массив данных изображения в любую стандартную библиотеку машинного зрения Python, которая ожидает массив на входе: например, Keras или TensorFlow.
Поскольку проблема повсеместная, я опубликовал эту функцию в качестве pip-библиотеки под названием image_to_numpy. Можете установить её следующим образом:
pip3 install image_to_numpy
Она работает с любой программой Python, исправляя загрузку изображения, например:
import matplotlib.pyplot as plt
import image_to_numpy
# Load your image file
img = image_to_numpy.load_image_file("my_file.jpg")
# Show it on the screen (or whatever you want to do)
plt.imshow(img)
plt.show()
См. файл readme для более подробной информации.
Наслаждайтесь!
Комментарии (17)
sena
11.10.2019 13:59А если EXIF данных нет (как, например у этой модели)? Или ещё хуже, они неверные?
Нужна сетка для распознавания ориентации фотографии. Есть такие? У меня целый склад фоток без EXIF.roryorangepants
11.10.2019 14:06Да просто нужно навернуть аугментации D4-группы (повороты на 90 градусов).
Browning
11.10.2019 18:08Повороты — это C4. D4 — это ещё и отражения. Что тоже иногда может быть полезно, впрочем.
al_sh
11.10.2019 15:51>>>У меня также есть опенсорсная библиотека распознавания лиц на Python
А питонообертка над dlib точно «своя библиотека»?ainu
12.10.2019 11:51Да, потому что создание удобного API это тоже работа. Также библиотеки для отправки почты тоже могут быть обёртками над sendmail, а какой нибудь pixijs — обёртка над WebGL.
nerudo
11.10.2019 15:54Есть у меня старый фотик, у которого нет датчика поворота. При разборе фотографий это бесит до такой степени, что хочется забацать нейросеточку, которая будет по изображению искать где у него верх, где низ. Может, кстати, что готовое уже есть?
ArXen42
12.10.2019 14:07+1Это несложно сделать даже с околонулевым знанием ML (проверял), примерно так:
- Взять свои уже отсортированные фото, сложить в одну директорию, написать простенький скрипт, который сгенерирует из них пережатые (скорее всего 224х224) и повернутые на соответствующий угол фото, раскиданные по директориям "0", "90", "180" и "270".
- Взять NVIDIA DIGITS или аналог, одной кнопкой загрузить в него этот датасет, другой кнопкой выбрать модель и поставить на обучение. Подождать N часов.
- Получить готовую модель, написать второй скрипт, который вызывает её для каждого изображения из директории на входе и создает её копию с перевернутыми изображениями (например, записав поворот в тот же EXIF). Можно повесить вызов на контекстное меню проводника.
А вот тут статья с более продвинутым вариантом.
ZlodeiBaal
11.10.2019 17:52+1Вот они, ньюфаги… ;)
Не надо в проде использовать PIL, matplotlib и прочий бажный набор библиотек. Давным давно есть OpenCV, с которым всё интегрировано и который решает 90% проблем. И EXIF, и стриминг видео, и сохранение, и поддержку десятков форматов, и.т.д., и.т.п.
По сути это стандарт последние 10 лет. В отличие от того же matplotlib он быстрый, стартанёт на любом устройстве и не тащит кучу зависимостей.kAIST
12.10.2019 00:48Это был сарказм, да?
То есть для простейших манипуляций с изображениями, вы предлагаете тащить комбаин OpenCV, который как бы несколько для другого предназначен?ZlodeiBaal
12.10.2019 00:55Это работающий комбайн. Который можно тащить. В отличии от PIL | matplotlib | dlib и прочего. Который скорее всего и так понадобиться для захвата потока с камеры/для загрузки/сохранений/для инференса нейронок если это какие-то неспециализированные устройства.
Конечно, если вы на калькуляторе обрабатываете — то лучше всё самому написать. А если уже на RPi — то памяти вам хватит, а по процессору OpenCV — это весьма неплохой вариант. Сколько в нём ковырялся — ни разу не видел именно «убогих» реализаций, в отличие от тех же dlib | PIL | PCL | AForge, которые я тоже мало-мало ковырял.
semibiotic
11.10.2019 18:34Я думаю, что это, прежде всего, проблема самой технологии нейросети (или процесса ее обучения), что она может внезапно не распознать объект при минимальных изменениях, иногда даже не связанных с самим объектом, а следовательно — ненадежка и потенциально уязвима.
khim
11.10.2019 19:12Есть такое. Но как показали практические испытания в течении нескольких миллионов (миллиардов?) лет — лучше такая, чем никакой.
Для проверки: берёте любую фотку Луны или Марса и переворачиваете на 180 градусов. Если на фотке были кратеры — они превратятся в такие «бляшкообразные» горы. И наоборот…
И да — мы же с этим как-то живём…
sergey-b
11.10.2019 22:13Не думаю, что это проблема технологии. Просто не предусмотрено использование алгоритмов с входными данными в разной ориентации. Уверен, что допилить это при желании не составит труда.
vvzvlad
12.10.2019 16:00А ничего не бывает бесплатно: можно легко для обучения добавлять в датасет те же картинки, повернутые на стандартные углы или даже на произвольные. Но каждый угол это дополнительное время обучения. Хотите стандартные углы? Обучайте в 4 раза дольше. Хотите произвольные углы? Обучайте в х раз дольше, где х — количество поворотов. Готовы к десятикратному увеличению продолжительности обучения для поддержки углов, в то время как ваши конкуренты прекрасно идут в прод без этого?
Ну, и плюс ориентация — это важный фактор в детекции объектов. Сверху красное, снизу зелёное? Добавляем вероятность к объекту "цветок". Сверху зелёное, снизу красное? Нож с зелёной рукояткой, воткнутый в человека. Убираете ориентацию — теряете точность распознавания, приходится увеличивать датасет или придумывать хитрости для обхода.
veydlin
15.10.2019 13:32Я думаю можно добавить перед основной нейросетью ещё одну, которая будет исправлять угол поворота
token
Спасибо за статью. Было интересно! Сам недавно столкнулся с этим, простая загрузка аватарки в браузере — давала странные результаты в виде развернутых изображений и так простая загрузка аватарки — превратилась в не очень простую — с разбором EXIF.
ZloyIng
Меня больше лет 7-8 назад ошарашило когда в одной программке делаешь при просмотре «поворот» фотографии (с обычного фотоаппарата, который был на штативе, те там EXIF «правильный») а фото остаётся «без изменений» потом если открыть в PhotoShop и развёрнутой если открыть в той же программе просмотра…
Дальше было веселее — третья программа после того, как повернули фото в PhotoShop показывало фото развёрнутым неправильно…
К величайшему сожалению сейчас не могу вспомнить названий программ и, самое главное, их версий, возможно одной из программ была ACDSee 2.41 или 5+, а одной встроенный просмотрщик WindowsXP, а возможно что-то из известных на тот момент мелких бесплатных, но за давностью лет просто не могу вспомнить.
Через примерно год я узнал что такое EXIF, но воспроизводить проблему что бы узнать кто точный «виновник» было сначала некогда, а потом забылось.