「[改訂新版]Emacs実践入門」を読んだ

この本です。

[改訂新版]Emacs実践入門―思考を直感的にコード化し、開発を加速する (WEB+DB PRESS plus)

[改訂新版]Emacs実践入門―思考を直感的にコード化し、開発を加速する (WEB+DB PRESS plus)

著者tomoyaさんによるエントリがこちら。

d.hatena.ne.jp

前回のときに献本いただいた縁もあり、今回もまたいただいてしまいました。ありがとうございます!!!

memo.sugyan.com

あれから5年、僕も色々な変化があり 昨年末くらいからはVSCodeをメインで使うようになり この記事を書くにあたり久々にEmacsを立ち上げるか…と思ったら新しいマシンにはインストールすらしていなくてビックリした!

ていうくらい離れてしまっている状態ではありますが、色々と思い出しながら読ませていただきました。

上記以外の初版からの大きな変更は次の通りです。

  • 拡張インストールをpackage.el (ELPA) に全面書き換え
  • Anything から Helm へ
  • Flymake から Flycheck へ
  • Egg から Magit へ
  • 『web-mode』の追加
  • 『コードの実行(quick-run)』の追加
  • 『差分の表示──git-gutter』の追加
  • 『テスティングフレームワークとの連携(phpunitrspec-mode)』を追加

と書かれているあたりは、自分も毎日Emacsを使っていたときに愛用していたものたちばかりで、自分の使い方は間違っていなかった、というのを再確認できました。 最新のアップデートとトレンドを網羅していて とても良い改訂だと思います。たぶん。

Emacsは本当に良いエディタで、VSCodeに移った今でも当然 Emacs Keymap を入れて基本的にはEmacsキーバインドで動かしているし それでも選択まわりの挙動がちょっと気に食わなくてイラっとすることがあるくらい。

じゃあEmacsに戻れよって感じですが。 なんとなく、エディタに縛られることなく多少開発環境が変わっても対応できるようにするためにも慣れておこうというのもあって乗り換えてみたのだけど それはそれで良かったと思うし、この本を読んで 今またEmacsに戻ろうと思ってもそれほど苦労なく切り替えられそうだな、と自信が持ててよかったです。

「プロジェクト」の概念についても思うところはあって 確かにEmacsでは一つのウィンドウですべてバッファの切り替えだけで扱っていたの便利だなーと思うし でもプロジェクトごとにウィンドウが分かれていた方がスッキリするのも確かだし しかし2つ3つならまだしも4つ5つ…となってくると切り替えがすごく面倒で… と 悩ましいところではある

今後もちょいちょいこうしてEmacsを振り返りつつ 他のエディタもそこそこ使いこなせるようになれればいいな、と改めて思えた良い本でした。

顔検出に関する最近の論文

Face Detector関連だけでもなんか色々あるようだ。

(中身はまだまったく見てない)

GitHub の number9473/nn-algorithm っていうリポジトリで、arXivTimes のように論文の紹介をしているようだ。"Face Detection"っていうラベルまである。

github.com

TensorFlowで顔検出器を自作する

f:id:sugyan:20170820172842j:plain

19日に行われた Kyoto.なんか #3 で発表・デモをさせていただいた内容まとめです。

はじめに: 検出器の重要性

アイドル顔識別 をずっとやっている中で、顔の識別・分類(Classification)はCNNを使って出来ているけれど まだ上手く出来ていない別のタスクがあって。

それが画像内からの顔領域の検出 (Detection, Localization)。

「画像内に写っている人物が誰であるか」を識別するためには、まずはその画像に写っている「顔」を検出する必要がある。 その検出された顔それぞれについて分類器にかけて「この顔は○○さん」「この顔は××さん」と分類していくことになるわけで。

分類器に与える入力画像を切り抜いて抽出するのにもまず顔領域を検出する必要があるし、その分類器を学習させるためのデータセットも、様々な画像から顔領域を検出して切り抜いてそれぞれに対してラベル付けすることで作っている。 なので、顔識別タスクには「顔領域の検出」が不可欠となっている。

従来の方法

今までは、データセット作成のための顔画像収集にはOpenCVを使った回転補正機能つきの検出器を自作して使っていた。

OpenCVのHaar特徴によるカスケード型分類器を使った領域検出は、正面向き顔・目などを検出するため学習済みデータが標準で同梱されており、最も手軽に使える検出器と言える。 しかし、この検出器は斜めに傾いた顔に対しては一気に精度が下がるという弱点があり、斜めに写っていることが多いアイドルの自撮りでは上手く検出できない場合が多い。 それを克服するために、元画像を少しずつ回転させたものを生成し それぞれに対して検出器にかけ それらの結果をマージする、という方法を使って斜めのものもそれなりの精度で顔検出できるものを作った。

詳しくは過去のこの記事。

memo.sugyan.com

これによってある程度の精度で顔領域を検出することができ、また 顔と同時に両目の位置も検出するようにしたので その検出された目の座標のx差分, y差分を使った逆正接 atan2 で傾き角度も求めることができる。

これで大体やりたいことは実現できそうだったので、自分の取り組んでいるアイドル顔識別においてはこの検出器を使ってデータセット用の顔画像抽出を行ってきた。

しかし この検出器でもまだ問題は残っていて。

  • とにかく処理が重く時間がかかる
    • 回転した複数の画像を作る、それぞれから検出する、ので当然
  • まだ誤検出が多い
    • 顔ではない壁や服の模様を顔として検出してしまうことが多い
  • 学習させカスタマイズ、などしづらい
    • データセットを用意して学習させることは出来るらしいが…

遅いのはデータセット用の収集にはそれほど問題ではないけれど、例えば顔識別BOTのようにインタラクティブにレスポンスを返したい場面においては致命的で、仕方ないのでBot用の検出には Cloud Vision API を使うようにしているのが現状。

また精度的にも少し問題があって、特に金髪の人物の場合に 実際の顔領域より大きく検出されることが多いようだった。顔と髪の区別がつきづらい、から…?

例:


LBPなど他の特徴を使った検出器に切り替える、またdlibなど他のライブラリを使用することで改善も出来たかもしれないけど、折角なのでここは Deep Learning を使った検出器を作って自前の学習データを食わせて学習させたい、という思いがあり 今回はそれに挑戦してみることにした。

Deep Learning による物体検出

Deep Learning を使った物体検出の手法もたくさん研究されていて近年めざましい発展を遂げているようで、代表的な手法としてこんなものが提案されてきた、と下記記事で紹介されている。

tech-blog.abeja.asia

一番最近のものとして紹介されている SSD (Single Shot MultiBox Detector) がとても高速に高精度で検出をできそうで良さそうだな と思って、いちおう論文も少し目を通してみた。 元の実装はcaffeによるもので、TensorFlow版も書いている人が数人いたけど 何となくの原理は分かったような気がするし自分でも勉強がてらTensorFlowで書いてみよう…として、難しすぎて途中で挫折した。 のが今年の1月頃の話。

Object Detection API

時は過ぎ、今年の6月中旬。 TensorFlow公式のモデル群 TensorFlow Models リポジトリで、 “Object Detection API” が公開された。 ここでは、MS COCO dataset を使って学習済みの5種類の一般物体検出モデルが公開されている。

github.com

Model name Speed COCO mAP
ssd_mobilenet_v1_coco fast 21
ssd_inception_v2_coco fast 24
rfcn_resnet101_coco medium 30
faster_rcnn_resnet101_coco medium 32
faster_rcnn_inception_resnet_v2_atrous_coco slow 37

下の方がより精度が高く、その分モデルは大きくなるし処理も重くなるようだ。 上2つはまさに SSD を使ったものであり、ベースとなる CNN を元論文では VGG16 を使っていたのに対し軽量な MobileNet を使ったもの、 Inception V2 を使ったもの と2種類それぞれで実現しているようだ。 Faster RCNN などを使ったものより検出精度は劣るものの、やはり処理速度は圧倒的に SSD の方が早そう。

さらにこのリポジトリでは、学習済みモデルを利用した転移学習で 別のデータセットを利用して学習しなおす方法についても仕組みが用意され 丁寧に説明されている。 つまり、このリポジトリのモデルで扱う形に適合した tfrecord ファイルを自分で用意できれば、簡単にそれを使った検出器を学習させ使うことができる、ということのようだ。

これを使わない手は無い、ということで試してみた。

FDDB dataset から学習用データセットを作る

自分が集めてきたアイドル顔画像から用意しても良かったけど、まずは一般に公開されているデータセットで試してみよう、と思って探してみたところ、FDDB というデータセットがヒットした。

FDDB : Main

2,845点の画像それぞれについて、写っている顔領域を楕円で表現し その中心座標、長径・短径、傾き角度 のセットがアノテーションとして計5,171件 与えられている。

これで顔領域の検出だけなら学習させられそうだけど、これだけでは顔の傾きは取得できない。 OpenCVを使ったものと同様、両目の位置さえ取れればそこから角度は算出できそうだけど、両目の位置の情報は残念ながら付属のアノテーションには含まれていない。

しかし「顔の存在する位置」「傾き」が与えられているなら、その領域を狙い打ちして OpenCV で検出することも可能なはず。

アノテーションそれぞれについて、

  • 与えられている傾きを補正するよう回転させて
  • 顔の中心座標を中心とする 長径 * 1.1 の、少し大きめのサイズの正方形で切り抜く

という操作で「縦に真っ直ぐになった顔が写っているはずの領域」を抽出した画像を一度作り、それに対して OpenCV による顔検出をかける。 そうして検出された目の領域を表す座標をそれぞれ回転前の座標に変換すれば、元画像に対する目の領域も取得できる。

やはりある程度の誤検出はあるので、適当にフィルタリングして補正し、除外。 こんな感じのコードで

import cv2
import math
import os

CASCADES_DIR = os.path.normpath(os.path.join(cv2.__file__, '..', '..', '..', '..', 'share', 'OpenCV', 'haarcascades'))
FACE_CASCADE = cv2.CascadeClassifier(os.path.join(CASCADES_DIR, 'haarcascade_frontalface_default.xml'))
EYES_CASCADE = cv2.CascadeClassifier(os.path.join(CASCADES_DIR, 'haarcascade_eye.xml'))

def detect_faces(img, lines):
    results = []
    for line in lines:
        e = line.split(' ')
        size = max(float(e[0]), float(e[1])) * 1.1
        # 小さすぎるものは除去
        if size < 60.0:
            break
        # 真っ直ぐになっているはずの領域を切り抜く
        center = (int(float(e[3])), int(float(e[4])))
        angle = float(e[2]) / math.pi * 180.0
        if angle < 0:
            angle += 180.0
        M = cv2.getRotationMatrix2D(center, angle - 90.0, 1)
        M[0, 2] -= float(e[3]) - size
        M[1, 2] -= float(e[4]) - size
        target = cv2.warpAffine(img, M, (int(size * 2), int(size * 2)))

        # 切り抜いた画像から顔と目を検出する
        faces = FACE_CASCADE.detectMultiScale(target)
        if len(faces) != 1:
            print('{} faces found...'.format(len(faces)))
            break
        face = faces[0]
        face_img = target[face[1]:face[1] + face[3], face[0]:face[0] + face[2]]
        eyes = []
        for eye in EYES_CASCADE.detectMultiScale(face_img):
            # 始点の高さが元画像の下半分にあるようならおそらくそれは誤検出
            if eye[1] > face_img.shape[0] / 2:
                break
            eyes.append(eye)
        if len(eyes) != 2:
            print('{} eyes found...'.format(len(eyes)))
            break
        # 両目のサイズがあまりにも異なるのは不自然なので検出失敗とする
        if not (2. / 3. < eyes[0][2] / eyes[1][2] < 3. / 2. and 2. / 3. < eyes[0][3] / eyes[1][3] < 3. / 2.):
            break
    ...

これでだいたいは上手く検出できたようだった。

この方法で上手く検出でき、与えられているアノテーションと同数の顔が正しく両目と共に検出されたものだけを用いてデータセットを作成。 結果として、使用できたのは2,845点のうち936点だった。 ちょっと少ないけど仕方ない。 train用とvalidation用に分ける必要があるようだったのでこれをさらに 843:93 に分割して使用した。

で、あとはこれをそれぞれ画像に対する image/objcet/bbox/*image/object/class/* といったkeyに情報を含めて tfrecord 形式に書き出す。

feature = {
    'image/height': tf.train.Feature(int64_list=tf.train.Int64List(value=[h])),
    'image/width': tf.train.Feature(int64_list=tf.train.Int64List(value=[w])),
    'image/filename': tf.train.Feature(bytes_list=tf.train.BytesList(value=[filepath.encode('utf-8')])),
    'image/source_id': tf.train.Feature(bytes_list=tf.train.BytesList(value=[filepath.encode('utf-8')])),
    'image/encoded': tf.train.Feature(bytes_list=tf.train.BytesList(value=[encoded])),
    'image/format': tf.train.Feature(bytes_list=tf.train.BytesList(value=['jpeg'.encode('utf-8')])),
    'image/object/bbox/xmin': tf.train.Feature(float_list=tf.train.FloatList(value=xmin)),
    'image/object/bbox/xmax': tf.train.Feature(float_list=tf.train.FloatList(value=xmax)),
    'image/object/bbox/ymin': tf.train.Feature(float_list=tf.train.FloatList(value=ymin)),
    'image/object/bbox/ymax': tf.train.Feature(float_list=tf.train.FloatList(value=ymax)),
    'image/object/class/text': tf.train.Feature(bytes_list=tf.train.BytesList(value=class_text)),
    'image/object/class/label': tf.train.Feature(int64_list=tf.train.Int64List(value=class_label)),
}
example = tf.train.Example(features=tf.train.Features(feature=feature))
writer.write(example.SerializeToString())

これで一応、データセットが作成できたので あとはこれを使って学習させる。 ssd_inception_v2_coco の学習済みモデルをベースにFine-Tuningする形で。 Google Cloud Machine Learning を使う方法も書いてあったのだけど ちょっと何故か上手くいかなかった(要 再挑戦)ので、今回はEC2のg2.2xlargeインタンスを使って学習を行った。 1stepあたり2秒弱くらい、丸一日で 50,000stepほど学習が進み、だいたいは学習が出来た雰囲気だった。

f:id:sugyan:20170820190012p:plain

これを使って検出してみた結果が冒頭の画像。

用意したデータセットに傾いている顔もある程度は含まれていたので、そういうものもある程度は検出できるようだった。 たった800件ちょっとの画像でのデータを用意だけでもこれだけ検出できるようになっているのだから十分かな、という感触。 ここからさらにデータセットを増やしていけばどんどん精度は上げられそうな気がする。

あとは実際の顔識別に使うような自撮りの多い画像たちを どうアノテーション付けてどう管理し、どう性能評価していくか、って話になってくると思う

Webアプリ化

ここからは完全に余談なのだけど、せっかく高速に顔検出できるモデルをTensorFlowで構築できたのだから、Webサービスとして公開できるようにしよう、と。 顔検出モデルはFlaskを使ってJSON API化できる。 あとはフロントエンドだけどうにかしてUIを作るだけ。

以前もちょいちょいReactとかwebpackとか使って似たようなものは作っていたので使い回しだけど、今回はTypeScriptで.tsxを書いてts-loaderでトランスパイル、という感じでやってみた。 型が付くと分かりやすく書きやすい、んだけど なかなか慣れなくて思った以上に苦戦した…

で、とりあえず最低限動くところまで出来たので公開したのがこちら。

これくらいならHerokuで動かせるかと思ったけど、いざdeployしてみたところ “Memory quota exceeded” のエラーが出まくってしまって、どうもメモリの使用量がヤバいらしい…。 いちおう動くことは動くけど、いつ止まってしまってもおかしくない、という感じ。 畳み込み4層の識別モデルくらいなら大丈夫だったけど これくらいの規模だと厳しいか、、、

Herokuでメモリ多めのdynoにアップグレードすると$25くらいかかるみたいだし、それだったらどこかのVPSで2GBくらいあるやつを借りた方がいいか…? 真面目に運用することになったら考えよう。。

Repository

ターミナルでの炎上のさせ方

f:id:sugyan:20170809005307p:plain

話題になってたコレが面白いなーと思って

ターミナル上でそれっぽく再現させてみた。 こんなかんじ。

f:id:sugyan:20170809005416g:plain

(これの作成は以前に作ったttyrecからGIFアニ生成やつ にて) memo.sugyan.com

そんなに細かくなくても大丈夫であればターミナルの縦横のマスでもできそうだなーと思って 256色のターミナルなら黒→赤→黄→白の間でも12段階くらいの変化が出せるので

描画系はtermbox-goを使えば簡単に出来た。色はRGBでは指定できなそうだったのでカラーコードを直指定で。 github.com

いちおう最下段の値、ノイズの載せ方を少し工夫した。

最下段は前のコマからの差分が大きくなりすぎないように変化量を制限。 あとどうしてもチラつきやすいので1段分余計に計算して、下から2段目以降を表示に使用。

ノイズは各セルに一定範囲で加減するが、上に行くに従って少しずつ暗くなるように期待値が僅かに負の値になるような乱数に。

とりあえずはこれで何となく炎っぽい動きになった、かな? 最下段の値の変化のさせ方をもっと時系列に滑らかにするともう少し良くなりそうな気はする。

色のパターンを変えるだけでこういうのも作れて面白い

f:id:sugyan:20170809011031p:plain

builderscon tokyo 2017 に参加した

builderscon.io

昨年は不参加だったので、今年が初参加。

https://scontent-nrt1-1.cdninstagram.com/t51.2885-15/e35/20481806_1195358713934417_200836583476166656_n.jpg

話した

折角なので何か話してみよう、と応募したところ無事に通ったので、以下のセッションでトークをさせていただきました。

DeepLearningによるアイドル顔識別を支える技術 - builderscon tokyo 2017

資料はこちら。

speakerdeck.com


基本的にはこのブログに書いてきたことのまとめ的な内容で、今まで他の勉強会とかでもちょいちょい話していたし 自分的にはあまり新しい内容もなくてアレかなぁ…という感じもあったけど、 さすがにこれくらいの規模のカンファレンスだと僕のことをまったく知らない人も聴きに来て下さっていたようで。

そんな方々に少しでも「知らなかった、を聞く」を提供できていたら何よりです。


個人的には1時間枠で喋るというのが初めてだったので、当然緊張したし時間配分的なものも難しくて大変でした。 とりあえず誰か1人にでも「すごい」って思えてもらえたら嬉しいな、くらいの気持ちで挑んだ。 時間に余裕があったおかげで少しデモで実際に動いてるものや画面などを見せながら出来たので良かったかな、と思います。


初日最初の枠で発表おわったので気持ちに余裕が出来たので、枠が空いてるようならLTも飛び込んでしまえ、と 午後から申し込んだ。

speakerdeck.com

作ったもの自体は TOKYO IDOL FESTIVAL 2016 のタイムテーブル画像化ツールを作った - すぎゃーんメモ のものの使い回しだけど、今年は予想以上に多くの人たちに使ってもらえて嬉しかったので自慢したかった。ただそれだけです。@さんがタイムテーブルネタで被るよう順番を組んでくれたおかげでタイトルだけで出落ちの笑いが取れて良かったです。

聞いた

前夜祭は内容については口外NGということで「面白かった」としか書けないけど、良い企画でした。

本編では 幾つか聞いた中で特に面白かったのは以下の3つ。

builderscon.io

builderscon.io

builderscon.io


横山三国志は完全にネタな内容なのに(人のこと言えないけど) たくさんの工夫や応用のアイデアが詰め込まれていて、とても面白かった。

階差機関、HADOのあたりは本当にまったく触れたことも無いような世界の話だったので、まさに「知らなかった、を聞く」ことが出来たトークでした。 こういう 特定の技術やプログラミング言語のコミュニティだけではなかなか出会えなそうな話に触れられたのはとても良かったと思います。


他にも気になっていたけど聴きに行けなかったトークも幾つかあるので、後で資料を拝見して追いかけたいと思います。

楽しかった

こういう大きめのカンファレンスに参加するのも久しぶりだったので、懇親会などで久々の方とかと会えたり また初めましての方ともご挨拶してお話できて、とても楽しかったです。

運営スタッフの皆様、素晴らしいトークをして下さった方々、聴いて下さった方々、ありがとうございました!

「TensorFlowはじめました2」を読んだ

TensorFlowはじめました2 機械学習で超解像─Super Resolution (NextPublishing)

TensorFlowはじめました2 機械学習で超解像─Super Resolution (NextPublishing)

「TensorFlowはじめました」を読んだ - すぎゃーんメモ で読ませていただいた「TensorFlowはじめました」の第2弾。

今回はレビュアーとして声をかけていただき、微力ながら協力させていただきました。有山さん、ありがとうございます!!

最初の第1章ではTensorFlowの基礎、データフローグラフの概念などが前作同様に丁寧に解説されていて、TensorFlowに触れたことがない/前作を読んでいない方でも問題なく入りこめるようになっていると思います。

第2章以降の、今作のテーマである「超解像」については私自身はまだ触れたことがなかったので勉強になりました。

第3章では今回も奮闘記となっており「予想に反し上手くいかない」「原因を推察する」「改善し試してみる」という過程がとても丁寧に書かれていて、とても面白いです。

第4章ではさらに踏み込んで質を向上させるための実験、評価についても触れていて、こちらも大変勉強になりました。

今回も参考文献に私の「すぎゃーんメモ」も載せていただいていて、光栄であります。最近あまり何も書けていないので頑張らねば。。。

KarabinerからHammerspoonへ

そろそろ新しいMacBook欲しい… けど El Capitan から Sierra になるとKarabinerが使えなくなるって話だし… とか色々不安があり躊躇していた。 代替、というかちゃんと設定すれば代替できそうなHammerspoonが良さげだったので それを使ってKarabinerからの脱却を試みてみた。

Hammerspoon

www.hammerspoon.org

Mac OSの様々な操作を自動化するためのツールで、ユーザがLuaスクリプトで設定を書くことでOSの様々な機能を使えるようにブリッジしてくれる。

Key Remap

基本的に 以下の記事のコピペで大丈夫そう。

qiita.com

自分は以下のように書いた。

local function keyStroke(mods, key)
  return function() hs.eventtap.keyStroke(mods, key, 0) end
end

local function remap(mods, key, fn)
  return hs.hotkey.bind(mods, key, fn, nil, fn)
end

-- global
remap({'ctrl'}, 'h', keyStroke({}, 'delete'))
remap({'ctrl'}, '[', keyStroke({}, 'escape'))
remap({'ctrl'}, 'j', keyStroke({}, 'return'))
remap({'ctrl', 'cmd'}, 'j', keyStroke({'cmd'}, 'return'))

-- disable when a specific application is active
local remapKeys = {
  remap({'ctrl'}, 'b', keyStroke({}, 'left')),
  remap({'ctrl'}, 'f', keyStroke({}, 'right')),
  remap({'ctrl'}, 'n', keyStroke({}, 'down')),
  remap({'ctrl'}, 'p', keyStroke({}, 'up')),
  remap({'ctrl'}, 'y', keyStroke({'cmd'}, 'v'))
}

local function handleGlobalAppEvent(name, event, app)
  if event == hs.application.watcher.activated then
    if name == 'iTerm2' or name == 'Code' then
      for i, key in ipairs(remapKeys) do
        key:disable()
      end
    else
      for i, key in ipairs(remapKeys) do
        key:enable()
      end
    end
  end
end

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

hs.hotkey.bindで指定したキー操作時の振る舞いを指定できるので、hs.eventtap.keyStrokeで置き換えたいキー操作を指定してそれをemitさせるfunctionをセットする。pressedfn, releasedfn, repeatfnと3種類の振る舞いを定義できるので注意。リマップは基本的にpressedfn, repeatfnを同じkeyStrokeを起こすfunctionにしておけば良さそう。

あと上述記事にもある通りiTermやVSCodeなどのターミナルやエディタではそれぞれ独自にキーバインド設定できたりするし リマップによる変換があると逆にそれが災いして期待した動作にならなかったりするので、applicationの切り替え時にイベントを取得してdisable()/enable()するように。

別にすべてのリマップを無効にする必要は無かったりもするので、常に有効にしておきたいものとは別に切り替え対象のリマップのkeybindだけを保持しておいて それらだけを有効化/無効化の対象にした。

Sticky Shift

Karabinerでもう一つお世話になっていたのが、所謂sticky-shift的なものをセミコロン;で行う、というもの。

セミコロン;を押した後にキーを押す、という操作でShiftキー押しながらの操作を代替する。(別に使うのはセミコロンじゃなくてもいいのだけど 最も一般的なのがこれ、なのかな)

openlab.ring.gr.jp

AquaSKKを使っていると変換位置の指定の際にShiftキーを多用することになりつらいので、ずっとこの方法にお世話になっていたし、すっかりそれに慣れてしまっていた。

Karabinerでは AquaSKK で ; (セミコロン) を Sticky Shift に使う - Qiita のように設定して使っていたのだけど、これもHammerspoonでどうにか。

local stickyShift = false
local targets = {}
for i = 96, 122 do
  targets[hs.keycodes.map[string.char(i)]] = true
end

hs.hotkey.bind({}, ';', function()
  if hs.keycodes.currentMethod():find('AquaSKK') then
    stickyShift = true
  else
    hs.eventtap.keyStrokes(';')
  end
end)

keyTap = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(event)
  if stickyShift and targets[event:getKeyCode()] then
    event:setFlags({shift = true})
  end
  stickyShift = false
end):start()

stickyShiftという変数を持っておいて、入力ソースがAquaSKKのときに;が押されたらフラグを立てる。それ以外のときは普通にそのまま;keyStrokeをemitするだけ。

keyDownイベントを監視し、stickyShiftフラグが立っていて かつtargetのkeyCode(母音と主な子音だけで良いのだけど とりあえず全アルファベットを対象にしている)だったら、setFlagsを使ってshiftが押されている状態にイベントを書き換える。stickyShiftフラグは継続させる必要ないので強制的にfalseに戻す。

これで今まで通りに;を使ったsticky-shiftでの日本語入力ができるようになった。

ひらがなモードのときだけフラグを変えて ASCIIモードなら;はそのまま通す、とかも出来れば良いのだけど、入力ソースの判別までは出来ても 現在の入力モードが何か、までは取る方法が分からなかった(無い?)ので;を普通に打ちたい場合は普通に英字入力に切り替えるしかない。 まぁこれはKarabinerのときもそうだったし仕方ない。

あと どういうわけか、SierraだとVisual Studio Code上でAquaSKKがまったく効かなくて、ひらがなモードに入ることすらできないようだ。キー設定とかそういう問題じゃなさそうで どうにもならないんだけど、、他の人は普通に使えているのだろうか…?

まぁVSCodeはコード書くものだし日本語の文章書きたいときは他のアプリケーションを使えばいいか…ということで割り切る(この記事の下書きはAtomで書いてる)。

Launcher

あとよく使っていたのがhotkeyでのアプリ切り替え。cmd+ctrl+左手で打てるキー の組み合わせでターミナル/エディタ/ブラウザ/チャット/ツイッター などをサクサク切り替えたくて 以前はSlatePhoenix、もしくは有料アプリのShortcutsなんかを使っていたりもしたけど、これもHammerspoonで解決できるようになったのでLauncherアプリは不要になった。

local function launcher(mods, key, appname)
  hs.hotkey.bind(mods, key, function()
    hs.application.launchOrFocus('/Applications/' .. appname .. '.app')
  end)
end

launcher({'cmd', 'ctrl'}, 'q', 'iTerm')
launcher({'cmd', 'ctrl'}, 'w', 'Visual Studio Code')
launcher({'cmd', 'ctrl'}, 'e', 'Google Chrome')
launcher({'cmd', 'ctrl'}, 't', 'Twitter')
launcher({'cmd', 'ctrl'}, 's', 'Slack')

Key Repeat

あとそういえばkarabinerで便利だったのがキーリピート間隔の設定だったのだけど、 システム環境設定での設定可能範囲を超える場合はdefaultsコマンドで設定すれば良いだけ、というのをググって知ったので 問題なかった。

$ defaults write -globalDomain KeyRepeat -int 1
$ defaults write -globalDomain InitialKeyRepeat -int 10

とかやって再起動すれば反映される(システム環境設定での変更ではKeyRepeat2InitialKeyRepeat15が最小値っぽい)

雑感

とりあえずKarabinerが無くても困らない程度の設定は出来たっぽい。他にもカスタマイズしたければinit.luaを頑張って書けば色々できそうではある。

しかしまぁKarabinerがSierraでも動いてくれるのが何よりだし このHammerspoonだって いつまたMac OSの変化で使えなくなってしまうか分からないし

こうしてMacの変更に振り回されて色々と苦労しないといかんのはどうにからならないもんかなぁ、と思ってしまう