斜めに写った画像をCanvasで矩形に補正する

将棋駒画像分類の話の続きのような、あんまり関係もないような。

memo.sugyan.com

memo.sugyan.com

結局、素材を組み合わせて自動で生成しただけの駒画像ではやはりデータが足りていないようで、「やはりもっと様々な画像から人力でラベル付けしてデータセットを作っていく必要がありそう」ということになった。

とはいえ、インターネットから画像を拾ってこようと思うと、例えば以下のような感じで

f:id:sugyan:20180903172533j:plain

f:id:sugyan:20180903172543j:plain

f:id:sugyan:20180903172555j:plain

(引用元: フリー写真素材ぱくたそ)

多少ならともかく 斜めの角度から写っているものは、そのまま矩形に切り出して学習用画像データに利用するのは難しそう。 これらはうまいこと変形して使いたい。 いわゆるperspective projectionの逆変換のような操作が必要になる。

JavaScriptを使ったCanvas APIでの変換では簡単な拡大・縮小などの変換は可能だけど こういったperspectiveなtransformationは無理っぽい。OpenCVなどの画像処理ライブラリを使えば可能そうだけど、できればやっぱりJSでグリグリ動かしながら出来ると嬉しいな、と思って探してみた。

OpenCVEmscriptenを使ってビルドすればJSで利用できるのだけど、

今回みたいなちょっとした変換だけのためにわざわざこれをやるのも過剰だよね…

ということで探し当てたのが、glfx.js

WebGLなどを上手いこと使って様々な画像変換処理をしてくれるライブラリ、のようだ。 perspective というAPIが含まれていて、デモもある。

よーし これを使うぞ!と思ったものの 最終更新が5年前とかで TypeScriptで利用するためのdefinitionsも無い。 仕方ないので見様見真似で自分が使いたいAPIの部分だけ書いてみた。

declare module 'glfx' {
    function canvas(): Canvas;

    export class Canvas extends HTMLCanvasElement {
        draw: (texture: Texture, width?: number, height?: number) => Canvas;
        perspective: (before: number[], after: number[]) => Canvas;
        update: () => Canvas;
        getPixelArray: () => Uint8Array;
        texture: (image: HTMLImageElement) => Texture;
    }
    export class Texture {
    }
}

これで、TypeScriptからでも

import * as fx from "glfx";

...

const canvas: fx.Canvas = fx.canvas();
const texture: fx.Texture = canvas.texture(img);
canvas
    .draw(texture)
    .perspective(src, dst)
    .update()

のような形で Image elementからtextureを読み込み、変形させた結果を描画できる。 getPixelArray()Uint8Array を取り出し new ImageData(new Uint8ClampedArray(data), size, size)ImageData オブジェクトを作ってやれば その内容を別のcanvas内にコピーするようなこともできる。

こうして最初の例の画像は以下のように矩形に補正された盤面画像を得ることが出来るし、

f:id:sugyan:20180903205513p:plain

マウス操作でグリグリ動かしながら変形パラメータを調整することが出来る。 パフォーマンス的にも不満は無い。

f:id:sugyan:20180903210714g:plain

あとは指定した数で分割すればマスごとの画像を取得できるので、

f:id:sugyan:20180903211908p:plain

(↑の右下部分が分割結果)

こうして得た一つ一つのマスの画像にラベルを付けていけばデータセットが作りやすいんじゃないだろうか(まだそこまでは出来ていない)。

実際に動かして試せるデモはこちら