1. ホーム
  2. Web制作
  3. html5

画像処理におけるキャンバスの活用

2022-01-14 01:29:30

先日、前任者が面白いプロジェクトを薦めてくれました。TensorFlow.jsとcanvasの画像処理を使って、動画中の人の消失を実現する「Real-Time-Person-Removal」です。この機会に、canvasでの画像処理の基本をおさらいしておこうと思います。

基本API

canvasの画像処理機能は、ImageDataオブジェクトを通してピクセルデータを扱います。主なAPIは以下の通りです。

  • createImageData():空のImageDataオブジェクトを作成します。
  • getImageData():キャンバスのピクセルデータを取得します。
  • putImageData() : ピクセルデータをキャンバスに書き込む

imageData = {
  width: Number,
  height: Number,
  data: Uint8ClampedArray
}

width はキャンバスの幅、つまり x 軸のピクセル数、height はキャンバスの高さ、つまり y 軸のピクセル数、data はキャンバスのピクセルデータの配列で、全長 w * h * 4、4 つの値(rgba)はそれぞれ 1 ピクセルに対応します。

画像の処理

canvasの基本的な画像処理機能を、いくつかの例で見てみましょう。

オリジナル画像効果。

const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");
const img = new Image();
img.src = "Image URL";
img.onload = function () {
  ctx.drawImage(img, 0, 0, w, h);
}

ネガティブ/マイナス効果

アルゴリズム:255とピクセルポイントのrgbの差を現在値として取る。

function negative(x) {
  let y = 255 - x;
  return y;
}

レンダリング

const imageData = ctx.getImageData(0, 0, w, h);
const { data } = imageData;
let l = data.length;
for(let i = 0; i < l; i+=4) {
  const r = data[i];
  const g = data[i + 1];
  const b = data[i + 2];
  data[i] = negative(r);
  data[i + 1] = negative(g);
  data[i + 2] = negative(b);
}
ctx.putImageData(imageData, 0, 0);

モノクローム効果

モノクローム効果とは、現在のピクセルのrgbの3つの値のうち1つを保持し、他の色の値を削除するものです。

for(let i = 0; i < l; i+=4) { // remove the values of r, g
  data[i] = 0;
  data[i + 1] = 0;
}

レンダリング

グレースケール画像

グレースケール。1画素に1つの色値しかない画像。0から255の色値で、黒から白まである。

for(let i = 0; i < l; i+=4) {
  const r = data[i];
  const g = data[i + 1];
  const b = data[i + 2];
  const gray = grayFn(r, g, b);
  data[i] = gray;
  data[i + 1] = gray;
  data[i + 2] = gray;
}

アルゴリズム1 - 平均化。

const gray = (r + g + b) / 3;

レンダリング

アルゴリズム2 - 人間の目の知覚:人間の目が赤、緑、青をどの程度知覚しているかに基づいて、緑 > 赤 > 青に、重み付けをした分割を行う。

const gray = r * 0.3 + g * 0.59 + b * 0.11

レンダリング

これ以外にも、あります。

最大値または最小値をとります。

const grayMax = Math.max(r, g, b); // large value, brighter
const grayMin = Math.min(r, g, b); // small value, darker

1つのチャンネル、すなわちrgbの3つの値のうちの1つを取る。

バイナリグラフ

アルゴリズム 色値を決定し、現在のrgb値と比較し、これより大きい値には黒を、そうでなければ白を表示します。

for(let i = 0; i < l; i+=4) {
  const r = data[i];
  const g = data[i + 1];
  const b = data[i + 2];
  const gray = gray1(r, g, b);
  const binary = gray > 126 ? 255 : 0;
  data[i] = binary;
  data[i + 1] = binary;
  data[i + 2] = binary;
}

レンダリング

ガウシアンブラー

ガウスぼかしは、ぼかしアルゴリズムの1つで、各画素値は、周囲の隣接する画素値の加重平均となります。元のピクセル値は最大のガウス分布を持ち(最大の重みを持ち)、隣接するピクセルは元のピクセルから離れるにつれて小さな重みを持つようになります。

一次方程式です。

(一次式の方がアルゴリズムが単純なので一次式が使われる)

const radius = 5; // fuzzy radius
const weightMatrix = generateWeightMatrix(radius); // weight matrix
for(let y = 0; y < h; y++) {
  for(let x = 0; x < w; x++) {
    let [r, g, b] = [0, 0, 0];
    let sum = 0;
    let k = (y * w + x) * 4;
    for(let i = -radius; i <= radius; i++) {
      let x1 = x + i;
      if(x1 >= 0 && x1 < w) {
      let j = (y * w + x1) * 4;
      r += data[j] * weightMatrix[i + radius];
      g += data[j + 1] * weightMatrix[i + radius];
      b += data[j + 2] * weightMatrix[i + radius];
      sum += weightMatrix[i + radius];
      }
    }
    data[k] = r / sum;
    data[k + 1] = g / sum;
    data[k + 2] = b / sum;
  }
}
for(let x = 0; x < w; x++) {
  for(let y = 0; y < h; y++) {
    let [r, g, b] = [0, 0, 0];
    let sum = 0;
    let k = (y * w + x) * 4;
    for(let i = -radius; i <= radius; i++) {
      let y1 = y + i;
      if(y1 >= 0 && y1 < h) {
        let j = (y1 * w + x) * 4;
        r += data[j] * weightMatrix[i + radius];
        g += data[j + 1] * weightMatrix[i + radius];
        b += data[j + 2] * weightMatrix[i + radius];
        sum += weightMatrix[i + radius];
      }
    }
    data[k] = r / sum;
    data[k + 1] = g / sum;
    data[k + 2] = b / sum;
  }
}
function generateWeightMatrix(radius = 1, sigma) { // sigma standard deviation of normal distribution
  const a = 1 / (Math.sqrt(2 * Math.PI) * sigma);
  const b = - 1 / (2 * Math.pow(sigma, 2));
  let weight, weightSum = 0, weightMatrix = [];
  for (let i = -radius; i <= radius; i++){
    weight = a * Math.exp(b * Math.pow(i, 2));
    weightMatrix.push(weight);
    weightSum += weight;
  }
  return weightMatrix.map(item => item / weightSum); // normalize
}


レンダリング

その他の効果

ここでは、その他の画像効果処理について簡単に説明します。例は単純で繰り返しが多いため、コードと効果画像は改めて示さない。

  • 明るさ調整:rgbの値を取り、それぞれに所定の値を加算する。
  • 透明度の処理:rgba 値の a 値を変更します。
  • コントラスト強調:rgb値をそれぞれ2倍し、所定の値を差し引く。

概要

さて、以上が基本的な画像処理アルゴリズムです。

参考文献

ガウスぼかしのアルゴリズム
ガウシアンブラー

キャンバス画像処理の活用についての記事は以上です。キャンバス画像処理の詳細については、スクリプトハウスの過去記事を検索していただくか、引き続き以下の記事をご覧ください。