第10回 膨張・収縮

元画像が白と黒のいずれかのピクセルからなる二値画像の場合は、第05回で見た平滑化よりも効率よくノイズを消す方法がある。ここでは収縮・膨張という処理を紹介する。

収縮
膨張
「収縮」「膨張」の意味は、「白い部分」を基準にして考えれば覚えやすい。

今回のプログラムの最終的な機能

プログラムを実行して少し待つとコンソールに「完了」が表示され、元画像を縦横2倍に拡大した画像が実行画面上に表示される。
dataフォルダには5つの画像が作られる。
そのあとでキーボードの0~5のキーを押すとこれらの画像が画面に表示される。
(すべての画像は自動的に作成されるので、キーボードを使うのは結果確認のためだけ)
キー 画像 意味 特徴
0 元.bmp 元画像
1 1ノイズ.png 元画像にノイズを加えた画像 全体の1%のピクセルが元画像と明度反転している
2 2収縮1.png ノイズ画像にfilter関数で収縮処理を加えた画像 黒ノイズが大きくなり、白ノイズが消える
3 2収縮2.png ノイズ画像に周囲のピクセルとの比較で収縮処理を加えた画像 2キーの画像と全く同じもの
4 3膨張1.png ノイズ画像にfilter関数で膨張処理を加えた画像 白ノイズが大きくなり、黒ノイズが消える
5 3膨張2.png ノイズ画像に周囲のピクセルとの比較で膨張処理を加えた画像 4キーの画像と全く同じもの
キーと表示される画像

1. ノイズ追加

概要

第05回でも平滑化の効果を確認するために元画像にノイズを追加した。
そのときはランダムに決めた位置にランダムな色 (R, G, Bそれぞれに0~255のランダムな値を入れた色) を設定したが、今回は元画像もノイズ画像も二値画像なので、どのピクセルも白か黒のどちらかでなければならない。
そこで、ランダムに決めた位置のピクセルの明度を反転させる処理を行う。 つまり、そこが白なら黒、黒なら白に変更する。

課題 1

  1. Processingを起動する。
  2. 以下のコードをコピー&ペーストする。
  3. // 画像用の変数
    PImage[] img = new PImage[6];
    // 出力ファイル名
    String[] fName = {"元", "1ノイズ", "2収縮1", "2収縮2", "3膨張1", "3膨張2"};
    int w=400;
    int h=200;
    
    void setup() {
      size(800, 400);
      noSmooth();
      boolean isRightSize = true;
      boolean isBW = true;
      img[0] = loadImage("元.bmp");
      // サイズ判定
      if (img[0].width != w || img[0].height != h) {
        isRightSize = false;
      }
      // 白黒判定
      for (int j=0; j<h; j++) {
        for (int i=0; i<w; i++) {
          if (img[0].pixels[i+j*w]!=color(0) && img[0].pixels[i+j*w]!=color(255)) {
            isBW = false;
          }
        }
      }
      if (!isRightSize || !isBW) {
        println("サイズが違うか、白黒画像ではありません");
        exit();
      } else {
        addNoise(1); // ノイズを追加した画像をimg[1]に保存
        erode1(2);   // filter関数で収縮した画像をimg[2]に保存
        erode2(3);   // 周囲のピクセルとの比較で収縮した画像をimg[3]に保存
        dilate1(4);  // filter関数で膨張した画像をimg[4]に保存
        dilate2(5);  // 周囲のピクセルとの比較で膨張した画像をimg[5]に保存
        println("完了");
        textFont(createFont("MS Pゴシック", 48));
        background(0);
        image(img[0], 0, 0, width, height);
      }
    }
    
    void draw() {
    }
    
    // ノイズ追加(課題1)
    void addNoise(int n) {
      // n番を元画像と同じ画像にする
      img[n] = img[0].get();
      // 画素数の1/100回の繰り返し
      for (int i=0; i<w*h/100; i++) {
        // 0~w*h-1の範囲の乱数をint rに入れる
        // n番目の画像のr番目のピクセルの明度をfloat bに入れる
        // n番目の画像のr番目のピクセルに明度255-bの色を設定する
      }
      img[n].save("data/"+ fName[n] +".png");
      // 2~5番をノイズ画像と同じ画像にする
      for (int i=2; i<img.length; i++) {
        img[i] = img[1].get();
      }
    }
    
    // ノイズ画像の(x, y)のピクセルの色を返す
    String getC(int x, int y) {
      if (x<0 || x>=w || y<0 || y>=h) {
        return "";
      }
      if (brightness(img[1].pixels[x+y*w]) == 0) {
        return "";
      }
      return "";
    }
    
    // filter関数による収縮(課題2)
    void erode1(int n) {
      img[n].save("data/"+ fName[n] +".png");
    }
    
    // 周囲のピクセルとの比較による収縮(課題2)
    void erode2(int n) {
      for (int j=0; j<h; j++) {
        for (int i=0; i<w; i++) {
          // (i, j)のピクセルの上下左右のどれかが黒なら(i, j)を黒にする
        }
      }
      img[n].save("data/"+ fName[n] +".png");
    }
    
    // filter関数による膨張(課題3)
    void dilate1(int n) {
      img[n].save("data/"+ fName[n] +".png");
    }
    
    // 周囲のピクセルとの比較による膨張(課題3)
    void dilate2(int n) {
      for (int j=0; j<h; j++) {
        for (int i=0; i<w; i++) {
          // (i, j)のピクセルの上下左右のどれかが白なら(i, j)を白にする
        }
      }
      img[n].save("data/"+ fName[n] +".png");
    }
    
    void keyPressed() {
      int k = key-'0';
      if (k>=0 && k<=5) {
        background(0);
        image(img[k], 0, 0, width, height);
        fill(255, 0, 0);
        text(fName[k], 30, height-30);
        fill(255);
      }
    }
    
  4. 「img10」という名前で保存する。
  5. ペイントを起動してサイズ400x200ピクセルの白黒画像を作る。
  6. 「元.bmp」をProcessingのウインドウにドラッグ&ドロップする。
  7. addNoise関数の「// 0~w*h-1の範囲の乱数をint rに入れる」の下に、対応するコードを追加する。
    • random関数では「0以上で引数未満」の範囲の実数の乱数が得られる。
    • その結果を整数化してint rに代入する。
  8. addNoise関数の「// n番目の画像のr番目のピクセルの明度をfloat bに入れる」の下に、対応するコードを追加する。
    • n番目の画像のr番目のピクセルは「img[n].pixels[r]」。
    • 明度はbrightness関数で取得する。
  9. addNoise関数の「// n番目の画像のr番目のピクセルに明度255-bの色を設定する」の下に、対応するコードを追加する。
    • n番目の画像のr番目のピクセルは「img[n].pixels[r]」。
    • 色はcolor関数で作る。
  10. プログラムを実行する。
    • コンソールに「完了」が表示されてから1キーを押すと、元画像にノイズを加えた画像が表示される。

2. 収縮処理

概要

収縮処理では、参照ピクセルの上下左右のどれか一つでも黒ならそのピクセルを黒にする。
(参照ピクセルが最初から黒だった場合は黒のまま)。

その結果、こういう元画像に収縮処理をかけると

出力画像はこうなる。

Processingでは、これまでに何度か使ったfilter関数の引数を「ERODE」(英語では「浸食」という意味。白い部分が削られるというイメージ) にすることで簡単にこの処理を行うことができる。
これとは別に、自前の処理でも同様のことを行って結果を比較してみる。

課題 2

  1. getC関数が以下の動作になるように、3箇所の""の中にしかるべき文字を入れる。
    • ノイズ画像の (x, y) のピクセルが白なら「白」を返す
    • ノイズ画像の (x, y) のピクセルが黒なら「黒」を返す
    • (x, y) がノイズ画像の範囲内になければ「外」を返す
  2. erode1関数の先頭に、filter関数でimg[n]に収縮処理を行うコードを追加する。
    • 書式は「画像.filter(引数);」。
    • 引数を何にするかは概要の説明を参照。
  3. プログラムを実行する。
    • コンソールに「完了」が表示されてから2キーを押すと、ノイズ画像に収縮処理を加えた画像が表示される。
    • 黒いエリアの中の白いノイズは消える。
    • 白いエリアの中の黒いノイズは大きくなる (「+」の形になる)。

  4. erode2関数の「// (i, j)のピクセルの上下左右のどれかが黒なら(i, j)を黒にする」の下に、対応するコードを追加する。
    • (i, j)の上の座標は (i, j-1)。下、左、右も同様に座標のどちらかから1を引くかどちらかに1を足したものになる。
    • (i, j-1) のピクセルが 黒かどうかは「if (getC(i, j-1).equals("黒"))」で調べられる。
    • 「4つのピクセルのどれかが黒なら」は、4つの判定を「||」でつなげば調べられる。
    • 条件を満たしたときに色を設定するピクセルは img[n].pixels[i+j*w]。
    • 色はcolor関数で作る。黒を作るための引数は0。
  5. プログラムを実行する。
    • コンソールに「完了」が表示されてから2, 3キーを押すと、ノイズ画像に収縮処理を加えた画像が表示される。
    • 2キー、3キーで表示される画像は全く同じものになる。

3. 膨張処理

概要

膨張処理では、参照ピクセルの上下左右のどれか一つでも白ならそのピクセルを白にする。
(参照ピクセルが最初から白だった場合は白のまま)。

その結果、こういう元画像に膨張処理をかけると

出力画像はこうなる。

Processingでは、filter関数の引数を「DILATE」(英語では「拡張する」という意味。白い部分が膨らむというイメージ) にすることで簡単にこの処理を行うことができる。
これとは別に、自前の処理でも同様のことを行って結果を比較してみる。

課題 3

  1. dilate1関数の先頭に、filter関数でimg[n]に収縮処理を行うコードを追加する。
    • 書式は「画像.filter(引数);」。
    • 引数を何にするかは概要の説明を参照。
  2. プログラムを実行する。
    • コンソールに「完了」が表示されてから4キーを押すと、ノイズ画像に膨張処理を加えた画像が表示される。
    • 白いエリアの中の黒いノイズは消える。
    • 黒いエリアの中の白いノイズは大きくなる (「+」の形になる)。

  3. dilate2関数の「// (i, j)のピクセル上下左右のどれかが白なら(i, j)を白にする」の下に、対応するコードを追加する。
    • やることはelode2関数の逆。elode2関数からコピーして書き換えると楽。
    • 色はcolor関数で作る。白を作るための引数は255。
  4. プログラムを実行する。
    • コンソールに「完了」が表示されてから4, 5キーを押すと、ノイズ画像に膨張処理を加えた画像が表示される。
    • 4キー、5キーで表示される画像は全く同じものになる。

提出

締切後提出用フォーム
※ 点数はつきますが、欠席だった場合は出席にはなりません。
※ コードはTeamsのClass Notebookに保存してください。
戻る