[Перевод] AI, практический курс. Сбор и исследование изображений

_jdrq0ffcxcf86rbei-wxierd5u.jpeg


В данной статье обсуждаются методы, используемые при сборе данных с изображениями в музыкальном проекте со слайд-шоу. Возникли ограничения, вынудившие нас воспользоваться существующей базой данных изображений, а не изображениями, взятыми с Flickr. Тем не менее, в данной статье рассматриваются оба подхода, чтобы читатель мог научиться извлекать данные при помощи Flickr API.

Далее, поскольку качество значительной части изображений, собранных с Flickr, оказалось низким, было решено использовать изображения из существующих баз данных изображений. В частности, были собраны изображения из трех баз данных для психологических исследований.
Напомним, что изначально для данного проекта были выбраны следующие наборы данных:

  1. Набор данных для обучения, содержащий 7000 эмоционально-окрашенных изображений с Flickr для алгоритма извлечения эмоций.
  2. Набор данных для обучения, содержащий произведения Баха, для алгоритма завершения мелодии.
  3. Набор мелодий, служащих шаблонами для модуляции эмоций.


Теперь необходимо собрать наборы данных. Как будет показано в статье, объем требуемой для этого работы существенно различается в зависимости от выбранного набора данных.

Сбор данных с изображениями


Для данного проекта требовался набор изображений, вызывающих семь различных эмоций: счастье, грусть, страх, тревога, трепет, решимость, злость. Для сбора изображений было решено использовать Flickr, популярный сайт по обмену фотографиями, благодаря его размеру и лицензированию Creative Commons*.

Поиск по Flickr 7000 изображений вручную — задача пугающая. К счастью, у Flickr есть API, обеспечивающий набор методов, которые позволяют легко обмениваться данными с Flickr на языке программирования. Однако прежде, чем использовать API для сбора изображений, важно знать, что искать, чтобы вызвать соответствующие эмоции. Для определения списка условий поиска использовалась задача на платформе Amazon Mechanical Turk*.

Flickr API


Для использования методов, предлагаемых Flickr API потребуется создать учетную запись Flickr и запросить ключ API. Для этого обязательно иметь учетную запись Flickr или Yahoo!*. Далее необходимо пройти по этой ссылке и получить ключ.

uhq30kznjns9goydmrjwufzdwl0.png
Скриншот страницы www.flickr.com/services/apps/create/apply

Процесс обработки заявки на некоммерческий ключ довольно прост. Он включает в себя описание планируемого использования и принятие условий использования. Ключ API представляет собой меру безопасности и используется для предотвращения неправильного использования API. В методах, предоставляемых API, он является обязательным параметром.

После получения ключа API можно загрузить и установить набор инструментов API для одного из языков программирования из The App Garden. В данном проекте используется Beej’s PythonI Flickr API, который можно использовать с языком Python 3. Необходимо следовать руководству по установке Flickr API.

Код, использованный для загрузки изображений, приведен ниже. В основном здесь используется функция API walk, осуществляющая поиск изображения по тегу. Теги хранятся в файле .txt и перечисляются по одному в строке. Если изображение найдено, его URL создается из шаблона на farm{farm-id}.staticflickr.com/{server-id}/{id}_{secret}.jpg, где содержимое фигурных скобок заменяется на атрибуты изображения. Затем топ-30 изображений для каждого тега (отсортированные по релевантности) извлекаются и организуются в папки, в зависимости от эмоции и условий поиска.

import flickrapi
import urllib.request
import os
 
project_path = '/path/to/your/project'
photos_per_tag = 30
filenames = ['Awe.txt', 'Happiness.txt', 'Fear.txt', 'Determination.txt',
             'Anxiety.txt', 'Tranquility.txt', 'Sadness.txt']
 
 
def download_files(flickr, t, category, num_photos):
    # Downloads the files of a specific tag
    os.mkdir(t)
    os.chdir(t)
    s = []
    for photo in flickr.walk(tag_mode='all', sort='relevance', tags=t, license=4, per_page=50):
        url = 'https://farm{}.staticflickr.com/{}/{}_{}.jpg'.format(photo.get('farm'),

                             photo.get('server'), photo.get('id'), photo.get('secret'))
        s.append(url)
        if len(s) == num_photos:
            break
    for i in range(len(s)):
        filename = '{}_{}_{}.jpg'.format(category, t, str(i))
        urllib.request.urlretrieve(s[i], filename)
    os.chdir(os.path.join(project_path, category))
 
 
if __name__ == '__main__':
    # Creates flickr object
    # These keys should be requested from flickr
    api_key = u'xxxxxxxxxxxx'
    api_secret = u'xxxxxxxxxxx'
    flickr = flickrapi.FlickrAPI(api_key, api_secret)
 
    # Runs the program, cycles through the emotions and downloads the images for each tag.
    os.chdir(project_path)
    for fname in filenames:
        categ = fname[:-4]
        with open(fname, 'r') as f:
            tags = f.read().splitlines()
        os.mkdir(categ)
        os.chdir(categ)
        for t in tags:
            download_files(flickr, t, categ, photos_per_tag)
        os.chdir(project_path)


Чтобы использовать данный код, необходимо клонировать репозиторий по ссылке с GitHub. После этого следуйте инструкциям, содержащимся в файле README. Необходимо заменить параметры api_key и api_secret на ключи API, полученные на Flickr. Как упоминалось выше, этот скрипт работает только на Python 3.

После того, как программа отработает, папка выглядит следующим образом:

d6vg4uz-dqluh8gznempvdzwxrk.png

dbn5l_zbd8-w-zjvkjyyelpumz8.jpeg
Набор данных по результатам поиска на Flickr.

В сумме собрано около 8800 изображений. Получено больше изображений чем требовалось, так как мы планировали отбросить часть изображений низкого качества, которые нельзя использовать. Следующим шагом был поиск этих изображений.

Отбор изображений


Качество собранных изображений было разным. Некоторые условия поиска, например, цветы (показанные на рисунке) давали пригодные к использованию изображения высокого качества. Однако менее конкретные условия поиска зачастую давали абсолютно непригодные к использованию изображения. Например, по тегу чудо (в связи с эмоцией трепет) было получено изображение торта с чудо-женщиной*, а по тегу амбициозный (в связи с эмоцией решимость) было найдено изображение капусты из Ambitious Farms.

xkia0kxzikvgjlowgyg8b6_bemo.jpeg


Непригодные к использованию изображения.

Любому, кто планирует использовать Flickr API для поиска изображений, рекомендуется использовать в качестве условий поиска конкретные существительные. Найденные по ним изображения гораздо лучше, чем при использовании прилагательных или абстрактных существительных. Например, при поиске изображений, вызывающих трепет, следует использовать такие условия поиска, как океан или Большой каньон, а не трепет или чудо.

После просмотра изображений команда пришла к выводу, что более 40 процентов изображений непригодны к использованию. В результате был пересмотрен подход к выбору набора данных. После обсуждения ряда возможностей, таких как ограничение набора изображениями лиц с соответствующими эмоциями, было принято решение использовать изображения из существующих баз данных, которые обычно используются в психологических исследованиях (Geneva Affective PicturE Database (GAPED), Open Affective Standardized Image Set (OASIS) и Image Stimuli for Emotion Elicitation (ISEE)).

Несмотря на то, что изображения в существующих базах данных менее разнообразны, чем могли бы быть в новом наборе данных, выбор был сделан в пользу существующих баз данных благодаря более высокому качеству изображений и наличию информации о параметрах. Наличие информации о параметрах является огромным преимуществом, так как благодаря этому отпадает необходимость в аннотировании при помощи Amazon Mechanical Turk, что заметно снижает стоимость.

Источник данных


Процесс сбора данных для нового набора данных был значительно проще. В частности, больше не требовались шаги с Amazon Mechanical Turk и Flickr API. Наборы данных GAPED и OASIS (включающие разметку параметров) доступны в Интернете для скачивания. Набор данных ISEE стал доступен после электронного письма автору с запросом доступа. Если инструкции по загрузке наборов данных недостаточно понятны, вероятнее всего, поиск Google* поможет найти контакты авторов, у которых можно напрямую запросить доступ к наборам данных.

Для данного проекта были созданы два набора данных. В первом использовался Flickr API для загрузки изображений по тегам эмоций, второй представлял собой компиляцию из существующих баз данных, используемых в психологических исследованиях. Каждый из этих наборов данных имеет свои плюсы и минусы; однако для проекта был выбран второй — благодаря таким преимуществам, как качество изображений, наличие размеченных параметров и стоимость.

Метод, используемый для сбора данных, напрямую зависит от того, какие данные требуются. Тем не менее, процессы и методы, описанные в данной статье, вероятно, окажутся полезными для многих проектов.

Теперь, когда наборы данных созданы, проект готов к выполнению следующих шагов — исследованию и предварительной обработке данных.

Исследование данных с изображениями


Поскольку качество значительной части изображений, собранных с Flickr, оказалось низким, было решено использовать изображения из существующих баз данных изображений. В частности, были собраны изображения из трех баз данных для психологических исследований. Каждое изображение включает в себя информацию о рейтинге (не)приятности и интенсивности, собранную с нескольких исполнителей. 1986 изображений из этих баз данных были разбиты на 4 категории. Эти категории покрывали 87% изображений и включали 34% животных, 28% людей, 13% сцен и 12% объектов. Оставшиеся 13% были отнесены к категории «разное».

Животные


_jdrq0ffcxcf86rbei-wxierd5u.jpeg


Примеры изображений из категории «Животные»

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

Изображения справа — спящий кот, улыбающаяся собака — наоборот, могут вызывать симпатию и счастье.

Люди


z6kdvfa0yknvuqh3_sdboqobqa8.jpeg


Примеры изображений из категории «Люди»

Категория изображений «Люди» включает изображения отдельных людей и групп людей, при этом изображения групп людей часто содержат больше информации о контексте. Например, изображение марширующего оркестра, похоже, снято на фоне заполненного фанатами стадиона, наводя на предположение, что на изображении снято некое представление во время спортивных состязаний. Изображение злой женщины, наоборот, лишено контекста — у зрителя нет возможности узнать или догадаться о причине ее злости. Следует отметить, что не все изображения со множеством людей или с группами имеют дополнительную информацию.

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

Сцены


Категория «Сцены» набора изображений включает разнообразные сцены — от рукотворных строений и объектов до сцен природы и даже космоса.

ho2vttl3iuauwnr94ce8uivrwdw.jpeg


Примеры изображений из категории «Сцены»

Объекты


vl5_bkiof6y8puazjhku6d9yfuy.jpeg


Примеры изображений из категории «Объекты»

В категорию «Объекты» набора изображений вошли изображения, сфокусированные на одном объекте, как показано на примерах выше. В этих изображениях отсутствует ситуационный контекст, особенно если сравнивать их с другими категориями в наборе изображений.

Разное


mdqf1gyo2nw5sdu4oo2vyhbyxfm.jpeg


Примеры изображений из категории «Разное»

Наконец, в наборе осталась подгруппа изображений, которые нельзя было отнести ни к одной из четырех категорий. Часто, как показано на примерах, эти изображение представляли собой сцены с несколькими объектами, но без контекста, характерного для изображений категории «Сцены». Этот тип изображений, как правило, содержал нейтральный рейтинг — они не были ни приятными, ни неприятными.

Категории эмоций для базы данных изображений


Чтобы определить категории эмоций для базы данных изображений, мы опирались на нормативные субъективные рейтинги значимости, сопровождающие каждое изображение баз данных Geneva Affective PicturE Database (GAPED) и Open Affective Standardized Image Set (OASIS). Поскольку в GAPED использовалась шкала Лайкерта от 0 до 100, а в OASIS — шкала Лайкерта от 1 до 7, была применена линейная трансформация, приводящая все оценки в непрерывную шкалу от 0 до 100. Затем были исследованы два потенциальных правила категоризации эмоций.

Во-первых, интуитивно желательным является сортировка изображений по уровню приятности с последующим разделением на три части по шкале рейтинга — так, чтобы изображения с оценками 0–33,33 представляли отрицательную категорию, c оценками 33,33–66,67 — нейтральную, а с оценками 66,67–100 — положительную категорию. Для реализации этого правила разделения на три категории был использован код на Python:

import os
import shutil
import csv
  
def organizeFolderGAPED(original, pos, neg, neut):
  # Копирует каждое изображение базы данных GAPED в соответствующую папку
  # Создать словарь имен файлов и уровня значимости
  dict = {}
  files = os.listdir(original)
  for file in files:
      if '.txt' in file:
          with open(os.path.join(original, file), 'r') as f:
              for l in f:
                  l = l.split()
                  dict[l[0][:-4]] = l[1]
  
  # Перебрать изображения и разделить файлы на категории pos/neg/neut в зависимости от уровня значимости
  for roots, dirs, files, in os.walk(original):
      for file in files:
          if '.bmp' in file:
              if float(dict[file[:-4]]) < 100/3:
                  shutil.copy(os.path.join(roots, file), neg)
              elif float(dict[file[:-4]]) > 200/3:
                  shutil.copy(os.path.join(roots, file), pos)
              else:
                  shutil.copy(os.path.join(roots, file), neut)
  
def organizeFolderOASIS(original, pos, neg, neut):
  # Копирует каждое изображение базы данных GAPED в соответствующую папку
  # Создать словарь имен файлов и уровня значимости
  dict = {}
  with open('/Users/harrys/Desktop/OASIS.csv') as file:
      reader = csv.reader(file)
      for row in reader:
          dict[row[1]] = row[4]
  
  # Перебрать изображения и разделить файлы на категории pos/neg/neut в зависимости от нормированного уровня значимости
  for roots, dirs, files, in os.walk(original):
      for file in files:
          if '.jpg' in file:
              if (float(dict[file[:-4]])-1)*100/6 < 100/3:
                  shutil.copy(os.path.join(roots, file), neg)
              elif (float(dict[file[:-4]])-1)*100/6 > 200/3:
                  shutil.copy(os.path.join(roots, file), pos)
              else:
                  shutil.copy(os.path.join(roots, file), neut)
  
if __name__ == '__main__' :
  gaped = 'path/to/your/project/directory/GAPED'
  oasis = 'path/to/your/project/directory/Oasis'
  pos = 'path/to/your/project/directory/Positive'
  neg = 'path/to/your/project/directory/Negative'
  neut = 'path/to/your/project/directory/Neutral'
  organizeFolderOASIS(oasis, pos, neg, neut)
  organizeFolderGAPED(gaped, pos, neg, neut)


Данный подход позволил разделить базу данных на категории: 417 отрицательных изображений, 774 нейтральных и 442 положительных. В данном подходе деления на три категории в равных долях неприятные изображения, рейтинг которых не достигал порогового значения, были отнесены к категории нейтральных; например, изображения мертвого тела, плачущего ребенка, кладбища были отнесены к нейтральным. Хотя эти изображения были менее неприятными, чем другие в категории отрицательных, возникло сомнение в их нейтральности.

Поэтому было принято решение применить оптимизированное правило категоризации на основе нормального распределения данных, а также улучшить разделение параметров на эмоциональные категории. Значения 0–39 были отнесены к отрицательной категории, 40–60 — к нейтральной, а 61–100 — к положительной. Для реализации этого правила был использован код на Python:

import os
import shutil
import csv
  
def organizeFolderGAPED(original, pos, neg, neut):
  # Копирует каждое изображение базы данных GAPED в соответствующую папку
  # Создать словарь имен файлов и уровня значимости
  dict = {}
  files = os.listdir(original)
  for file in files:
      if '.txt' in file:
          with open(os.path.join(original, file), 'r') as f:
              for l in f:
                  l = l.split()
                  dict[l[0][:-4]] = l[1]
  
  # Перебрать изображения и разделить файлы на категории pos/neg/neut в зависимости от уровня значимости
  for roots, dirs, files, in os.walk(original):
      for file in files:
          if '.bmp' in file:
              if float(dict[file[:-4]]) < 40:
                  shutil.copy(os.path.join(roots, file), neg)
              elif float(dict[file[:-4]]) > 60:
                  shutil.copy(os.path.join(roots, file), pos)
              else:
                  shutil.copy(os.path.join(roots, file), neut)
  
def organizeFolderOASIS(original, pos, neg, neut):
  # Копирует каждое изображение базы данных GAPED в соответствующую папку
  # Создать словарь имен файлов и уровня значимости
  dict = {}
  with open('path/to/your/project/directory/OASIS.csv') as file:
      reader = csv.reader(file)
      for row in reader:
          dict[row[1]] = row[4]
  
  # Перебрать изображения и разделить файлы на категории pos/neg/neut в зависимости от нормированного уровня значимости
  for roots, dirs, files, in os.walk(original):
      for file in files:
          if '.jpg' in file:
              if (float(dict[file[:-4]])-1)*100/6 < 40:
                  shutil.copy(os.path.join(roots, file), neg)
              elif (float(dict[file[:-4]])-1)*100/6 > 60:
                  shutil.copy(os.path.join(roots, file), pos)
              else:
                  shutil.copy(os.path.join(roots, file), neut)
  
if __name__ == '__main__' :
  gaped = 'path/to/your/project/directory/GAPED'
  oasis = 'path/to/your/project/directory/Oasis'
  pos = 'path/to/your/project/directory/Positive'
  neg = 'path/to/your/project/directory/Negative'
  neut = 'path/to/your/project/directory/Neutral'
  organizeFolderOASIS(oasis, pos, neg, neut)
  organizeFolderGAPED(gaped, pos, neg, neut)


С таким правилом категоризации 40–60–40 567 положительных изображений были оценены как более приятные, чем 502 нейтральных, а 564 отрицательных изображения были оценены как менее приятные, чем нейтральные. Таким образом, было сохранено целевое значение эмоциональных категорий и улучшено распределение изображений по категориям. Ниже на рисунке проиллюстрирован уровень приятности, связанный с каждой из категорий. Разная длина усов на диаграмме рассеивания указывает, в какой эмоциональной категории (положительной или отрицательной) наблюдается больший разброс рейтингов по сравнению с нейтральной категорией.

yx3fsvbh9cpl_jyfz4_1j9xf-io.jpeg
Средние рейтинги приятности для каждой из эмоциональных категорий

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

Эмоциональная категория 1: Отрицательная

wgmoxxsmn1b8zp5sbsapzfpgsxi.jpeg


Эмоциональная категория 2: Нейтральная

rzdv8a89hkcqc5gbrtqt8k_yc-q.jpeg


Эмоциональная категория 3: Положительная

fxu2otvmennaundgxobtgb-empq.jpeg


Подведем итог. Мы разделили базу данных изображений на нейтральную, отрицательную и положительную эмоциональные категории, используя нормативные рейтинги значимости в диапазоне от 0 до 100, отнеся значения 0–39 к отрицательным, 40–60 к нейтральным, а 61–100 — к положительным. Изображения соответствующим образом распределились по этим эмоциональным категориям. Наконец, в каждую эмоциональную категорию вошли изображения животных, людей, сцен, объектов и разного.

© Habrahabr.ru