BlankTar

about | blog | works | photo

メインのデスクトップにHDDを入れてみたのですが、動作がうるさくってたまらない。
ゲーム入れたりするのにしか使わない予定なので、普段は止まっていて欲しい。

というときに使うのが、hdparmというツールです。
gentooならportageで普通に入ります。OSによっては最初から入ってるかも。

# emerge hdparm

たとえば、5秒間アクセスが無かったらスタンバイに入る設定は以下のような感じ。

# hdparm -S 1 /dev/sda
/dev/sda:
 setting standby to 1 (5 seconds)

1のところを2にすれば10秒になるし、10にすれば50秒になります。
スタンバイを無効にしたいときは0に設定すればおっけー。

速攻で止めたいときは以下のような感じ。

# hdparm -y /dev/sda
/dev/sda:
 issuing standby command

これをやってもアクセスがあれば勝手に起動してくれます。
静かになった。

で、本題。
このままだとOSが再起動すると設定が消えてしまうので、永続化を自分でしなければなりません。

設定ファイルが/etc/conf.d/hdparmにあるので、これに例えば以下のように追記する。

sda_args="-S 1"

sdaのところをallにすれば全てのドライブに適用出来るみたい。

で、hdparmのサービスが自動起動するように設定する。

# rc-update add hdparm

以上、これだけ。
これで再起動しても5秒アクセスが無ければ自動でスタンバイに入ってくれるようになります。

参考: hdparm - Gentoo Wiki

うちのサイトはnginxで配信していて、さりげなくssl通信にも対応しています。
せっかく環境が揃っているので、いい加減重い腰を上げてHTTP2の通信に対応してみることにしました。

設定する

HTTP2のサポートが入っているバージョン1.9はまだ安定版の扱いを受けていないので、/etc/portage/package.keywordsに以下を追記します。

=www-servers/nginx-1.9.14 ~amd64

で、インストール。

# emerge nginx:mainline

USEフラグのhttp2は最初から有効になっていましたが、環境によっては分からないので確認してから入れた方が良いかも。

設定ファイルに変更があるっぽいので更新しておく。

# etc-update

最後。設定ファイルを以下のように変更します。

listen 443 ssl http2;

追記したのはhttp2の部分。
これだけで対応してくれるらしいです。

あとは再起動して完了。

# /etc/init.d/nginx restart

試してみる

導入してみても効果がよく分からないので、このサイトのトップページをwebpagetestで測ってみました。

まずは何も導入していないHTTP/1.1から。
http1.1での表示速度。

で、こちらがHTTP/1.1 over tls。
https1.1での表示速度。

最後、HTTP/2。
http2での表示速度。

Load Timeで言うとhttpが最速で0.955s、二番目がhttpsの1.325s、三番目がhttp2の1.428s。
つまり、どういうことかと言うと、SSLを導入すると遅くなる。

といっても、なんでか知らないけどgoogle analyticsのjsを読み込むのにhttp2のときだけやたらと時間掛かってたりするので、ちょっと不公平な気がしますね。
CSSが読み込み終わるのはhttpsよりもhttp2の方が若干速く終わっているので、SSLを使う前提ならhttp2の方が優位なのかもしれません。

まあぶっちゃけ。このサイトだとファイル数もファイルサイズも少なすぎてちょっとよく分かりませんという結論です。
極端に悪くなることは無さそうだから、とりあえず導入かなぁ…。

OpenCVとscikit-learnを組み合せて何か面白いことが出来ないか、なんて試行錯誤をしています。
画像の類似度を調べて何か出来そうな気がしたので、ひとまず類似度を調べてみることにしました。

画像が似ているかどうかを調べる方法として、Bag-of-Visual Wordsというのがあるそうです。Bag-of-Wordsっていう文書をベクトルとして表現する手法の応用だとか。
一枚一枚にベクトルを対応付けて、そのベクトルの距離で画像が似ているかどうかを判別するもののようです。
ま、難しいことは良いでしょう、とりあえず。

ここで掲載したプログラムを実行するにはpython3scikit-learnOpenCVnumpyが必要になります。適当にインストールしておいてください。

手順

大雑把に言うと、以下のような手順でVisual Wordというものを作ります。

  1. 全ての画像の特徴点(局所特徴量)を抽出する。
  2. 抽出した特徴量を全部まとめてクラスタリングする。
  3. クラスタリングで出来た代表点をVisual Wordとする。

で、その後以下のようにして個々の画像の特徴ベクトルを計算します。

  1. 特徴ベクトルが欲しい画像の特徴点を抽出する。
  2. 各特徴点と最も近いVisual Wordを探す。
  3. 見つけたVisual Wordに投票する。

投票ってのが謎ですが、要は特徴点をクラスタリングして、各クラスタに帰属した点の数を数えるイメージになります。

Visual Wordを作ってみる

で、ここからは実際に書いたものを見ていくことにします。
全部実装したソースコードは末尾にありますが、最適化が入っているので見た目が違ったりします。ご了承ください。

特徴点を抽出する

特徴点の抽出にはAKAZEってやつを使いました。何か性能がとても良いらしい。
この特徴点の抽出アルゴリズムが非常に重要らしいので、色々試してみると良いかと思います。ちなみに私は試していません。

akaze = cv2.AKAZE_create()

features = []
for img in images:
	features.extend(akaze.detectAndCompute(img, None)[1])

だいたいこんな感じです。imagesはOpenCV形式の画像データが入った配列と思ってください。
detectAndComputeを使うと二次元の配列として特徴点の情報を返してくれるのでとても便利。

p.s. 2016-03-04

コードの誤りを修正しました。

クラスタリングする

visual_words = MiniBatchKMeans(n_clusters=128).fit(features).cluster_centers_

どかーんとsklearnがやってくれます。素敵。
n_clustersというパラメータがクラスタの数になります。前述の通りクラスタの数はそのまま特徴ベクトルの次元数になりますので、この例だと128次元ということになります。
画像枚数にもよりますが、もっと次元を増やした方が良いような気がします。

特徴ベクトルを計算してみる

なんとこれだけでVisual Wordが出来てしまいました。もう全て終わったも同然です。
仕上げに特徴ベクトルを計算してみます。

features = akaze.detectAndCompute(img, None)[1]

vector = numpy.zeros(len(visual_words))
for f in features:
	vector[((visual_words - f)**2).sum(axis=1).argmin()] += 1

出来ました。心無しかややこしい処理が入っている気がしますが、気のせいです。
個々の特徴点について、全てのVisual Wordとの距離を計算して、一番近いものに投票する、というような手順になっています。

ここまでで必要なソースはほぼ出揃ったことになります。
理論を調べていると頭がごちゃごちゃしてくるのですが、手順は結構簡単で素敵ですね。

計算した特徴ベクトル同士の距離が近い画像は似ている画像、離れている画像は似ていない画像、ということになります。
なので、似ている画像が欲しいときはベクトルが近い画像を探せば良いことになります。

実行してみた

Caltech 101という画像データセットを使わせて頂いて実験をしてみました。
入っていた9,145枚の画像を全て学習させてもそこそこの時間で終わりました。結構速い。
なお、学習用のデータとテスト入力のデータには同一のものを仕様しました。適当です。

以下の画像を同じものを探してみます。
入力に使った太極図

類似度で上位20件の画像が以下の通り。

適当に書いたプログラムですが、そこそこの精度が出ているようです。良い感じ。
パンダが出ているあたり色で見ているように思えますが、特徴点を使う方法では色は関係無い、はず。多分。

おまけ。ソースコード

以下は軽く高速化を行なったプログラムになります。無尽蔵にキャッシュするようになっているので、そこそこのメモリが必要になるかもしれません。

import functools
import pathlib
import shutil

from sklearn.cluster import MiniBatchKMeans
import cv2
import numpy


input_dir = '/path/to/images/'
output_dir = '/path/to/save/'

akaze = cv2.AKAZE_create()
images = tuple(pathlib.Path(input_dir).glob('*.jpg'))


@functools.lru_cache(maxsize=1024)
def read_image(path, size=(320, 240)):
	img = cv2.imread(str(path))
	if img.shape[0] > img.shape[1]:
		return cv2.resize(img, (size[1], size[1]*img.shape[0]//img.shape[1]))
	else:
		return cv2.resize(img, (size[0]*img.shape[1]//img.shape[0], size[0]))


@functools.lru_cache(maxsize=None)
def load_kps(path):
	return akaze.detectAndCompute(read_image(path), None)[1]


def detect_all(verbose=False):
	for i, path in enumerate(images):
		if verbose:
			print('read {0}/{1}({2:.2%}) {3}'.format(i+1, len(images), (i+1)/len(images), path))

		try:
			yield from load_kps(path)
		except TypeError as e:
			print(e)


def make_visual_words(verbose=False):
	features = numpy.array(tuple(detect_all(verbose=verbose)))
	return MiniBatchKMeans(n_clusters=128, verbose=verbose).fit(features).cluster_centers_


def make_hist(vws, path):
	hist = numpy.zeros(vws.shape[0])
	for kp in load_kps(path):
		hist[((vws - kp)**2).sum(axis=1).argmin()] += 1
	return hist


def find_nears(vws, hist, n=5, verbose=False):
	nears = []
	for i, path in enumerate(images):
		if verbose:
			print('read {0}/{1}({2:.2%}) {3}'.format(i+1, len(images), (i+1)/len(images), path))

		try:
			h = make_hist(vws, path)
		except TypeError:
			continue

		nears.append((((h - hist)**2).sum(), h, path))
		nears.sort(key=lambda x:x[0])
		nears = nears[:n]
	return nears


if __name__ == '__main__':
	vws = make_visual_words(True)

	path = images[0]
	img = read_image(path)
	hist = make_hist(vws, path)

	nears = find_nears(vws, hist, n=20, verbose=True)
	for x in nears:
		print('{0:.2f} - {2}'.format(*x))
		shutil.copy(str(x[2]), '{0}{1:.2f}.jpg'.format(output_dir, x[0]))

参考:
Visual Wordsを用いた類似画像検索 - 人工知能に関する断創録
Bag of Visual Words - n_hidekeyの日記

[ << ] [ 9 ] [ 11 ] [ 13 ] [ >> ]