Создание приложения, позволяющего отмечать и сохранять аннотации к данному изображению.

Аннотации к изображениям - это процесс маркировки различных объектов на изображении. Основное применение аннотации изображений - это генерация данных, которые можно использовать для обучения алгоритмов машинного обучения. Аннотации изображений - это задача, которая в основном выполняется вручную. Работа с аннотациями изображений теперь является неотъемлемой частью машинного обучения и искусственного интеллекта и в основном передается на аутсорсинг в такие страны, как Индия и Филиппины. В этой части мы создадим приложение для аннотации изображений, которое позволит вам отмечать и сохранять аннотации на данном изображении.

Существует множество библиотек и инструментов, которые позволяют нам делать аннотации к изображениям, в том числе многие платные варианты, такие как Labelbox, но для этого приложения мы будем использовать бесплатную библиотеку Annotorious.

Прежде чем приступить к разработке части аннотации изображений, давайте быстро взглянем на внутреннюю часть нашего приложения. Annotorious - это библиотека javascript, которую можно использовать с любыми серверными фреймворками. Поскольку мне удобнее работать с рельсами, мы создадим приложение для рельсов. Давайте быстро настроим наше приложение и серверную часть.

Создадим новое приложение. Введите Терминал:

rails new image_annotater

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

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

gem ‘carrierwave’, ‘~> 0.11.2’
gem ‘mini_magick’, ‘~> 4.8’

Я не буду вдаваться в подробности реализации несущей волны.

Сгенерируем модель Items.

rails g model Item

Также добавьте следующую строку в routes.rb

resources :items

Миграция модели должна иметь следующие поля:

image_annotater / db / migrate / 20190123073338_create_items.rb

class CreateItems < ActiveRecord::Migration[5.2]
 def change
   create_table :items do |t|
     t.string :name
     t.text :description
     t.string :image
     t.timestamps
   end
 end
end

Теперь давайте создадим модель Labels:

rails g model Label

Также добавьте это в routes.rb:

resources :labels

Это самая важная таблица в этом приложении. Координаты аннотаций или прямоугольников, созданных Annotorious, должны быть сохранены в этой модели. Итак, миграция должна выглядеть так:

class CreateLabels < ActiveRecord::Migration[5.2]
  def change
    create_table :labels do |t|
      t.string :text
      t.string :context
      t.decimal :x_value
      t.decimal :y_value
      t.decimal :width
      t.decimal :height
      t.references :item, index: true
      t.timestamps
    end
  end
end

Мы видим, что координаты x и y, а также высота и ширина ожидаются в аннотации вместе с текстом. Контекст - это путь к изображению. Мы также можем видеть, что есть ссылка на Item. Это означает, что один элемент или изображение может иметь несколько меток.

Модель Item должна быть такой:

/image_annotater/app/models/item.rb

class Item < ApplicationRecord
 mount_uploader :image, ImageUploader
 has_many :labels
end

Модель Label должна быть такой:

/image_annotater/app/models/label.rb

class Label < ApplicationRecord
 belongs_to :item
end

ItemsController будет обычным контроллером CRUD.

LabelsController будет таким:

class LabelsController < ApplicationController
  before_action :set_label, only: [:show, :edit, :update, :destroy]
  def index
    @labels = label.all
  end

  # GET /labels/1.json
  def show
  end
  def new
    @label = label.new
  end
  def edit
  end

  def create
    @label = Label.new(label_params)
    @label.save
    render(:json => {}, :status => :created)
  end
  def update
    respond_to do |format|
      if @label.update(label_params)
        render(:json => {}, :status => :updated)
      else
        render(:json => {}, :status => :not_created)
      end
    end
  end

  def destroy
    @label.destroy
    render(:json => {}, :status => :removed)
  end
private

    def set_label
      @label = Label.find(params[:id])
    end
    def label_params
      params.require(:label).permit(:text, :context, :x_value, :y_value, :width, :height, :item_id)
    end
end

Вы можете видеть, что наш LabelsController имеет возможность создавать, обновлять и удалять новые метки.

Наша серверная часть почти готова. Давайте создадим два пользовательских интерфейса.

Первой будет форма для создания предметов. Пользователи смогут загрузить изображение и создать элемент с этой страницы.

Следующей будет страница показа для отображения предметов. Эта страница важна, так как аннотации (создание ярлыков) будут сделаны на этой странице.

<p>
  <strong>Name:</strong>
  <%= @item.name %>
</p>
<p>
  <strong>Description:</strong>
  <%= @item.description %>
</p>
<p>
  <strong>Image:</strong>
  <div>
  <%= image_tag(@item.image.url, size: "800x500")%>
  </div>
<%= hidden_field_tag 'item_id', @item.id %>
</p>

Известная реализация

Чтобы установить Annotorious в нашем приложении, сначала загрузите последнюю версию Annotorious с официального сайта. Загруженный zip-архив содержит файл annotorious.min.js, папку CSS с файлом annotorious.css. Папка CSS также будет содержать некоторые необходимые файлы изображений.

Если мы добавляем аннотацию в простой HTML-файл, нам нужно только добавить следующие строки в заголовок страницы:

<link type=”text/css” rel=”stylesheet” href=”css/annotorious.css” /><script type=”text/javascript” src=”annotorious.min.js”></script>

Но поскольку мы используем это в приложении rails, разделите файлы и поместите файл js в папку app / assets / javascripts, файл CSS в app / assets / stylesheets и изображения в app / assets / images.

Теперь мы можем сделать наше изображение аннотируемым. Есть два способа сделать это. Annotorious Javascript API теперь доступен на наших страницах, которые можно вызывать с помощью переменной anno.

Вариант 1. Аннотируемый класс CSS

Добавьте аннотируемый класс CSS к тегу изображения. При загрузке страницы Annotorious автоматически просканирует вашу страницу на предмет изображений с этим классом и сделает их аннотируемыми.

Пример:

<img src="example.jpg" class="annotatable" />

Вариант 2: Использование JavaScript

Annotorious Javascript API можно использовать для создания аннотаций к изображениям «вручную».

Пример:

<script>
  function init() {
    anno.makeAnnotatable(document.getElementById('myImage'));
  }
</script>
...
<body onload="init();">
  <img src="example.jpg" id="myImage" />
</body>

Мы будем использовать вариант 2.

Наше изображение находится в app / views / items / show.html.erb. Добавьте к нему идентификатор, чтобы мы могли однозначно идентифицировать его.

<%= image_tag(@item.image.url, size: "800x500", id: "annotatable")%>

Теперь о JS-части. Добавьте функцию, чтобы сделать изображение с "аннотируемым" идентификатором аннотируемым.

function init() {
   anno.makeAnnotatable(document.getElementById(‘annotatable’));
 }

Вызовите эту функцию, когда документ будет готов.

$( document ).ready(function() {
  init();
  function init() {
    anno.makeAnnotatable(document.getElementById('annotatable'));
    }
}

Теперь, если мы снова проверим страницу показа элемента и перетащим мышь на изображение, мы увидим, что можем создавать аннотации на изображении.

Потрясающие!!

Библиотека Annotorious делает всю работу за нас, и есть возможность добавлять аннотации. Но остаются еще две вещи.

  1. Параметры для создания, обновления и удаления меток / аннотаций из пользовательского интерфейса
  2. Отображать все метки на изображении элемента при загрузке страницы.

Метки Create, Delete и Update могут обрабатываться обработчиками событий, предоставляемыми Annotorious.

Идея состоит в том, чтобы получить данные аннотации и передать их в качестве функции AJAX в LabelsController, когда происходят такие события, как создание, редактирование и удаление аннотации .

Весь следующий код должен быть написан внутри нашей init() функции.

Создайте ярлыки

Каждый раз, когда рисуется аннотация и нажимается кнопка сохранения, мы можем запустить обработчик событий onAnnotationCreated(annotation) для переменной anno . Итак, в нашем случае мы можем написать обработчик события как:

anno.addHandler('onAnnotationCreated', function(annotation) {
     var text = annotation.text;
     var context = annotation.src;
     var x = annotation.shapes[0].geometry.x;
     var y = annotation.shapes[0].geometry.y;
     var width = annotation.shapes[0].geometry.width;
     var height = annotation.shapes[0].geometry.height;
     var id = $("#item_id").val();
     $.ajax({
         type: 'POST',
         url: "/labels/",
         data: {
           label :{
                 text:text,context:context,
                 x_value:x,y_value:y,width:width,
                 height:height,item_id:id
            } 
          },
       success: function(data) {}
     });
    });

Мы можем видеть, как координаты и текст из объекта аннотации извлекаются и затем передаются как AJAX в функцию Create в LabelController .

Показать все ярлыки

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

#app/controllers/items_controller.rb
def get_labels
  labels = Item.find(params[:id]).labels
  render json: labels
end

В части JS нам нужно вызвать CreateAnnotation метод annotorious, чтобы перерисовать все сохраненные метки. Сделать это можно так:

$.ajax({
        type: "POST",
        dataType: "json",
        url: "/items/get_labels",
        data: {
         id: 6
        },
        success: function(data){
         $.each(data, function() {
          var myAnnotation = {}
          $.each(this, function(k, v) {
          if(k == 'text'){
            myAnnotation["text"] = v;
          }
          if(k == 'id'){
           myAnnotation["id"] = v;
          }
          if(k == 'context'){
           myAnnotation["src"] = v;
          }
          if(k == 'x_value'){
           myAnnotation['x_value'] = v;
          }
          if(k == 'y_value'){
           myAnnotation['y_value'] = v;
          }
          if(k == 'height'){
           myAnnotation['height'] = v;
          }
          if(k == 'width'){
           myAnnotation['width'] = v;
          }
         });
         var annotation = create_annotation(myAnnotation);
         anno.addAnnotation(annotation)
        });
        }
    });
create_annotation = function(myAnnotation_hash){
     var myAnnotation = {
      src : myAnnotation_hash["src"],
      text : myAnnotation_hash["text"],
      shapes : [{
          type : 'rect',
          geometry : {
            x : parseFloat(myAnnotation_hash["x_value"]),
            y: parseFloat(myAnnotation_hash["y_value"]),
            width : parseFloat(myAnnotation_hash["width"]), 
            height: parseFloat(myAnnotation_hash["height"]),
            label_id: myAnnotation_hash["id"] }
      }]
  }
     return myAnnotation;
    } 
  }
  });

Обратите внимание, что мы создали хэш данных аннотаций из базы данных и использовали этот хеш для создания аннотаций. Кроме того, вы можете видеть, что я также добавил первичный ключ метки к геометрии аннотации как label_id . Созданная аннотация добавляется к объекту аннотации как anno.addAnnotation(annotation).

Обновление этикеток

Чтобы обновить текст в существующей этикетке, щелкните значок карандаша на аннотации. Будет предложено добавить новый текст:

Когда мы нажимаем на опцию Edit, запускается обработчик событий onAnnotationUpdated(annotation) . Мы можем использовать это для обновления метки.

anno.addHandler('onAnnotationUpdated', function(annotation) {
 var label_id = annotation.shapes[0].geometry["label_id"];
if(label_id == "" || label_id != null){
   var text = annotation.text;
   var context = annotation.src;
   var x = annotation.shapes[0].geometry.x;
   var y = annotation.shapes[0].geometry.y;
   var width = annotation.shapes[0].geometry.width;
   var height = annotation.shapes[0].geometry.height
   var item_id = $("#item_id").val();
   $.ajax({
       type: 'PUT',
       url: "/labels/"+label_id,
       data: {
        label :{
           text:text,
           context:context,
           x_value:x,
           y_value:y,
           width:width,
           height:height,
           item_id: item_id
        } 
       },
       success: function(data) {}
     });
     }
  });

Удаление этикеток

Чтобы удалить сохраненную аннотацию, щелкните значок X на аннотации. Это вызовет anno.onAnnotationRemoved обратный вызов .

anno.addHandler('onAnnotationRemoved', function(annotation) {
 var label_id = annotation.shapes[0].geometry["label_id"];
 if(label_id == "" || label_id != null){
   $.ajax({
       type: 'DELETE',
       url: "/labels/"+label_id,
       data: {
       },
       success: function(data) {}
     });
     }
  });

Вот и все.

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

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

Кроме того, в Annotorious доступно множество плагинов, попробуйте и их. Создавать плагины так просто - я даже создал плагин, чтобы добавить раскрывающийся список в текстовое поле Аннотации.

Я надеюсь, что это было полезно!

Код: https://github.com/amkurian/image_annotater