2017-05-29

Laravel と Google カレンダーを自動で同期

現在の開発で、とある日付付きのデータが追加/変更/削除されたら Google Calendar へデータを反映させるという機能を追加しました。
正直 Google Calendar API の部分でハマって時間がかかってしまったので、今後のためにもこの記事で Laravel側のプログラムも含めてまとめておこうと思います。


[開発環境]
Laravel: 5.4

[やりたいこと]
日付がついたデータがデータベースに{追加/変更/削除}されたら自動で Google Calendar のイベントを同じく{追加/変更/削除}する。
つまり、DB内のデータと Google Calendar のイベントデータの同期がしたいわけです。
そしてカレンダーをウェブページで表示する。


[手順]
では、大まかな手順です。

1.Google Calendar API を使って読み込み、書き込み、削除ができるよう設定する

2.Laravel に自動実行用のイベント を設定。

3.カレンダーを表示


1.Google Calendar API を使って読み込み、書き込み、削除ができるよう設定する

最初に必要となるのは API と、これから自動的に操作するカレンダーの設定です。
正直、昔のGoogle API それほど複雑ではなく API KEY を取得して URL に含めるだけでOKでしたが、久しぶりに見たらいろんなアプローチの仕方があって困惑してしまいました。
セキュリティ上が大きな原因でしょうから仕方ないといえば仕方ないですね。

ということで、 手順です。

(1) 以下のリンクから Google API でプロジェクトを作成し、サービスアカウントを取得します。

https://console.developers.google.com/permissions/serviceaccounts

やり方は動画を参照してください。
(設定が完了すると json ファイルが自動でダウンロードが開始になるので適当な場所に保存しておいてください。)
↓↓↓



そしてカレンダーAPI を有効に設定しましょう。
↓↓↓



最後にカレンダーの方にカレンダーの書き込み権限を付与します。
そのためにはサービスアカウントを取得した際にダウンロードした json の中に書かれているメールアドレスが必要になるのでテキストエディタなどでファイルを開いてコピーしておきましょう。

"client_email": "********@************************.iam.gserviceaccount.com",

ではカレンダー内での設定です。
↓↓↓



モザイクをかけているので少しわかりづらいかもしれませんが、先ほどダウンロードした json ファイルの中の "client_email" にかかれているメールアドレスを入力しています。
つまり、カレンダーの権限を API に付与しているわけですね。


さて、これで Google API 側の設定は完了です。
次からは Larave 側への設定になります。


2.Laravel に自動実行用のイベント を設定。

準備1: 実行用のイベントを作る前に Google API を操作できるパッケージをインストールしておきましょう。

composer require google/apiclient:^2.0

準備2: ダウンロードした json ファイルを /storage/json/google_api_secret_key.json として設置します。

準備3:カレンダーID を以下のように .env に追加します。

GOOGLE_CALENDAR_ID=****************@group.calendar.google.com

※カレンダー ID の取得は以下の動画を参照してください。



さぁ、ここからは実際にプログラムコードを書いていく作業になります。
(ここでもう作業の 50% は完了しています。)


では、最初に Google API のインスタンスを作成しましょう。
 $key_path = storage_path('app/json/google_api_secret_key.json');  
 putenv('GOOGLE_APPLICATION_CREDENTIALS='. $key_path);  
 $client = new \Google_Client();  
 $client->useApplicationDefaultCredentials();  

次にカレンダー API へアクセスできるよう以下のように設定をします。 
 $client->addScope(\Google_Service_Calendar::CALENDAR);  
 $service = new \Google_Service_Calendar($client);  
はい。これで API へアクセスする準備は整いました。
ここからはカレンダーイベントの追加、編集、削除について説明をしていきます。
※これは実際の実装ではなくイベント操作の基本編です。
実装については「Laravel の Modelイベント」まで読み飛ばしてください。

1.カレンダーイベントの追加

イベントの追加は insert() を使います。
 $calendarId = env('GOOGLE_CALENDAR_ID');  
 $event = new \Google_Service_Calendar_Event([  
   'summary' => 'テストイベントの投稿',  
   'location' => '東京都墨田区押上1丁目1−2',  
   'description' => '東京スカイツリー・バンジージャンプイベント!',  
   'start' => [  
     'dateTime' => '2017-05-16T09:00:00-07:00',  
     'timeZone' => 'Asia/Tokyo',  
   ],  
   'end' => [  
     'dateTime' => '2017-05-16T17:00:00-07:00',  
     'timeZone' => 'Asia/Tokyo',  
   ]  
 ]);  
 $new_event = $service->events->insert($calendarId, $event);  
$calendarId は .env に設定したカレンダーIDになります。
上の例では一日だけのイベントですが2日連続、3日連続などの設定も可能です。
その際は本家のリファレンスページを参照してください。

ちなみに $new_event->getId(); 
とするとイベントIDが取得できるので、このIDをDBなどに保存しておくことでこのイベントの変更、削除をすることができます。


2.カレンダーイベントの変更
 $calendarId = env('GOOGLE_CALENDAR_ID');  
 $event_id = '******************';  
 $event = $service->events->get($calendarId, $event_id);  
 $event->setSummary('変更されたイベント');  
 $service->events->update($calendarId, $event_id, $event);  
さきほどのイベントIDを使って該当するイベントの内容を変更できます。
summary だけでなくその他の情報も変更できます。リファレンスを参照してください。 

※ちなみに変更されたイベント名は以下のように取得します。
 echo $event->getDescription();  

3.カレンダーイベントの削除
 $calendarId = env('GOOGLE_CALENDAR_ID');  
 $event_id = '******************';  
 $service->events->delete($calendarId, $event_id);  
これもカレンダーID と イベントID を指定することで削除が可能です。

さぁ、ここまでがカレンダーイベントを操作する基本になります。
ここからは Laravel の Modelイベントでの実装(つまり実践編)です。

Laravel の Modelイベント

1.Modelの追加イベント

コマンドラインから起動するイベントファイルを作成しましょう。
以下のようにします。

php artisan make:event ItemCreated

これで app/Events フォルダに ItemCreated.php というファイルが作成されているかと思います。

では中身を開いて実際にイベントを追加してみましょう。 
 public function __construct(Item $item)  
 {  
   $calendarId = env('GOOGLE_CALENDAR_ID');  
   $event = new \Google_Service_Calendar_Event([  
     'summary' => $item->summary,  
     'location' => $item->location,  
     'description' => $item->description,  
     'start' => [  
       'dateTime' => $item->date_start,  
       'timeZone' => 'Asia/Tokyo',  
     ],  
     'end' => [  
       'dateTime' => $item->date_end,  
       'timeZone' => 'Asia/Tokyo',  
     ]  
   ]);  
   $service->events->insert($calendarId, $event);  
 }   
これでイベントのプログラム自体は完了ですが、このイベントを起動するプログラムが準備できていません。
しかし、さすがの Laravel です。起動は Model に trait と変数を指定するだけで完了します。
以下のように設定しましょう。
 class Order extends Model  
 {  
   use Notifiable;  
   /**  
    * The event map for the model.  
    *  
    * @var array  
    */  
   protected $events = [  
     'created' => ItemCreated::class  
   ];  
これで、Item テーブルにデータが追加されたら自動で ItemCreated が呼ばれ、その中でイベントがカレンダーに追加されることになります。
このようにイベントを設定しておくとどこでデータを操作をしても忘れずにプログラムを実行してくれるのでとても便利ですね。
 
2.Modelの変更イベント

基本的には追加と代わりませんので簡単な説明だけです。

php artisan make:event ItemUpdated

 public function __construct(Item $item)  
 {  
   $calendarId = env('GOOGLE_CALENDAR_ID');  
   $event = $service->events->get($calendarId, $item->event_id);  
   $event->setSummary('変更されたイベント');  
   $service->events->update($calendarId, $item->event_id, $event);  
 }   
   /**  
    * The event map for the model.  
    *  
    * @var array  
    */  
   protected $events = [  
     'updated' => ItemUpdated::class  
   ];  

3.Modelの変更イベント
 
削除も同じです。
 
php artisan make:event ItemDeleted
 
 public function __construct(Item $item)  
 {  
   $calendarId = env('GOOGLE_CALENDAR_ID');  
   $service->events->delete($calendarId, $item->event_id);   
 }   
   /**  
    * The event map for the model.  
    *  
    * @var array  
    */  
   protected $events = [  
     'deleted' => ItemDeleted::class  
   ];  


はい。
これでModelイベントを利用してカレンダー・イベントを操作してみました。
 
ちなみにもしイベントを好きなタイミングで起動したい場合は以下のようにします。

 event(new ItemCreated($item));  
 event(new ItemUpdated($item));  
 event(new ItemDeleted($item));  



この辺も Lravel のいたれりつくせり感があふれています。
 


3.カレンダーを表示

 
さぁ、せっかくここまでイベントを設定してきたので、自動操作されるカレンダーを実際にウェブページに表示してみましょう。
とはいっても、Google カレンダーでは HTMLタグを提供してくれているのでとても簡単に実装することができます。
 
やりかたは、カレンダーIDを取得したページの「このカレンダーを埋め込む」の項目にある HTMLタグをコピー&ペーストするだけです。

DBにデータを追加/編集/削除するとリアルタイムで(少し時間がかかる場合もあります)変更されると思います。
 
 
以上、今回は Google API を使ってデータを同期する方法でした。
実現するには少し時間がかかるかもしれませんがとても便利な機能なのでぜひチャレンジしてみてください。
 
ではでは! 

2017-05-22

Vue + jQuery の弱点に初めて遭遇!

このところ Vue.js を使って開発効率が格段に上がってきているのを感じています。しかも以前の Vue.js 2.0 を使ってみた9つの感想 でも取り上げましたが、jQuery との衝突が全く無かったので機嫌よく Vue での開発を進めていたのですが、今回初めて衝突というか弱点に遭遇したのでまとめと対処法をお届けしたいと思います。



まずは遭遇した状況から。
jQuery(Bootstrap) を使ったページ内で JavaScript を使って input 内の値を変更しようとしました。(※詳しく言うと、後でも言及する datepicker を使ってテキスト内に日付を入力しようとしてました。また、冗長になるので、 this.input = 'xxxxxx'; を使うのは無しの方向です。)

私の頭の中では以下のように通常通り $('#id').val('xxxxx'); を使ってテキストの中身を変更すれば Vue の方でもデータが更新されるものだと思っていました。
しかし、以下のデモのようにテキスト内は変更できるものの、Vue の方では変更は全くないという状況でした。

Vue + jQuery の change イベント実験 - 1



、、、なぜだろう。
jQuery の val() でデータを変更しても change イベントは呼ばれないというのは知っていました(参考ページ)が、今回使っているのは vue の v-model なので問題はないはずなのに・・・?


そこで少し stackoverflow を探ってみると、vue には $forceUpdate() という強制的にデータを更新する方法があるよ、ということなので以下のページのように実際に試してみました。

Vue + jQuery の change イベント実験 - 2


でも、これもうまくいかない、、、、
クリックを続けているとたまに自動入力されるテキストがちらつくことがあるので、おそらく変更はされたが、$forceUpdate() によって瞬間的に元にもどされてしまっているという状況なのだろう(つまり Vue には伝わっていない)と思いました。


またしてもスタート地点に戻ってしまったので、もう一度いろいろとネット上の情報を探ってみると本家 GitHub の Issue で手がかり見つけました。

Triggering vuejs on programatical change of element.


どうやら、jQuery の changeイベントはネイティブの JavaScript のイベントとは別のものなので、もし Vue の変更をやりたいならネイティブイベントを作って dispatch しないといけないということでした。

そこで、早速以下のようにイベント送出をしてみました。

Vue + jQuery の change イベント実験 - 3


おっ、いけました!
この状態だと直接テキスト入力してもボタンをクリックしても Vue のデータが書き換わるのでリアルタイムに上のテキストも変更されるようになっています。

へぇ、Vue にはネイティブ JS のイベントが必要だったんですね。


では、本題の datepicker を使うにはどうすればいいのでしょうか。
今回は jQuery の changeイベントとのコラボでやってみました。

$('.datepicker').datepicker().on('change', function() {

    var event = document.createEvent('HTMLEvents');
    event.initEvent('input', true, true);
    $(this).get(0).dispatchEvent(event);

});

実際のテストは以下です。

Vue + jQuery の change イベント実験 - 4


やってみたら分かっていただけると思いますが、テキスト入力(数字しか入力できません)でもカレンダー選択のどちらでも Vue へデータが伝わっていると思います。

※ちなみに送出するイベントが「input」ではなく「change」の場合だと、これもうまくいきませんでした。

ということは、この方法を使えば「dispach-native-event」などのクラス名に changeイベントを作っておき、その中でネイティブ・イベントを送出することができるので、サイト全体での対処も比較的楽にできるかと思います。


んー、それにしてもこんな形で Vue + jQuery の問題点に遭遇するとは思いませんでした。
でもその他の部分では問題もなく、開発速度も上がり、保守もしやすいと思うので Vue の利用は続けていこうと思います。


【あとがき】

フロントエンド界の活動は近年ホントに活発でそれ自体はとてもいいことなのですが、正直なところをいうとたくさんありすぎてもう少し集約してほしい気はしています。
また、複雑さが年を追うごとにひどくなってきていて、「あれ、なんのためのフレームワークだったけ?開発効率、ほんとにこれで上がってる??」なんてことになってきているなぁ、と pythonプログラミングをしているとよく感じます。

この間どこかのブログで「3年後も react 使ってると思う? vue 使ってると思う? でも確実に jQuery は使ってるよね?」という趣旨の記事を読みました。

これには Vue が大好きな僕でも「確かにそれはあるかもー」と思いました。
そんなこんなもあるので、変化の速い(早すぎる)これからのプログラミングは学習コストの低さも重要なファクターの一つになってくるんじゃないかな、と今日出してきた扇風機にあたりながら考えてました。
(もちろん何を専門にするかで変わってくるでしょうけどね。)


それはともかく、今年の夏は涼しかったらいいなー(笑)




2017-05-09

Python でバーコードをスキャン(ソースコード・ダウンロード可)

今回は、たくさんの商品画像の中にあるバーコードの内容をプログラムでスキャンして取得&その写真の分類ができるようにしてみたいと思います。
利用するのは次の3つです。

1.Python(2.7.12)
2.OpenCV2
3.Zbar

※実際のコードは GitHub からダウンロードできます。


【準備】

まずバーコードをスキャンするには zbar というパッケージが必要になりますのでインストールをしましょう。Ubuntu だと以下のコマンドで zbar をインストールできます。
 sudo apt-get install libzbar-dev  
そして、python から zbar が使えるようにするため pip でインストールです。
 pip install zbar  
はい。
これでインストールは完了です。(OpenCVはメジャーなのでインストールは省きます。)

【基本編】

では、zbar を使って画像の中にあるバーコードをスキャンする簡単なコードを作っていきましょう。
 import cv2  
 import zbar  

 scanner = zbar.ImageScanner()  
 scanner.parse_config('enable')  
必要なパッケージをインポートして、zbar のスキャナーを作成します。

 im = cv2.imread('images/barcode1.jpg')  
 gray_im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)  
OpenCV でスキャンしたい画像を読み込んでグレースケールに変換します。

 rows,cols = im.shape[:2]  
 image = zbar.Image(cols, rows, 'Y800', gray_im.tostring())  
 scanner.scan(image)  
ここで zbar の image を作成します。
1行目で画像のサイズとさっき作った gray_im をセットして実際にスキャンを開始します。

 for symbol in image:  
   print 'Type: %s, Data: %s' % (symbol.type, symbol.data)  
スキャンした結果を表示します。
type には 「qrcode」や「isbn10」などのデータタイプ、そして data には読み取ることができた値が入っています。

以上が簡単な例でした。
ただ、この例ではバーコードが斜めになっている場合は、検出できないことがあります。
そこで、次の応用編では斜めになったバーコードにも対応できるようにしてみましょう。

【応用編】

 import cv2  
 import zbar  

 scanner = zbar.ImageScanner()
 scanner.parse_config('enable')

 im = cv2.imread('images/barcode2.jpg')  
 gray_im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)  
 rows,cols = im.shape[:2]  
ここまでは基本編のおさらいです。
(ちなみに barcode2.jpg ではわざとバーコードがななめになっています。)

では、ここから斜めになったバーコードに対応するため輪郭を取得していきましょう。
 ret,threshold_im = cv2.threshold(gray_im, 150, 255, cv2.THRESH_BINARY)  
 im,contours,hierarchy = cv2.findContours(threshold_im, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  
まず、しきい値を使って画像を白と黒だけに変換します。
そして、findContours() で輪郭を取得します。

 for contour in contours:  
   rect = cv2.minAreaRect(contour)  
   center_pt = (int(rect[0][0]), int(rect[0][1]))
   w = int(rect[1][0])  
   h = int(rect[1][1])  
   angle = int(rect[2])
次に、取得した contours を for ループで回して一つ一つの長方形を取得しましょう。
minAreaRect がその部分になります。

rect は 中心座標、横幅、高さ、傾きのデータを持っているのでこれらをわかりやすいようにひとつひとつ変数(center_pt, w, h, angle)に格納していきます。

 M = cv2.getRotationMatrix2D(center_pt, angle, 1)
 rotated_im = cv2.warpAffine(im.copy(), M, (cols,rows))  
長方形の傾き(角度)が分かったのでこれを使って画像を回転させましょう。
一行目は行列を作って2行目で回転させた画像を取得しています。

 zbar_image = zbar.Image(cols, rows, 'Y800', rotated_im.tostring())  
 scanner.scan(zbar_image)  
そして、さっきと同じく zbar の image をつくります。

 for symbol in zbar_image:  
     symbol_type = symbol.type  
     symbol_data = symbol.data  
       if(symbol_type not in scanned_data.keys()):  
           scanned_data[symbol_type] = []  
           if symbol_data not in scanned_data[symbol_type]:  
               scanned_data[symbol_type].append(symbol_data)  
for ループで回して、もし scanned_data に type 別の値が入っていなければデータを格納。(←つまり、重複防止ですね)

これで、 scanned_data の中にスキャンされたバーコードの情報が入っていることになります。
あとは MySQL や JSON にデータを格納するなどして分類結果を保存するなどすればいいでしょう。

今回は以上です。












2017-04-28

Vue.js で年齢をリアルタイム計算(lodashで遅延処理も)

久しぶりの更新です。
今人気が上がってきている Vue.js のウォッチャの勉強したので、まとめておこうと思います。

デモページ


では、まずやり方を以下の2点で説明します。

1.HTML 部分
2.JavsScript 部分


1.HTML 部分

基本的には <input>タグが2つあるだけです。

 <input v-model="birthdate">  
 <input v-model="age">  

特徴は v-model という Vue の命令が入っているだけです。
そして、この中で指定されている「birthdate」「age」という名前が JavaScript 部分で使う変数名になり、birthdate がウォッチャで監視されることになります。

2.JavaScript 部分

     var vm = new Vue({  
       el: '#demo',  
       data: {  
         birthdate: '',  
         age: 0
       },  
       watch: {  
         birthdate: function(val) {  
           this.age = this.calcAge(val);
         }  
       },  
       methods: {  
         calcAge: function(birthdate) {  
           var ageDifMs = Date.now() - birthdate.getTime();  
           var ageDate = new Date(ageDifMs);  
           return ageDate.getUTCFullYear() - 1970;  
         }  
       }  
     })  


とてもとてもシンプルにしたのでデモページのコードとは違っていますが、内容としては次のようになります。

1.data 内は、これから必要になる変数。ということで、さきほど HTML 部分で指定した「birthdate」「age」の2つを設置しましょう。v-model を使用しているので変更があったらリアルタイムでこの中身が更新されます。

2.そして、watch で変数に変化が起こっていないかを監視します。今回は誕生日が変更になったことを知りたいので「birthdate」の中で年齢を計算&変更します。

ちなみに

デモページでは lodash と呼ばれる便利機能をたくさん提供してくれる JavaScript パッケージを使って遅延処理を行っています。(この方が計算してるぞ!感がでるので(笑))

実際には以下のようにします。

 _.delay(function(birthdate) {  
   // ここが0.35秒後に呼ばれる  
 }, 350, val);  

そして、指定時間を待っている間は「enteringFlag」を切り替えることで、「計算中...」というテキストを(表示/非表示)しています。


終わりに

状況によって jQuery を使うほうがスマートな場合もありますが、やはり Vue はなにかと便利です。(^^)
 ではでは。


2017-03-23

機械学習で顔の向きを取得する【scikit-learn】

今回、以前投稿した「顔の入れ替えを試してみた」で取得した顔データからその顔がどっちを向いているのか(角度)?を取得する必要がでてきたので機械学習の scikit-learn を使って実現してみました。
元々は人力で公式を作ろうとしてみましたが、数学の基礎知識が乏しい私には難かったため、これを機に以前からずっと気になっていた機械学習を導入することにしました。

そして、結果としては満足できる精度になりそうになったのでここにその作業の手順を残しておこうと思います。

追記: joblib は使わず pickle を使うようコードを変更しました。 (2017.03.24)




【おおまかな手順】
  • 角度データのある顔画像を用意
  • 顔データを加工して機械学習&結果を保存
  • 学習結果を使って未知の画像から顔の角度を取得

【実際の手順】 

まず角度データつきの顔画像を探しました。
機械学習で一番骨が折れるのはデータ収集です。
ある程度の数がないと精度を高めることができないためです。

ただ、今回はテストということで数は少ないですが以下のページからダウンロードして試してみました。

http://www-prima.inrialpes.fr/perso/Gourier/Faces/HPDatabase.html


次にこの画像から学習データとラベルを取得して学習させます。
(今回は顔が左右どちらにどのくらいの角度向いているかを学習させます。)


学習データ

データは、以下のような顔の右側と左側の比率を使うことにしました。

(左のこめかみから鼻までの距離 + 左のほほから鼻までの距離) / (右のこめかみから鼻までの距離 + 右のほほから鼻までの距離)

※2本ずつの距離を使ったのは縦方向の回転(pitch)の影響が少なくなるのではないかと思ったからです。


ラベル

ラベル(学習後、テストデータを入れると取得できる答え)はダウンロードしたファイル名に顔の角度が「+30」や「-45」などの形で含まれているのでこれを正規表現で切り出してそのまま利用します。

※つまり、ある顔データを入力すると「この顔の角度は+15(左に15度)です」などという形で答えを教えてくれるという形になります。


【実際のコード】

実際の機械学習コードは以下になります。

※このコードを使うには 「顔の入れ替えを試してみた」 で紹介した dlib の顔検出環境が必要になりますので事前に準備しておく必要があります。
(後で知りましたが、dlib は python のパッケージマネージャーの pip や anaconda で楽に準備できるようですね^^;)

※ path/to/***** となっている部分は自分の環境に合わせてください。テストした環境は python 2.7.12 です。

 # -*- coding: utf-8 -*-  
 import sys  
 import cv2  
 import dlib  
 import glob  
 import re  
 import pickle
 import numpy as np  
 from sklearn import svm
 def get_distance(landmarks, ids):  
   id1 = ids[0]  
   id2 = ids[1]  
   point1 = landmarks[id1]  
   point2 = landmarks[id2]  
   return np.linalg.norm(np.array(point2)-np.array(point1))  
 def get_face_h_ratio(landmarks):  
   length1 = get_distance(landmarks, [0,30])  
   length2 = get_distance(landmarks, [16,30])  
   length3 = get_distance(landmarks, [4,30])  
   length4 = get_distance(landmarks, [12,30])  
   return (length1+length3)/(length2+length4)  
 predictor = dlib.shape_predictor('/path/to/shape_predictor_68_face_landmarks.dat') # Predictorファイル  
 detector = dlib.get_frontal_face_detector()  
 train_angles = []  
 train_labels = []  
 for i in range(1,16):  
   dir_path = '/path/to/head_pose_images/Person%02d/*' % i  # ダウンロードした顔画像フォルダ  
   print dir_path  
   paths = glob.glob(dir_path)  
   for path in paths:  
     m = re.search(r'([\+\-]+([0-9]{1,2}))([\+\-]+([0-9]{1,2}))\.jpg$', path)  
     if m:  
       im = cv2.imread(path, cv2.IMREAD_COLOR)  
       rects = detector(im, 1)  
       if len(rects) == 0:  
         continue  
       landmarks = np.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()])  # 顔ポイントを取得  
       if landmarks is not None:  
         label = m.group(1)           # ファイル名から顔の左右角度を切り出す(yaw)  
         ratio = get_face_h_ratio(landmarks)   # 顔の左右距離から比率を取得  
         train_angles.append([ratio])  
         train_labels.append(label)  
 clf = svm.SVC()  
 clf.fit(train_angles, train_labels) # 取得したデータで機械学習する  
 pickle.dump(clf, open('yaw_angles.pkl', 'wb')) # 学習した結果を後でも使えるようにファイル保存  

このコードを実行すると「yaw_angle.pkl」というファイルが作成されます。
これが学習結果になりますので後で顔角度を取得したい場合は、以下のようにこのファイルをロードして使うことになります。

 path = 'checking_image.jpg'  
 im = cv2.imread(path, cv2.IMREAD_COLOR)  
 rects = detector(im, 1)  
 if len(rects) == 0:  
   sys.exit()  
 landmarks = np.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()])  # 顔ポイントを取得  
 if landmarks is not None:  
   with open('yaw_angles.pkl', 'rb') as f:
      clf = pickle.load(f)
   ratio = get_face_h_ratio(landmarks)   # 顔の左右距離から比率を取得  
   pre = clf.predict([ratio])  
   print pre  # 配列で答え(ここでは角度)が返ってきます。  

※ 注: ここでは import は省略しています。

以上が今回の機械学習の手順になります。
ただし、途中でも書きましたがこれだけでは学習データが少ないと思いますので他のデータも必要になってくるかと思います。

それにしても機械学習はすごいですね。
今回は左右の顔の向き(yaw)でしたが、同じようにすることで上下(pitch)も学習できるでしょうし、顔だけでなくインターネット上に無限にあるデータをうまく学習させればいろいろと面白いことができるのではないでしょうか。

今後は tensorflow の方にもチャレンジしてみたいと思います。
ではまた(^^)

2017-03-13

テキストだけHTMLから抽出する方法を考えてみた【ソースコード・ダウンロード可】

現在の開発でウェブ上のHTMLページから重要なテキストだけを取得する必要がでてきたのでどのようなアプローチがいいのかを考えてみました。

いろいろなアプローチを試しては失敗をしたのですが、最終的にある程度の精度を出せる方法に行きついたのでここで紹介したいと思います。


【抽出アルゴリズム】

実際にはアルゴリズムというほど複雑な方法ではありませんが、結局は「人間の目で見てテキストが密集している部分をグループ化する」というアプローチが功を奏しました。

流れとしては以下になります。
  1. もしテキスト間のHTMLタグが5つ以下なら(つまり近くにあるなら)そのテキストは全てひとつのテキストとして結合させる。
  2. 結合したテキストをひとつひとつチェックし、テキストの長さが100以上あれば、それはコンテンツとして残す。
文章ではわかりにくいと思いますので、HTML タグの例を見てみましょう。

(HTML例)

<div>
    <div>テキスト1</div>
    <div>テキスト2</div>
    <div>テキスト3</div>
</div>

<img src="***">
<img src="***">
<img src="***">

<div>
     <div>テキスト4</div>
</div>


テキストが近いければ結合する

まず、「テキスト1」と「テキスト2」 の間には HTML が2つ(</div>と<div>)だけです。
つまり HTML 構造的にいうと距離は「2ステップ」の位置にあるため近いテキストということになります。
なので、この2つのテキストは結合します。

では、「テキスト3」と「テキスト4」はどうでしょう?
間にあるのは、
</div></div><img><img><img><div><div>
なので7ステップです。

デフォルトの基準は5ステップ以下なら結合することになっているのでこれは「遠いテキスト」ということで結合はしません。

これを全てのテキストで実行すると、ほぼ「見た目で近いテキストが集まったグループ」が作成できることになります。


テキストが長ければコンテンツとして抽出する

グループ別のテキストが作成できたので次にこのテキストがある程度以上長ければ残し、短ければ重要度が低いと判断し削除します。

このフィルターを通過したテキストがコンテンツ・テキストということになります。


ただし

この方法でもパーフェクトではありません。
また、紹介した方法をする前に <br> タグなどを一時的に退避させるなど HTML の加工が必要になりますのでご注意ください。


ソースコードのダウンロード

ということで、このコンテンツ抽出アルゴリズム(細々とした HTML の加工を含んでいます)を PHP クラス「Shellless」として公開しました。
Github でダウンロードか composer でインストールできるかと思いますのでもし興味がありましたらぜひアクセスしてみてください。

https://github.com/SUKOHI/Shellless


今回は以上です。(^o^)




2017-02-28

アフィン変形の他の座標を取得する【Python】

前回の「 Python で画像を引っぱるように歪める」に引き続き python を使ったアフィン変形(回転、縮小/拡大、並行移動を一括で行う変形)のお話です。

アフィン変形は3点を使って回転などをするのですが、数学が苦手な私には「じゃあ、その他の座標はどうやって計算するの?」ということがわかりませんでした。
そこで、今回も備忘録的にこの記事を書きたいと思います。



まずはアフィン変形の使い方(Python)

使い方は簡単で、移動前と移動先の3つの点(つまり三角形)を行列にして変形させるだけ。
コードはこんな感じです。

 import cv2
 import numpy as np
 im = cv2.imread('squares.jpg')  
 points1 = [[50,50],[150,50],[50,150]]  
 points2 = [[75,75],[150,50],[50,150]]  
 M = cv2.getAffineTransform(np.float32(points1), np.float32(points2))  
 im2 = cv2.warpAffine(im, M, (200,200))  
 cv2.imwrite('transformed_squares.jpg', im2)  

(流れ)
1.画像を読み込む
2.移動前の点を決める
3.移動後の点を決める
4.上の2つを使って行列を取得
5.200x200サイズでアフィン変形する
6.画像を保存

※つまり、他の2点は同じなので、(50,50) の点を (75,75)に移動させるだけになります。


実際に画像は以下になります。

(変形前)


(変換後)



移動した後、その他の点は?

さぁ、ではここからが本題です。
先ほど見ていただいた画像の右下に「?」マークと少し大きな赤丸があったかと思います。
この点は変形前は (150,150) の位置にあります。
じゃあ、変形後の座標をどうやって計算したらいいの?を解決する方法です。

実際のコードはこんな形です。

 import numpy as np 
 point = np.float32((150,150))  
 x = point[0]
 y = point[1]
 new_x = M[0][0]*x + M[0][1]*y + np.float32(M[0][2])  
 new_y = M[1][0]*x + M[1][1]*y + np.float32(M[1][2])  
 print (int(new_x), int(new_y))  

変形前の点を公式に当てはめるだけでOKです。

※ M は上のコードで作った行列になります。


終わりに

行列という概念は高校の数学ででくる(らしい)のですが、正直言って今回初見ぐらいの気分でした。
ただ、画像処理にはとても便利なのでやはりきちんと数学(幾何学?)も勉強していこうと思います。
学生の頃は何に役に立つんだ?と思ってましたが、これはとても役に立つし、正直やってて面白いですね。

新しい発見でした(^o^)



2017-02-24

Python で画像を引っぱるように歪める

最近の開発で python の画像処理を勉強しているのですが、なかなか「引っぱるように画像を歪める」ということができませんでした。
いわゆる gimp でいうところの「対話的歪め」とか iwarp というやつです。
そして、それを今回なんとか実現することができましたので、備忘録として残しておこうと思います。

(追記) pip でインストールできるようパッケージ化しました

 PiecewiseDistortion


(つまり画像で言うと↓↓↓)


ということです。

※ちなみにこの処理には scikit-image を利用するので以下のようにインストールをしておいてください。

pip install scikit-image
 
 

まずは手順

手順としては

  1. 歪めたい範囲を指定
  2. どこからどこまで移動させたいかを指定
とするとやりたいことが実現できました。


実際のコード

では実際のコードを紹介します。

import skimage.transform  
from PIL import Image  
import numpy as np  

def distort(file_path, roi_points, from_points, to_points):  
    im = Image.open(file_path).convert('RGBA')  
    from_points = np.concatenate((roi_points, from_points))  
    to_points = np.concatenate((roi_points, to_points))  
    affin = skimage.transform.PiecewiseAffineTransform()  
    affin.estimate(to_points, from_points)  
    im_array = skimage.transform.warp(im, affin)  
    im_array = np.array(im_array * 255., dtype=np.uint8)  
    if im_array.shape[2] == 1:  
        im_array = im_array.reshape((im_array.shape[0],im_array.shape[1]))  
    warped_im = Image.fromarray(im_array, 'RGBA')  
    im.paste(warped_im, (0, 0), warped_im)  
    return im  

file_path = 'lena.jpg'  
roi_points = [(100,100),(400,100),(400,400),(100,400)]  
from_points = [(300,300),(250,250)]  
to_points = [(350,350),(200,200)]  
im = distort(file_path, roi_points, from_points, to_points)  
im.save('lena_distorted.jpg')  


(上のコードを実行した結果↓↓↓)


※画像のサイズは 512x512でした。元画像の lenaさんには申し訳ないですがうまくゆがんでいるのがわかるかと思います。


この作業を通じて思ったこと

どこかの記事で「プログラミング別収入ランキング」というのを読んだことがあるのですが、たしか python が一位でした。
その時はそうなんだ、ぐらいに思っていたのですがその理由が少し分かった気がします。
つまり、

python の収入が高いというよりは、python は数学、科学との連携が強みのため、そういった理数系の知識が豊富な開発は収入が高い

ということになるのだと思いました。
やっぱり理数系の知識って必要ですね。
今回久しぶりに sin cos などを使って懐かしい反面、「え!こんな便利に使えるものなんだ!」と思いました。
学校の勉強がいかに面白くなく感じていたか、、、と思う今日このごろです(笑)

ではでは。


2017-02-14

React.js は SEO に悪いか?

最近では React や Angular などの JavaScript フレームワークの開発がとても活発になっていますが、ふと「クライアントサイドでコンテンツを作ってしまったら検索エンジンはページを読み込めないんじゃ?(つまりSEO的にやばいんじゃ?)」と思ったので少し調べてみました。


検索エンジンはJSのレンダリングに対応してる?

https://medium.freecodecamp.com/seo-vs-react-is-it-neccessary-to-render-react-pages-in-the-backend-74ce5015c0c9#.qwb5reylb


こちらの記事でいろいろな調査を行っているので参考にさせてもらいました。

まずこの記事では、「SEO業者はJavaScriptは使わずHTMLを使うほうがいい」と言っているがこれは間違っていて、その証拠に Google が公式ブログで投稿したページをあげています。

内容としては

Times have changed. Today, as long as you're not blocking Googlebot from crawling your JavaScript or CSS files, we are generally able to render and understand your web pages like modern browsers.

(日本語超意訳)  JS とか CSS をブロックしてない限り Google はちゃんとページを読んでるよ。(≒検索エンジンに反映するよ!)

となっています。

これは 2015年に投稿されたものですので2017年の現在では当然のように JavaScript & CSS を使ったクライアントサイドのレンダリングにも対応していると考えていいでしょう。

さらに、個人的な感想でいうと Angular は Google の開発プロジェクトなのでクライアントサイドのレンダリングは重要視していると考えています。

※ちなみにこの記事には2016/10/25に追記があって、他の人の調査でも Google は問題なくページを読み込んでいることが分かったとしていますが、検索エンジンに反映されるには数日遅れているという結論に達しています。


他の検索エンジンは?

では、Google は問題ないとして、Bing や Yahoo など他の検索エンジンはどうなのでしょうか。
正直言って日本の検索エンジンは Google の独占状態(YahooもGoogleの検索結果をもとにしているため)なのであまり他の検索エンジンを考えなくてもいいかもしれませんが以下に彼の調査結果を紹介したいと思います。

調査の対象になったのは Preact と呼ばれるたった 3KB しかない React の軽量版のウェブページです。Preactはいわゆる SPA(Single Page Application) なので、もし Google のクローラーが JavaScript を理解できていなければ検索エンジンに表示されることはないということになります。

※ちなみにPreact は今回初めて知りました。実際アクセスすればよくわかりますがホントに超高速で表示してくれます。少し今後の展望を考えてしまいました。(^^)


結果は以下のとおりです。


ご覧いただけるとわかると思いますが、問題なくインデックスされています。

ただし、一点だけインデックスされていない検索エンジンがあったようで、それが「Baido(百度)」です。

そのため、現時点では Baido へのインデックスが必須であるなら従来通りサーバーサイドでのレンダリングをするべきでしょう。


個人的な結論

Google をはじめ、クライアントサイドでのレンダリングも問題ないようなので、SEOに関しては気にする必要はなさそうです。
しかも、我々が持っているスマートフォンなどの機会がよりハイスペックになってきているため、クライアントサイドでのレンダリングは時代にかなったテクニックだとも思います。
ホントに高速表示が可能ですしね。

ということで、今回は以上です。

2017-02-03

Vue.js 2.0 を使ってみた9つの感想

これまで、ウェブサイト開発にはメインで Bootstrp(つまり jQuery) を利用していたので、最近よく聞くようになった React、Angular、それから今回感想を書く Vue.js は敬遠していました。
なぜなら、コードがどこかで衝突するんじゃないかと敬遠していたからです。

ただ、最近の盛り上がりを見るとそうもいっていられないな、と思い今回メインとして Vue 2.0 を試してみましたのでその感想をまとめておきたいと思います。



1.速い!!

とにかく初めて Vue を使ったときに感じたのが「速い!」でした。
元々 jQuery でも遅いとは感じたことはなかったのですが、Vue はさらに快適なリアクションをしてくると感じました。
特にループでコンテンツを量産する場合では速いですね。


2.簡単!

フレームワークなりプログラミング言語なり、新しいものを採用するときにどうしても避けられないのが「学習コスト」です。
みんながいいと言ってるから便利なのは間違いないんだろうけど、一から勉強するには時間がかかってしまうのはときに大きなハードルになります。

でも、Vue ならそんな心配は不要だと思います。
正直私も一連の YouTube 動画を見ただけですぐに使いはじめることができました。

※ちなみに以下が YouTube 動画のリスト(英語)です。

動画の一覧


3.メンテナンスがしやすい

書いたコードが jQuery よりもすっきりするのでメンテナンスもしやすいですね。


4.サーバーからのサイズが(少しだけ)減る

通常 PHP などでコンテンツを作成すると フルに HTML を作成する必要があるため、ループする内容が多いときなどは特にページのサイズが大きくなってしまいます。

でも、Vue を利用するとループする部分は実際にページを見る PC やスマートフォンで作成してくれるのでサイズが比較的少なくなり、これも速さに貢献できる部分だと思います。

最近のスマートフォンはハイスペックになってきているので時代にも合っているんじゃないでしょうか。


5.jQuery との衝突がなかった

まだそこまで Vue での開発経験があるわけではないので、今後何か問題がおこる可能性はありますが、今のところ全く衝突はありませんでした。

実際に開発した例では bootstrap を利用していたので Ajax 部分や タブの切り替えなどは jQuery を利用していましたが快適に開発を進めることができました。


6.デザイナーさんにもわかりやすい

例えば、Vue を使って画像データのバインディングをする場合は

<img :src="{{ url }}">
という風に通常の HTML と同じようにパッと見ただけでわかりやすい書き方になっています。
おそらくデザイナーさんにもわかりやすいので使いやすいんじゃないでしょうか。


7.単体で動く

以前 React を使ったことがあり React もいいなとは思っていたのですが、書き始めるまでにBabel を用意するなどの必要がありました(今は違う?)。でも Vue は単体でシンプルに動きますし、cdn も用意されているのでとても簡単に開発を始められます。


8.Laravel に採用されている

まだ実際には使ってはいませんが Laravel には Vue Component という機能があり、より Vue との統合がしやすくなっています。
私は Laravel Lover ですのでここもプラス要素ですね。


9.日本語のドキュメントも用意されている

やっぱり複雑なことを学ぶには母国語が一番です。


 【結論】

とても Vue は便利で使いやすいです。
ですので、今後の開発でも積極的に利用したいと思いました。

また、バージョンがまだ2ということなので、今後開発が進むと、より便利な機能が追加されるかもしれません。

みなさんも興味があったらぜひ使ってみてはいかがでしょうか。
ではではー!


2017-01-24

顔の入れ替えを試してみた【Ubuntu】

ふとしたきっかけで画像の顔だけを入れ替える(正確にはある画像の顔を別の人の顔にする)というプログラムが公開されているのを知ったので、いつも利用しているPCにインストールして試してみました。
思ったより簡単にインストールできたので興味のある方はぜひ試してみてください。

【実行環境】 Ubuntu 16.04


※顔の入れ替えにはPythonが利用されています。もしPythonがインストールされていない場合はインストールしておいてください。


【手順1】 まず、メインとなるPythonプログラムは GitHub で公開されています。
そのため、まずこのプログラムを以下のようにしてダウンロードしましょう。


ダウンロードしたら適当な場所に展開しましょう。


【手順2】 そして、ついでにもうひとつダウンロードする必要があるので以下のようにGitHubのリンク(SourceForge)からダウンロードして中身を展開しておきます。


【手順3】 以下のURLへアクセスし、ページ中央からdlib C++ Libraryをダウンロード、これも展開しておきましょう。



展開したらそのフォルダにターミナルで移動し、以下の順番でコンパイルします。

cd examples
mkdir build
cd build
cmake ..
cmake --build . --config Release

そして、またdlibトップフォルダに戻り、setup.py を実行します。

sudo python setup.py install


【手順4】そして、Pyhon用の OpenCV もインストールしましょう。

sudo apt-get install python-opencv
 
 
では次に【手順1】で展開したフォルダの中に入って「faceswap.py」を
テキストエディタで開くと、

PREDICTOR_PATH = "/home/matt/dlib-18.16/shape_predictor_68_face_landmarks.dat"
 
という行があるはずなので、このパスを【手順2】でダウンロードしたファイルを指定します。

※私の環境では以下のようになりました。

PREDICTOR_PATH = "/home/sukohi/Downloads/shape_predictor_68_face_landmarks.dat"


【顔の入れ替えを実行】
では準備が完了したので実際に試してみましょう。
画像は以下の2枚をPixabay からダウンロードしてきました。

 face.jp

head.jpg

うまくいくと、head.jpg の顔が face.jpg の顔と入れ替えになります。

では、この2つの画像を【手順1】で作成した faceswap のフォルダの中に保存し、
以下のコマンドを実行しましょう。

sudo ./faceswap.py head.jpg face.jpg

うまくいくと output.jpg という画像が作成されているはずです。
上の画像を使って実際に作成されたは以下になります。



どうでしょう、すごくないですか?
お気づきかと思いますが、face.jpg 顔の暗い部分も元画像に適用されて
一枚の写真になっています。
こんなプログラムが公開されているなんてすごい時代になりましたね。

※ちなみに、入れ替えたい顔の目や口元などに髪の毛がかかってしまっていると
あまりうまくいかないパターンもありました。
また、背景にいろいろとものが写っていたりすると顔を検出できないパターンや、
まれなケースでは全く別の部分に顔が移植されてしまうこともありましたので
まだ完璧ではないようですが、こういうのを作る人ってすごいですよね。

今回は以上です。