【Java】二値画像の細線化

■ サンプル

https://blogs.yahoo.co.jp/dk521123/37815897.html
で、細線化を勉強した時に、以下のサイトでJavaのプログラムがあった。
https://codezine.jp/article/detail/98
実行しようとしたら、Appletだったので、もっと簡単に実行できるように、Swingで書き直してみた。
ついでに汎用的な画像でも使えるようにしたり、色々とリファクタリングした。

■ サンプル

 * 使用する画像は、ペイントで適当に白黒でテストデータ画像を作って実行した

Main.java

import javax.swing.JFrame;

public class Main {
  public static void main(String[] args) {
    try {
      JFrame frame = new JFrame("Thinning Demo");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      ThinningPanel panel = new ThinningPanel("C:\\temp\\sample.png");
      frame.add(panel);
      frame.pack();
      frame.setSize(1200, 500);
      frame.setVisible(true);
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}

ThinningPanel.java

import java.awt.Color;
import java.awt.Graphics;
import java.awt.MediaTracker;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

public class ThinningPanel extends JPanel {
  private static final long serialVersionUID = 1L;

  private final static int UPPER_LEFT = 2;
  private final static int LOWER_RIGHT = 6;
  private final static int UPPER_RIGHT = 0;
  private final static int LOWER_LEFT = 4;
  private final static byte BLACK = 1;
  private final static byte WHITE = 0;
  private final static int THRESHOLD = 128;

  private BufferedImage srcImage;
  private boolean hasChanged;
  private byte[][] newPixels;
  private byte[][] oldPixels;
  private int imageWidth;
  private int imageHeight;

  public ThinningPanel(String imagePath) throws IOException, InterruptedException {
    init(imagePath);
  }

  private void init(String imagePath) throws IOException, InterruptedException {

    File pathToFile = new File(imagePath);
    this.srcImage = ImageIO.read(pathToFile);
    MediaTracker mediaTracker = new MediaTracker(this);
    mediaTracker.addImage(this.srcImage, 0);
    mediaTracker.waitForAll();
    this.imageWidth = this.srcImage.getWidth();
    this.imageHeight = this.srcImage.getHeight();
    this.newPixels = new byte[this.imageWidth + 2][this.imageHeight + 2];
    this.oldPixels = new byte[this.imageWidth + 2][this.imageHeight + 2];
    this.toBinaryExpanded(this.srcImage, this.imageWidth, this.imageHeight);

  }

  // 原画像を拡張し、細線化のための二次元二値データを生成するメソッド
  private void toBinaryExpanded(BufferedImage image, int width, int height) {
    int[] rgbPixcels = new int[width * height];

    // 原画像imgを一次元RGBデータrgb[]にする
    PixelGrabber grabber = new PixelGrabber(image, 0, 0, width, height, rgbPixcels, 0, width);
    try {
      grabber.grabPixels();
    } catch (InterruptedException e) {
    }

    // 拡張画像を作り、その二次元二値データをすべて白(0)に設定する
    for (int j = 0; j < height + 2; j++) {
      for (int i = 0; i < width + 2; i++) {
        this.newPixels[i][j] = WHITE;
      }
    }

    // 原画像の一次元RGBデータrgbPixcels[]を、
    // 拡張画像の中央部に二次元化して書き込む
    for (int j = 1; j < height + 1; j++) {
      for (int i = 1; i < width + 1; i++) {
        Color color = new Color(rgbPixcels[(j - 1) * width + (i - 1)]);
        int redValue = color.getRed();
        if (redValue < THRESHOLD) {
          // 黒に設定
          this.newPixels[i][j] = BLACK;
        }
      }
    }
  }

  @Override
  protected void paintComponent(Graphics graphics) {
    int width = this.imageWidth + 2;
    int height = this.imageHeight + 2;

    // 原画像を画面の左側に描画
    graphics.drawImage(this.srcImage, 10, 10, null);

    // 細線化を実施する
    do {

      this.hasChanged = false;

      // 描画とコピー
      this.drawAndCopy(graphics, width, height);
      // 左上から細線化
      for (int j = 1; j < height - 1; j++) {
        for (int i = 1; i < width - 1; i++) {
          if (oldPixels[i][j] == BLACK) {
            this.thinImage(i, j, UPPER_LEFT);
          }
        }
      }

      // 描画とコピー
      this.drawAndCopy(graphics, width, height);
      // 右下から細線化
      for (int j = height - 2; j >= 1; j--) {
        for (int i = width - 2; i >= 1; i--) {
          if (this.oldPixels[i][j] == BLACK) {
            this.thinImage(i, j, LOWER_RIGHT);
          }
        }
      }

      // 描画とコピー
      this.drawAndCopy(graphics, width, height);
      // 右上から細線化
      for (int j = 1; j < height - 1; j++) {
        for (int i = width - 2; i >= 1; i--) {
          if (this.oldPixels[i][j] == BLACK) {
            this.thinImage(i, j, UPPER_RIGHT);
          }
        }
      }

      // 描画とコピー
      this.drawAndCopy(graphics, width, height);
      // 左下から細線化
      for (int j = height - 2; j >= 1; j--) {
        for (int i = 1; i < width - 1; i++) {
          if (this.oldPixels[i][j] == BLACK) {
            this.thinImage(i, j, LOWER_LEFT);
          }
        }
      }
    } while (this.hasChanged);
  }

  // 描画とコピーのメソッド
  private void drawAndCopy(Graphics graphics, int width, int height) {
    for (int j = 0; j < height; j++) {
      for (int i = 0; i < width; i++) {
        if (this.newPixels[i][j] == BLACK) {
          graphics.setColor(Color.black);
        } else {
          graphics.setColor(Color.white);
        }

        // 細線化画像を画面右側に描画
        graphics.drawRect(this.imageWidth + 40 + i, 10 + j, 1, 1);
        // 次の細線化のためのコピー
        this.oldPixels[i][j] = this.newPixels[i][j];
      }
    }
  }

  // 細線化のメソッド
  public void thinImage(int i, int j, int start) {

    byte[] pixcels = new byte[8];
    pixcels[0] = this.oldPixels[i - 1][j - 1];
    pixcels[1] = this.oldPixels[i - 1][j];
    pixcels[2] = this.oldPixels[i - 1][j + 1];
    pixcels[3] = this.oldPixels[i][j + 1];
    pixcels[4] = this.oldPixels[i + 1][j + 1];
    pixcels[5] = this.oldPixels[i + 1][j];
    pixcels[6] = this.oldPixels[i + 1][j - 1];
    pixcels[7] = this.oldPixels[i][j - 1];

    for (int k = start; k < start + 3; k++) {
      int product = pixcels[k % 8] * pixcels[(k + 1) % 8] * pixcels[(k + 2) % 8];
      int sum = pixcels[(k + 4) % 8] + pixcels[(k + 5) % 8] + pixcels[(k + 6) % 8];
      if (product == 1 && sum == 0) {
        // 消去する
        this.newPixels[i][j] = WHITE;
        this.hasChanged = true;
        return;
      }
    }
  }
}

関連記事

二値化 / 領域抽出 / 細線化

https://blogs.yahoo.co.jp/dk521123/37815897.html