アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

ruby on railsで機械学習用の画像にラベル付けするアプリを作ってみた

普段は機械学習を学んでいてpythonを使っていますが、データ画像にラベル付けをする作業がめんどいのでラベル付け専用のアプリをruby on railsで作成しました。

rails初心者ですが、Githubオープンソースから自力でアプリを動かせるまでにしたこと、またそれをいじくった部分のアプリのコードを紹介しようと思います。

目次
・アプリの概要
・実際にアプリを作ってみた
・いじくった部分のコード

f:id:trafalbad:20170630095800j:plain



アプリの概要
今回作ったのは、機械学習用のデータセット(特に画像)にラベル付けするためのアプリです。機械学習ではデータにラベルをつけていくんですが、画像の場合、画像を見ながらラベルをつけるという作業が非常に面倒くさい。

そこでアプリで画像を見ながら、ラベルを入力できるものを作成しました。

参考ソースコードhttps://github.com/ryo813/tegaki_recog


f:id:trafalbad:20170630095817p:plain

今回アプリを作るまでにやったこと


学習
ほぼオールマイティの人気サイト「ドットインストール
ruby入門(全26回)

・ruby on rails4入門 (全28回)
をやりました。一通り動画を見た。その後、アプリを作りながら、ルーティングやコマンドなどわからないところは復習する感じで何度も見ました。基礎的なことがよくわかるので、オススメです。あと書籍が一冊手元にあれば十分。自分が使ったのはこれ





パーフェクト Ruby on Railsもオススメらしいので、どちらか1つ手元にあれば、後はネット上の情報で十分です。



使用環境
ruby 2.4.0
ruby on rails5.0.0
OS:mac book pro
DB:SQlite
環境:cloud9

htmlの代わりにslimを使いました。(インストールはこちらを参照)



コードの役割理解

・各モデルの役割の理解
モデルが3つ(Image、TmpImage、TextBlock)あるので、その役割を理解。

・controllerのアクションの理解
controller内のアクションを解析すれば、そのアプリがどのようにして動くのか知れるので解読

・ルーティングの理解
名前空間とか、結構突っ込んだ内容だったので、ルーティングも必須

・viewsの理解
controllerの内容を反映するコード。メイン画面とかダウンロード画面など理解しておくのに必要


だいたいmodel、controller、views、ルーティング(routes.rb)の理解は鉄板ですね。逆にこれさえわかれば、後は雛形作ってエラーがでたら対処していけば作れるでしょう。



実際にアプリを作ってみた
実際にGithub内のコードから作ってみました。ただ欲しいのはラベル情報だけなので、画像のxとy成分のラベル情報はいらない。そこでTextBlockのx1、x2、y1、y2に関するところは削ってあります。

Dir.foreachでディレクトリから画像を持ってくると順番通りにならない。そこで画像の名前を1から始まる番号順に変えます。そうすることで、先頭から順番に表示できるように改良してあります。

*まず始めに設計図を作る
modelのテーブル、カラム、データ型はきちんと区分けしておく必要があります。設計段階でこれらがおざなりになっていると、後で誤作動が起きても「手遅れ」なんてことも多いです。



実行手順

あとは作るだけです。簡潔に手順だけまとめるとこんな感じ

1.gemfileでtherubyracerとslimをbundle install

2.modelの作成

3.controllerの作成

4.ルーティングの整理

5.viewsをいじる

6.mkdirでcontrollerとviewsにimageディレクトリを作成してimageと紐付け

7.asset内に画像を入れる


ここまでで、一通りアプリ自体は完成。
あとは
image/resetにアクセス→ラベル情報入力→image/downloadにアクセスでダウンロード→完了

で出来上がりです。
f:id:trafalbad:20170630113344g:plain

rails初心者だけど、かなり基礎から学べたので、オープンソースのみで動くアプリを作ってみると得られるものはデカイです

自分は2週間ぶっ続けでやり通しましたが、1ヶ月分のインプットとアウトプット量の学習ができた感があります。
面倒くさいけど、オープンソースから実装してみるというのは、費用対効果として非常に有益ではないかと思います。


いじくった部分のコード
3つのmodel

#Image
filename: string
is_complete: boolean
is_none: boolean

#TextBlock
image_id: integer 
text: string
image: references

#TmpImage
filename: string 
image_id: integer
#projects_controller.rb
class Image::ProjectsController < ApplicationController
    
    def index
      tmp = TmpImage.first
    @image = tmp[:filename]
    end
  
  def register
    pos = params[:pos]
    text = TextBlock.create(
      image_id: TmpImage.first[:image_id],
      text: pos[:text])
    redirect_to root_path
  end
  
  # *** 前の操作を取り消すメソッド ***
 def delete
    TextBlock.last.delete
    redirect_to root_path
 end
 
 # *** 次の画像に移動するメソッド ***
  def next
    # 現状の画像を完了に
    tmp = TmpImage.first
    image = Image.find_by(filename: tmp[:filename])
    image.update(is_complete: true)
    # 新しい画像に切り替える
    fily = Dir.foreach("app/assets/images/").to_a
    files=fily.sort_by{|x| File.basename(x, File.extname(x)).to_i}
    files.each do |file|
      next unless %w(.jpg .png .jpeg).include?(File.extname(file))
      # ファイル名が既に書き込まれていないか確認
      db_files = Image.where(filename: file)
      if db_files.empty?
        image = Image.create(filename: file)
        TmpImage.first.update(filename: file, image_id: image.id)
        break
      end
    end
    # トップページへ遷移
    redirect_to root_path
  end
  
  # *** ダウンロードページ ***
  def download
    respond_to do |format|
      format.html
      format.csv do
        filename = 'recognition_result'
        headers['Content-Disposition'] = "attachment; filename=\"#{filename}.csv\""
      end
    end
  end


  # *** リセット(初期化)用のページ ***
  def reset
    # データベースの中身を削除する
    Image.delete_all
    TmpImage.delete_all
    TextBlock.delete_all
    # 最初の画像を指定する ※ サンプル画像以外の場合はこちらを書き換え
    image = Image.create(filename:"00asfs1.jpg")
    TmpImage.create(filename:image[:filename], image_id: image.id)
    redirect_to root_path
  end
end
#download.csv.ruby
require 'csv'
CSV.generate do |csv|
  field_names = %w(id filename text )
  csv << field_names
  TextBlock.all.each do |t|
    record = field_names.map do |field_name|
      case field_name
      when "filename"
        Image.find(t.image_id).filename
      else
        t[field_name]
      end
    end
    csv << record
  end
end.encode('CP932')
#index.html.slim
div.top-title
  div.left-button
    = link_to "前の登録を取り消し", image_delete_path, method: :delete, data: {confirm: "取り消しますか?"}, class: "btn btn-danger"
  div.right-button align="right"
    = link_to "次の画像へ", image_next_path, method: :post, class: "btn btn-warning"

div.float2
  div#main-image
    = image_tag @image, id:"image, :size => '100x100'"
    
div.float2
  div.form
    = form_for(:pos, url: image_register_path, method: :post) do |f|
      div.form-group
        = f.label :text, "3.テキスト"
        = f.text_area :text, class:"form-control", style:"height: 200px;"
      div.form-group
        = f.submit "登録", class:"btn btn-primary"

    div
      = link_to "リセット", root_path
      
javascript:
  count = 1  // ボタンで使うカウンター
  document.getElementById( "main-image" ).addEventListener( "click", function( e ) {
     // マウス位置を取得する
     var mouseX = e.pageX ;  // X座標
     var mouseY = e.pageY ;  // Y座標

    
  } ) ;
#routes.rb
Rails.application.routes.draw do

namespace :image do
    post 'register' => 'projects#register'
    post 'next'     => 'projects#next'
    get  'reset'    => 'projects#reset'
    get  'download' => 'projects#download'
    delete 'delete' => 'projects#delete'
  end
root 'image/projects#index'
end 
広告を非表示にする