Similar image @ Gjs

あるアプリを作るのに近似画像を探す処理が必要になった。
Lab色空間 – Wikipedia
を得て元画像の全体ピクセル近似値を調べればいいと解った。

[PHP]似た画像を検索して近い順番に並べる(類似画像検索) | PHP Archive
手段を見つけた、PHP だけど計算方法はどの言語でも同じだろう。
ただ GD を利用しているので他言語では別の画像解析手段が必要。

ということで、同様な処理を Gjs(Linux) でやってみる。
後で osascript(Mac) 版や Web 版を作りたくなった場合に流用できるので。

Gjs だから画像は当然 GdkPixbuf を GD の代わりに使う。
とはいえ GD と同じ関数があるはずもなく違う手段で同様な処理を作る。

画像処理のサンプル一覧
imagecolorat は gdk_pixbuf_get_pixels からオフセット。
imagecolorsforindex は rowstride 値を使って取得。
実は随分前からコレで詰まっていたおかげでブログの更新が…

正直上手く変換できた自信は無いけどなんとかなったっぽいコード。

#!/usr/bin/gjs

const GdkPixbuf = imports.gi.GdkPixbuf;

// 比較元ファイル
let sample = GdkPixbuf.Pixbuf.new_from_file("nae.jpg");
let sample_lab = getImageLab(sample);

// 比較対象ファイル名の配列
let datas = ["nae01.jpg","nae02.jpg","nae03.jpg","nae04.jpg","nae05.jpg"];

let diff = [];
datas.forEach(function(element, index) {
    let data = GdkPixbuf.Pixbuf.new_from_file(element);
    let lab = getImageLab(data);
    // 比較
    let distance = 0;
    sample_lab.forEach(function(element, index) {
        distance += getLabDistance(element, lab[index]);
    });
    diff.push({n:distance, name:datas[index]});
});

// sort
diff.sort(function(a, b) {
    return a.n > b.n;
});

// 確認
diff.forEach(function(element, index) {
    print(element.name, element.n);
});

// 画像をリサイズしピクセルごとのLab色空間上の座標を取得する
function getImageLab(image){
    let thumb = image.scale_simple(4, 4, GdkPixbuf.InterpType.BILINEAR)
    let pixeldata = thumb.get_pixels();
    let rowstride = thumb.get_rowstride();
    let lab = [];
    for(let x=0; x<4; x++){
        for(let y=0; y<4; y++){
            let i = y * rowstride + x * 3;
            lab.push(rgb2lab([pixeldata[i], pixeldata[i+1], pixeldata[i+2]]));
        }
    }
    return lab;
}

// xyz色空間上の座標をlab色空間上の座標に変換する
function xyz2lab(xyz) {
    let threshold = 0.008856;
      
    //Chromatic Adaptation Matrices
    // D50
    let ref_x = 0.96422;
    let ref_y = 1.0000;
    let ref_z = 0.82521;
 
    let var_x = xyz[0] / (ref_x * 100);
    let var_y = xyz[1] / (ref_y * 100);      
    let 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);
      
 
    let l = ( 116 * var_y ) - 16;
    let a = 500 * ( var_x - var_y );
    let b = 200 * ( var_y - var_z );
    return [l, a, b];
}
 
// rgb値をxyz色空間上の座標に変換する
function rgb2xyz(rgb) {
    let r = rgb[0] / 255;
    let g = rgb[1] / 255;
    let 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;
      
    let xyz = new Array();
      
    //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) {
    let xyz = rgb2xyz(rgb);
    let lab = xyz2lab(xyz);
    return lab;
}
 
// 2つの座標を比較し距離を返す
function getLabDistance(p1, p2){
    let dist = Math.sqrt( Math.pow(p2[0] - p1[0], 2) + Math.pow(p2[1] - p1[1], 2) + Math.pow(p2[2] - p1[2], 2) );
    return dist;
}

similar_pic

苗ちゃんがカワイイのは当然でどうでもよくて。
同一画像なら完全にゼロ、後は色が似た順に取得できた。
GdkPixbuf なので C や Python でも同じことができるはず。

後は osascript はともかく Web で画像解析はどうするかだ。
というか Web アプリでやりたいんですけどね。