読者です 読者をやめる 読者になる 読者になる

機械学習の用語をまとめてみた

Coursera - Free Online Courses From Top Universities機械学習コースで勉強を始めたのですが、難しい用語がたくさん出てくるので忘れないようにメモしておきます。

随時加筆、修正していく予定です。

機械学習用語集

英語 日本語 意味
supervised learning 教師あり学習 トレーニングセットとしてデータとそれに対する回答が与えられて学習する方法。新しいデータに対しての回答を予測する。
unsupervised learning 教師なし学習 トレーニングセットとしてデータのみが与えられて学習する方法。データを意味のあるグループに分類する。
regression problem 回帰問題 連続的に変化する値を予測する問題。(住宅のサイズの変化に伴う価格の推移を予測するなど)
classification problem 分類問題 非連続的(離散的)な値を予測する問題。(メールがスパムかどうか、腫瘍が良性か悪性かなど)
univariate linear regression 変数が1つの時の線形回帰。
hypothesis function 仮説関数?
cost function 目的関数? トレーニングセットに対するhypothesis functionの精度の指標を算出する関数。
squared error function
mean squared error 平均二乗誤差
gradient descent 最急降下法 関数の最小値を求めるためのアルゴリズム
learning rate 学習係数、学習率 最急降下法で使用するパラメータ。値が大きすぎると収束せず、小さすぎると収束が遅くなる。
leaner regression 線形回帰
training set トレーニングセット 学習に用いるデータの集まり
Batch gradient descent 最急降下法の各ステップで全てのトレーニングセットを参照する方法。
feature scaling 複数の異なるフィーチャーが似たような範囲の値となるようにスケールを合わせること。スケールが大きく異なると最急降下法での収束が遅くなる。
mean normalization フィーチャースケーリングの方法の1つ。フィーチャの値の平均が0になるように調整すること。
normal equation 正規方程式 コスト関数が最小となる時のシータを導く方程式。フィーチャの数がとても多い場合は計算コストが高くなる。O(n3)
logistic regression ロジスティック回帰 分類問題を解決するためのアルゴリズム
sigmoid function シグモイド関数 = logistic function
decision boundary 決定境界 ロジスティック回帰によって分類した時の境界を表す式。
conjugate gradient 共役勾配法
BFGS
L-BFGS
under fit トレーニングセットにフィットしていない状態。
high bias under fitと同じ
over fitting オーバーフィッティング トレーニングセットに過剰にフィットさせており、予測のためには有効でない状態。
high variance over fittingと同じ。
regularization 正規化 オーバーフィッティングを緩和する方法。
regularization parameter 正規化パラメータ 正規化で使用されるパラメータ(λ)。

React & Flummoxチュートリアル

Flummoxは、いくつもあるFlux実装のうちの1つです。

ドキュメントにはいくつかの特徴が挙げられていますが、 Isomorphicであること(サーバとクライアントのどちらでもOK!)、ES6記法に対応していることなどがあります。

今回は、簡単なTODOアプリを作りながらFlummoxの使い方を紹介しようと思います。

このチュートリアルでは、ReactやFluxの初心者を対象にしています。簡単な概要ぐらいは知っているけど、これから始めるために簡単なサンプルが欲しいというような人にちょうど良いと思いますが、React全く初めてでも順を追えばなんとなくやっていることがわかると思います。

ソースコードこちらです。

<完成イメージ>

f:id:gibachan03:20150429140911p:plain

準備

コードを書き始める前にいくつか必要となるツールがありますので紹介します。

Gulp

よく使われているタスクランナーです。この後でコードを変換する必要が出てくるので、このツールを使ってその処理を自動化させます。

こちらはグローバルな環境にインストールしておく必要があるので、無い場合は次のようにインストールしましょう。

npm install -g gulp

Browserify

JavaScriptでモジュール管理をするためのツールです。JavaScriptのファイルを複数に分けておき、必要なモジュールをrequireで取得できるようにしてくれます。

Babel

ここではJavaScriptをES6記法で記述していくのですが、実際にブラウザで動作させるにはES5に変換する必要があります。Babelはその変換をしてくれるツールです。

面倒ですが、これらのツールを使ってコードを書く準備をしましょう。 まずはnpmでReactとFlummoxをインストールし、別に開発に必要となるツールを開発環境用にインストールします。

mkdir flummox-excersize && cd flummox-excersize

npm init

npm install --save react flummox

npm install --save-dev babelify browserify gulp vinyl-source-stream

babelifyはBrowserifyでBabelの変換をしてくれるモジュールで、vinyl-source-streamは、最終的に1つのJavaScriptファイルにまとめるためのモジュールです。

インストールが済んだら、gulpfile.jsを作成し、Gulpで実行する作業を書きます。

gulp

詳細に説明はしませんが、これでコマンドにgulpと打つだけでコードの変換が行われます。 今の時点ではファイルが無いのでエラーになります。

React - Hello World -

まずはReactでブラウザにHello Worldを表示させましょう。

Appコンポーネント

アプリのルートとなるコンポーネントを/components/App.jsxとして作成します。

import React from 'react';

class App extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <h1>Hello World</h1>
      </div>
    );
  }
}

export default App;

ES6記法で書いています。そのためReactのバージョンが0.13以上である必要があります。

app.js

次に、このコンポーネントを使用して描画するコードをapp.jsに書きます。

import React from 'react';

import App from './components/App.jsx';

React.render(<App />,
  document.getElementById('app'));

こちらもES6記法である以外は問題無いと思います。

index.html

最後にブラウザで表示されるindex.htmlを書きます。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Flummox TODO</title>
</head>
<body>
  <div id="app"></div>

  <script src="build/bundle.js"></script>
</body>
</html>

ここで読み込んでいるbundle.jsは、gulpで変換して出力されるファイルです。

gulp

ブラウザでindex.htmlを開いて、"Hello World"が表示されたら成功です。

次からFlummoxを使って行きます。また、次からスペースの都合上コードは全て掲載しません。 必要であればサンプルコードをダウンロードしてご覧ください。

Action

Actionはビュー(Component)等で発生したイベントに応じて動作します。 モデル(Store)はActionを監視するので、Actionが動作したらそれに応じて内部のデータを変化させます。

ここではTODOアプリを作るので、TODOの新規作成、削除、終了フラグの切り替えのActionを定義します。

actions/Actions.js

class TodoActions extends Actions {
  // Todo新規作成
  createTodo(text) {
    return text;
  }

  // Todo削除
  deleteTodo(id) {
    return id;
  }

  // Todo終了フラグの切り替え
  toggleTodo(id) {
    return id;
  }
}

ActionsはFlummoxのActionsクラスを継承します。

Store

Storeはモデル(データ)を格納し、その状態を管理します。 Storeは、Actionsからの操作によりその状態を変化させ、その他からの影響は受けません。 また、Storeの状態が変化するとビュー(Component)がそれに応じて更新されます。

stores/TodoStore.js

import { Store } from 'flummox';

class TodoStore extends Store {
  ...
}

export default TodoStore;

StoreはFlummoxのStoreを継承します。

まずコンストラクタで、ActionとStoreの関連付けを行います。

constructor(flux) {
  super();

  // fluxインスタンスからActionsを取得
  const todoIds = flux.getActionIds('todo');

  // ActionとStoreメソッドを関連付け
  this.register(todoIds.createTodo, this.handleCreateTodo);
  this.register(todoIds.updateTodo, this.handleUpdateTodo);
  this.register(todoIds.deleteTodo, this.handleDeleteTodo);
  this.register(todoIds.toggleTodo, this.handleToggleTodo);

  // Storeの初期状態(初期データ)
  this.state = {
    todos: {}
  };

Actionが発生した場合、StoreはSetState状態を変化させます。

handleCreateTodo(text) {
  ...

  // 状態を変化
  this.setState({ todos });
}

setStateを呼ぶと状態が変化し、それがComponentへ伝えられる仕組みになっています。

Flux

ActionsとStoreを作ったので、これらを元にFluxインスタンスを作成します。

app.js

class Flux extends Flummox {
  constructor() {
    super();

    this.createActions('todo', Actions);
    this.createStore('todo', TodoStore, this);
  }
}

const flux = new Flux();

FluxはFlummoxを継承します。

fluxインスタンスを作成したらAppのComponentにインスタンスを渡します。

React.render(
  <FluxComponent flux={flux}>
    <App />
  </FluxComponent>,
  document.getElementById('app'));

ここで、FlummoxのFluxComponentというComponentを使用しています。 このComponentは、その配下のComponent(App)にFluxインスタンスを渡す役割があります。 つまり次のように書くのと同じイメージです。

<App flux={flux} />

ですがFluxComponentは便利な機能を持っています。 例えばAppの配下に子Componentがあり、その子ComponentもFluxインスタンスを必要とするのであれば、毎回このようにFluxインスタンスを渡してあげる必要があります。 ですがFluxComponentを使うとその記述を省くことができるようになります。 このような機能については次以降で確認できます。

Component

ComponentはStoreの状態を監視し、Storeの状態が変わったらComponentを更新します。 また、ボタンクリック等のユーザ操作に応じてActionを発生させます。

ここではComponentの階層構造を次のように考えます。

App
  - TodoEdit : Todo新規追加フォーム
  - TodoList : Todoリスト
    - TodoListItem

まず、components/App.jsxを書き換えます。

class App extends React.Component {
  ...
  render() {
    return (
      <div>
        <h1>Todo list</h1>
        <FluxComponent>
          <TodoEdit />
        </FluxComponent>
        <FluxComponent connectToStores={['todo']}>
          <TodoList />
        </FluxComponent>
      </div>
    );
  }
}

ここで、TodoEdit Componentの親としてFluxComponentを指定しています。 これにより、App Componentからfluxインスタンスが自動的に渡され、TodoEdit内でthis.props.fluxの形で使用できるようになります。

components/TodoEdit.jsx

...
handleSubmit(e) {
  e.preventDefault();
  if (this.state.newTodo.length === 0) return;

  // fluxインスタンスからActionsを取得し、Actionsを発生させる
  this.props.flux.getActions('todo').createTodo(this.state.newTodo);

  this.setState({
    newTodo: ''
  });
}
...

handleSubmitは新規Todo作成フォームのSubmitイベントハンドラです。 この中でActionを発生させ、その結果Storeの状態が変化することになります。

また、TodoList Componentの親のFluxComponentにはconnectToStoresの指定をしています。 これにより、'todo'で識別されるStore(TodoStore)がTodoListのpropsに展開されます。

components/TodoList.jsx

render() {
  var todos = this.props.todos;
  ...
}

結果

動作を確認しましょう。

gulpを実行した後にindex.htmlを開くとTodoアプリが動きます。 f:id:gibachan03:20150429140911p:plain

確認して頂きたいのはデータやイベントが常に1方向に流れていることです。

  • Todo新規作成 (ユーザがテキストを入力、ボタンクリック) => TodoActions(ceateTodo) => TodoStore => TodoList => TodoListItem

  • Todo削除 TodoListItem => (ユーザが削除ボタンクリック) => TodoActions(deleteTodo) => TodoStore => TodoList

このような構造でアプリを作る考え方がFluxというアーキテクチャであるということです。 より詳細な説明は公式をご確認ください。

まとめ

Reactを触りだすとFluxという概念が出てきます。初めてみると結構複雑そうで面倒な印象を持つかもしれません。 ですが、使ってみるとそれほど難しくないことがわかります。 なので、このチュートリアルがFlummoxもしくはReact/Fluxの最初の一歩として、ちょっとでも何かの参考になれば嬉しいです。

あと何か不足や間違いなどありましたらご連絡頂けるととてもありがたいです。 => Twitter@gibachan03

Flux入門してみた

Fluxとは?

Fluxはアプリケーションアーキテクチャの1つです。要は"こんな構造でアプリを構成すると見通しが良くなるよ"というような方針みたいなものですね。

元々はFacebookが提唱した考え方で、Reactと共に用いられるアーキテクチャです。

Reactでアプリを構築する際に、ある程度アプリが複雑になってくるとデータやイベントの流れが判りにくくなるので、Fluxの考え方を利用することで綺麗にまとめられるようになります。

こちらの記事にも少し詳しく紹介されています。最初見た時はちょっと複雑そうで拒否反応が起こりましたが、実際触ってみるとそんなでもないので大丈夫です。

で、Fluxの考え方を実際にコードに落とした実装はいくつもあり、どれを使うか迷います。 今回はわかり易そうだったFluxxorRefluxJSを使って簡単なアプリを作ってみました。

つくってたもの

B-Viewer はてなブックマークのホットエントリを閲覧するためのChrome拡張機能です。Fluxxorを使用しています。

f:id:gibachan03:20150406164038p:plain

piccheck 画像ファイルに埋め込まれているEXIFのデータを閲覧するWebアプリです。Refluxを使用しています。

f:id:gibachan03:20150406164033p:plain

感想

Fluxの考え方ではデータやイベントの流れが常に一方向に流れます。

データの流れ  : Store -> View
イベントの流れ:          View -> Action -> Store 

(かなり簡略化しています。詳しくは上記の記事をご覧下さい)

これが意識できるとアプリの構造をシンプルに考えられます。ReactはVirtualDOMについて良く取り上げられてるみたいですが、React & Fluxでアプリの構造全体がシンプルに記述できるところが個人的に気に入りました。

FluxxorもRefluxJSも基本的には考え方は同じだし、正直どちらも簡単なアプリだったので、あんまり比較はなりませんでしたね。 個人的にはRefluxの方がActioins等が簡略的に書けるので好みです。 ただRefluxJSについてはネット上の情報が少ないようです。 せっかくなので、後で使い方の解説みたいなものを書けたらと思っています。

もしこれから始めてみようという方の参考になればと思い、ここで公開しておきます。

Backbone.jsを使ってみて気になったこと

書籍の新着情報をチェックするWEBサービスmybookifyを作った時に、クライアントサイドのフレームワークとしてBackbone.jsを使用しました。

その時に気になったことがいくつかあったので、ここでまとめてみました。Backbone.jsの経験は少ないのでおかしな点があるかもです。何か気づいたことがありましたら教えてもらえると嬉しいです。

ここで使用したBackbone.jsのバージョンは1.1.2でした。

Model初期化時に引数を渡したい

Modelのメンバのデフォルト値は、defaultsを設定してあげれば良いです。

var Book = Backbone.Model.extend({
  defaults: {
    'title': 'MyBook'   // デフォルト値
  }
});

var book = new Book(); // => Object {title: "MyBook"}

ですが、インスタンス化するタイミングで値を設定したい時には次のようにオブジェクトを渡してあげます。

var Book = Backbone.Model.extend({
  defaults: {
    'title': 'MyBook'   // デフォルト値(この値は無視される)
  }
});

var book = new Book({
  'title': 'NewMyBook'
});
console.log(book.toJSON()); // => Object {title: "NewMyBook"}

ビューが2つあるとき、片方のビューで発生したイベントに対してもう片方のビューで処理したい時

これにはこちらで紹介されている方法が良さそうでした。

異なるビュー間のイベントを、どちらのビューからもアクセスできる別のオブジェクト(mediator)を経由する方法です。 次の例では、ViewAとViewBがある時に、ViewAでクリックされたイベントに対してViewBで処理を行っています。

// mediatorを定義
mediator = {};
_.extend(mediator, Backbone.Events);

// ViewAの定義
var ViewA = Backbone.View.extend({
  el: '#ViewA',

  events: {
    'click': 'OnViewAClick'            // 自分のビュー内のイベントを補足する
  },

  OnViewAClick: function() {
    mediator.trigger('OnViewAClick');  // mediatorにイベントを伝える
  }
});

// ViewBの定義
var ViewB = Backbone.View.extend({
  el: '#ViewB',

  initialize: function() {
    this.listenTo(mediator, 'OnViewAClick', this.OnViewBClick); // mediatorから伝わるイベントを拾う
  },

  OnViewBClick: function() {
    console.log('Hit!');
  }
});

ModelとREST APIとの連携

REST APIと対応付けるためには、Modelを次のように定義します。

var Book = Backbone.Model.extend({
  urlRoot: '/mybook-api',   // APIのurl

  idAttribute: '_id',       // ModelインスタンスのID
});

これによりGET、POST、PUT 、DELETEのリクエストを発行できるようになります。

発行先は/mybook-api/(_idの値)となり、それぞれ次のメソッドが対応しています。

var book = new Book();
// インスタンスがIDを持っていない時
console.log(book.isNew());  // => true

book.fetch();     // GET  .../mybook-api
book.save();      // POST .../mybook-api
book.destroy());  // リクエストは発行されない

// インスタンスがIDを持っている時
book.set('_id', 'myid');    // 仮にIDをmyidとしておく
console.log(book.isNew());  // => false

book.fetch();     // GET  .../mybook-api/myid
book.save();      // PUT .../mybook-api/myid
book.destroy();   // DELETE  .../mybook-api/myid

また、それぞれのメソッドではリクエスト成功時と失敗時の処理をコールバックで受け取ります。詳しくはドキュメントを確認して下さい。そしてこれらのメソッドは内部でsyncメソッドを呼んでいます。リクエストを細かくカスタマイズしたい場合はsyncメソッドを自分で再定義することになります。

続いて、モデルにparseメソッドを定義しておくと、サーバから返ってくるレスポンスを自分好みに処理できます。

var Book = Backbone.Model.extend({
    urlRoot: '/mybook-api',

    idAttribute: '_id',

    parse: function(response, options) {
      // レスポンス例: response == { status: 'success', book: { 'title': 'mybook' } }

      // レスポンスを処理して、モデルオブジェクトを返す
      return response.book;
    }
  });

複数のViewからアクセスしたいModelの持ち方(未解決)

BackboneではModelまたはCollectionはViewと1:1の関係になると思っています。

var MyModel = Backbone.Model.extend({});
var MyView = Backbone.View.extend({ model: MyModel });

var myModel = new MyModel();
var myView = new MyView({
  model: myModel
});

でも、Viewを越えて別のModelを参照したい時にはどうするのが良いのかわかりませんでした。

例えばAというModelとBというModelがあります。そしてViewAとViewBという、それぞれに対応するViewがあります。 このとき、ViewBでBを描画する際に、Aの値によって表示を変えるとします。

こんなときにどのような構造にすれば良いかわかりませんでした。 現状はAをグローバルなオブジェクトにして、どこからでもアクセスできるようにして対応しているのですが、あんまり綺麗じゃないような気がする。。。

取り敢えずこんなところです。まだちょっと思考錯誤しながらBackboneを使ってコードを綺麗にまとめられる方法を模索してます。もしかしたらAngularとか別のフレームワークに手を出したほうが良いのかなぁ?

はじめてのWebサービスをつくってみた

最近こんな感じのエントリを良く目にする気がしますが、自分もこっそりコツコツつくってみたのでここで紹介させて頂きます。

つくったもの

mybookify は、自分の気になる事柄に関する書籍についての新刊情報をチェックでるサービスです。

最初にTwitterアカウントでログインして、キーワードを登録します。そのキーワード毎にAmazonで検索して、過去6ヶ月以内に発刊された書籍を表示します。また、TwitterWebサービスのアカウントをフォローしておくと新しい書籍が発売された時にメッセージが送られてきます。

一応テスト版ということで公開していますが、テスト運用と本番運用の境は結構曖昧。。。(正直バグあるだろうけど許してねって事です) でも完成度を求めていつまで公開せずにいると、そのうち飽きてお蔵入りになる可能性大なので思い切って公開してみました。

前知識

プライベートでも仕事でもWebサービスの経験はほとんど0。事前知識があったといえばHTML/CSS/JavaScriptはある程度知っていて、expressは遊びでちょっと触ったことがある程度。

技術的なこと

  • Node.js / JavaScript
    使用したプログラミング言語です。多少は知識があったので選びました。Webサービスを作るんだったら最近はRubyが多いような印象がありますね。Node.jsはWebSocketを使ったリアルタイムな通信が必要なアプリには向いているらしいです。ゲームとか。

  • MongoDB
    データベースです。JavaScriptと相性がよく、MySQLとかみたいに事前にテーブル定義する必要もないので使いやすかったです。
    ただ、普通のRDBであるテーブルの結合のような処理は得意じゃないみたいなので、データ構造を考えるときに注意が必要そうです。例えば、ユーザのテーブルとユーザの書籍情報のテーブルを別々に用意し、特定のユーザのIDを使って書籍情報テーブルからレコードを抽出するなんてことは良くあると思います。MySQLとかだと簡単なのですが、MongoDBだと一手間かかったりパフォーマンスが落ちたりするかもしれません。(あんまり自信はないです)なので今回は、1つのユーザのモデルデータの中に全ての書籍情報がまるごと入っている形にしています。

  • express
    Node.jsでWebアプリを作るためのフレームワークです。恐らくRubyにとってのRuby on Railsに相当するのでしょう。有名なようでたくさん情報が見つかります。

  • mongoose
    Node.jsでMongoDBにアクセスするためのライブラリです。似たようなライブラリがいくつかありましたが、ネット上にたくさん情報のありそうなこちらを使用しました。

  • passport
    Node.jsでTwitterログインを簡単に実装できるライブラリです。他にもFacebookなどかなりたくさんのSNSに対応しているようです。使い方も簡単で良い感じです。でも何となくログインが遅い気がする。

  • twitter
    Node.jsからTwitterAPIを使用するためのライブラリです。予めTwitterで登録しておく必要があります。アプリに権限を設定しておかないとツイートやメッセージの送信ができないので注意です。気づかずに少しハマりました。

  • apac
    Node.jsからAmazonAPIを使用するためのライブラリです。

  • Backbone.js
    JavaScriptMVCフレームワークで、クライアントに使用しています。覚えることはそれほど多くないので良いのですが、人によって色々な書き方ができるので「こういう風に作るといいよ!」って感じのベストプラクティスが無いそうです。Marionetteというライブラリを組み合わせると良い感じになるみたいですが、そこまで手が回らなくて試していません。

  • async
    JavaScriptで非同期処理を同期的に処理するときに便利なライブラリです。例えば、データを保存して、それが終わったら次の処理をして、、、というような時間の掛かる処理を順次実行していくような時に綺麗に書けるようにしてくれます。

  • Bootstrap
    Webサイトを作るためのテンプレートです。ボタンやフォームの表示が綺麗になったり、画面の大きさの大小に自動的に対応してくれたりします。デザインセンスの絶望的な自分にはとっても優しいツールでした。

  • heroku
    Webアプリを公開できるサービスです。英語のサイトですが面倒な設定もなく使用できます。ある程度までは無料で使用できるので、お試しにもちょうど良いですね。怖がらなくていいです。

参考になったもの

わからないことはほとんどネットから拾い集めましたが、ドットインストールさんには本当にお世話になりました。 ここで敢えて紹介するまでもない有名なサイトだとは思いますが、サーバーサイドのexpressやMongoDBから始まってクライアントサイドのBackbone.js、Bootstrapの使い方、そしてherokuでアプリを公開する方法に至るまで全部カバーできました。特にherokuの講座は同じ動画を何度も見返して試しました。 ホントに素晴らしいです。

今後の課題

  • Facebookなどでもログインできるようにしたい
  • 検索対象をAmazon以外に広げたい
  • 通知の機能を増やしたい(iPhoneアプリにしてPush Notificationしたい)
  • 見た目や使い勝手をもっと良くしたい(いろいろ気になる)
  • 障害発生時にすぐに対応できるような仕組みを入れたい

感想

制作にかかってから1ヶ月半くらい掛かりました。素人なので常に調べながらコツコツやってきて、取り敢えず最低限の機能は実現できました。何とかここまで挫折せずにここまでこれて満足です。

きっと他の人が使うとたくさんボロが出てくるとは思います。ちょっと怖くもありますが思い切って公開しました。もしよろしければ触って頂けるとありがたいです。

まだまだ勉強しなきゃいけないことがいっぱいあるし、どこまで出来るかわかりませんが今後も少しずつ手を入れて行くつもりです。また、このWebサービスを作る過程で色々つまづいたりしたので、そのことについても今後紹介していきたいと思っています。

もしよければ何かご意見や感想など頂けると嬉しいのです。 つくったひと: Twitter

ニコニコ動画のiPhoneアプリ作ってみた

初めてSwiftでアプリを作りました。せっかく作ったのでここで公開しときます。
試行錯誤しながらコツコツ頑張りました。

App Storeには公開するつもりはありません。著作権の問題がありそうですしね...
(よくわかんないけど個人で楽しむ分にはOKかな?)

機能

ニコニコ動画の「歌ってみた」カテゴリの動画をよく見るので、「歌ってみた」に特化したアプリです。
動画をダウンロードし、オーディオファイルに変換していつでも楽しめるようしています。
ミュージックプレイヤとしては基本的な機能しか実装できていないのですが、最初なのでここまでやれば取り敢えずいいかなぁと。

f:id:gibachan03:20141218213130p:plain f:id:gibachan03:20141218213137p:plain f:id:gibachan03:20141218213142p:plain

ソース

一応コードをgithubにあげておきます。swiftでの書き方に不安があるので、誰か奇特な方がダメ出しをしてくれるといいなぁ・・・
gibachan/NicoMusic · GitHub

Swift + OpenCVでリアルタイムに顔認識してみた2

前回に引き続き、今回はOpenCVを使って顔認識を行います。

分類器

画像データの中から顔を検出するための分類器となるファイルを用意します。
Githubからこちらをダウンロードします。
ダウンロードしたデータの中から、data/haarcascades/haarcascade_frontalface_alt.xmlファイルを取り出し、プロジェクトに追加します。

顔認識

OpenCVの処理はC++で書く必要があります。そのためにObjective-Cでラップしてあげます。
まずXcodeで「New File」から「Objective-C」のファイルを追加します。すると「Would you like to configure an Objective-C bridging header?」と聞かれるのでYesとします。 作成された.mファイルの名前を変更して.mmファイルとするとC++が使用できます。

では処理を書いていきます。

OpenCVSample-Bridging-Header.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface Detector: NSObject

- (id)init;
- (UIImage *)recognizeFace:(UIImage *)image;

@end

Detector.mm


#import "OpenCVSample-Bridging-Header.h"
#import <opencv2/opencv.hpp>
#import <opencv2/highgui/ios.h>

@interface Detector()
{
    cv::CascadeClassifier cascade;
}
@end

@implementation Detector: NSObject

- (id)init {
    self = [super init];
    
    // 分類器の読み込み
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *path = [bundle pathForResource:@"haarcascade_frontalface_alt" ofType:@"xml"];
    std::string cascadeName = (char *)[path UTF8String];
    
    if(!cascade.load(cascadeName)) {
        return nil;
    }
    
    return self;
}

- (UIImage *)recognizeFace:(UIImage *)image {
    // UIImage -> cv::Mat変換
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;
    
    cv::Mat mat(rows, cols, CV_8UC4);
    
    CGContextRef contextRef = CGBitmapContextCreate(mat.data,
                                                    cols,
                                                    rows,
                                                    8,
                                                    mat.step[0],
                                                    colorSpace,
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault);
    
    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);
    
    // 顔検出
    std::vector<cv::Rect> faces;
    cascade.detectMultiScale(mat, faces,
                             1.1, 2,
                             CV_HAAR_SCALE_IMAGE,
                             cv::Size(30, 30));

    // 顔の位置に丸を描く
    std::vector<cv::Rect>::const_iterator r = faces.begin();
    for(; r != faces.end(); ++r) {
        cv::Point center;
        int radius;
        center.x = cv::saturate_cast<int>((r->x + r->width*0.5));
        center.y = cv::saturate_cast<int>((r->y + r->height*0.5));
        radius = cv::saturate_cast<int>((r->width + r->height));
        cv::circle(mat, center, radius, cv::Scalar(80,80,255), 3, 8, 0 );
    }
    
    
    // cv::Mat -> UIImage変換
    UIImage *resultImage = MatToUIImage(mat);
    
    return resultImage;
}

@end

イニシャライザで分類器を読み込んでおきます。
recognizeFaceで受け取ったUIImageをOpenCVで扱うデータ構造のcv::Matへ変換し、顔検出の処理を行ったあと、最後にまたUIImageへ変換して返しています。

実は、UIImage->cv::Mat変換ではcvMatFromUIImageメソッドがあり、簡単に変換できるようなのですが、何度か繰り返しこのメソッドを実行していくと不意に実行時エラーとなってしまいました。原因不明だったので、その処理を置き換えています。

最後にカメラからの画像データを変換する処理を次のように変更して終わりです。

    // 顔検出オブジェクト
    let detector = Detector()

   // ---------- 省略 ---------- //


    // 毎フレーム実行される処理
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, fromConnection connection: AVCaptureConnection!)
    {
        dispatch_async(dispatch_get_main_queue(), {
            // UIImageへ変換
            let image = CameraUtil.imageFromSampleBuffer(sampleBuffer)
            
            // 顔認識
            let faceImage = self.detector.recognizeFace(image)
            
            // 表示
            self.imageView.image = faceImage
        })
    }

これで実行すると、カメラに写した顔の位置に丸が描かれるはずです。
(何にも場所に反応されるとちょっとビビります・・・)

たぶん顔認識の処理のところで工夫すると、パフォーマンスをあげられるんだろうけど良く分かりません!!
どなたか教えてくださいませませ。

Githubにソースを上げておきます。

参考

OpenCV iOS - Image Processing

2014/11/30 追記

上記のコードにはメモリ処理上の問題があることを教えていただきました!
[自在]OpenCVかCIDetectorを使ってiOSで顔認識してみよう!

dispatch_asyncをdispatch_syncに変更で、明らかにメモリ使用量が改善します。

感謝感謝!