みさご解体新書

メディアンフィルタ

実行例

ソースコード

TypeScript

0.jpg / app.ts

解説/アルゴリズム

メディアンフィルタは画像にあるノイズを取り除くアルゴリズム。

全てのピクセルを走査し、下記の処理を実行する。

17, 8, 4, 2, 130, 21, 10, 5, 19

自身を含めた周り 3x3 ピクセルの明るさを取り出す。

2, 4, 5, 8, 10, 17, 19, 21, 130

値を昇順、あるいは降順にソートする。

2, 4, 5, 8, [10], 17, 19, 21, 130

中央にある明るさの値を選択する。

この選択した明るさの値を計算した元々のピクセルの色を自身のピクセルの色として採用する。

同じ画像のピクセルに計算結果を上書きすると、別のピクセルで同上の処理を行う場合に、もとのピクセルの値ではなく計算済みの値を取得してしまう場合がでてくるため、別で用意した新しい画像のピクセルに計算結果を入れる。

自身が周りと違った飛び抜けた値だったとしても、ソートした中央値を採用してセット直すので細かいノイズを取り除くことができる。

ただし常に中央値を採用するので、フィルタ適用後は全体としてぼやんとした画像に変換される。

フィルタを複数回実行するとより効果が出るが、画像がぼやける傾向も強くなる。

コード例

import * as p5 from "p5";

type ColorInfo = {
  gray: number;
  color: number[];
};

new p5((p: p5) => {
  let image: p5.Image;

  p.preload = () => {
    image = p.loadImage("./0.jpg");
  };

  p.setup = () => {
    p.createCanvas(p.windowWidth, p.windowHeight);
    p.loadPixels();
    image = medianFilter();
    p.image(image, 0, 0);
  };

  function medianFilter(): p5.Image {
    // フィルタ適用後の新しい画像を用意
    const dest = p.createImage(image.width, image.height);
    dest.copy(
      image,
      0,
      0,
      image.width,
      image.height,
      0,
      0,
      image.width,
      image.height
    );
    dest.loadPixels();

    // 画像の全ピクセルを走査
    for (let y = 0; y < image.height; y++) {
      for (let x = 0; x < image.width; x++) {
        // 自身を中心とした3x3範囲のピクセルと明るさ情報を集める
        const colorInfo: ColorInfo[] = [];
        for (let yy = y - 1; yy <= y + 1; yy++) {
          for (let xx = x - 1; xx <= x + 1; xx++) {
            if (onBoard(xx, yy)) {
              const color = getPixel(image, xx, yy);
              const r = p.red(color);
              const g = p.green(color);
              const b = p.blue(color);
              const gray = 0.299 * r + 0.587 * g + 0.114 * b;
              colorInfo.push({ gray, color });
            }
          }
        }

        // 明るさを基準にソートする
        colorInfo.sort(function (a: ColorInfo, b: ColorInfo): number {
          return a.gray - b.gray;
        });

        // 中央にあるピクセルを新しい画像の現在位置にセットする
        const i = p.floor(colorInfo.length / 2);
        const value = colorInfo[i].color;
        setPixel(dest, x, y, value);
      }
    }

    dest.updatePixels();
    return dest;
  }

  function onBoard(x: number, y: number): boolean {
    return 0 <= x && x < image.width && 0 <= y && y < image.height;
  }

  function getPixel(image: p5.Image, x: number, y: number): number[] {
    const i = (y * image.width + x) * 4;
    return [
      image.pixels[i],
      image.pixels[i + 1],
      image.pixels[i + 2],
      image.pixels[i + 3],
    ];
  }

  function setPixel(
    image: p5.Image,
    x: number,
    y: number,
    color: number[]
  ): void {
    const i = (y * image.width + x) * 4;
    image.pixels[i + 0] = color[0];
    image.pixels[i + 1] = color[1];
    image.pixels[i + 2] = color[2];
  }
});

内部で利用しているアルゴリズム

床関数, グレースケール