Keras.js によるランドマーク検出の Web アプリケーション化2
前回 はランドマーク検出対象の画像サイズを固定(256x256)しましたが、今回は任意の画像サイズに対応できるように改造してみます。
ソースは http://github.com/fits/try_samples/tree/master/blog/20190506/
可変サイズ対応
ドラッグアンドドロップした画像のサイズに合わせてランドマーク検出を実施するようにしてみます。(ファイル構成などは 前回 と同じ)
ただ、Keras.js を通常とは異なる使い方をするため、何らかの不都合が生じるかもしれませんし、別バージョンでは動作しないかもしれません。
(a) UI 処理(src/app.js)
canvas のサイズを画像サイズに合わせて変更し、ランドマーク検出処理へ画像サイズ(幅、高さ)の情報を渡すようにします。
src/app.js
・・・ const loadImage = url => new Promise(resolve => { const img = new Image() img.addEventListener('load', () => { // canvas のサイズを画像サイズに合わせて変更 canvas.width = img.width canvas.height = img.height ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.drawImage(img, 0, 0) const d = ctx.getImageData(0, 0, canvas.width, canvas.height) resolve({width: img.width, height: img.height, data: imgToArray(d)}) }) img.src = url }) ・・・ const ready = () => { ・・・ canvas.addEventListener('drop', ev => { ev.preventDefault() canvas.classList.remove('dragging') const file = ev.dataTransfer.files[0] if (imageTypes.includes(file.type)) { clearLandmarksInfo() const reader = new FileReader() reader.onload = ev => { loadImage(reader.result) .then(d => { detectDialog.showModal() // 画像のサイズ情報を追加 worker.postMessage({type: 'predict', input: d.data, width: d.width, height: d.height}) }) } reader.readAsDataURL(file) } }, false) } ・・・
(b) ランドマーク検出処理(src/worker.js)
通常は(Keras.js の)モデル内でレイヤー毎の入出力の形状が固定化されているので、このままでは任意の画像サイズには対応できません。
そこで、検出処理の度に入出力の形状を強制的にリセットする処理(以下)を加える事で可変サイズに対応します。
- (1) 入力データの形状(画像サイズ)を変更
- (2) 各レイヤーの出力形状をクリア
- (3) inputTensorsMap のリセット
src/worker.js
・・・ onmessage = ev => { switch (ev.data.type) { ・・・ case 'predict': const inputLayerName = model.inputLayerNames[0] const outputLayerName = model.outputLayerNames[0] const w = ev.data.width const h = ev.data.height // (1) 入力データの形状(画像サイズ)を変更 const inputLayer = model.modelLayersMap.get(inputLayerName) inputLayer.shape[0] = h inputLayer.shape[1] = w // (2) 各レイヤーの出力形状をクリア model.modelLayersMap.forEach(n => { if (n.outputShape) { n.outputShape = null n.imColsMat = null } }) // (3) inputTensorsMap のリセット model.resetInputTensors() const data = {} data[inputLayerName] = ev.data.input Promise.resolve(model.predict(data)) .then(r => { const shape = model.modelLayersMap.get(outputLayerName) .output.tensor.shape return new KerasJS.Tensor(r[outputLayerName], shape) }) .then(detectLandmarks) .then(r => postMessage({type: ev.data.type, output: r}) ) .catch(err => { console.log(err) postMessage({type: ev.data.type, error: err.message}) }) break } }
動作確認
(1) 画像サイズ 128x128
(2) 画像サイズ 307x307
(3) 画像サイズ 100x128
(4) 画像サイズ 200x256