Създаване на приложение, което ви позволява да маркирате и запазвате пояснения към дадено изображение
Анотацията на изображението е процес на етикетиране на различни обекти в изображение. Основното приложение на анотацията на изображения е генерирането на данни, които могат да се използват за обучение на алгоритми за машинно обучение. Анотацията на изображението е задача, която се извършва главно ръчно. Работата по анотациите на изображения вече е неизбежна част от машинното обучение и AI и се възлага основно на външни изпълнители в страни като Индия и Филипините. В тази част ще изградим приложение за анотации на изображения, което ви позволява да маркирате и запазвате анотации към дадено изображение.
Има много библиотеки и инструменти, които ни позволяват да правим анотации на изображения, включително много опции на цена като Labelbox, но за това приложение ще използваме безплатна библиотека, наречена Annotorious.
Преди да започнем разработването на частта за анотации на изображения, нека набързо да разгледаме задната част на нашето приложение. Annotorious е javascript библиотека, която може да се използва с всякакви бек-енд рамки. Тъй като се чувствам по-удобно в релсите, ще изградим приложение за релси. Нека бързо да настроим нашето приложение и back-end.
Нека създадем ново приложение. Въведете в терминала:
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
Сега нека създадем модела на етикетите:
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, заедно с височината и ширината, се очакват в анотация, заедно с текст. Контекстът е пътят към образа. Можем също да видим, че има препратка към артикул. Това означава, че един елемент или изображение може да има няколко етикета.
Моделът на артикула трябва да бъде като този:
/image_annotater/app/models/item.rb
class Item < ApplicationRecord mount_uploader :image, ImageUploader has_many :labels end
Моделът на етикета трябва да бъде като този:
/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 в нашето приложение, първо изтеглете най-новата версия на Annotorious от официалния сайт. Изтегленият zip съдържа файл annotorious.min.js, CSS папка с файла annotorious.css. Папката CSS също ще съдържа някои необходими файлове с изображения.
Ако добавяме annotorious към обикновен 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 иimages вприложение/активи/изображения.
Сега можем да направим нашето изображение годно за анотиране. Има два начина да направите това. API на Annotorious Javascript вече е наличен в нашите страници, който може да бъде извикан от променливата anno
Вариант 1. CSS клас с възможност за анотиране
Добавете CSS клас с анотация към етикета на изображението. При зареждане на страницата Annotorious автоматично ще сканира страницата ви за изображения с този клас и ще ги направи анотиращи.
Пример:
<img src="example.jpg" class="annotatable" />
Вариант 2: Използване на JavaScript
Javascript API на Annotorious може да се използва, за да направите изображенията „ръчно“ анотирани.
Пример:
<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 върши цялата работа вместо нас и опцията за анотиране е готова. Но остават още две неща.
- Опции за създаване, актуализиране и изтриване на етикети/анотации от потребителския интерфейс
- Показване на всички етикети върху изображението на елемента, когато страницата се зареди.
Етикетите за създаване, изтриване и актуализиране могат да се обработват от манипулаторите на събития, предоставени от 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 като anno.addAnnotation(annotation)
.
Актуализиране на етикетите
За да актуализирате текста в съществуващ етикет, щракнете върху иконата на молив върху анотацията. Ще бъде предложена опцията за добавяне на нов текст:
Когато щракнем върху опцията Редактиране, манипулаторът на събитие 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 са налични много плъгини, проверете и тях. Създаването на плъгини е толкова лесно — дори създадох плъгин за добавяне на падащо меню към текстовото поле за анотации.
Надявам се това да е било полезно!