同一画像検索(4): 簡易版を作ってみた
これまでの確認などを踏まえ、今回は簡易版を作ろう。仕様では4x4解像度の画像比較をして 一致したら次に16x16解像度でもう少し詳細に比較する、としているところ、まずは4x4解像度での 比較だけで作ってみようと思う。
最初に、「外枠」で示したfindSame
関数の型シグネチャに問題があったので訂正する。
これまで示したのは下記の形だった。
findSame :: [String] -> [[String]]
画像比較のためImageMagickを呼び出す仕様なので、どうしても副作用が発生する。
haskellではそれが含まれる場合はどうやっても純粋な(副作用のない)型シグネチャには
できなさそうだ。(言われてみれば、その関数の処理のどこか一部に副作用があるなら
全体として関数の処理結果が引数だけで一意に決まらないのは当然か。)
下記が訂正版とその呼び出し元main
の変更部分。
findSame :: [String] -> IO [[String]] : main = do ds <- getArgs fs <- mapM getFileLists ds ss <- findSame $ concat fs putGroups ss
さて、肝心のfindSame
をどうするかだが、処理の流れは次の通りかと。
- 各画像のfingerprint(前々回説明)を取得(ここでImageMagickを使う)
- 同じfingerprintを持つ画像群をListにまとめる
- 同じものが見つかった(=Listの長さが2以上)ら、そういった画像群のListを返す
これを関数にしてみる。プログラムが上の"処理の流れ"そのまんまなのはさすが haskellといったところか。なお、以後のコード片ではファイル名をStringではなく 別名のFilePath(Preludeで定義済み)、fingerprintもByteStringではなく FingerPrint型とした。
findSame :: [FilePath] -> IO [[FilePath]] findSame fs = do fps <- mapM getFingerPrint4 fs let es = Map.elems $ foldl insertItem Map.empty (zip fps fs) return $ filter (\x -> length x > 1) es
getFingerPrint4
は4x4解像度のfingerprintを取得する関数だが、実際は
解像度を引数で与えるgetFingerPrint
に第一引数を4としたもの。この辺も
関数型言語らしい。
getFingerPrint :: Int -> FilePath -> IO FingerPrint getFingerPrint r f = do (sin, sout, serr, ph) <- runInteractiveCommand command waitForProcess ph BS.hGetLine sout where geo = (show r) ++ "x" ++ (show r) size = r * r * 3 command = "convert -define jpeg:size=" ++ geo ++ " -filter Cubic -resize " ++ geo ++ "! " ++ f ++ " PPM:- | tail -c " ++ (show size) getFingerPrint4 = getFingerPrint 4
insertItem
は前回書いたままである。万が一、全ソースに興味がある方は
GitHubをどうぞ。
早速実行して試してみよう。以下がサンプルで使った画像(の25%縮小版)。
オリジナル(IMG_0309.jpg)、その単純なコピー(IMG_0309-2.jpg)、
50%縮小版(IMG_0309-3.jpg)、25%縮小版(IMG_0309-4.jpg)を用意して試した。
結果は惨敗。
$ ghc -o picf Main.hs [1 of 2] Compiling Finder ( Finder.hs, Finder.o ) [2 of 2] Compiling Main ( Main.hs, Main.o ) Linking picf ... $ ./picf ~/work probably same: work/IMG_0309.jpg, work/IMG_0309-2.jpg
同じサイズの画像しか同一とみなしていない。オリジナルと50%版のfingerprintを 比べてみる。
$ convert -define jpeg:size=4x4 -filter Cubic -resize 4x4! ~/work/IMG_0309.jpg PPM:- | tail -c 48 | od -x 0000000 4f47 5d52 6561 9996 bd9b c6c1 3d3e 4d36 0000020 3b43 6171 7e59 7476 555d 5a40 3b4c 596f 0000040 7f47 5970 707a 7b4f 4f6e 7485 8c54 5a7c 0000060 $ convert -define jpeg:size=4x4 -filter Cubic -resize 4x4! ~/work/IMG_0309-3.jpg PPM:- | tail -c 48 | od -x 0000000 4f47 5d52 6561 9996 bd9b c5c1 3d3e 4d37 0000020 3b43 6171 7e59 7476 555d 5a40 3b4c 596f 0000040 7f47 5970 707a 7b4e 4f6e 7485 8c54 5a7c 0000060
違う箇所が3つあるので3つの画素でRGBのうち一色だけ1/256の差があると。。。 本当に微妙な差だが、fingerprintが違うのだから「別の画像」ですね・・・。
さてどうしたものか。ちょっと考えてみて、結果は次回で報告。