第13回 細線化

OCRソフトでの文字認識や指紋の判別などでは、対象画像との直接的な一致の度合いよりも、構造が同じかどうかを調べて判定する手法が使われる。そのためには、元画像の特定のエリアを単純な線にまで細くして、線の構造を見る必要がある。この処理のことを細線化という。


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

はじめに、これから作るプログラムが完成した時点の機能をざっと見ておく。


細線化


課題 1

    ベースのプログラム (このままでは元画像を二値化した画像が表示されるだけ)
    PImage img;          // 元画像
    PImage[] outputImg = new PImage[2]; // 出力・画面表示画像(0:細線化前、1:細線化後)
    char prevKey = ' ';  // 直前に押されたキー
    int stime;           // カウント開始時刻
    boolean haveNoise;   // ノイズ付加済みフラグ
    int dir;
    
    int[][] mask = {
      {0, 0, 2, 0, 2, 1, 2, 1, 1}, // 左上マスク
      {}, // 上マスク
      {}, // 右上マスク
      {}, // 左マスク
      {}, // 欠番
      {}, // 右マスク
      {}, // 左下マスク
      {}, // 下マスク
      {}  // 右下マスク
    };
    
    void setup() {
      size(400, 300);
      dir = 0;
      img = loadImage("1元.png");
      img.filter(THRESHOLD, 0.1); // 二値化する(灰色は白寄りに)
      // 画面表示用の画像に同じものをコピー
      outputImg[0] = img.get();
      outputImg[1] = img.get();
      stime = millis();
    }
    
    // 2秒ごとに細線化前・細線化後の画像を交互に表示
    void draw() {
      image(outputImg[1-(millis()-stime)/2000%2], 0, 0, width, height);
    }
    
    // 指定された方向から1層分削り、削ったピクセル数を返す
    int removeLayer(int dir) {
      int n = 0; // 削ったピクセル数
      int[][] mTypes = {
        {0, 1, 3}, // 左上用のマスクの組み合わせ
        {}, // 右下用のマスクの組み合わせ
        {}, // 右上用のマスクの組み合わせ
        {}  // 左下用のマスクの組み合わせ
      };
      int[] mType =  mTypes[dir];// その方向で使うマスクの組み合わせ
    
      int[][] b = new int[img.width][img.height]; // 出力画像の白、黒を1, 0に対応させた整数配列
      for (int j=0; j<img.height; j++) {
        for (int i=0; i<img.width; i++) {
          // outputImg[1]の(i, j)のピクセルの明度に応じてb[i][j]に0か1を入れる
        }
      }
      for (int j=1; j<img.height-1; j++) {
        for (int i=1; i<img.width-1; i++) {
          int[] count = new int[3]; // 
          int pos = 0; // マスクの現在地
          // (i, j)からx, y方向に±1の範囲で調べる
          for (int l=j-1; l<=j+1; l++) {
            for (int k=i-1; k<=i+1; k++) {
              // 3つのマスクで順番に調べる
              for (int m=0; m<3; m++) {
                // マスクの現在地と画像をコピーしたデータの(k, l)の位置の値が一致したらcount[m]を1増やす
              }
              pos++;
            }
          }
          // 3つのマスクの条件のうち一つでも満たしたらそこを黒に変える
          if (count[0]==6 || count[1]==6 || count[2]==6) {
            if (b[i][j]==1) {
              n++;
            }
            outputImg[1].pixels[i+j*img.width] = color(0);
          }
        }
      }
      outputImg[1].updatePixels();
      return n;
    }
    
    // ノイズを付加する
    void addNoise() {
      for (int i=0; i<img.pixels.length/100; i++) {
        int r = (int)random(img.pixels.length);
        outputImg[0].pixels[r] = color(255-brightness(outputImg[0].pixels[r]));
      }
      outputImg[0].updatePixels();
      outputImg[1] = outputImg[0].get();
    }
    
    // 細線化(削るものがなくなるまで左上・右下・右上・左下の順に削る)
    void thining() {
      while (true) {
        int n=0; // 4方向トータルの削り数
        // 左上から削り、削った数をnに加える
        // 右下から削り、削った数をnに加える
        // 右上から削り、削った数をnに加える
        // 左下から削り、削った数をnに加える
        if (n==0) {
          break;
        }
      }
    }
    
    // 収縮×n, 膨張×2n, 収縮×n
    void edde(int n) {
    }
    
    void keyPressed() {
      stime = millis();
      // それまでと違うキーを押した場合は出力画像をリセット
      if (prevKey != key) {
        outputImg[0]=img.get();
        outputImg[1]=img.get();
      }
      prevKey = key;
      // dirに対応する方向から削り、次の方向に移る
      if (key=='1') {
        println(removeLayer(dir));
        dir = (dir+1)%4;
      }
      // 細線化
      if (key=='2') {
        thining();
        outputImg[1].save("data/1細線化.png");
      }
      // ノイズを付加してから細線化
      if (key=='3') {
        addNoise();
        thining();
        outputImg[0].save("data/2ノイズ.png");
        outputImg[1].save("data/2ノイズ細線化.png");
      }
      // ノイズを付加し、収縮×n・膨張×2n・収縮×nしてから細線化
      if (key=='4') {
        addNoise();
        edde(1);
        thining();
        outputImg[0].save("data/3ノイズ収膨収.png");
        outputImg[1].save("data/3ノイズ収膨収細線化.png");
      }
    }
    

  1. Processingのエディタに上のサンプルプログラムのコードをコピー&ペーストする。
  2. 「img13」という名前で保存する。
  3. ペイントを起動して画像サイズを横400、縦300にする。
  4. 文字入力機能(72ポイント、黒 (太字にしない))で左半分に収まるように2画以上のひらがなを1文字書く。


  5. 「ブラシ」の「カリグラフィブラシ 2」で左と同じ文字を手書きで書く。


  6. 画像をネガポジ反転させる。


  7. 画像を「1元.png」という名前で保存する。
  8. 「1元.png」をProcessingのウインドウにドラッグ&ドロップする。
  9. 上の説明を参考にして、プログラム先頭の配列maskを完成させる。

  10. 上の説明を参考にしてremoveLayer関数の配列mTypesを完成させる。

  11. removeLayer関数の「// outputImg[1]の(i, j)のピクセルの明度に応じてb[i][j]に0か1を入れる」の下に対応するコードを入れる。

  12. removeLayer関数の「// マスクの現在地と画像をコピーしたデータの(k, l)の位置の値が一致したらcount[m]を1増やす」の下に対応するコードを入れる。

  13. 実行して画面をクリックしてからキーボードの「1」キーを押す。

  14. コンソールに0しか表示されなくなるまで「1」キーを押し続ける。

  15. thining関数の4つのコメント文の下に、それぞれ対応するコードを追加する。

  16. 実行して画面をクリックしてからキーボードの「2」キーを押す。


  17. dataフォルダの「1細線化.png」を同じ場所にコピー&ペーストし、名前を「1細線化比較.png」に変更する。


  18. 「1細線化比較.png」をペイントで開き、左右の文字を比較して分岐の構造が異なっている所すべてに黄色・3pxの楕円で印をつけ、上書き保存する。


  19. ※ 文字認識では、このような端のゴミを無視して文字を比較するために、細線化後に「分岐からの長さが数ピクセル以内の枝を削除」などの処理を行って精度を上げる。

    この時点でできる提出ファイル
    ※ うまくいかないときは最終的なプログラム先頭部分最終的なremoveLayer関数最終的なthining関数、とコードを見比べる。

ノイズがある画像の細線化

課題 2

すでに「ノイズを追加する機能」「それを細線化する機能」は実装済み。ここでは細線化前後の画像を比較し、上記の説明のように黒いノイズが囲まれて残ることを確認する。

  1. プログラムを実行し、画面をクリックしてから「3」キーを押す。

  2. dataフォルダの「2ノイズ.png」を同じ場所にコピー&ペーストし、名前を「2ノイズ選択.png」に変更する。


  3. 「2ノイズ選択.png」をペイントで開き、少なくとも1つの黒いノイズが含まれたひとつながりのエリアを長方形か多角形(黄色、3px)で囲み、上書き保存する。

  4. 良い例
    悪い例
    (一つながりになっている白いエリアを選ぶ。一部分だけとか、どれを選んでいるのかが曖昧なものはNG)



  5. 選んだ範囲内の黒ノイズの数を数えて記録する (上の例では6個)。
  6. この図でカウントすべき黒ノイズは赤線で示した2個。

    (下側の赤線のピクセルのように、斜めで黒に接するものも黒ノイズとしてカウントする)
    (黄色線で示したものは、細線化の処理の邪魔にはならないため黒ノイズとしてはカウントしない)


  7. 「2ノイズ細線化.png」をダブルクリックして「フォト」で開き、上で選んだ部分に対応する場所をズームして、黒ピクセルが白で囲まれて「ダマ」になっている部分の数を数えて記録する。
  8. この範囲のダマの数は6個。

    処理が正しければ、この数は4番目のステップの結果と一致するはず。


    ここで作る提出物
    ここで記録するもの

収縮・膨張の組み合わせによるノイズ除去後の細線化

課題 3

  1. edde関数の中に、第12回のプログラムのedde関数の中身をコピー&ペーストする。

  2. edde関数の中の初めの行「outputImg[1] = outputImg[0].get();」を削除する。

  3. edde関数の中の最後の行「outputImg[1].save("data/2収膨収.png");」を削除する。

  4. edde関数のコメント文、コードの「outputImg[1]」をすべて「outputImg[0]」に書き換える (6箇所)。

  5. edde関数の中の最後に「outputImg[1]=outputImg[0].get();」を追加する。

  6. 実行して画面をクリックしてからキーボードの「4」キーを押す。

  7. ここで作る提出物
    ※ うまくいかないときは最終的なedde関数とコードを見比べる。

提出

小テスト予告



戻る inserted by FC2 system