Similar image @ Canvas

前回 Gjs でやった近似画像検索を Web ブラウザで。

Web で画像のピクセルデータ取得は意外に簡単だった。
…けれど困った結果になってしもーたがね。

とりあえず
16 3月 2014 | while(isプログラマ)
なるほど、Image と Canvas だけでできるのか。
つまり外部ライブラリは必要無いと。

[3][HTML][canvas]canvasで画像解析 – wataメモ
ピクセル毎のデータはこんなにアッサリ得られるようだ。

ただ Web での画像読み込みはローカルファイルが使えない。
下記コードを試すには Apache 等を使う必要あり。

更に有名な話で、ブラウザの画像読み込みは非同期である。
処理は onload ハンドラから行うのを忘れずに。

現行ブラウザはまだ let が使えないので var に書き換えて。
それと webkit では sort 関数の戻り値は整数にする必要があった。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Similar Image Sample</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    -webkit-text-size-adjust: 100%;
}
</style>
<script type="text/javascript"><!--

    const XY = 48;

    function init() {
        // 画像のロードは非同期なので onload 内で処理
        // ローカルファイルは読み込めないので Apache 等で
        var sample = new Image();
        sample.onload = function() {
            // ロードしたので Lab 値を得る
            var sample_lab = getImageLab(sample);
            // 比較対象
            var datas = ["nae01.jpg","nae02.jpg","nae03.jpg","nae04.jpg","nae05.jpg"];
            var diff = [];
            datas.forEach(function(element, index) {
                var data = new Image();
                data.onload = function() {
                    var lab = getImageLab(this);
                    // 比較
                    var distance = 0;
                    sample_lab.forEach(function(element, index) {
                        distance += getLabDistance(element, lab[index]);
                    });
                    diff.push({n:distance, name:datas[index]});
                    // 5枚ともロードが完了したら出力
                    if (diff.length == 5) {
                        output(diff);
                    }
                }
                data.src = element;
            });
        }
        sample.src = "nae.jpg";
    }

    function output(diff) {
        // sort
        diff.sort(function(a, b) {
            //return a.n > b.n; // no webkit
            return a.n - b.n;
        });
        // 確認
        var out = "";
        diff.forEach(function(element, index) {
            out += element.name + " " + element.n + "<br />";
        });
        document.getElementById("ID_TEXT").innerHTML += out;
    }

    // 画像をリサイズしピクセルごとのLab色空間上の座標を取得する
    function getImageLab(image){
        //
        var canvas = document.createElement("canvas");
        canvas.width = canvas.height = XY;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(image, 0, 0, XY, XY);
        // 取り出し、コレは同期する
        var pixeldata = ctx.getImageData(0, 0, XY, XY);
        var lab = [];
        for(var i=0; i<XY; i++){
            for(var j=0; j<XY; j++){
                var r = pixeldata.data[j*4 + i*XY*4];
                var g = pixeldata.data[j*4 + i*XY*4 + 1];
                var b = pixeldata.data[j*4 + i*XY*4 + 2];
                lab.push(rgb2lab([r, g, b]));
            }
        }
        document.getElementById("ID_CANVAS").appendChild(canvas);
        return lab;
    }

    // xyz色空間上の座標をlab色空間上の座標に変換する
    function xyz2lab(xyz) {
        var threshold = 0.008856;

        var ref_x = 0.96422;
        var ref_y = 1.0000;
        var ref_z = 0.82521;

        var var_x = xyz[0] / (ref_x * 100);
        var var_y = xyz[1] / (ref_y * 100);      
        var var_z = xyz[2] / (ref_z * 100);
          
        var_x = (var_x > threshold) ? var_x = Math.pow(var_x, 1/3 ) : (7.787 * var_x) + (16 / 116);
        var_y = (var_y > threshold) ? var_y = Math.pow(var_y, 1/3 ) : (7.787 * var_y) + (16 / 116);
        var_z = (var_z > threshold) ? var_z = Math.pow(var_z, 1/3 ) : (7.787 * var_z) + (16 / 116);

        var l = ( 116 * var_y ) - 16;
        var a = 500 * ( var_x - var_y );
        var b = 200 * ( var_y - var_z );
        return [l, a, b];
    }
     
    // rgb値をxyz色空間上の座標に変換する
    function rgb2xyz(rgb) {
        var r = rgb[0] / 255;
        var g = rgb[1] / 255;
        var b = rgb[2] / 255;
     
        r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
        g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
        b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
     
        r = r * 100;
        g = g * 100;
        b = b * 100;
          
        var xyz = [];
          
        //sRGB D50
        xyz.push(r * 0.4360747 + g * 0.3850649 + b * 0.1430804);
        xyz.push(r * 0.2225045 + g * 0.7168786 + b * 0.0606169);
        xyz.push(r * 0.0139322 + g * 0.0971045 + b * 0.7141733);
        return xyz;
    }
     
    // rgb値をlab色空間上の座標に変換する
    function rgb2lab(rgb) {
        var xyz = rgb2xyz(rgb);
        var lab = xyz2lab(xyz);
        return lab;
    }
     
    // 2つの座標を比較し距離を返す
    function getLabDistance(p1, p2){
        return Math.sqrt( Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[2] - p1[2], 2) );
    }
    //-->
</script>

</head>
<body onLoad="init()">
<div id="ID_TEXT" style="font-size:12pt"></div>
<div id="ID_CANVAS"></div>
</body>
</html>

blink_draw

webkit_draw

Gjs の時と全然順番が違うんですけど。
blink と webkit も数値が全然違う。
いや iPhone 版 Chrome は Safari と同じ数値になってしまう。
それより縮小率によって順番がコロコロ変わるのは頂けない。
色々とどういうことだってばよ。

Gjs は縮小率を変えても順番はほぼ変わらないようです。
圧縮手段が違うのがそんなに影響するのかな。

GdkPixbuf で上記と同じ 24x24px の画像を作って比較してみる。

#!/usr/bin/env python3

from gi.repository import Gtk, Gdk, GdkPixbuf
  
class DrawWin(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.pixs = []
        for s in ["nae.jpg", "nae01.jpg","nae02.jpg","nae03.jpg","nae04.jpg","nae05.jpg"]:
            p = GdkPixbuf.Pixbuf.new_from_file(s)
            pixbuf = GdkPixbuf.Pixbuf.scale_simple(p, 24, 24, GdkPixbuf.InterpType.BILINEAR)
            self.pixs.append(pixbuf)
        # self
        self.resize(24*6, 24)
        self.connect("delete-event", Gtk.main_quit)
        self.show_all()

    def do_draw(self, cr):
        offset = 0
        for pixbuf in self.pixs:
            Gdk.cairo_set_source_pixbuf(cr, pixbuf, offset, 0)
            offset += 24
            cr.paint()
 
DrawWin()
Gtk.main()

って、小さすぎてわかんないや。
両方のスクリーンショットを撮って eog で拡大比較。

pixbuf_and_blink

なんだこれ、blink 雑すぎ!
これじゃ縮小率でコロコロ順番が変わって当然。
webkit も多分同じなんだろうな。

ということで、この方法は使えない。
同一画像がゼロにはなるので同一画像検索には使える。
って、それでいいならもっと簡単な手段があるってば。

縮小しなければなんとかなりそうだけど色々と効率が悪いかも。
外部ライブラリに頼るか別の手段にするか、うーん。。。