Amazon Rekognitionを使って写真に自動でタグ付けする

Amazon Rekognitionを使ってMacOS Finderのタグ機能を更に良いものにしよう | Amazon Web Services ブログ

こちらを読んでいて、面白そうだったのでやって見ました。
やっていることは、あるフォルダに画像が置かれると、Amazon Rekoginitionを使ってLabelを取得し、その値をplistにformatしてxattrでtagをセットするという仕組みです。
内部的に一旦jpg変換してからrequestしているのもあり、1画像あたり2〜6秒程度時間が掛かります。
 
MacのFolder Action初めて使いましたが、これは面白いです。
色々自動化できそう。
 

こんな感じ

この画像だと、
f:id:rochefort:20140122155711j:plain:w300
Alcohol, Beer, Beverage,Drink, Cup が設定されました。

spotlightのtag検索だと、こういう感じ。finderの検索も同じようにできます。
f:id:rochefort:20170322012622p:plain
 

また、finder上でもtagを見ることができます。
f:id:rochefort:20170322013300p:plain
 

やり方

pip installとかAWS側の設定は基本的な話なので省略。

ソース

mkosut/rekognition_osx_tagfile: Takes local OSX directory of images and uses Amazon Rekognition to tag them using OSX tags
に一式置かれています。

automator workflow

対象フォルダをworkflowで設定すると、installすることができます。
shellのパスなどを修正すればautomatorとしてはokです。
 
shellについては、私は以下のようにしてみました。

export AWS_DEFAULT_PROFILE=rek
LOG="/var/log/aws_rekognition.log"
echo "-- start $(date)" >> ${LOG}
for f in "$@"
do
  echo "/usr/local/bin/python3 ~/lib/rekognition_osx_tagfile/rek_osx_tag.py -f $f" >> ${LOG}
  /usr/local/bin/python3 ~/lib/rekognition_osx_tagfile/rek_osx_tag.py -f "$f"  >> ${LOG} 2>&1
done
echo "-- end   $(date)" >> ${LOG}
echo "" >> ${LOG}

AWS_DEFAULT_PROFILE についてですが、 Amazon Rekognition のregionは東京にはないので、profileを分けて実行するようにしています。 profileは aws configure --profile your_profile_name で作成可能です。
 

Folder Action

対象フォルダを右クリックし、Services > Folder Action Setup… で対象フォルダと実行サービスの紐付けを行い、有効化します。
f:id:rochefort:20170322014007p:plain

使い勝手について

Beerの画像のように分かりやすといいのですが、もう少し複雑なものだと大量にタグがセットされてしまいます。
例えば、台湾のお寺の写真だと
f:id:rochefort:20140319202843j:plain

こんなに沢山(script内で最大数は50に設定されています)。
People, Person, Human, Architecture, Shrine, Temple, Worship, Bar Counter, Pub, Night Club, Night Life, Clothing, Dress, Road, Street, Town, Subway, Train, Train Station, Vehicle, Costume, Alley, Alleyway

python script内でconfidenceを50としているのが、少し緩い印象。
ここは、-c オプションで変更可能ですが、調整した方がいいかもしれません。
 

HammerspoonではWatcherはグローバルでないとGCされる件

にてwatcherを使った実装をしていましたが、sleepから戻ると実行できなくなっていました。

調べて見ると

Hammerspoon のイベント監視が止まるのはGCのせい - Qiita
GC対象となってしまっていたようです。

対応方法は

local 宣言を外しグローバルスコープにするだけ。

@@ -40,7 +40,7 @@ local notVimBindingApps = {
   "Atom", "CotEditor", "iTerm", "Google Chrome", "Night Owl"
 }

-local function handleGlobalAppEvent(name, event, app)
+function handleGlobalAppEvent(name, event, app)
   if event == hs.application.watcher.activated then
     -- hs.alert(name)
     if hasValue(notVimBindingApps, name) then
@@ -51,7 +51,7 @@ local function handleGlobalAppEvent(name, event, app)
   end
 end

-local appsWatcher = hs.application.watcher.new(handleGlobalAppEvent)
+appsWatcher = hs.application.watcher.new(handleGlobalAppEvent)
 appsWatcher:start()

WITHOUT REPETITIONS(CODEEVAL)

すごく久しぶりにcodeevalやって見た。
繰り返し文字列を削除する問題。
愚直にできなくはないのですが、なんか便利メソッドないかと調べて見たら
そのものズバリの instance method String#squeeze (Ruby 2.4.0) というものがありました。

CHALLENGE DESCRIPTION:

In a given text, if there are two or more identical characters in sequence, delete the repetitions and leave only the first character.

For Example:

Shellless mollusk lives in wallless house in wellness. Aaaarrghh!

↓

Sheles molusk lives in wales house in welnes. Aargh!

INPUT SAMPLE:

But as he spake he drew the good sword from its scabbard, and smote a heathen knight, Jusssstin of thee Iron Valley.
No matttter whom you choose, she deccccclared, I will abide by your decision.
Wwwhat is your will?
At his magic speech the ground oppened and he began the path of descent.
I should fly away and you would never see me again.

OUTPUT SAMPLE:

But as he spake he drew the god sword from its scabard, and smote a heathen knight, Justin of the Iron Valey.
No mater whom you chose, she declared, I wil abide by your decision.
Wwhat is your wil?
At his magic spech the ground opened and he began the path of descent.
I should fly away and you would never se me again.

CONSTRAINTS:

  1. The text is case-sensitive: ‘a’ and ‘A’ are different characters.
  2. The input consists of 40 text lines.
  3. The maximum size of the text is 10 KB.

My Code

#!/usr/bin/env ruby -w

ARGF.each_line do |line|
  puts line.chomp.split(" ").map(&:squeeze).join(" ")
end

これだけ。