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

ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

Swift3でRealmを使って多対多のモデルを作るには

こんにちは。スマートフォンエンジニアのまさです。
最近サーバとやりとりをするアプリを作成していますが、APIをなんども叩くアプリでは、 どうしてもローディングが多くなりがちで、ユーザーにストレスを与えてしまいます。
何度も叩く必要のないAPIはレスポンスをRealmに保存しておいて、より快適なアプリを実現したいですね。
今回は、Realmで多対多のモデルを作る方法をご紹介します。

Realmのインストール

まずは、podでrealmを入れます。

$ pod init
$ vi Podfile
pod 'RealmSwift' # 追加
$ pod install

Realmのモデルを作成する

今回は複数の子供が複数のグループに所属しているモデルを作ってみようと思います。 基本的には、多対多は、中間テーブルを用意し、それぞれのテーブルに一対多にすることで実現します。

f:id:unifa_tech:20170419141521p:plain

import RealmSwift

class Kid: Object {
    dynamic var id = 0
    dynamic var name = ""
    var kidGroups = List<KidGroup>()
     
    override static func primaryKey() -> String? {
        return "id"
    }
}
 
class KidGroup: Object {
    dynamic var group: Group?
    dynamic var kid: Kid?
    let ownerKid = LinkingObjects(fromType: Kid.self, property: "kidGroups")
    let ownerGroup = LinkingObjects(fromType: Group.self, property: "kidGroups")
}
 
class Group: Object {
    dynamic var id = 0
    dynamic var name = ""
    var kidGroups = List<KidGroup>()
     
    override static func primaryKey() -> String? {
        return "id"
    }
}

Realmのモデルに実際にデータを入れてみる

モデルクラスを用意したら、データを入れてみます。 テストなので、viewDidLoadに入れてログを出して確認しています。

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
     
    // テストのため、毎回realmのDBを消して再度新規生成されるようにしています
    let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let path = paths[0] + "/test.realm"
    let url = URL(fileURLWithPath: path)
    let fileManager = FileManager.default
    if fileManager.fileExists(atPath: path) {
        try! FileManager.default.removeItem(atPath: path)
        try! FileManager.default.removeItem(atPath: path + ".lock")
        try! FileManager.default.removeItem(atPath: path + ".management")
    }
     
    let realm = try! Realm(fileURL: url)
     
    // たろう君は、たぬき組に、所属、
    // じろう君は、たぬき組、きつね組どちらにも所属させてみる
     
    let kid = Kid()
    kid.id = 1
    kid.name = "たろう"
     
    let kid2 = Kid()
    kid2.id = 2
    kid2.name = "じろう"
     
    let group = Group()
    group.id = 101
    group.name = "たぬき組"
     
    let group2 = Group()
    group2.id = 102
    group2.name = "きつね組"
     
    let kidGroup = KidGroup()
    kidGroup.group = group
    kidGroup.kid = kid
     
    kid.kidGroups.append(kidGroup)
    group.kidGroups.append(kidGroup)
     
    let kidGroup2 = KidGroup()
    kidGroup2.group = group2
    kidGroup2.kid = kid2
     
    kid2.kidGroups.append(kidGroup2)
    group2.kidGroups.append(kidGroup2)
     
    let kidGroup3 = KidGroup()
    kidGroup3.group = group
    kidGroup3.kid = kid2
     
    kid2.kidGroups.append(kidGroup3)
    group.kidGroups.append(kidGroup3)
     
    try! realm.write() {
        realm.add(kid)
        realm.add(kid2)
    }

}

正しく保存されているか確認してみる

それでは、正しく参照できるのか、また中間テーブルが削除されれば きちんと関連がきれるのか、テストしてみようと思います。下記のコードをviewDidLoadの下側に追記してみます。

    // クエリ
    print("Kidのオブジェクトをすべて取得する")
    let kids = realm.objects(Kid.self)
    print(kids.count) // => 2
    print(kids[0].kidGroups.count) // => 1
    print(kids[1].kidGroups.count) // => 2 
     
    print("Kidのidが2のオブジェクトを取得する")
    let kidsId_2 = realm.objects(Kid.self).filter("id = 2")
    print(kidsId_2[0].kidGroups.count) // => 2
    print(kidsId_2[0].kidGroups[0].group?.name ?? "") // => たぬき組
    print(kidsId_2[0].kidGroups[1].group?.name ?? "") // => きつね組
     
    print("Groupがきつね組に所属しているkidを取得する")
    let groupsId_102 = realm.objects(Group.self).filter("id = 102")
    print(groupsId_102[0].kidGroups.count) // => 1
    print(groupsId_102[0].kidGroups[0].kid?.name ?? "") // => じろう
     
    print("Kidじろうのgroupきつね組との関連を切ってみる")
    try! realm.write {
        realm.delete(groupsId_102[0].kidGroups[0])
    }
     
    print("きちんと関連が切れているか確認する groupから参照した場合")
    let updateGroupsId_102 = realm.objects(Group.self).filter("id = 102")
    print(updateGroupsId_102[0].kidGroups.count) // =>0
     
    print("きちんと関連が切れているか確認する kidから参照した場合")
    let updateKidsId_2 = realm.objects(Kid.self).filter("id = 2")
    print(updateKidsId_2[0].kidGroups.count) // => 1
    print(updateKidsId_2[0].kidGroups[0].group?.name ?? "") // => たぬき組
 

正しく参照できているようです。中間テーブルを削除して関連を切れば、kidから参照しても、groupから参照しても正しい結果と なっているのがわかります。多対多を実現させることができました。
まだまだRealm初心者なので、今後もいろいろと試してみようと思います。
もしここがおかしいとか、こういう書き方もあるよなどありましたら、ご指摘頂けると幸いです。

ローカルでdockerを使って複数サービスをテストする

こんにちは、WEBエンジニアのチョウです。

1つのサービスに新機能がどんどん追加されると、ロジックが複雑になり、開発やメンテナンスは難しくなります。その解決方法はいつくかあると思いますが、1つの方法として、1つのサービスを複数サービスに分割して別々で開発するというやり方があります。複数サービスへの分割には、スケールアウトに優れているなどのメリットがありますが、テストの実施やインフラの準備は以前よりも難しくなります。今回、複数サービスにおけるローカルテストについてすこし共有したいと思います。

普通のローカルテストはアプリケーションを立ち上げて、ブラウザでテストするのです。複数サービスの場合は、必要なサービス一つ一つを起動しないといけないです。そこで、設定の問題だったり、サービスが依頼する環境の問題だったり、一回だけならいいですが、毎回やらないといけないと無駄に時間かかります。もし設定は事前に用意できて、環境はちゃんと管理されているツールがあれば一番いいかなと思う人はきっといますでしょう。

AWSにすでにAMIみたいなサーバーインスタンスのsnapshotを取る仕組みがあります。ローカルの仮想マシンでsnapshotを取ってテストするのも可能ですが、パソコンで同時に起動できる仮想マシンが限られてるようで、AMIのやり方でテストするのは非現実的だと思います。もう1つの方法は、dockerなどのコンテナ技術です。

コンテナ技術というのはcgroupsなどの仕組みを利用して、サーバー一台で複数サーバーのように、1サーバー1アプリケーションを起動します。cgroups自体は「プロセスグループのリソース(CPU、メモリ、ディスクI/Oなど)の利用を制限・隔離するLinuxカーネルの機能」です。コンテナ技術の中で一番有名なのはdockerです。

つまり、dockerを利用すれば、一台サーバー(dockerの仮想マシン)で複数サービスを起動することが可能になります。もっと正確にいえば、dockerで各サービスのimageを作って、docker-composeを使って複数サービスを起動してテストすることができます。

最近こちらが携わったプロジェクトを紹介します。サービスの構成

nginx ----> Service A ----> Database A
        |
        --> Service B ----> Database B

元々これらのサービスを起動するには

  1. Service Aを起動する
  2. Service Bを起動する
  3. ローカルのnginxを設定し、再起動する
  4. もちろん、ローカルにDBなども起動

dockerを使う場合

docker-compose up -d

で一気に起動できる。

もちろん、設定ファイルが必要です。docker-compose.ymlでこんな設定があります。

version: '2'
services:
  haproxy:
    image: 'nginx'
    ports:
      - '8081:8081'
    depends_on:
      - service-a
      - service-b

  service-a:
    image: 'service-a'
    command: rails s -p 3000
    volumes:
      - /path/to/service/a:/usr/src/app
    depends_on:
      - service-db
    environment:
      RAILS_ENV: development

  service-b:
    image: 'serivce-b'
    command: rails s -p 3001
    volumes:
      - /path/to/service/b:/usr/src/app
    depends_on:
      - service-db
    environment:
      RAILS_ENV: development

  service-db:
    image: 'postgres:9.4'
    volumes:
      - data-service-db:/var/lib/postgresql/data

volumes:
  data-service-db:

docker-compose.yml自体は複数のdocker containerを組み合わせる設定ファイルで、パラメータ名は基本dockerがcontainer作る時のパラメータと同じです。docker containerというのは、docker imageのインスタンスです。docker imageはアプリケーションの稼働環境と理解してもいいです。docker imageを作るには、Dockfileを利用し、アプリケーションが依頼してるソフトウェアのインストールしたり、アプリケーションのファイルをコピーしたりして、アプリケーションバイナリのように1つコマンドで起動できるリソースグループを作ります。

Railsアプリケーションにとっては、ローカルで開発する場合が多いですので、Gemfile/Gemfile.lockだけでdocker imageを作って、docker-compose.ymlの中でソースコードをマッピングするほうがおすすめします。

例えば、こんなDockerfile

FROM ruby:2

RUN echo 'deb http://http.debian.net/debian jessie main' >> /etc/apt/sources.list && apt-get update && apt-get install -y nodejs && apt-get clean && rm -rf /var/lib/apt/lists/*

RUN echo "Asia/Tokyo" > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata

RUN mkdir /usr/src/app
WORKDIR /usr/src/app

COPY Gemfile /usr/src/app/Gemfile
COPY Gemfile.lock /usr/src/app/Gemfile.lock
RUN bundle install

ちなみに、複数サービスではなくても、application + databaseみたいなパターンも使えます。そして、多数なサービスを起動するのもしんどいかもしれません。一部のサービスだけ変更したり、影響されるサービスが少なかったりする時、Dockerでテストするのは楽になると思います。サービスの数が多くなれば、リモートでサーバーの立ち上げて、リモートのサーバーでテストするほうがいいかもしれません。

いかがでしょうか。複数サービスがかかわるプロジェクトでDockerを使ってみませんか。

Rails + grape + Swagger UIでアプリケーション開発を円滑に進めるAPIサーバーを構築する

こんにちは、サーバーサイドエンジニアの本間です。

以前弊社のしだより、デジタル連絡帳アプリ るくみーnoteクライアントサイド開発の一部 をご紹介させていただきました。 今回は、このプロダクトのサーバーサイド開発で工夫した点をご紹介したいと思います。

この開発におけるサーバーサイドエンジニアの主なタスクは、デジタル連絡帳のデータを保管するためのAPIサーバーの構築です。 今回、アプリとサーバーは完全分業で並列に開発を進めることから アプリケーション開発者にいかに早くAPIの仕様や動作を理解してもらえるか が開発を円滑に進める上での鍵になりそうだなと考えていました。

この問題に対応するため、以下の2つの対策を可能な限り工数をかけずに実施したいと思い、調査しました。

  • わかりやすいドキュメンテーションの用意
  • 簡単にAPIを試すことができる環境の整備
続きを読む

AWS Organizations でアカウント管理してみる

インフラエンジニアのすずきです。2回目!

先日弊社社長がシリコンバレーにいき”Startup World Cup 2017”に参加し戦ってまいりました。 そして見事優勝することができました。 応援してくださった皆様ありがとうございました!!

その様子を記事にて

スタートアップの“W杯”で日本代表のユニファが優勝--世界16地域が参加 - CNET Japan

新たな投資を受けたことで、さらなる事業拡大を図っていきたいところです。

その流れでと言うか、事業拡大というか新しいサービスを作る際に、 AWSを利用されてる方はサービスごとにアカウントを分けたりされるのではないかな?と思います。 でもアカウント発行ってなかなか面倒で、

  • 会社の情報入力したり
  • クレジットカードの情報を入力してもらうために会計責任の方にお願いしたり
  • クレジットカード情報入力したのに一括請求の設定の為に親アカウントに連携したり
  • etc…

手間がかかりますよね、完璧に覚えてる情報でもないですし…

そんななか2017年2月27日AWS Organizations が一般公開されました これで一括請求アカウントから、子アカウントを作成することができるようになるようです。

そしてちょうど良いタイミングで、新規アカウント発行する需要があったので、新規アカウント作成のついでにAWS Organizationsも設定してみました。 設定じゃなくてアカウント作るところだけ見たい方は、先頭を少し読んでから最後の方に飛んでもらったら良いかなと思います。

続きを読む

iOSエンジニアですがDroidKaigi 2017に行ってきました

f:id:unifa_tech:20170322011253j:plain

iOSエンジニアのしだです。
DroidKaigi 2017 が先日 3/9 ~ 3/10 にベルサール新宿グラントで行われました。
今回で3回目の DroidKaigi で参加人数約800人と年々盛り上がりを感じます。
私は3/9 は終日一般参加、3/10は午前中だけ参加して来たので少し振り返りたいと思います。

逆引き マテリアル デザイン

マテリアルデザイン

マテリアルデザインのガイドラインを読んだことがなかったので勉強になりました。

  • 日本語のマテリアルデザインガイドライン https://material.io/jp/guidelines/
  • Typographyのページに Jonathan Lee (material designの偉い人)が織田信長扱いされてますw
  • サポートライブラリではできないのでガイドラインに従ってほしいこと
    • FABは、主要なアクションで使う
    • リストの一番下のアイテムがが隠れないように padding 入れる
    • など

サポートライブラリのFABは、影・アニメーションがガイドライン通りの動作しますが、 上記のことは開発者が実装することで、サポートライブラリではどうしようもないので開発者がガイドラインに従う必要があります。
マテリアルデザインのガイドラインを読むときにはこういう視点で読むほうが良いかもしれません。

テーマとスタイル

表記 利用
@style/foobar 定義しているスタイルを参照する場合
?attr/foobar テーマに定義している属性を参照する場合

サポートライブラリのデザインを変更したい場合、themeに定義されていれば、自分のアプリの themes.xml に属性を記述すればすぐ変更できる。 テーマもしくは、スタイルに定義されているものを調べて、サポートライブラリのデザインを変更するのは容易な感じでした。

iOS の nibファイルや storyboard はレイアウトとスタイルが一緒になっているの対して、Androidはレイアウトとスタイルのファイルが別れているので利便性があってうらやましいです。

アニメーション

マテリアルデザインのガイドラインにこうゆうアニメーションがいいですよ。というのがあります。あとで参考にしようと思います。

解剖Kotlin ~バイトコードを読み解く~

Kotlin はもともと興味ありまして、この機会に試しに軽く触って見ました。

項目 Kotlin・デコンパイルコード
型安全
/// kotlin
fun nullable() {
  val notNull: String = ""
  notNull.length
  val nullable: String? = null
  nullable?.length
  val nullable2: String? = null
  nullable2!!.length
}
/// java
public final void nullable() {
  String notNull = "not_null";
  notNull.length();
  String nullable = (String)null;
  if(nullable != null) {
     nullable.length();
  }

  String nullable2 = (String)null;
  if(nullable2 == null) {
     Intrinsics.throwNpe();
  }

  nullable2.length();
}
関数型
/// kotlin
var onClick: (View) -> Unit = { }
/// java
@NotNull
private Function1 onClick;
拡張関数
/// kotlin

/// Extensions.kt
fun SharedPreferences.clear(
    context: Context, name: String) {
    context.getSharedPreferences(name, Context.MODE_PRIVATE)
      .edit().clear().apply()
}
/// java
public final class ExtensionsKt {
  public static final void clear(
    @NotNull SharedPreferences $receiver,
    @NotNull Context context,
    @NotNull String name) {
     Intrinsics.checkParameterIsNotNull(
       $receiver, "$receiver");
     Intrinsics.checkParameterIsNotNull(
       context, "context");
     Intrinsics.checkParameterIsNotNull(
       name, "name");
     context.getSharedPreferences(name, 0)
       .edit().clear().apply();
  }
}
プロパティ
/// kotlin
class Horse(name: String, weight: Float) {
    val name: String
    var weight: Float

    init {
        this.name = name
        this.weight = weight
    }
}
/// java
public final class Horse {
   @NotNull
   private final String name;
   private float weight;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final float getWeight() {
      return this.weight;
   }

   public final void setWeight(float var1) {
      this.weight = var1;
   }

   public Horse(@NotNull String name, float weight) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
      this.weight = weight;
   }
}

今まで Kotlin を使うのにためらいがありましたが、Javaと相互運用可能なのでこれは使っていきたいと思いました。
iOS開発で Objective-C から Swift に移したときに、型安全・Optional型のお陰でクラッシュ減少やバグが見つけやすくなったという印象もっていて、 Android も Kotlin で幸せになれるのではないかと感じてます。

オフラインファーストなアプリケーション開発

Realm Mobile Platform のデモ行っていて、アプリがオフラインでも動作するし、コンフリクトせずに同期されていて便利な印象を受けました。

Realm の衝突解決(デフォルト)

  • 削除は優先
  • 同じプロパティは編集あとがち
  • リストへの挿入は時間順
  • Operational Transform いい感じにしてくれる

CAP定理

  • 一貫性 (Consistency)
  • 可用性 (Availability)
  • 分断耐性 (Partition tolerance)
  • 2つまでしか同時に満たすことができない
type e.g.
一貫性 + 可用性 一般的なRDBなど
一貫性 + 分断耐性 Apache HBase, MongoDBなど
可用性 + 分断耐性 DNSなど Realm Mobile Platform

実務でRealmを利用したことはありませんでしたが、 Realm の思想的な話が聞けたので、Androidの実装問わず、iOS開発でRealmを利用する際にも参考にしたいと思います。

Android ORMの選び方

Activerecord 3.0

  • Query
List<Todo> list = new Select().from(Todo.class)
  .where("id = ?", 1)
  .execute()
  • Pub-Sub: ContentProvider経由で利用可能
  • Migration: あるけどひどい
  • メンテナンスされていない

greenDAO 3.2.0

  • Query
List<Todo> list = todoDao.queryBuilder()
  .where(todoDao.Properties.Id.eq(1))
  .build().list()
  • Pub-Sub: なし
  • Migration: なし
  • High Performance

Requey

  • Query
Result<Todo> list = data.select(Todo.class)
  .where(Todo.ID.eq(1))
  .get()
  • Pub-Sub: RxJava1,2 で利用可能
  • Migration: SQLバージョンのみ
  • Annotation Processing
  • AbstractクラスかInterfaceを定義する

Realm 3.0.0

  • Query
RealmResults<Todo> list = realm.where(Todo.class)
  .equalTo("id", 1)
  .findAll()
  • Pub-Sub: サポート
  • Migration: バージョンで必要に応じてコードを書く
  • RealmObjectを継承する
  • アクセサの実装が必要

ORMA 4.2.1

  • Query
RealmResults<Todo> list = realm.where(Todo.class)
  .equalTo("id", 1)
  .findAll()
  • Pub-Sub: Experience
  • Migration: サポート schema-diff migration
  • Annotation Processing
  • ベースクラスなし
  • index が設定されてなければ検索helperメソッドが生成されない

これらのORMをAndroid開発で利用したことがありませんが、ORMの役割から比較までされていて興味深かったです。

まとめ

DroidKaigi 2017 で聴講したセッションをいくつか上げました。
Data Binding、Kotlin あたりは機会があったら実務で使っていきたいと思ってます。

ffmpegでクロマキー合成!

こんにちは。 エンジニアの田渕です。

3月に入り、だんだんと日が長くなってきたなぁと感じます。 気づけば今年も既に3ヶ月が経とうとしています……。 月日の流れに負けぬように精進せねばと思います。

さて、今回のタイトルは「ffmpegでクロマキー合成!」です。 タイトル通り、ffmpegを使って、動画のクロマキー合成をしてみよう、という内容です。
「クロマキー合成ってなに?」から、実際にffmpegを使ってクロマキー合成してみるコマンドの紹介までをやって行きたいと思います!

クロマキー合成ってなに?

クロマキー合成、名前だけは聞いたことがある方もいらっしゃるのではないでしょうか。
クロマキー合成とは、

特定の色の成分から映像の一部を透明にし、そこに別の映像を合成する技術 (wikipediaより)

のことです。

日常的に一番見かけるのは、テレビの天気予報での利用。 あとは、映画やドラマなどでの危険なシーンでの利用。 グリーンバックやブルーバックと呼ばれる、単一色の背景の前で撮影し、 それを他の映像と合成したりして利用します。

背景はグリーンやブルーじゃなきゃいけないのか?

動画の合成というと、緑色の背景の前で撮影しているイメージが強いかと思いますが、必ず緑じゃないといけないのでしょうか?
前述の通り、そもそもは「特定の色の成分から映像の一部を透明」にする技術なので、実は背景は緑や青でなくても良いのです。 ではなぜ、緑や青が使われているかと言うと、人の肌の色と補色関係にあるからです。 簡単に言えば、合成したい素材と似たような色の背景の前で撮影してしまうと、思ったように合成出来ない、という話。

人を素材として合成することが多い為、肌の色に合わせてグリーンやブルーの背景を利用することが多いのです。

理論はさておき

そんなわけで今回は、緑背景ではなく、白背景の動画と、普通の動画を合成してみたいと思います。 素材は、下記からお借りしました。

素材|After Effects Style|初心者からはじめるAfter Effectsの使い方

利用したのは、こちらの二点。

  • 元動画(空の動画)
  • 上に乗っける動画(ノイズ動画)

解像度や、秒数を合わせました。 ノイズ動画の「白い色」を透明にして、空の動画の上に乗せる形になります。

ffmpegとは?

その前に、今回利用するffmpegについてもちょっとご紹介。 ffmpegは、動画や音声を扱う為のフリーソフトの一つです。 コマンドラインから使用することが出来ます。

FFmpeg

このffmpegには、クロマキー合成をする為のオプションがあり、今回はそれを利用しました。 詳細な説明は、下記公式ドキュメントを参照ください。

FFmpeg Filters Documentation

ffmpeg Documentation

合成してみる!

ということで、ffmpegのコマンドを利用して、実際に合成してみます。

合成のコマンドはこんな感じ。説明の為、ファイル名を日本語にしておりますが、実際やるときは英語名を使っています。

ffmpeg -i 雲動画.mp4 -i ノイズ.mp4 -filter_complex “[1:v]colorkey=0xFFFFFF:0.3:0.2[ckout];[0:v][ckout]overlay[out]” -map “[out]” output.mp4

緑と青の部分が、それぞれ先に紹介した合成前の動画、赤い文字は、「白」を表すカラーコードです。 「緑文字と青文字の動画使って、青文字の動画の白を透明にして合成してね」というコマンドです。

結果はこうなります。

雲の流れる動画の上に、ノイズが重なってるのがわかるでしょうか。 これを使えば、プログラムを利用して動画の加工などが出来そうです。

今回は画像のみの動画の合成のご紹介でしたが、ffmpegでは音声つき動画同士のクロマキー合成も行えます。 (もっとコマンドが、複雑で長くなりますが……。) ffmpegでクロマキー合成している日本語のドキュメントは殆ど見かけない為、調査が少々大変ではありますが、出来上がると中々楽しいです。
ご興味のある方は試してみてください。

それでは!

ディープラーニングで写真チェックしてみる

こんにちは、田中です。

今日はディープラーニングを使ったピンぼけ写真検出について書いてみます。

るくみー撮影アプリの課題

f:id:sanshonoki:20170309113537j:plain

るくみー写真アップ用 on the App Store

弊社では保育士の方にiPodアプリを提供し、アプリを使って園内の日常写真を撮ってもらっています。 自動的にサーバーにアップロードされ、その後も簡単に保護者へ公開・販売できるので写真の管理がとても楽になります。

簡単、手軽、気軽に日常写真を撮れるのはよいのですがピンぼけ写真など公開に適さない写真もたくさん含まれてしまいます..。( ;∀;)

現状はこれらを1枚1枚チェックして公開/非公開を選別する必要があり、相応の負担となっています。><

これを流行りのディープラーニングを使って自動化しようというのが今回の試みです。

今回はいくつかある公開NG写真の種別のうちピンボケ写真だけを対象としています。

ピンぼけ写真とそうでない写真

着目したのは画像の周波数成分です。

正常写真 ピンぼけ写真
f:id:sanshonoki:20170309111535j:plain:w200 f:id:sanshonoki:20170309111547j:plain:w200
高周波成分を含む
(エッジ部分が鮮明)
高周波成分を含まない
(エッジ部分がのっぺりしている)

これにFFT (Fast Fourier Transform) をかけて周波数を画像として可視化してみます。

正常写真(FFT) ピンぼけ写真(FFT)
f:id:sanshonoki:20170309111606p:plain:w200 f:id:sanshonoki:20170309111622p:plain:w200

おお、明らかに違います。分類できそうな気がしてきます。(*´Д`)

FFTのコードは以下の記事を参考にしました。

yaritakunai.hatenablog.com

正直なところ、ディープラーニングを使わずともSVMなどの検出器で分類できそうな気はします。。

が、ディープラーニングの勉強も兼ねているのでディープラーニングを使います。

学習データセット

学習につかった写真の枚数は以下の通りです。

正常写真 ピンぼけ写真
ラベル 0 1
枚数 9000 9000

正常写真は実際にるくみー撮影アプリからアップロードされた写真を適当にピックアップして使い、ピンぼけ写真は正常写真にガウシアンフィルタをかけて人工的に作りました。

学習モデル

モデルはMNISTの分類で使ったものを使い回しているだけで適当です。。 (;^_^A

f:id:sanshonoki:20170309142417p:plain

ディープラーニングのライブラリは chainer を使いました。 国産のライブラリでコードも直観的に書けるので気に入っています。

学習は 20 epochs 回し、GPU使って 約90秒、CPUだと 約10分でした。 CPUでも十分学習できます。

評価

正常写真 と ピンぼけ写真 をそれぞれ 50 枚ずつピックアップして評価しました。 ピンぼけ写真は NG写真と判定されている写真の中からぼけを含む写真を適当に選んでいます。

ピンぼけ写真 正常写真
「ぼけ写真」と判定した数 28 0
「正常写真」と判定した数 22 50

モデルも学習データセットの作りも適当なこともあり、ピンぼけ写真の分類性能はそこそこです。 ただ、特筆すべきは正常写真をピンぼけ写真として誤分類することがなかったということでしょう。 これはつまり実用性が高いことを意味しています。

ちなみに、ピンぼけ写真を正常写真と分類してしまったのは被写体の一部だけがぼけていた写真などが多かったです。 全体的にぼけた写真を学習させたのでこれは仕方ないですね。。

API & ボット

せっかくなので Heroku 上にAPIとして実装して社内のslackチャンネルからボット経由で使えるようにしてみました。 :-)

f:id:sanshonoki:20170309143806j:plain

さいごに

私は参加してないのですがつい先日2/21, 22に「CNET Japan Live 2017 ビジネスに必須となるA.Iの可能性 」というイベントが開催されたようです。 そのWebページ上で 「導入前に知っておきたいAI活用のコツ~賢くコスト削減するには~」というタイトルで講演を行ったLIPの松村さんの発表スライドを見つけました。

https://japan.cnet.com/storage/2017/03/06/643b6711c05f9cb45f7fd46c3201d504/l03.JPG https://japan.cnet.com/storage/2017/03/06/643b6711c05f9cb45f7fd46c3201d504/l03.JPG (写真引用元)

写真チェックとAIの相性はかなり良いのではと思います。( ̄ー ̄)

ピンぼけ写真以外のNG写真の検出にもチャレンジしていきたいと思います。