Pull to refresh

Глупая причина, по которой не работает ваше хитрое приложение машинного зрения: ориентация в EXIF

Reading time 5 min
Views 20K
Original author: Adam Geitgey
Я много писал о проектах компьютерного зрения и машинного обучения, таких как системы распознавания объектов и проекты распознавания лиц. У меня также есть опенсорсная библиотека распознавания лиц на 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 для более подробной информации.

Наслаждайтесь!
Tags:
Hubs:
+97
Comments 18
Comments Comments 18

Articles