第08回 エッジ強調・鮮鋭化

前回のプログラムに変更を加え、元画像のエッジを強調した画像を作るプログラムを作る。
また、第05回のプログラムの関数も使い、画像をくっきりさせる機能も実装する。
第05回、第07回のコードの両方が必須なので、まだの場合は先にそちらを完成させる。

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

プログラムを実行して少し待つとコンソールに「完了」が表示され、元画像をグレースケール化した画像が実行画面上に表示される。
dataフォルダには6つの画像が作られる。
そのあとでキーボードの0~5のキーを押すとこれらの画像が画面に表示される。
(すべての画像は自動的に作成されるので、キーボードを使うのは結果確認のためだけ)
キー 画像 意味 特徴
0 グレー.jpg 元画像をグレースケール化した画像
1 1ラプラシアン縦横.png 縦横方向のラプラシアンフィルタで抽出した結果を元画像と合成した画像 エリア間のエッジ部分が白くなる
2 1ラプラシアン全.png 全方向のラプラシアンフィルタで抽出した結果を元画像と合成した画像 1キーの画像よりエッジ部分がさらに白くなる
3 2プレヴィット合成.png X, Y方向のプレヴィットフィルタの結果を合成した結果を元画像と合成した画像 2キーの画像よりエッジ部分がさらに白くなる
エリア内部のザラザラは少なくなる
4 2ソーベル合成.png X, Y方向のソーベルフィルタの結果を合成した結果を元画像と合成した画像 3キーの画像よりエッジ部分がさらに白くなる
5 3アンシャープ.png 移動平均フィルタによる平滑化の逆の処理を加えた画像 0キーの画像のエッジをくっきりさせた画像になる
キーと表示される画像

1. ラプラシアンフィルタによるエッジ強調

概要

前回見たように、ラプラシアンフィルタでは全方向のエッジが抽出できる。
そこで、各ピクセルでエッジ抽出した画像と元画像の明度を加えたものを明度とする画像を作れば、エッジ部分が明るく強調された画像ができる。
この課題では前回のプログラムを流用するためマスクは前回のものをそのまま利用するが、1回の処理でエッジを強調した画像を作ることもできる。
縦・横方向のラプラシアンフィルタのマスクは
0 1 0
1 -4 1
0 1 0
だが、明度を計算する処理ではマスクとそれぞれのピクセルの明度の積を足し上げて最後に絶対値をとるので、要素の符号をすべて逆にして
0 -1 0
-1 4 -1
0 -1 0
としても結果は同じになる。これと、「元画像と同じ画像を作るマスク」
0 0 0
0 1 0
0 0 0
を加えれば
0 -1 0
-1 5 -1
0 -1 0
という形になる。このマスクを使って前回と同じ処理を行えば、元画像の縦横のエッジ部分を強調した画像ができる。

全方向のラプラシアンフィルタのマスク
1 1 1
1 -8 1
1 1 1
についても同様に符号を反転させて元画像を得るマスクを加えれば
-1 -1 -1
-1 9 -1
-1 -1 -1
となる。これを使えば、全方向のエッジ部分を強調した画像ができる。

課題 1

  1. Processing 4.3を起動する。
    青森キャンパスの演習室では下図の赤枠のアイコンから起動する。黒い方ショートカットアイコンの古いバージョン (Processing 3.*) だと今回のプログラムはうまく動作しない。
    東京キャンパス、むつキャンパスや、オンデマンドで自分のPCで実行する場合に、もし古い方のProcessingしか入っていない場合はここから最新版のProcessingをダウンロードして展開し、「processing.exe」を実行する。
  2. 前回の完成状態のコードをコピー&ペーストする。
  3. 「img08」という名前で保存する。
  4. 適当に画像検索して元画像 (写っているものの輪郭線がくっきりしていて、解像度が800×600ピクセル以上のもの。低解像度の画像を引き延ばしたものはNG) を用意する。
  5. 画像の形式に応じて以下の変更を加える。
    • JPG形式の場合→名前を元.jpgに変更
    • それ以外の場合→ペイントで開き、形式をJPGに指定して「元」という名前で保存
  6. 「元.jpg」をProcessingのウインドウにドラッグ&ドロップする。
  7. プログラム先頭部分の
    // 画像用の変数
    PImage[] img = new PImage[9];
    // 出力ファイル名
    String[] fName = {"元", "1プレヴィットX", "1プレヴィットY",
      "1ソーベルX", "1ソーベルY", "2ラプラシアン縦横", "2ラプラシアン全",
      "3プレヴィット合成", "3ソーベル合成"};
    // 画像用の変数
    PImage[] img = new PImage[6];
    // 出力ファイル名
    String[] fName = {"グレー", "1ラプラシアン縦横", "1ラプラシアン全",
      "2プレヴィット合成", "2ソーベル合成",
      "3アンシャープ"};
    に変更する。

  8. 7番目のステップでの変更に合うように、setup関数とkeyPressed関数のfor文とコメント文の数値を変更する。
  9. (元画像を含む画像の数が9から6に変更された)
    (作られる画像の番号は1~5番になる)

  10. setup関数の
      single(mask_prewittX, 1); // プレヴィットフィルタ(X)でエッジ抽出した画像をimg[1]に保存
      single(mask_prewittY, 2); // プレヴィットフィルタ(Y)でエッジ抽出した画像をimg[2]に保存
      single(mask_sobelX, 3);   // ソーベルフィルタ(X)でエッジ抽出した画像をimg[3]に保存
      single(mask_sobelY, 4);   // ソーベルフィルタ(Y)でエッジ抽出した画像をimg[4]に保存
      single(mask_laplacian4, 5);       // ラプラシアンフィルタ(横・縦)でエッジ抽出した画像をimg[5]に保存
      single(mask_laplacian8, 6);       // ラプラシアンフィルタ(全方向)でエッジ抽出した画像をimg[6]に保存
      compose(mask_prewittX, mask_prewittY, 7); // 縦横のプレヴィットフィルタの結果を合成した画像をimg[7]に保存
      compose(mask_sobelX, mask_sobelY, 8);     // 縦横のソーベルフィルタの結果を合成した画像をimg[8]に保存
      single(mask_laplacian4, 1);// ラプラシアンフィルタ(横・縦)でエッジ抽出して元画像に合成した画像をimg[1]に保存
      single(mask_laplacian8, 2);// ラプラシアンフィルタ(全方向)でエッジ抽出して元画像に合成した画像をimg[2]に保存
    に変更する。

  11. setup関数の最後に以下のコードを追加する。
    (「これによって「グレー.jpg」が作成されるようになる)
      img[0].save("data/"+ fName[0] +".jpg");

  12. single関数の前のコメント文を以下のように変更する。
    // 1種類のフィルタでエッジ抽出した結果を元画像に合成する(課題1)

  13. compose関数とkeyPressed関数の間に以下の関数を追加する。
    (引数で2つの画像の番号を受け取っている)
    (全ピクセルについてそれぞれの画像の (i, j)のピクセルの明度を取り出し、brn, brmに入れている)
    (そのあとで画像nの(i, j)のピクセルに明度(brn+brm)の色を設定している)
    // 画像n, 画像mの明度を加えたものを画像nに保存し、n番目の名前でファイル保存
    void superimpose(int n, int m) {
      for (int j=0; j<h; j++) {
        for (int i=0; i<w; i++) {
          float brn = brightness(img[n].pixels[i+w*j]);
          float brm = brightness(img[m].pixels[i+w*j]);
          img[n].pixels[i+w*j] = color(brn+brm);
        }
      }
      img[n].save("data/"+ fName[n] +".png");
    }

  14. single関数の
      img[n].save("data/"+ fName[n] +".png");
      superimpose(n, 0);
    に変更する。
    (こうすることで、single関数の最後の行の直前まではimg[n]にエッジ抽出した画像が保存され、それがsupreimpose関数で元画像と合成される)
    (画像のファイルへの保存はsuperimpose関数側に記述してあるので、single関数で行う必要はなくなる)

  15. プログラムを実行する。
    • コンソールに「完了」が表示されてから1, 2キーを押すと、元画像の被写体のエッジ部分が強調された画像が表示される。

2. プレヴィットフィルタ、ソーベルフィルタによるエッジ強調

概要

前回の課題3で見たように、X方向、Y方向の1次微分フィルタの計算結果を合成したものでは、ラプラシアンフィルタよりきれいにエッジが抽出される。
そこまでの計算は前回の処理をそのまま流用し、最後に課題1で導入したsuperimpose関数を使えば元画像にエッジを合成した画像ができる。

課題 2

  1. compose関数の前のコメント文を「// 複数方向のフィルタの結果を合成したものを元画像に合成する(課題2)」に変更する。
  2. compose関数の
      img[n].save("data/"+ fName[n] +".png");
      superimpose(n, 0);
    に変更する。

  3. setup関数の
      single(mask_laplacian4, 1);// ラプラシアンフィルタ(横・縦)でエッジ抽出して元画像に合成した画像をimg[1]に保存
      single(mask_laplacian8, 2);// ラプラシアンフィルタ(全方向)でエッジ抽出して元画像に合成した画像をimg[2]に保存
    の下に
      // X, Y方向のプレヴィットフィルタの計算結果を合成したものを元画像に合成した画像をimg[3]に保存
      // X, Y方向のソーベルフィルタの計算結果を合成したものを元画像に合成した画像をimg[4]に保存
    を追加する。

  4. 上記で追加したコメント文に対応したコードを追加する。
    (いずれも呼ぶのはcompse関数)
    (第1引数、第2引数にはマスク名、第3引数に画像番号を入れる)
    (マスク名はプログラム先頭を見れば確認できる)

  5. プログラムを実行する。
    • コンソールに「完了」が表示されてから3, 4キーを押すとX, Y方向のプレヴィットフィルタ, ソーベルフィルタの計算結果を合成したものを元画像に合成した画像が表示される。
    • (ラプラシアンフィルタの結果を合成したものと比べて、ザラザラした白い点は少なくなる)

3. 鮮鋭化

概要

第05回でみた平滑化処理では、いずれも元画像にくらべてぼやけた画像が作られる。
このときに変化をあるピクセルについてだけ考えてみる。
そのピクセルの明度が元画像ではA、平滑化した画像ではBだったとすると、画像のピクセルの明度は「B-A」だけ変化した (増えた) ことになる。
このように変えると全体がぼやけるということは、「A-B」だけ変化させれば逆の変化が起こる、つまりくっきりさせられることが期待できる。
元の明度がAなので、この処理を行うとピクセルの明度は A+(A-B) = 2A - B になる。
このような処理をアンシャープマスク処理という。

課題3では第05回で作成した移動平均フィルタを使うsimpleMovingAverage関数でぼやけさせた画像と元画像の明度を使って上記の計算を行うが、この結果を1回の処理で得ることもできる。
移動平均フィルタの結果Bを得るためのマスクは
1/9 1/9 1/9
1/9 1/9 1/9
1/9 1/9 1/9
で、元画像の明度Aを得るためのマスクは
0 0 0
0 1 0
0 0 0
なので、後者を2倍したものから前者を引いた
-1/9 -1/9 -1/9
-1/9 17/9 -1/9
-1/9 -1/9 -1/9
でこれまでと同様の処理を行えば、元画像よりもくっきりした画像が得られる。この処理を鮮鋭化という。

課題 3

  1. superimpose関数をコメント文ごとコピーし、そのすぐ下にペーストして、下の方の名前をsubtract関数に変更する。
  2. subtract関数のコメント文を「// 画像mの明度の2倍から画像nの明度を引いたものを画像nに保存し、n番目の名前でファイル保存」に変更する。
  3. subtract関数の
          img[n].pixels[i+w*j] = color(brn+brm);
          img[n].pixels[i+w*j] = color(brm*2-brn);
    に変更する。

  4. 第05回の完成状態のプログラムのうちのsimpleMovingAverage関数を、今回のプログラムのsubtract関数とkeyPressed関数の間にコピー&ペーストする。
  5. simpleMovingAverage関数の名前をunsharp関数に変更する
  6. unsharp関数の前のコメント文を「// アンシャープマスク処理をかける(課題3)」に変更する
  7. unsharp関数の
      img[n].save("data/"+ fName[n] +".png");
      subtract(n, 0);
    に変更する。

  8. setup関数の
      single(mask_laplacian4, 1);// ラプラシアンフィルタ(横・縦)でエッジ抽出して元画像に合成した画像をimg[1]に保存
      single(mask_laplacian8, 2);// ラプラシアンフィルタ(全方向)でエッジ抽出して元画像に合成した画像をimg[2]に保存
      compose(mask_prewittX, mask_prewittY, 3); // X, Y方向のプレヴィットフィルタの計算結果を合成したものを元画像に合成した画像をimg[3]に保存
      compose(mask_sobelX, mask_sobelY, 4); // X, Y方向のソーベルフィルタの計算結果を合成したものを元画像に合成した画像をimg[4]に保存
    のあとに
      // アンシャープマスク処理をかけた画像をimg[5]に保存
    を追加する。

  9. 上記で追加したコメント文に対応したコードを追加する。
    (呼ぶのはunsharp関数)
    (第1引数は平滑化処理の半径なので1とする)
    (第2引数は画像番号)

  10. プログラムを実行する。
    • コンソールに「完了」が表示されてから5キーを押すとアンシャープマスク処理をした画像が表示される。
    • 元画像よりもくっきりした画像になる。

提出

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