Я много писал о проектах компьютерного зрения и машинного обучения, таких как системы распознавания объектов и проекты распознавания лиц. У меня также есть опенсорсная библиотека распознавания лиц на Python, которая как-то вошла в топ-10 самых популярных библиотек машинного обучения на Github. Всё это привело к тому, что новички в Python и машинном зрении задают мне много вопросов.



По опыту, есть одна конкретная техническая проблема, которая чаще всего ставит людей в тупик. Нет, это не сложный теоретический вопрос или проблема с дорогими 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)


  1. token
    11.10.2019 13:52

    Спасибо за статью. Было интересно! Сам недавно столкнулся с этим, простая загрузка аватарки в браузере — давала странные результаты в виде развернутых изображений и так простая загрузка аватарки — превратилась в не очень простую — с разбором EXIF.


    1. ZloyIng
      13.10.2019 10:49

      Меня больше лет 7-8 назад ошарашило когда в одной программке делаешь при просмотре «поворот» фотографии (с обычного фотоаппарата, который был на штативе, те там EXIF «правильный») а фото остаётся «без изменений» потом если открыть в PhotoShop и развёрнутой если открыть в той же программе просмотра…
      Дальше было веселее — третья программа после того, как повернули фото в PhotoShop показывало фото развёрнутым неправильно…

      К величайшему сожалению сейчас не могу вспомнить названий программ и, самое главное, их версий, возможно одной из программ была ACDSee 2.41 или 5+, а одной встроенный просмотрщик WindowsXP, а возможно что-то из известных на тот момент мелких бесплатных, но за давностью лет просто не могу вспомнить.
      Через примерно год я узнал что такое EXIF, но воспроизводить проблему что бы узнать кто точный «виновник» было сначала некогда, а потом забылось.


  1. sena
    11.10.2019 13:59

    А если EXIF данных нет (как, например у этой модели)? Или ещё хуже, они неверные?

    Нужна сетка для распознавания ориентации фотографии. Есть такие? У меня целый склад фоток без EXIF.


    1. roryorangepants
      11.10.2019 14:06

      Да просто нужно навернуть аугментации D4-группы (повороты на 90 градусов).


      1. Browning
        11.10.2019 18:08

        Повороты — это C4. D4 — это ещё и отражения. Что тоже иногда может быть полезно, впрочем.


  1. al_sh
    11.10.2019 15:51

    >>>У меня также есть опенсорсная библиотека распознавания лиц на Python

    А питонообертка над dlib точно «своя библиотека»?


    1. ainu
      12.10.2019 11:51

      Да, потому что создание удобного API это тоже работа. Также библиотеки для отправки почты тоже могут быть обёртками над sendmail, а какой нибудь pixijs — обёртка над WebGL.


  1. nerudo
    11.10.2019 15:54

    Есть у меня старый фотик, у которого нет датчика поворота. При разборе фотографий это бесит до такой степени, что хочется забацать нейросеточку, которая будет по изображению искать где у него верх, где низ. Может, кстати, что готовое уже есть?


    1. ArXen42
      12.10.2019 14:07
      +1

      Это несложно сделать даже с околонулевым знанием ML (проверял), примерно так:


      1. Взять свои уже отсортированные фото, сложить в одну директорию, написать простенький скрипт, который сгенерирует из них пережатые (скорее всего 224х224) и повернутые на соответствующий угол фото, раскиданные по директориям "0", "90", "180" и "270".
      2. Взять NVIDIA DIGITS или аналог, одной кнопкой загрузить в него этот датасет, другой кнопкой выбрать модель и поставить на обучение. Подождать N часов.
      3. Получить готовую модель, написать второй скрипт, который вызывает её для каждого изображения из директории на входе и создает её копию с перевернутыми изображениями (например, записав поворот в тот же EXIF). Можно повесить вызов на контекстное меню проводника.

      А вот тут статья с более продвинутым вариантом.


  1. ZlodeiBaal
    11.10.2019 17:52
    +1

    Вот они, ньюфаги… ;)

    Не надо в проде использовать PIL, matplotlib и прочий бажный набор библиотек. Давным давно есть OpenCV, с которым всё интегрировано и который решает 90% проблем. И EXIF, и стриминг видео, и сохранение, и поддержку десятков форматов, и.т.д., и.т.п.
    По сути это стандарт последние 10 лет. В отличие от того же matplotlib он быстрый, стартанёт на любом устройстве и не тащит кучу зависимостей.


    1. kAIST
      12.10.2019 00:48

      Это был сарказм, да?
      То есть для простейших манипуляций с изображениями, вы предлагаете тащить комбаин OpenCV, который как бы несколько для другого предназначен?


      1. ZlodeiBaal
        12.10.2019 00:55

        Это работающий комбайн. Который можно тащить. В отличии от PIL | matplotlib | dlib и прочего. Который скорее всего и так понадобиться для захвата потока с камеры/для загрузки/сохранений/для инференса нейронок если это какие-то неспециализированные устройства.
        Конечно, если вы на калькуляторе обрабатываете — то лучше всё самому написать. А если уже на RPi — то памяти вам хватит, а по процессору OpenCV — это весьма неплохой вариант. Сколько в нём ковырялся — ни разу не видел именно «убогих» реализаций, в отличие от тех же dlib | PIL | PCL | AForge, которые я тоже мало-мало ковырял.


  1. semibiotic
    11.10.2019 18:34

    Я думаю, что это, прежде всего, проблема самой технологии нейросети (или процесса ее обучения), что она может внезапно не распознать объект при минимальных изменениях, иногда даже не связанных с самим объектом, а следовательно — ненадежка и потенциально уязвима.


    1. khim
      11.10.2019 19:12

      Есть такое. Но как показали практические испытания в течении нескольких миллионов (миллиардов?) лет — лучше такая, чем никакой.

      Для проверки: берёте любую фотку Луны или Марса и переворачиваете на 180 градусов. Если на фотке были кратеры — они превратятся в такие «бляшкообразные» горы. И наоборот…

      И да — мы же с этим как-то живём…


    1. sergey-b
      11.10.2019 22:13

      Не думаю, что это проблема технологии. Просто не предусмотрено использование алгоритмов с входными данными в разной ориентации. Уверен, что допилить это при желании не составит труда.


    1. vvzvlad
      12.10.2019 16:00

      А ничего не бывает бесплатно: можно легко для обучения добавлять в датасет те же картинки, повернутые на стандартные углы или даже на произвольные. Но каждый угол это дополнительное время обучения. Хотите стандартные углы? Обучайте в 4 раза дольше. Хотите произвольные углы? Обучайте в х раз дольше, где х — количество поворотов. Готовы к десятикратному увеличению продолжительности обучения для поддержки углов, в то время как ваши конкуренты прекрасно идут в прод без этого?


      Ну, и плюс ориентация — это важный фактор в детекции объектов. Сверху красное, снизу зелёное? Добавляем вероятность к объекту "цветок". Сверху зелёное, снизу красное? Нож с зелёной рукояткой, воткнутый в человека. Убираете ориентацию — теряете точность распознавания, приходится увеличивать датасет или придумывать хитрости для обхода.


      1. veydlin
        15.10.2019 13:32

        Я думаю можно добавить перед основной нейросетью ещё одну, которая будет исправлять угол поворота