Pythonでスマホで撮った写真の向きを補正する

リアルタイムスライドショーアプリを作って、披露宴で使った話の続きです。

この自作アプリでは、スマホで撮影してもらった画像をスライドショーに投影しています。しかし、無補正では写真の天地が揃っていませんでした。

そのため、この記事を参考に向きを補正しました。

なぜ向きがずれるのか?

カメラで撮影された写真は、撮影時の様々な情報をExifという規格にメタデータとして保存しています。このExifの中に撮影時の向きの情報が含まれています。

最近のビューワーだとこのExif情報を参照し、正しい向き情報を取得して画像表示を行うようですが、ブラウザ上で<img>で表示しようとするとメタデータ情報を参照してくれないようです。

参考

chromeなら、<iframe>を使えば補正してくれるようですが、全てのブラウザに適用させるにはやはり画像自体を補正させる必要がありそう。

ということで、pythonで補正を行いました。

Pythonによる向き補正

以下の情報はすべて参考先に乗っていますので、ここではピンポイントで情報記載します。

まずは画像操作用モジュールをインストール。

Pillowモジュールを利用します。

pip install Pillow

画像の向き情報はExif情報内のOrientation要素に記載されています。この値を変更することで向きを補正します。

以下が対応表です。 Orientationに以下の補正番号を与えて画像を補正させ、正しい向きに直します。

画像 Orientation 補正内容 補正番号
f:id:yason0319:20200726105938p:plain 1 - -
f:id:yason0319:20200726110139p:plain 2 左右反転 0
f:id:yason0319:20200726110153p:plain 3 180°回転 3
f:id:yason0319:20200726110200p:plain 4 上下反転 1
f:id:yason0319:20200726110209p:plain 5 上下反転+270°回転 5
f:id:yason0319:20200726110215p:plain 6 270°回転 4
f:id:yason0319:20200726113029p:plain 7 左右反転+90°回転 6
f:id:yason0319:20200726113039p:plain 8 90°回転 2

Pillowモジュールでは、Image.transpose(補正番号)でOrientationの更新ができます。

# Orientation番号に合わせて、補正番号を設定する
trans_ = [0, 3, 1, 5, 4, 6, 2][orientation_ - 2]

if orientaion_ > 2 :
  Image.transpose(trans_)

このImageを上書き保存してあげれば、補正完了です。

Node.jsでOAuth認証を行いDropboxのAPIを使うまで

こんにちは、yasonです。

以前、自分の結婚披露宴でリアルタイムスライドショーアプリを自作し、使いました。

リアルタイムスライドショーアプリを作って、披露宴で使った話

写真のアップロードサーバーとしてDropboxを使い、アプリサーバーからAPI経由で写真を取得するという構成で実装しました。

今回はそのDropboxAPIを利用するまでをまとめます。

目次

  1. 登録
  2. 環境構築
  3. OAuthの設定
  4. テスト

1. 登録

なにはともあれ、Dropboxにユーザー登録します。

登録ができたら、APIを利用するためのアプリを新規作成します。

developerページからアプリを作成します。

f:id:yason0319:20200526224700p:plain
Dropbox App Console

App ConsoleからCreate Appをクリック。

アプリ種別は以下の通りに設定しました。

f:id:yason0319:20200526225012p:plain
App Setting

その他、APIアクセスに必要になる情報を取得・設定します。

  • App key, App secret

App Consoleから以下の2値を記録しておきます。

  • Redirect URL

App ConsoleのRedirect URLsに認可サーバーからのリダイレクト先を登録しておきます。

このリダイレクト先ではトークンを発行し、セッションに保存した後、アプリの元のURLへ再度リダイレクトします。

f:id:yason0319:20200527214804p:plain
Setting

2. 環境構築

次に、APIを使う環境を整えていきます。

前回の記事に書いたとおり、APIを使う環境はNode.jsです。

パッケージが用意されているので、こいつをインストールしてしまえば完了です。

npm install --save dropbox

OAuth

OAuthは、ざっくり言うとAPIを使うために必要なアクセストークンを発行するためのしくみです。

実はアクセストークンはApp Consoleの「OAuth 2」から発行が可能です。

f:id:yason0319:20200527212617p:plain
OAuth 2

ただ、今回は勉強の意味も込めてOAuthによるアクセストークン発行を試してみました。

シーケンスは以下の通りです。

f:id:yason0319:20200528212537p:plain
OAuth flow

詳しく書いていきます。

ブラウザからの初回スタートページアクセス時、サーバーは、Dropboxの認証用URLを生成し、リダイレクト要求を送ります。 リダイレクト要求によって認証用URLに遷移し、トークン発行を許可すると、再度リダイレクト要求が送られます。 このリダイレクト要求先は、リダイレクト要求時にセットしたURLです(このURLはRedirect URLsに登録されている必要があります)。

リダイレクト要求先でアクセストークンを生成し、成功したらそれを保存します。処理が完了したら再びスタートのURLをリダイレクトし、アプリが開始されます。

実装は以下のようになりました。

const dropbox = require('dropbox').Dropbox,
const fetch = require('isomorphic-fetch');

const config = {
  fetch: fetch,
  clientId: YOUR_APP_KEY,
  clientSecret: YOUR_APP_SECRET
};
 
var dbx = new Dropbox(config);

module.exports = dbx

まずはDropboxAPIのインターフェースを作ります。ここでは、記録したApp keyおよびApp secretをセットしてオブジェクトを生成しています。

function home(req, res, next) {
  //get authentication URL and redirect
  var authUrl = dbx.getAuthenticationUrl(OAUTH_REDIRECT_URL, state, 'code')
  res.redirect(authUrl)

getAuthenticationUrlというAPIをコールします。第1引数にセットしたリダイレクトURLがApp Consoleに設定されているものと一致すると、 認可サーバーのURLが取得されるので、これに対してリダイレクトします。

続いてはブラウザ上で認証を行います。

「続行」を選択し、

f:id:yason0319:20200527222406p:plain
OAuth flow 1

「許可」をクリックすると、アクセストークン取得許可がおります。

f:id:yason0319:20200527222436p:plain
OAuth flow 2

async function auth(req, res, next) {
  let token = await dbx.getAccessTokenFromCode(OAUTH_REDIRECT_URL, req.query.code);
 
  //store token and invalidate state
  req.session.token = token
  dbxtoken.setAccessToken(token)
  mycache.del(state)
 
  res.redirect('/')
}

認可サーバーで許可したあとにリダイレクトされて走る処理です。getAccessTokenFromCodeをコールし、ようやくアクセストークンが取得できました。 あとはこのトークンを保存し、開始URLへリダイレクトします。

無事アプリを開始することができました。

テスト

認証が完了したら、公式ドキュメントを見て、APIを好きに使いまくるだけです!

例えば、フォルダ内のファイル一覧を取得するにはfilesListFolderを使います

dbx.setAccessToken(token);
dbx.filesListFolder({path: process.env.FILE_REQUEST_PATH})
  .then(function(response) {
    response.entries.forEach(entry => {
      lists.addList(entry.name)
    })
    dbx.setAccessToken(null)
  }).catch(function(error) {
    dbx.setAccessToken(null)
  })

他にもファイルをダウンロードするfilesDownloadをアプリ内で使用しています。

以上、DropboxAPIを使うまでの設定でした。

これでDropboxから出席者が投稿した写真をダウンロードする制御を作り込むことができました。

リアルタイムスライドショーアプリを作って、披露宴で使った話

こんにちはyasonです。  

コロナ騒動が本格化する前の話ですが、結婚披露宴をしました。
挙式は別日に親族のみで執り行ったので、この披露宴は友人たちのみを招いたフランクな雰囲気を目指していました。
肩肘を張らない良い式ができたと思っています。

式の開始前や中座の際、出席者が暇にならないよう考え、『スマホで撮影した写真をリアルタイムでスライド投影するアプリ』を自作して本番で動かしました。

その内容について書いていきたいと思います。

目次

  1. 概説
  2. システム選定
  3. システム構成
  4. 動作確認
  5. 結果
  6. 振り返り

概説

ここではアプリを作った経緯を書きますので、システムに興味がある人は、次の章から読んでいただければ。

さて、最初にも書きましたが、披露宴をしました。

夫婦で相談し、『出席者が飽きない式にする』をコンセプトに、準備を進めました。

まずは自分たちの出席した結婚披露宴の経験をもとに、出席者が飽きるポイントを抽出しました。

  • 新郎新婦の中座
  • テーブルラウンド
  • 他のグループが新郎新婦と写真撮影+会話しているとき

イベントがなく、またテーブルにいる友人たちとも話すことがなくなってしまうとやることがなくなりますよね。

かと言って、この時間に全員が参加せざるを得ないイベントを入れるというのも、会場の雰囲気がとっちらかってしまうし(何よりネタがない…)どうしたものか。

調べてみると、いいのがありました。↓

フォトシュシュ

一言でまとめると、
参加者が撮った写真を、その場でリアルタイムにスライドに表示させるアプリ
です。

これはいいんじゃないかと料金を調べてみると……なかなかのお値段。

前述の通り挙式は別日に行っていましたので、すでにだいぶ出資しておりお財布も寂しく…

どうしたもんかと更に調べていくと、この記事に行き当たりました。

親友の結婚式二次会のためにAngularJSでリアルタイムスライドショーを開発した話

よし、自作しよう。

システム選定

写真アップロードサーバー

まず考えなければいけないのが、参加者に写真をアップロードしてもらうサーバーです。

これについては、先程のリンク先の方が採用していたDropboxファイルリクエスト機能を利用しました。

ファイルリクエスト機能とは、Dropboxのユーザーではない人からでもファイルを受け付けることができる機能です。

これで複数人からの写真アップロードに対して安定したサーバーが用意できました。

アプリサーバー

後述しますが、アプリのサーバーはざっと以下のような処理を行ってもらいます。

  • アップロードサーバーから写真を取得する。
  • 写真情報を生成する。
  • 写真をスライドショー投影用に変換する。

これには学習コストを考え、Node.jsでサーバーをたてることにしました。

スライドショー部分

日頃業務でガリガリVue.jsを書いているので、これはVue.jsで実装します。

以上でシステム選定が完了しました。

役割 採用システム
アップロードサーバー Dropbox(ファイルリクエスト)
アプリサーバー Node.js
フロントエンド Vue.js

システム構成

アプリの構成は以下の通りです。

f:id:yason0319:20200524142508p:plain
システム構成図

席次表にファイルリクエストURLのQRコードを印字しておき、参加者にはそこから写真をDropboxへアップロードしてもらいます。

アプリサーバーは定期的にDropboxAPIをコールし、新しい写真がアップロードされたらダウンロードします。

しかし、ダウンロードした写真は、そのままではスライドショーに映せません。

なぜかというと、

  1. PC環境では画像の向き情報を格納しているメタデータ(Exif)を認識しない
  2. スマホで撮影した写真は大きすぎて、そのままスライドショーに映すとアニメーションがカクつく

というような問題があるためです。

そのため、写真ダウンロード後、アプリサーバーでExif情報に基づいて向きを修正し、カクつかない程度に圧縮するという2つの処理をpythonで行っています。

処理済みの写真は静的ファイルとして提供しています。

最後にフロントエンドにて、アプリサーバーから処理済みの写真を取得して画面に表示しています。

ソースコードこちら

動作確認

打ち合わせで会場に行く機会が何度かあったので、そこでスライドショーアプリの動作確認を行うことに。

自前のMacBook Airでサーバー起動させ、それを会場のスライドに投影しようと目論んでいましたが、問題が発生。

なんと会場のHDMIケーブルが長すぎるせいか、MacBook Airを検出できず!

しかし、windowsPCなら映せるよとプランナーさんからお聞きし、さらにPCも貸していただけるとのこと。

そこで、プランナーさんのwindowsPCからMacBook Airで起動したアプリサーバーにアクセスし、なんとかアプリをスライドに表示することができました。

プランナーさん、ありがとうございました。

結果

実際に使ってみた結果ですが…

半分成功、半分失敗という感じでした。

出席者が会場に入り新郎新婦入場を待っているタイミングから、運用を開始しました。

最初は調子よく表示できていたようです。

しかし、入場終わってからしばらくすると異変が。

新しくアップロードした写真は優先的に表示するようにしていたのですが、優先表示されなくなってしまいました。

また、スライドショーはアップロードされた直近10枚をランダムで表示するようにしていたのですが、それもあるタイミングの10枚を延々と表示し続けるという…

サーバー再起動してみたものの結果は変わらず。

20枚程度までは動作確認していたのですが、どうも50枚くらいアップロードされたあたりから不具合がでていたようです。

参加していただいた友人たちからは、入場前であらかたアプリを楽しんだから、不具合出ててもあまり気にしてなかったという話を聞きました。

悔しかったですが、まあ、結果オーライといったところでしょうか。

動作確認はしっかりしておきましょう。

振り返り

リアルタイムスライドショーアプリを自作して結婚披露宴で使ってみたところ、デバッグが足りなかったため不具合がでてしまったが、まあなんとかなった、という話でした。

自作することでコストは大幅に抑えられましたが、完璧を求めるなら既製品を使ったほうがよいですね笑

ただ、自作したことで、夫婦にとって思い出深い披露宴にはなったかなあと思います(妻の態度もこころなしか優しくなりました笑)。

細かい実装の話は別の記事でまとめていきたいと思います。

『レガシーコードからの脱却』の学びを本業へ活用したい

レガシーコードからの脱却を読みました。

学び・気付き

  1. コードは書いた瞬間に時代遅れになる。
  2. 常に自動ビルド自動テストを行うべき。
  3. アジャイル開発はベターだが、無理に実践する必要はない。

手に取った経緯

前年度まで担当していたモジュールは、誰の目からも明らかにリファクタリングを必要としていました。しかし圧倒的工数不足やデグレ地獄が容易に想像できるため、誰もやろうとはせず…

結局私はそのモジュールの担当ではなくなってしまったのですが、かなり後ろ髪を引かれていました。

そんな中この本の存在を知り、もしあれをリファクタリングするとしたら、どうやれば良かったのか…何かしらのヒントが得られるかもと思い、読みました。

結果的に、今担当しているモジュールでもすぐに実践できるようなプラクティスが得られたので、それをまとめます。

まずは個人的に重要だと思ったことをまとめます。

レガシーコードとは?

ズバリ、全てのコードです。 レガシーコードからの脱却とはつまり、常にコードを改善し続けよ、ということです。

単一責務の原則

これはいろんな本で取り上げられている考え方です。

一つのモジュールは一つの責務を背負うべきである。

この原則を大前提として、カプセル化疎結合などの考え方が発展していくわけです。

仕様書は最後に作る

この世には、全ての実装が最初に決めた仕様通りに行われたプロジェクトがあるんでしょうか?

少なくとも私は経験したことがないので、仕様書→実装より、実装→仕様書の方が自然であるように感じます。

『コードは嘘をつかない』

優秀な先輩が良く口にしていました。

仕様書は最後に作る。それ自体は非常に良い考えです。

では、仕様書がなければ何を参考に実装をするのでしょうか?

仕様書を最後に作る代わりにストーリーを定義し、ストーリー通りに実装をすべきと主張しています。

ストーリーは、

  • 何が(目的)

  • 何のために(理由)

  • 誰のためにあるのか

という定義で構成されます。

大事なのは、最初から全てを網羅するつもりでストーリーを作るのではなく、開発の中で見つけて拡張していくことです。

テスト駆動開発はダメ?

あまりいい評判を効かないテスト駆動開発。本当に実践に耐えられない開発手法なのでしょうか?

  • 筆者の主張

必ずしもそうではなく、 プロジェクトによりけりだろうとのことです(こういう結論が一番困りますよね)。

結局は実践して知見を蓄える必要がありそうです。

ローマは一日にして成らず、ということです。

  • テストは『ふるまい』を記述する

それでも、汎用的に活用できる知見も紹介されています。

コードベースでテストを書こうとするとコードの修正とテストの修正が延々とループする状態に陥りかねません。 テスト駆動開発では往々にしてこういう状態に至ってしまうと聞いたことがあります。

テストは、コードベースではなく『ふるまい』の単位で記述しましょう。

『ふるまい』単位でテストを書くためには、そのコードを利用する側の視点に立つことが必要です。

もし利用する側が外部ユーザーでなく、内部の他モジュールだったりする場合は、そこのチームにレビューしてもらったりするといいかもしれないです。

  • テストはいつ実施する?

常に実施するべきです。

バグを治すタイミングはいつがいいか。それは作り込んだ瞬間に決まってます。

せいぜい翌日であれば書いたコードを思い返す時間がほとんどいらないので、迅速にバグを治せます(…よね?)

ウォーターフォール(笑)

以上より、

  • 実装に取り掛かる前に仕様をかっちりと決め、
  • 各モジュールでUTを終わらせてから統合テストを行い、
  • 一度統合したコードはバグが発生しない限り変更しない(できない)

ウォーターフォールではレガシーコードからの脱却などできはしないよ、という衝撃の事実が。

(全てのプロジェクトがそうだというわけではありません。あしからず)

実戦投入

今は新しく発足するモジュールの実装部隊の取りまとめを担当しています。

この開発において、レガシーコードからの脱却を図るため、以下のようなことを検討しています。

  • テストコードは仮説と捉え、テスト駆動開発というより仮説検証という形で開発を行う。
  • 無理やりアジャイルを取り入れる必要はない。ウォーターフォールの中であがく。
  • コードを常に改善し続けるため、自動ビルド、自動UTを常に行う環境を構築する。

おそらく、もっと感度の高い企業などでは当たり前に行われていることだとは思います。

ただ、うちはうすのろで、開発もウォーターフォール律速なので、なかなか難しいところではあります。

その律速があるなかで自分のモジュールを守るため、色々やってみようかなと思っています。

実際に開発が始まり効果のほどが見えてきたらまた記事にしようと思います。