第11回 収縮・膨張の組み合わせ

前回学んだ収縮・膨張の処理では、1ピクセルの大きさの白・黒のノイズを消すことができるが、もう一方の色のノイズが逆に大きくなってしまうという問題点があった。それらを組み合わせて使うことで、もう一方のノイズをもとのままの大きさに保ったり、両方のノイズを消すことができる。

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

プログラムを実行すると実行画面の左上に元画像が表示される。
さらに実行画面上でクリックすると、dataフォルダに の9つの画像が作られる。
コンソールに「完了」が表示されてからキーボードの0~9のキーを押すとこれらの画像が画面に表示される。
(オープニング処理とは、収縮を特定回数行ったあと、膨張を同じ回数行う処理のこと。白ノイズが消え、黒ノイズはあまり大きさが変わらずに残る)
(クロージング処理とは、膨張を特定回数行ったあと、収縮を同じ回数行う処理のこと。黒ノイズが消え、白ノイズはあまり大きさが変わらずに残る)
(「収膨膨収」では1段階で白黒両方の1ピクセルのノイズが消え、2段階で2x2の白ノイズもほぼ消える)
(「膨収収膨」では1段階で白黒両方の1ピクセルのノイズが消え、2段階で2x2の黒ノイズもほぼ消える)
(2段階の「収膨膨収」「膨収収膨」では、ノイズは消えるが漢字の線はつぶれてつながってしまったり、線が途切れてしまったりする)
キー画像
0元.png
11ノイズ.png
22オープニング1.png
32オープニング2.png
42クロージング1.png
52クロージング2.png
63収膨膨収1.png
73収膨膨収2.png
83膨収収膨1.png
93膨収収膨2.png


ノイズ追加

概要

今回は収縮と膨張を組み合わせてノイズを消すが、収縮や膨張の回数によっては大きいノイズも消せる。そこで、それを確認するために前回と同じ1ピクセルのノイズのほかに 2x2 のサイズのノイズも加えてみる。

課題 1

ベースのプログラム (クリックしても元画像を二値化したものとそれをネガ・ポジ反転したものを2つずつ並べた画像が作られるだけ)
// 出力用画像の変数
PImage[] img = new PImage[10];
// 出力ファイル名
String[] fName = {"1ノイズ", 
  "2オープニング1", "2オープニング2", "2クロージング1", "2クロージング2", 
  "3収膨膨収1", "3収膨膨収2", "3膨収収膨1", "3膨収収膨2"};
int w, h;

void setup() {
  size(800, 600);
  img[0] = loadImage("元.png");
  w = img[0].width;
  h = img[0].height;
  if (w!= 400 || h!=300) {
    println("サイズが違います");
    exit();
  }
  noSmooth();
  background(0);
  image(img[0], 0, 0, width/2, height/2);
}

void draw() {
}

// ノイズ追加
void addNoise() {
  // 元画像を画像1にコピー
  img[1] = createImage(w*2, h*2, ARGB);
  for (int j=0; j<h; j++) {
    for (int i=0; i<w; i++) {
      // 左上・右上に元画像のピクセルをそのままコピー
      img[1].pixels[i+j*w*2] = img[0].pixels[i+j*w];
      img[1].pixels[i+w+j*w*2] = img[0].pixels[i+j*w];
      // 左下・右下に元画像のピクセルの明度を反転してコピー
      img[1].pixels[i+(j+h)*w*2] = color(255-brightness(img[0].pixels[i+j*w]));
      img[1].pixels[i+w+(j+h)*w*2] = color(255-brightness(img[0].pixels[i+j*w]));
    }
  }
  // 画像1を二値化する
  img[1].filter(THRESHOLD);
  // 画像の左半分に大きさ1のノイズを加える
  for (int i=0; i<w*h*2/50; i++) {
  }
  // 画像の右半分に大きさ2のノイズを加える
  for (int i=0; i<w*h*2/50/4; i++) {
  }
  // それ以降の画像をこれと同じにする
  for (int i=2; i<10; i++) {
    img[i] = img[1].get();
  }
}

// 番号nImageの画像にn段階のオープニング処理を行う
void opening(int nImage, int n) {
}

// 番号nImageの画像にn段階のクロージング処理を行う
void closing(int nImage, int n) {
}

// 番号nImageの画像にn段階の「収膨膨収」処理を行う
void edde(int nImage, int n) {
}

// 番号nImageの画像にn段階の「膨収収膨」処理を行う
void deed(int nImage, int n) {
}

// フィルタ処理を実行
void mousePressed() {
  addNoise();
  opening(2, 1);
  opening(3, 2);
  closing(4, 1);
  closing(5, 2);
  edde(6, 1);
  edde(7, 2);
  deed(8, 1);
  deed(9, 2);
  for (int i=1; i<=fName.length; i++) {
    img[i].save("data/" + fName[i-1] + ".png");
  }
  println("完了");
}

// 押したキーに応じて対応する画像を表示
void keyPressed() {
  int k = key-'0';
  if (k>=0 && k<10) {
    background(0);
    if (k==0) {
      image(img[k], 0, 0, width/2, height/2);
    } else {
      image(img[k], 0, 0, width, height);
    }
  }
}
  1. Processingのエディタに上のサンプルプログラムのコードをコピー&ペーストする。
  2. 「img11」という名前で保存する。
  3. ペイント3Dで以下のような文字画像「元.png」を作る。
    • 画像サイズは400x300
    • 文字のサイズは72ポイント、設定は太字
    • 半角数字、ひらがな、15画以上の漢字を含める


  4. 「元.png」をProcessingのエディタにドラッグ&ドロップする。
  5. addNoise関数の「// 画像の左半分に大きさ1のノイズを加える」の下のfor構文の{}の中に、前回の「// 画像1にノイズを加える」の下のfor構文の{}の中のコードをコピー&ペーストする。
  6. (手入力はしないこと。前回の分が完了していない場合はまずそちらを完成させる)


  7. コメント文「// 画像内の位置をランダムに決める」を「// 画像の左半分の範囲で位置をランダムに決める」に変更する。

  8. 追加したコードの最後の行の「w」のところを「w*2」にする (2か所)。
  9. (今回は出力画像の横幅が元画像の2倍なので、通し番号 (横位置 + 縦位置 * 横幅) の計算が変わるため)

  10. 実行して画面をクリックする。
  11. (「完了」が表示されたあとでキーボードの1キーを押すと、左半分が前回と同じ状態になる)
    (この結果にならない場合はそのまま次に進まないこと。このコードをコピーして次の段階で使う)


  12. 「// 画像の右半分に大きさ2のノイズを加える」の下のfor構文の{}の中に、(ちゃんと動作している状態の)「// 画像の左半分に大きさ1のノイズを加える」の下のfor構文の{}の中のコード (5行分) をコピー&ペーストする。

  13. ステップ9で追加した部分の、xの値を決める行のrandom関数の引数を (w, w*2-1) にする。
  14. (こうすることで、乱数の範囲は「w以上2w-1未満」の実数になる。これを整数化すると、「w以上2w-2以下」の整数になる)
    (横幅が2wの画像の右端のx座標は2w-1。このあと2x2のノイズを配置するので、その左上の位置は右端より一つ左でないといけない)

  15. ステップ9で追加した部分の、yの値を決める行のrandom関数の引数を (h*2-1) にする。
  16. (こうすることで、乱数の範囲は「0以上2h-1未満」の実数になる。これを整数化すると、「0以上2h-2以下」の整数になる)
    (さっきと同様の理由で、下端のy座標2h-1より一つ上までの範囲にする)

  17. ステップ9で追加した部分の、コメント文「// x, yのピクセルの明度を反転させる」を「// x, yを左上とする2x2の範囲の明度を反転させる」に変更する。

  18. その下の行を「カウンタ変数 j が0, 1のように変わるfor文」「カウンタ変数 k が0, 1のように変わるfor文」で囲む。

  19. ステップ13のfor構文で囲まれた処理の x を x+j に、y を (y+k) に置き換える (それぞれ2か所ずつ)。
  20. (これによって2x2の範囲のピクセルに対する処理になる)

  21. 実行して画面をクリックする。
  22. (「完了」が表示されたあとでキーボードの1キーを押すと、右半分に2x2サイズのノイズが入る)
  • この段階でdataフォルダの出力画像がすべてこの状態になっている。本来こうなるべきなのは「1ノイズ.png」のみ。
  • 今回はコードのスクリーンショットは載せない。手順に従って作成すればできるはず。

オープニング・クロージング

概要

単純な収縮処理では、白ノイズは消せるが「黒ノイズが大きくなる」「黒いエリアが広がる」という難点がある。文字画像のケースでは、黒い字が太くなり、白い字は細くなってしまう。そこで

↓ 収縮

↓ 膨張


の順に処理を行うと、黒ノイズの大きさは元のままで、黒エリアと白エリアの境目も元画像と変わらない。
このような処理のことをオープニングという。
収縮→収縮→膨張→膨張 のように2回ずつ繰り返すと、もっと大きい白ノイズを消すこともできる。

同様に、単純な膨張処理で起こる「白ノイズが大きくなる」「白いエリアが広がる」という問題点も、

↓ 膨張

↓ 収縮


のように収縮処理と組み合わせることで解決できる。このような処理のことをクロージングという。
膨張→膨張→収縮→収縮 のように2回ずつ繰り返すと、もっと大きい黒ノイズを消すこともできる。

課題 2

  1. opening()関数の中に、「img[nImage].filter(ERODE);をn回繰り返し、そのあとでimg[nImage].filter(DILATE);をn回繰り返す」という処理を記述する。

  2. 実行し、画面をクリックしてコンソールに「完了」が表示されたあとでキーボードの2, 3キーを交互に押す。
  3. (どちらの結果でも白ノイズはほとんど消える)
    (2キーで表示される1段階のオープニングでは、右下の方に少し白いゴミが残ることがある)
    (3キーで表示される方が2段階のオープニングで、黒文字がつぶれ、白文字が途切れがちになる)


  4. closing()関数の中に、「img[nImage].filter(DILATE);をn回繰り返し、そのあとでimg[nImage].filter(ERODE);をn回繰り返す」という処理を記述する。

  5. 実行し、画面をクリックしてコンソールに「完了」が表示されたあとでキーボードの4, 5キーを交互に押す。
  6. (どちらの結果でも黒ノイズはほとんど消える)
    (4キーで表示される1段階のクロージングでは、右上の方に少し黒いゴミが残ることがある)
    (5キーで表示される方が2段階のクロージングで、白文字がつぶれ、黒文字が途切れがちになる)


  7. この段階でdataフォルダの出力画像のうち「2オープニング1.png」「2オープニング2.png」「2クロージング1.png」「2クロージング2.png」も然るべきものになる。

逆の処理を間に挟んだ組み合わせ

概要

オープニング、クロージングでも、白か黒のどちらかのノイズは残ってしまうが、

↓ 収縮

↓ 膨張

↓ 膨張

↓ 収縮

のように、反対の処理を間に挟むことで、白と黒のノイズを両方とも消し、白エリア・黒エリアの境目ももとのままにすることができる。
さらに「収縮2回、膨張4回、収縮2回」のようにすれば、もっと大きいノイズを消すこともできる。

逆に「膨張→収縮→収縮→膨張」の順で処理を行ってもほぼ同様の結果を得ることができる。
この処理には特に名前はついていないので、この演習では便宜上前者を「収膨膨収」、後者を「膨収収膨」と呼ぶことにする。

課題 3

  1. edde()関数の中に、「img[nImage].filter(ERODE);をn回繰り返し、そのあとでimg[nImage].filter(DILATE);をn*2回繰り返し、そのあとでimg[nImage].filter(ERODE);をn回繰り返す」という処理を記述する。

  2. 実行し、画面をクリックしてコンソールに「完了」が表示されたあとでキーボードの6, 7キーを交互に押す。
  3. (どちらの結果でも左側ノイズはほとんど消える)
    (6キーで表示される1段階の「収膨膨収」では、右上の方に少し黒いゴミが残る)
    (7キーで表示される2段階の「収膨膨収」では、白・黒どちらの文字もつぶれ気味になる)


  4. deed()関数の中に、「img[nImage].filter(DILATE);をn回繰り返し、そのあとでimg[nImage].filter(ERODE);をn*2回繰り返し、そのあとでimg[nImage].filter(DILATE);をn回繰り返す」という処理を記述する。

  5. 実行し、画面をクリックしてコンソールに「完了」が表示されたあとでキーボードの8, 9キーを交互に押す。
  6. (どちらの結果でも左側ノイズはほとんど消える)
    (8キーで表示される1段階の「膨収収膨」では、右下の方に少し白いゴミが残る)
    (9キーで表示される2段階の「膨収収膨」では、白・黒どちらの文字もつぶれ気味になる)


  7. この段階でdataフォルダの出力画像のうち「3収膨膨収1.png」「3収膨膨収2.png」「3膨収収膨1.png」「3膨収収膨2.png」も然るべきものになる。

提出

小テスト予告

次回の授業の初めに今回の内容についての小テストを行う。

戻る

inserted by FC2 system