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

前回学んだ収縮・膨張の処理では、1ピクセルの大きさの白・黒のノイズを消すことができるが、もう一方の色のノイズが逆に大きくなってしまったり、白いエリアが広がったり狭くなったりしてしまうという問題点があった。それらを組み合わせて使うことで、エリアの大きさがほぼ変わらないようにしたり、両方のノイズを消すことができる。
ここでは前回の完成状態のプログラムに変更を加え、収縮・膨張の処理を様々な順番で組み合わせた処理を行うプログラムを作る。
前回の完成状態のコードが必須なので、まだの場合は先にそちらを完成させる。

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

プログラムを実行して少し待つとコンソールに「完了」が表示され、元画像を縦横2倍に拡大した画像が実行画面上に表示される。
dataフォルダには10枚の画像が作られる。
そのあとでキーボードの0~9のキーを押すとこれらの画像が画面に表示される。
(すべての画像は自動的に作成されるので、キーボードを使うのは結果確認のためだけ)
キー 画像 意味 特徴
0 元.bmp 元画像
1 0ノイズ.png 元画像にノイズを加えたもの
2 0収縮.png ノイズ.pngに収縮処理を加えたもの 白のノイズが消え、白いエリアが狭くなる
3 0膨張.png ノイズ.pngに膨張処理を加えたもの 黒のノイズが消え、白いエリアが広くなる
4 1オープニング.png ノイズ.pngにオープニング処理を加えたもの 白のノイズが消え、白黒エリアの境界はノイズ.pngとほぼ同じ
5 1クロージング.png ノイズ.pngにクロージング処理を加えたもの 黒のノイズが消え、白黒エリアの境界はノイズ.pngとほぼ同じ
6 2オープンクローズ.png ノイズ.pngにオープニング・クロージングの処理を順に加えたもの 白黒両方のノイズが消え、白黒エリアの境界はノイズ.pngとほぼ同じ
7 2クローズオープン.png ノイズ.pngにクロージング・オープニングの処理を順に加えたもの 白黒両方のノイズが消え、白黒エリアの境界はノイズ.pngとほぼ同じ
8 3トップハット.png ノイズ.pngと1オープニング.pngの差分 1オープニング.pngで消えたノイズが白く表示される (背景は黒)
9 3ブラックハット.png ノイズ.pngと1クロージング.pngの差分 1クロージング.pngで消えたノイズが白く表示される (背景は黒)
キーと表示される画像

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

概要

単純な収縮処理では、白いノイズは消せるが「黒いノイズが大きくなる」「黒いエリアが広がる(白いエリアが狭くなる)」という問題点がある。
そこで、収縮のあとに膨張の処理を加えると、黒ノイズの大きさは元のままで、白・黒のエリアの大きさも処理前とほぼ同じになる。このような処理のことをオープニングという。


同様に、単純な膨張処理では黒いノイズは消せるが「白いノイズが大きくなる」「白いエリアが広がる(黒いエリアが狭くなる)」という問題点があるが、 膨張のあとに収縮の処理を加えると、白いノイズの大きさは元のままで、白・黒のエリアの大きさも処理前とほぼ同じになる。このような処理のことをクロージングという。

課題 1

  1. Processingを起動する。
  2. 前回提出した、完成状態のコードをコピー&ペーストする。
  3. 「img11」という名前で保存する。
  4. ペイントを起動してサイズ400x200ピクセルの白黒画像を作る。
  5. 「元.bmp」をProcessingのウインドウにドラッグ&ドロップする。
  6. プログラム先頭部分の
    // 画像用の変数
    PImage[] img = new PImage[6];
    // 出力ファイル名
    String[] fName = {"元", "1ノイズ", "2収縮1", "2収縮2", "3膨張1", "3膨張2"};
    // 画像用の変数
    PImage[] img = new PImage[10];
    // 出力ファイル名
    String[] fName = {"元", "ノイズ", "0収縮", "0膨張",
      "1オープニング", "1クロージング",
      "2オープンクローズ", "2クローズオープン",
      "3トップハット", "3ブラックハット"};
    に変更する。
    (これは画像数、画像名の変更に伴う変更だけ)

  7. getC関数、erode1関数、erode2関数、dilate1関数、dilate2関数を削除する。
  8. addNoise関数の前のコメント文から「(課題1)」を削除する。
  9. addNoise関数のコメント文「// 2~5番をノイズ画像と同じ画像にする」と、keyPressed関数の「if (k>=0 && k<=5)」に含まれる「5」を「9」に変更する。
    (どちらも画像数の変更に伴う変更。前者は動作には影響しないが、後者はキー操作による画像表示にかかわる)

  10. setup関数のエラーが出ている4行を削除する。
  11. addNoise関数とkeyPressed関数の間に以下のコードをコピー&ペーストする。
    (setupから第1引数に画像番号、第2引数に「膨」と「収」の組み合わせの文字列を入れてこの関数を呼ぶ)
    // n番目の画像に指定された処理を順に加える
    void process(int n, String cmd) {
      for (int i=0; i<cmd.length(); i++) {
        // cmdのi番目の文字が「収」なら収縮、「膨」なら膨張処理をimg[n]に加える
      }
      img[n].save("data/"+ fName[n] +".png");
    }

  12. process関数の「// cmdのi番目の文字が「収」なら収縮、「膨」なら膨張処理をimg[n]に加える」の下に、対応するコードを追加する。
    (if文かswitch文で場合分けする)

  13. setup関数の
        addNoise(1); // ノイズを追加した画像をimg[1]に保存
    の下に以下のコメント文を追加し、その左に対応するコードを追加する。
        // img[2]に収縮処理を加える
        // img[3]に膨張処理を加える
    (どちらの行でもprocess関数を呼ぶ)
    (第1引数には画像番号、第2引数には行いたい処理を漢字1文字で入れる)

  14. プログラムを実行する。
    • コンソールに「完了」が表示されてから1, 2, 3キーを押すと、元画像にノイズを加えた画像、それに収縮処理を加えた画像、膨張処理を加えた画像が表示される。
    (ここまでは前回のプログラムをスリム化しただけ)


  15. setup関数の
        // img[3]に膨張処理を加える
    の下に以下のコメント文を追加し、その左に対応するコードを追加する。
        // img[4]にオープニング処理を加える
        // img[5]にクロージング処理を加える
    (process関数を呼ぶ)
    (「コマンド」は2文字)
    (組み合わせは上の概要を参照)

  16. プログラムを実行する。
    • コンソールに「完了」が表示されてから4, 5キーを押すと、ノイズ画像にオープニング、クロージングの処理を加えた画像が表示される。
    • ノイズ画像とオープニング・クロージングの処理を加えた画像を比べると、白いエリア・黒いエリアの境界はほぼ変わらない。
    • オープニング処理した画像では白いノイズが消え、黒いノイズはそのまま。
    • クロージング処理した画像では黒いノイズが消え、白いノイズはそのまま。


2. オープニングとクロージングの組み合わせ

概要

オープニング、クロージングではエリアの広さが変わる問題は解決されるが、白か黒のどちらかのノイズは残ってしまう。
そこで、これらを組み合わせた処理を考えてみる。
オープニング処理につづけてクロージング処理を行うことは「収縮→膨張→膨張→収縮」順の4つの処理にあたる。
図からわかるように、この組み合わせでは白と黒のノイズを両方とも消すことができる。


逆にクロージング処理につづくオープニング処理、つまり「膨張→収縮→収縮→膨張」の順で処理を行ってもほぼ同様の結果を得ることができる。

課題 2

  1. setup関数の
        // img[5]にクロージング処理を加える
    の下に以下のコメント文を追加し、その左に対応するコードを追加する。
        // img[6]にオープニング・クロージングの順に処理を加える
        // img[7]にクロージング・オープニングの順に処理を加える
    (process関数を呼ぶ)
    (「コマンド」は4文字)
    (オープニングが「収膨」、クロージングが「収膨」)

  2. プログラムを実行する。
    • コンソールに「完了」が表示されてから6キーを押すと、ノイズ画像にオープニング・クロージングの順に処理を加えた画像が表示される。
    • 7キーを押すと、ノイズ画像にクロージング・オープニングの順に処理を加えた画像が表示される。
    • どちらも白・黒のノイズの両方が消える。
    • どちらも白いエリアと黒いエリアの境界はノイズ画像とあまり変わらない。
    • 結局、ノイズを加える前の状態がほぼ復元される。

3. トップハット・ブラックハット

概要

オープニング画像では白いノイズが消えてそれ以外はそのままになっているので、ノイズ画像とオープニング画像の差を明度とする画像を作れば、もともとあった白いノイズだけが残る。
この処理のことをトップハットという。


同様に、クロージング画像では黒いノイズが消えてそれ以外はそのままになっているので、ノイズ画像とクロージング画像の差を明度とする画像を作れば、もともと黒いノイズがあったところが白く表示される。
この処理のことをブラックハットという。ブラックハット処理で得られた画像の明度を反転すれば、ノイズ画像にあった黒いノイズだけが残る。

課題 3

  1. process関数の下に以下のコードを追加し、for構文の中の3行のコメント文の左に対応するコードを追加する。
    // 画像aと画像bの明度の差の絶対値を明度とするものを画像nに保存する
    void getDifference(int n, int a, int b) {
      for (int i=0; i<w*h; i++) {
        // float braに、画像aの i番目のピクセルの明度を入れる
        // float brbに、画像bの i番目のピクセルの明度を入れる
        // 画像nのi番目のピクセルに、braとbrbの差の絶対値を明度とする色を設定する
      }
      img[n].save("data/"+ fName[n] +".png");
    }
    (明度はbrightness関数で取得する)
    (色はcolor関数で作る)
    (絶対値はabs関数で取得する)

  2. setup関数の
        // img[7]にクロージング・オープニングの順に処理を加える
    の下に以下のコメント文を追加し、その左に対応するコードを追加する。
        // ノイズ画像とオープニングした画像の差をimg[8]に入れる
        // ノイズ画像とクロージングした画像の差をimg[9]に入れる
    (getDifference関数を呼ぶ)
    (3つの引数はどれも画像番号)
    (番号の意味はgetDifference関数の直前のコメント文を参照)

  3. プログラムを実行する。コンソールに「完了」が表示されてからキーを押してできた画像を確認する。
    • 8キーを押すと、ノイズ画像にトップハット処理を加えた画像が表示される (黒背景にノイズ画像の白ノイズが表示される)。
    • 9キーを押すと、ノイズ画像にブラックハット処理を加えた画像が表示される (黒背景にノイズ画像の黒ノイズが白で表示される)。

提出

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