覚書

Goを使って画像の類似度を超簡単に行う / perceptual(image) hash

コンビニの有人レジがすごい混んでるのに、セルフレジを誰も使わない日本人を理解できない長谷川です。 概要 2つの画像の類似度を算出したい。 得られるハッシュ値は64bit 対象は静止画, 画像, 音声等のマルチメディアデータ コンテンツ内容が類似しているケースでハッシュを得た場合、例えば静止画画像の拡大、縮小といった加工の場合ハッシュ値が全く同じになる また、色調の修正やノイズが加わった場合も得られるハッシュ値間のハミング距離が近くなる 64bitのハッシュ値なので最も遠いハミング距離は64 (=全くコンテンツが異なっている) 逆にハミング距離が0であればperceptual hashで得られた結果上は同一コンテンツ こういった特徴があるため具体的にWebアプリケーションでの用途を考えると コンテンツの重複判定 類似画像検索 などに使えそうというのは上の特徴でわかるのではないでしょうか。 引用元:http://hideack.hatenablog.com/entry/2015/03/16/194336 計算方法もいくつか種類があります。 ・aHash:画像の平均輝度からの差分を使った方法 ・pHash:画像を離散コサイン変換(DCT)し周波数領域に変換後、低周波領域に対してaHashと同じ方法で算出する方法 ・dHash:隣接領域との差分を使った方法 ・wHash:pHashのDCTの代わりに離散ウェーブレット変換(DWT)を用いた方法 参考:http://tech.unifa-e.com/entry/2017/11/27/111546 ここでは aHash と dHash を使ってみようと思います。 使用したライブラリはこちら https://github.com/devedge/imagehash   実装 [crayon-5ade611ed3ac9730678221/] imagehash.Ahash を imagehash.Dhash にするだけで aHash から dHash に切り替えることができます。   aHash と dHash の比較 こちらのサイトによると、dHash は aHash と同じ速度にも関わらず、精度が良好らしいです。 ちなみに2つの画像の distance(違う画像だと数値が大きい) を求めるには 2つの []byte をループさせて比較して値が違う場合に +1 すればいいだけです。 [crayon-5ade611ed3ad4046881787/]   試した画像 laptop1.jpg laptop2.jpg iqos.jpg laptop1.jpg と laptop2.jpg は似たような画像(若干右にズレてます)、 iqos.jpg は全く違う画像を用いりました。   aHash 平均輝度の差分を使った超シンプルな方法 スマホで撮影した画像の場合、撮影時にタップした場所によって露出が変わるためスマホの画像を取り扱う場合には不適切なような気がする。 [crayon-5ade611ed3ad8878588018/]   dHash 隣接領域との差分を使った方法でシンプルで精度も、速度も良いらしい。 グレースケールに変換した後、9×8サイズに縮小する。 右隣のピクセルと値を比較して大きければ1 同じか小さければ0 0を黒、1を白に置換し、1pixelを1bitとして、16進数に変換することで値を取得できる。 aHash と違い、輝度ではなく実際にピクセルを見るので精度が良いと言われるのも分かる。 [crayon-5ade611ed3add088324693/] aHash と dHash の比較をしてみたけど精度にそこまでの差を見つけられなかった。 まあ今回用いた画像が悪かった。 distance だけを見ると、dHash の方が粒度が高いように見えるけど似たような画像と、アイコスと1枚目の差が 4 しかない。 しかし、aHash では差が 5 ある。 しかも1000回ループした結果では、aHash の方が16秒速い。 全く違う iqos.jpg でも真ん中にディスプレイがあり、dhash の手法だと白黒に変換したタイミングで同じような結果となってしまい、たまたま aHash と差が内容になってしまったと推測。 どちらを使うにしても distance の閾値を適切に設定する必要性がありますね。 もっといろんな画像で試してどっちを使うか決めたいと思います。

AWS-SDK-Goを使って、ユーザーが投稿したファイルをS3から削除してみる

はじめに S3でユーザーが投稿した画像を管理している場合、ユーザーがアプリを退会した際にユーザーに関する情報、S3からもユーザーのファイルを削除する必要性があります。 S3上のプレフィックスは userID/ となっており、全体のキーはbucket/userID/fileName.jpg となっています。   問題点 AWSの制約上、bucket/userID以下にファイルが1つでもあると、 いきなりbucket/userIDを削除することはできません。 加えて、リストを取得するListObjectsでは1度に1000個のキーか取得することができず、 オブジェクトの削除を行うDeleteObjectも1度に1000個のキーまでしか指定できません。 もしユーザーが1000個以上のファイルをアップロードしている場合、 一筋縄ではいかないため工夫する必要性があります。   コード   解説 ユーザーが投稿したファイルを全て取得する [crayon-5ade611ed3eaa120979202/] 最初のリクエストをする際に、Delimiter: aws.String(userID + "/"),をつけてリクエストをすることで、レスポンスにIsTruncatedが含まれています。 IsTruncatedがtrueだと全てを取得できず、まだ残りのオブジェクトがある状態 falseだと全てのオブジェクトを取得できた意味をします   [crayon-5ade611ed3eb4668069101/] 最初のリクエストでIsTruncatedがtrueの場合、 NextMarkerがレスポンスに含まれているのでこれを次のリクエストに含めておきます。 ループの中でこれらを繰り返しておき、 IsTruncatedがfalseになった時点で終了。この関数では[]stringを返します。   オブジェクトを削除する [crayon-5ade611ed3ec0356207819/] 関数全体↑   [crayon-5ade611ed3ec8329765659/] getAllObjectで取得したキーが入った配列の長さが1000以上だった場合、 先程述べた様に、1度のリクエストで削除できるキーが最大1000のため配列を分割する必要性があります。   [crayon-5ade611ed3ecb245877500/] func chunk()では1000要素以上の配列に入ったキーから1つの配列が1000個未満のキーとなるように複数の配列に分割して[][]stringを返します。   [crayon-5ade611ed3ece898968389/] この分割された[][]stringをfor-rangeでループさせ、画像を削除していきます。   [crayon-5ade611ed3ed6531790868/] bucket/userID 以下のファイルが0になったら、bucket/userIDを削除します   使い方 ちなみに画像数が5,000枚レベルになると、マシンのスペックにもよりますがAPIサーバーからアプリへレスポンスを返すまでに1分程度、かかってしまいとても使えたものではありません。 なので、今回はDBからユーザーの情報を削除する部分はGoroutineで制御し、 このS3から画像を削除する部分はレスポンスを返した後に実行されるようにしてみました。 [crayon-5ade611ed3ed9744471262/] DBからユーザー情報を削除する部分はerrgroup(goroutine)で制御を行って、 エラー処理ができるように。全ての処理が終わったあとに、   [crayon-5ade611ed3edb307373095/] WaitGroupをインクリメントしておき、wg.Wait()をせずにそのままreturn nilでレスポンスを返します。 ただ、これを行うとS3から画像を削除する部分でエラーを起こるとユーザーへ通知できなくなってしまうので、Slackへ通知するようにしてます。 (そして手作業で該当ユーザーのファイルを削除していく…)   おわり Go言語に自信が全く無い素人のコードを晒して大変恐縮していますが もし間違いや、こうした方がいいのご指摘がありましたらぜひコメントしていただけると本当に幸いです。

[WIP]Locustで初心者がAPIサーバーの負荷試験をやってみた

この記事はチラ裏です。僕の頭の中を整理するためだけの記事です。 寒いなぁ…寒いなあ… 卒論を提出して2/6にはパワポとレジュメを作成して発表です。   負荷をかけるツールはいっぱいある 便利な世の中なので負荷ツール(Load test tool)はたくさんあります。 ここでどれを使うかを選定しないといけないわけですがいくつか候補を挙げてみたいと思います。 JMeter(有名、高機能、リクエストがいっぱい出せる、知見多) ab(シンプル、知見多、手軽) httpperf(手軽、リクエストがいっぱい出せる) vegeta(リクエストがいっぱい出せる) hey(手軽) siege(使ったことない) goad(Lambdaから負荷をかけれる、色んなリージョンからアクセスできる、JSONのPOSTのやり方分からず、手軽) Locust(分散して負荷をかけれる、Web GUIがある) 分散しないで負荷をかける vegeta や hey では家のネットワークとか、1台のマシンだけじゃサーバー側のリソースを使い切ってくれないため除外。 JMeterは設定がダルい、勉強するのは少し面倒。 Locust は Qiita 等で取り上げられてるので知見は結構あります。 JMeter のようなシナリオを Python で書き、負荷をかけるサーバーも簡単に実行することができる。 ドキュメントもきちんと整備されているので今回は Locust を使ってみました。     Locust を使ってみる インストールも簡単です。 pip install locustio これだけです。 実行は locust -H host --master Slaveは locust -H host --slave --master-host=masterip Master は実際に負荷をかけず、集計や Web GUI を担当し、Slaveが実際の負荷を行います。 今回は c4.large のインスタンスを6つ建てて 1Master 6Slave で負荷をかけました。 画面が多いと負荷試験がやりやすい pic.twitter.com/QozJrFR3ag — るいす (@lu_iskun) 2018年1月29日 負荷試験中は、LB(Nginx), API, DBのリソース状況、各サーバーのログを表示しておき、 Nginx のコネクション数や、FD数などは Nginx Amplify を表示しておくと便利です。   今回は 10万ユーザーを作成し、秒間1万ユーザーずつ増加していく方法を取りました。   Charts には数値がグラフ化されています。 この図を見るだけで、RPSは最大3200、ユーザー数が増えるごとにレスポンスタイムが増えていっていることが分かります。   失敗したリクエストは Failures にまとめられています。 先程の図から推測すると、何らかの要因でレスポンスタイムがどんどん増えていき、サーバーがコネクションを切ってしまったんだと思います。     [crayon-5ade611ed4188593875075/] ルーターのネットワークインターフェースを見てみると LB, API, DB が載ってるサーバーからのパケットで、いくつかドロップされてるパケットがあります。 CDN に Cloudflare を挟んでいるのですが原因は Cloudflare ではなく完全にこちら側ってことが分かります。     コンテナで動かしているAPIサーバーに16スレッドを割り当ててますがうまいこと全部のスレッドを使っているようです。Go はメモリが全然消費されなくてすごいですねぇ ここを見ている限り、平均70%のCPU負荷で %iowait もそこまで高くないため(そもそもディスクの読み書きがないようなAPI)、LBかDBのような感じもします。   LBは2段あって、3段目にAPIがあるわけですがこの画像には写って無くて申し訳ないですが(画像は最上段LB)、最上段LBは HTTP Error が発生していましたが、2段目のLBでは HTTP Error…

Node.jsでjpgをwebPエンコードする

前回の記事の続きのような Lambda+S3+EC2を使用してできるだけ安くリアルタイムwebPエンコードをやってみる   node-cwebp を使う https://github.com/Intervox/node-webp cwebp のラッパーです。 cwebp は Google 公式のライブラリです。 使ってるコードをそのまま載せます。 対して長いコードではないので読むと分かりますが、パスに cwebp がなければディレクトリを直接見に行ってます。 前回の記事を見ていただければどれぐらい重い処理か分かってもらえると思います。

Lambda+S3+EC2を使用してできるだけ安くリアルタイムwebPエンコードをやってみる

S3にアップロードされる jpg画像をリアルタイムで webP にエンコードしたかったので 色々やってみた。   要件 S3 バケットには毎月1000万枚の2.2MB程度の jpg画像がアップロードされると仮定 これをアプリが引っ張る前にできるだけで速く webP にエンコードする必要性があります。 webP にエンコードする理由としては、画像の容量が小さくなることでユーザーのストレスを無くす、S3 にかかる料金を安くする、CloudFront の料金を安くするため。 ちなみに何でアプリでやらないかというと、iPhone で撮った生の画像を iPhone でエンコードを行うと、アプリがフリーズするぐらい結構重い処理だからです。致し方なし。   ① Lambda だけで全てやってみる S3 にアップロードをトリガーとして Lambda を起動して、Lambda でエンコードから S3 にアップロード、既存の jpg画像を削除を行うと、最小スペックで 22秒という脅威的な数値を叩き出します。 ちなみにメモリの使用量は 108/107MB 程度です。重いなあ… 全ての Lambda スペックで試し、一番安いスペックは 256MB でした。 これでも 11秒です。 256MB 枚数 秒数 課金 1枚辺り実行時間 無料枠 1秒辺り課金額 10000000 110000000 452.028 11 1600000 0.00000417 20000000 220000000 910.728 ちなみにひと月で 10000万枚を捌き切るにはこの Lambda 関数は並列で 42 個、必要になります。 $452 * 42 = 約210万… センキューAWS! これに CloudFront の料金も加算したらとんでもない額ですね。   ② エンコード用のサーバーを用意してみる not EC2 Lambda 関数を2つ用意し、Lambda – webP request(128MB) は Convert Server にある変換用サーバーにリクエストを送り、Convert Server がステータスコード 200 以外を返したときは、Lambda – webP encoder を呼ぶ係、Lambda – webP encoder は Convert Server  に代わってエンコードしてくれます。 Convert Server は Lambda の料金を節約するために、リクエストを受け取った瞬間に 200 を返すようにしています。サーバーにリクエストがくれば途中で天変地異や、タイミングよくサーバーが壊れる、リソース不足以外で、きちんと動作するようなコードであれば対策はできるであろうと思います。 この構成にしたところ、通常時(Lambda – webP encoderが呼ばれない)であれば、課金対象実行時間が 1,500ms となるので、Lambda だけだと { (1.5 * 10000000)…

S3をCloudFrontから配信&LambdaとWAFを組み合わせて利便性とセキュリティを確保してみる

この記事は、AWS Lambda Advent Calendar 2017 の5日目となります。 今回は S3 にある画像ファイルを CloudFront から配信をしつつ、 CloudFront のアクセスログを Lambda で解析を行って、WAF の IPリストへ追加を行って、セキュリティの担保を行ってみようと思います。   S3 を CloudFront から配信する理由 前提条件に、弊社の S3 バケットにはユーザー個人が撮影した画像がアップロードされます。 なのでセキュリティには気を置いてしっかり運用していく必要性があります。 AWS SDK を使用すれば S3 から署名付きURLが吐き出され、これにアクセスすれば画像を取得できます。が、この署名付きURLには有効期限が設定されており、アプリから使うにはちょっと面倒です。 S3 を CloudFront から配信をすると、WAF が使えるようになるので固定URLで配信しつつ、セキュリティも担保できます。   構成 ①ユーザーが CloudFront にアクセスを行う ②リクエストヘッダー等を WAF へ送る ③WAF にあるルールと照合し、結果を返す ④③がOKなら S3 から画像を持ってくる(NG なら 403 を返す) ⑤アクセスログを S3 へ保存する ⑥⑤のタイミングで、Lambda が発火する ⑦アクセスログに HTTPステータスコードが 200 以外のものがあったら、IPを WAF の IPリストに追加する Lambda 関連のアドベントカレンダーなのに、Lambda の部分を特筆するところがねぇ…   CloudFront ディストリビューションの作成 オリジンに、対象の S3 バケットを。 ログの設定も何となく分かると思います。 S3 は CloudFront からのみアクセスできるようにしておきます。 ブルートフォースアタック等で、キー(/user_id/hoge_id/file_name_.jpg みたいな)がバレないように、こうします。CloudFront + WAF を組み合わせればキーがバレる前にブラックリストに入れて、アクセスを拒否できます。   WAF web ACL の作成 AWS resource to associate には先程作成した CloudFront のディストリビューションを設定します。 ルールの作成部分では IP address で作成する。 文字列判定を使えば特定の文字列をヘッダーに載せるとアクセスできるなんて仕組みもできます。   こんな感じです。 最初に IPアドレス判定を行ってから、文字列判定のルールでヘッダー認証をします。順番が大事です。今回はなんちゃってヘッダー認証があるので、デフォルトアクションは全て 403 を返すようにしています。   Lambda スクリプトを作る 言語は好きなものを選んでください。 今回僕が書いて運用しているコードを貼っておきます。 IP_SET_ID には WAF -> IP addresses の自分で作成した空のルールにアクセスすると、URLにある /ipsets/<ここ> を指定します。…

CentOS7/UbuntuからWindows10のPCをリモートシャットダウンしてみる

Windows側 管理者権限+パスワード設定がしているアカウントを作成する(既にある場合はスキップ) TCP:445, 139 を開ける Linux側 コマンドを入れる Debian系:apt install samba-common RHEL系:yum install samba 実行する net rpc shutdown -I ipaddress -U "username%password" -f -t 0 -C "Shutdown"

RDS for MySQL でSELECT結果をCSVに吐き出す

機会があって調べていると一筋縄ではいかなかったので覚書。 2ヶ月ぶりの記事ですが普通に生きています。   INTO OUTFILE は使えない RDS for MySQL(Aurora) v5.6.10 では INTO OUTFILEを実行するとエラーで使えない ERROR 1045 (28000): Access denied for user 'xxxx'@'%' (using password: YES)   StackOverFlow の人気コマンドを実行するとレイアウトが崩れる [crayon-5ade611ed437a119519609/] これで吐き出される CSV は見るに堪えないものができてしまう。 MySQLのバージョンだったりが関係あるのかもしれない。   mysqldump-to-csv を使う https://github.com/jamesmishra/mysqldump-to-csv [crayon-5ade611ed4384369250666/] これを使うと何故かヘッダが付与されないけど そこは手動で。