ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

ffmpegで写真や動画を加工してみよう!

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

東京では、夜になると秋の虫の声が聞こえるようになりました。ついこの間まで、寝苦しい夜に悩んでいたのに、月日の経つのは早いものです。

日本は秋から冬にかけ比較的行事が多く、その分、写真や動画を撮影する機会も増えます。 最近ではスマートフォンの無料アプリで驚くくらいに加工ができるので、利用されている方も多いのではないでしょうか。 今回は、そんな画像や動画の加工を、コマンドでやってみよう、という記事です。

以前、こちらのブログでffmpegを利用してクロマキー合成する、という記事を書かせてもらいましたが、今回も同じくffmpegを利用して、画像の加工をしていきます。 ffmpegって何?という部分については、前回記事を参照ください。 tech.unifa-e.com

加工してみよう!

では、さっそく加工して行きましょう。

元の画像

こちらの写真は、春先に道端で撮影したものです。夜、そしてスマートフォンのカメラ、という悪条件。 特に何か加工をしたりした訳ではなく、なんだか偶然、全般的に桜の色になっております。 ただ、……ちょっと、くらいかな。。。 f:id:unifa_tech:20170904233442j:plain

コマンド紹介

ffmpegには画像加工用のフィルターという概念があるのですが、そのフィルターのオプションを利用することで、画像や動画の加工が出来ます。 今回利用するのは、eqというオプション。

公式ドキュメントは下記になります。

FFmpeg Filters Documentation

なんだか、色々とたくさんありますが、今日はこの中の、brightness、saturation、contrastを使ってみたいと思います。

コマンドは至って簡単、下記のような具合に指定して使います。

ffmpeg -i source.jpg -vf eq=brightness=0.06 -c:a copy output.jpg

赤文字の部分が、今回の主役であるeqオプションの指定部分です。サンプルは、brightnessを指定した例。 他のオプションも、同じような感じで指定します。 組み合わせて使うことも出来ますが、今回はそれぞれの効果を見るために、一つだけ指定したものを順に見ていきます。

brightness

日本語で言う「輝度」です。サンプル画像は、上で紹介したコマンドをそのまま実行した結果です。 元の画像と比較して、全体的に明るくなっているのがわかるでしょうか? f:id:unifa_tech:20170904233603j:plain

saturation

こちらは「彩度」を変更した画像です。saturation=2という値で実行しています。かなり鮮やかになりましたね。 f:id:unifa_tech:20170904235821j:plain

contrast

最後が「コントラスト」。contrast=2で実行した場合です。元の画像と比べると、くっきり物が見えるようになったような? f:id:unifa_tech:20170904233650j:plain

どうしてffmpegか?

ここまで見てきてもらったように、コマンドでオプションを指定することで、画像の加工ができることがわかりました。実はコマンドラインで画像加工出来るソフトは他にももっと色々とあるのですが、(もっと良く使われているImage Magickとか。)なぜ今回ffmpegを使ったのか?といえば、ffmpegは↑に書かれているコマンドと、全く同じコマンドで、動画の加工が出来るからです。 入力と出力のファイル名を、それぞれ動画のファイルにするだけで、暗かった動画を明るく出来たりします。

まとめ

撮影チャンスは突然にやってくるもので、必ずしも撮影に最適な状況にはなっていません。それでも、一か八か!でシャッターを切るわけですが、結果、「真っ暗で何も見えない」とか「ぼんやりしていて……。」なんてことも、ままあります。

そんな時、園の先生方がパソコンのソフトなどを使って苦労して画像を修正するのではなく、サーバー側でなんとか出来ないかなぁ……なんてことを夢見ています。 もちろん、実現には、色々とクリアすべき課題があるのですが……。

ffmpegのフィルターオプションは膨大で、なかなか面白いものも眠っているので、また折を見てご紹介できればと思います。

それでは!

顔認識でハッピー分析

こんにちは、2週連続の田中です。(でも、先週とは別の田中です。。)

前々から気になってたことを少しだけではありますが解析したので今日はそれについて共有します。

気になっていたこと

それは、、

「保護者のみなさまが購入した写真はハッピーな表情が多いのか?」

きっと、そうだろう とは思いつつも調べてなかったので今回調べてみました。

分析ステップ

  1. 保護者の方が購入した写真を抽出する
  2. 顔認識を行い、顔を検出するとともに表情の認識も行う
  3. どんな表情が検出されているか集計、可視化する

ステップ3に関して、本来は写真とその認識結果を対で示しながらというのが分かりやすくベストなのですがプライバシーの問題のため園の実際の写真を出せないのが残念です。

数字とテキストでまとめただけの結果になりますがご容赦ください。。

顔認識

顔認識は Amazon Rekognition を使いました。 Rekognition は 元々、アメリカのベンチャー企業 Orbeus社 が開発したサービスで実は弊社の子どもの顔認識機能のリリース当初、この技術を使っていました。

突然、サービス停止のアナウンスがあり、慌てて代替の顔認識サービスを探して置き換えの開発を余儀なくされた思い出深いサービスです。その時は、

ええ、マジかよ?! ╰(゚x゚​)╯

と戸惑うばかりでしたがそういう事情(Amazonによる買収)だったんですね。。

当時、顔認識をAPIで提供するサービスはいくつかありましたがRekognitionの性能が頭一つは抜けていたので今回改めて使ってみました。

Rekognitionでは顔の認識とともに表情も検出でき、検出できる表情の種類は

  • HAPPY
  • SAD
  • ANGRY
  • CONFUSED
  • DISGUSTED
  • SURPRISED
  • CALM
  • UNKNOWN

の8種類です。

http://docs.aws.amazon.com/rekognition/latest/dg/API_Emotion.html

表情にはそれぞれ0から100までのスコアがつきます。 検出されなかった表情についてはスコアはつきません。

AWSのアカウントを持っている方は Rekognitionのコンソール画面からデモでお試しできます。

Rekognitionの注意点として現時点では使えるリージョンが限定されていて東京リージョンでは使えません。そのため、今回はOregon(us-west-2)を使いました。対象写真を格納しておくS3バケットも同じリージョンになります。

参考までに使用したコードはこちらです。

require 'optparse'
require "aws-sdk"
require 'dotenv'

options = {}
OptionParser.new do |opt|
  opt.on('-b VALUE', '--bucket VALUE', 'bucket name') { |v| options[:bucket] = v }
  opt.on('-p VALUE', '--prefix VALUE', 'key prefix') { |v| options[:prefix] = v }
  opt.on('-o VALUE', '--output VALUE', 'output directory') { |v| options[:output] = v }
  opt.parse!(ARGV)
end

Dotenv.load

Aws.config = {
  access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
  secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),
  region: 'us-west-2'
}

bucket = options[:bucket] || 'your_default_bucket'
prefix = options[:prefix] || ''
output_directory = options[:output] || '.'

s3 = Aws::S3::Client.new(region: 'us-west-2')
keys = s3.list_objects(bucket: bucket, prefix: prefix).contents.map do |object|
  object.key
end
puts "#{keys.count} objects"

rekog = Aws::Rekognition::Client.new(region: "us-west-2")
keys.each do |key|
  puts key

  result = rekog.detect_faces({
    image: {
      s3_object: {
        bucket: bucket,
        name: key
      },
    },
    attributes: ["ALL"]
  })

  output_filename = output_directory + "/" + File.basename(key, '.jpg') + ".json"
  File.write(output_filename, result.to_h.to_json)
end

分析

とりあえず、3枚以上購入があった写真を1000枚抽出して顔認識を行い結果を見てみました。1000枚なのは私の体力的な都合によるものです。

結果

まず、検出した顔が1つの写真に絞り集計してみます。(購入者のお子様だと考えられ、結果の解釈がしやすいため)

表情 カウント スコア平均値
HAPPY 95 73.5
SURPRISED 19 52.5
CALM 3 40.4
CONFUSED 9 35.6
DISGUSTED 3 45.8
ANGRY 9 54.0
SAD 23 36.9

実際の写真も確認して、まとめると

  • 当然ながら HAPPY が多くそのスコアも高い
  • 真面目な表情(何かに真剣に取り組んでいたりetc)は SURPRISED, CALM, CONFUSED あたりでそこそこカウントされた
  • ANGRY, SAD が想定よりカウントが多い。。

ANGRY, SAD となった写真を確認してみると実際には怒ったり悲しい表情をしているというより 下向き顔が多く、機械学習をかけるとこの分類になりやすいようです。下向き顔 = うつむき顔 = 悲しい ということで分類としては妥当な気がしました。

園児がデスクワークしている姿を保育士が立った位置から撮影するとどうしても園児の顔向きとしてはうつむき加減になってしまいます。このような子どもが作業に没頭している写真も残しておきたい大事な1枚ということになります。

続いて、集計する顔の数を増やしてみましたが傾向は大きく変わりませんでした。

今後…

購入のなかった写真には HAPPYな顔は少ないのか? と言ったら恐らくそうとは言えず(自分の子ども以外は良い写真であっても基本的には買わないため)今回の分析には課題や突っ込みどころがたくさんあります。(頑張って数値解析したら有意な差が出るかも?)

ただ、自分の子どもが写っている写真がたくさんある中、購入する写真はそのごく一部であり購入された写真には親の感情により響く、言わばハッピーをより感じさせる何か画像特徴としてのエッセンスがあると考えています。

引き続き検討を行い、保護者が感じるハッピーのエッセンスをシステムとして抽出し、それにマッチする写真を優先的に届けられたらなと思います。ƪ(•◡•ƪ)"

サービス開発時のユーザヒアリング

 ユニファCTOの赤沼です。今回はユニファでサービスを開発するときに行なっているユーザーヒアリングについて、少しご紹介します。

 ユニファのサービスは B2B2C の形になっていて、ユニファのサービスを主に保育園に導入していただき、保育士の方にサービスを提供し、さらに保育園に通っている園児の保護者の方にサービスを提供しています。ですのでユニファの営業メンバーはサービスを導入していただくために日々保育園を訪問し、サポートのメンバーはすでにサービスを導入していただいた園からの問い合わせの対応や、各種ご案内を行なうなど、日々ユーザの方とコミュニケーションをとっています。開発のメンバーは日常的には直接ユーザとコミュニケーションをとることはありません。こういった形は多くの会社と同様かと思います。

 ですが、ユニファでは新しいサービスや機能を開発する際には、開発メンバーもユーザの方に積極的にヒアリングを行わせていただいています。主なユーザは保護者の方や保育士の方でして、保護者ユーザの方は主に社内メンバーの友人・知人でお子さんが保育園に通われている方にご協力いただいています。保育士や園長先生の方はユニファの既存サービスをすでに導入いただいている園の方や、ユニファのサービスに興味を持っていただいている園の方にご協力いただいています。

 また、園の訪問の仕方についても、代表の土岐や営業メンバー主導で、開発メンバーが帯同する形もありますが、開発メンバーが自ら日程調整して、資料等も自分で用意して園を訪問することもあります。ヒアリング内容としては、サービス検討段階であれば主に現在の園内業務がどのように行われているか、どんなところを課題と思われているかといったことを聞かせていただき、プロダクト開発段階であればプロトタイプ段階のサービスを使っていただいて感想を聞かせていただいたりしています。

 ユニファに限らずですが、自分を始め開発側の人間はあまり外部の方とのコミュニケーションには慣れておらず、どちらかというと不得意なメンバーも多いので、一度の訪問でも、特に自分主導で訪問する場合は普段の開発業務とは違った消耗の仕方をしますが、直接のヒアリングは開発メンバーにとっても必要なことだと思っています。

 メリットとしては、やはり直接ユーザの声を聞けるというのは、人伝手で聞くのと違ってユーザの温度感を直接感じることができ、要望等についても本当に必要としている感じや、逆にあったらいいな程度のものなのかというのも感じられます。それに実際の業務を見させてもらうことで、どのような現場でどのように業務が行われているのか、保育士の方がどんな方々なのかということもわかります。特に保育士の方の、保育にかける情熱については驚かされます。また、プロトタイプ等を使っていただいて良い反応をいただけると、作っている側としては単純に嬉しいもので、それ以降の開発についてのモチベーションにもなります。

 こういったことは、サービス要件を考えるメンバーはもちろんですが、他のメンバーにとっても開発中の細かい判断に効いてきます。保育業界向けの B2B2C サービスを提供するユニファとしては、一般的な 2C のサービスとも全く対象が異なるので、こういったメリットはさらに大きいと思っています。

 ところでこういったヒアリングでは、もちろん当日のヒアリング自体も大事なのですが、さらに大事なのは事前準備で、そのヒアリングや検証で何を確認したいかを具体化しておくことが必要です。事前準備なくいきなりヒアリングをしても、結果をその後のステップに活かそうとした時に、情報が足りていないということになります。そのヒアリングで何を確認できればサービスの検討を先に進めることができるのかを具体化して、そのために必要な資料等を準備した上で、当日に臨むということが重要です。

 そして得られた回答を検討する段階で重要なのは、取捨選択です。いただいた意見や要望は貴重なユーザの声で、全て対応したくなってしまうものですが、あくまでユーザの一部の声であり、それが大多数のユーザの声と一致するものばかりではありません。全てのユーザの要望を満たすことができればもちろんそれが良いですが、そのための個別対応をしていくと、システムの構成もサポートの要件も複雑化してしまうので、現実的ではありません。サービスの方向性等も踏まえ、取捨選択の基準を内部で決めておきたいところです。

 また、実装すると決めたものでも、初回リリースから必要なものなのか、次以降のリリースでも問題ないのか、ユーザはこう言っているが、実は本当に満たしたい要件はこういうことで、それならば違う解決方法が良いということはないのか、と言った視点でも考える必要があります。

 さらには開発プロセスにおける各ポジションのメンバーの関わり方としても、例えば開発メンバー主導でヒアリングを進めて要件を詰めて行った場合、営業やサポートメンバーの声をどのように取り入れ、どう巻き込んでいくか、プロジェクトのマネジメントはどのポジションのメンバーが行うのか、などの課題もあります。

 これらの内容についてはユニファでもまだこれだというスタンダードが決まっているわけではなく、日々試行錯誤をしつつ進めています。プロジェクトの進め方についてはサービスの対象の業界や会社規模、メンバー構成等によって様々で正解がない領域だと思いますが、より良いサービスを提供できるよう、日々改善を図っていきたいと思っています。

エラー「A copy of xxx has been removed from the module tree but is still active!」と「ObjectContainer」

こんにちは、システム開発部のちょうです。今回はあるエラーから1つ小さいなライブラリを作った話について共有したいと思います。

先月の開発でまれに「A copy of xxx has been removed from the module tree but is still active!」というエラーに遭遇しまして、一回あったらRailsアプリを再起動しないといけない状態になります。回数が少ないですが、開発の邪魔になるので、少し調べました。

ここまでわかったのは、

  1. 解決策1つめ、Fooを::Fooに変更
  2. 本番環境は出ない(自分の経験でも開発環境以外見たことはない)
  3. エラーメッセージによると、モジュールは削除されましたがまだ使われてる(正直ピンとこない)

ひとまず、解決策はわかったので、使ったら元々エラーになった箇所も確実に直りました。でもたまには別のところで同じエラーで怒られました。すべてのクラス名をフルネームで書き直すのは煩雑な作業になるから、やはり根本的な原因を探さないといけないと思いました。

エラーメッセージの一部をキーワードとしてプロジェクトとライブラリ内で検索してみたら、ActiveSupport::Dependenciesのソースコードでこのエラーメッセージが見つかりました。

module ActiveSupport
  module Dependencies
    def load_missing_constant(from_mod, const_name)
      # ...

      unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
        raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
      end

      # ...
    end
  end
end

breakpointを入れて開発環境で何回debugしてみましたが、残念ながら、一回も再現できませんでした…

続きを読む

Railsのapp直下でよく使われるディレクトリとその用途を調べてみる

こんにちは、Webエンジニアの本間です。 最近、Railsアプリケーションのディレクトリ構成に関して考える機会があったので、そのことについて書いてみようと思います。

Railsに限らず「ディレクトリ構成をどうするか」という点は、設計初期の悩ましい問題の1つだと思います。 Railsでは標準のディレクトリ構成が決まっているため悩みは少ないのですが、多くの場合、標準外のディレクトリもある程度は追加していくと思います。 特に「app」以下のディレクトリ構成は、アプリケーション全体の設計に関わる部分であり、各メンバーの考えもあるため、なかなか決まらないケースもあると思います。

この問題に対する1つの参考情報として、著名なOSSのRailsアプリケーションが採用しているapp直下のディレクトリを調べてみようと思いました。 著名なOSSで採用されているのであればそれなりの理由があるはずだと思いますし、世の中の標準にも近いはずなので、あとから参加したメンバーも違和感なく開発できると考えたためです。

続きを読む

AWS Lambdaのログを手元へ

久しぶりにブログ書きますUniFaのインフラ見てますすずきです。

あっ、見てますすずきです。って読みづらいですね!!

どうでもいいことは置いておいてさっそく本題へ AWS Lambda ですがサーバレスやマイクロサービスなどで人気ですが。
そのLambdaのログは勝手に保存されていきますよね。(CloudWatch Logsにロググループ作成済みでLambdaに付与したロールに権限があれば…)

CloudWatch Logsのコンソールであれば検索とかできますが、ローカルでgrepとかして検索したいとか言う場合に、 どうやってローカルに持ってくるかの方法を今日は書いてみようと思います。

続きを読む