OpenCVエラー 〜contours.cpp:199: error〜

下のようなエラーが出ました。

opencv/modules/imgproc/src/contours.cpp:199: error: (-210:Unsupported format or combination of formats) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function 'cvStartFindContours_Impl'

ここで改めてログを見て見ると

[Start]FindContours supports only CV_8UC1 images

TRY1

とあるので、ソースを下のようなものから(テスト中で。。。)

Mat charactor = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_UNCHANGED);
// 背景除去
Mat hierarchy = Mat.zeros(new Size(200, 200), CvType.CV_8UC1);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(charactor, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_TC89_L1);
Mat dstContuor = Mat.zeros(new Size(charactor.width(),charactor.height()),CvType.CV_8UC3);
Scalar color=new Scalar(255,255,255);  
Imgproc.drawContours(dstContuor, contours, -1, color,1); 

描画モード(引数に渡している定数を「CvType.CV_8UC1」統一しました。

しかし、また。。。

opencv/modules/imgcodecs/src/loadsave.cpp:925: error: (-215:Assertion failed) code in function 'imencode'

Try2

調べて見ると、画像はグレースケースでないとけ内容だ。。。

読み取りを下のように修正

Mat charactor = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_GRAYSCALE);
Mat gray = new Mat();
Imgproc.cvtColor(charactor, gray, Imgproc.COLOR_BGR2GRAY);

駄菓子菓子またもエラー

Invalid number of channels in input image:
'VScn::contains(scn)'
where
'scn' is 1
]

こんな感じのエラー。。。

TRY3

色々と試して見るが、どれもエラーになる。。。
ソースを少し見直して見た!

// 表示イメージを読み取る
Mat charactor = Imgcodecs.imread(url.getPath(), Imgcodecs.IMREAD_COLOR);
Mat gray = new Mat();
Imgproc.cvtColor(charactor, gray, Imgproc.COLOR_BGR2GRAY);
// 背景除去
Mat hierarchy = Mat.zeros(new Size(200, 200), CvType.CV_8UC1);
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
Imgproc.findContours(gray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);
Mat dstContuor = Mat.zeros(new Size(charactor.width(),charactor.height()),CvType.CV_8UC1);
Scalar color=new Scalar(255,255,255);  
Imgproc.drawContours(dstContuor, contours, -1, color,1); 

//      Size resize = new Size(before.getWidth() / 3, before.getHeight() / 3);
//      Mat reSizeChar = new Mat();
//      Imgproc.resize(charactor, reSizeChar, resize);
MatOfByte charaByteBefore = new MatOfByte();
// 表示イメージをcharaByteに書き込む
Imgcodecs.imencode(".png", dstContuor, charaByteBefore);

Mat dst = new Mat();
//      Imgproc.threshold(reSizeChar, dst, 100, 255, Imgproc.THRESH_BINARY);
MatOfByte charaByte = new MatOfByte();

// この行で落ちていた。。。。
☆Imgcodecs.imencode(".png", dst, charaByte);

上の☆マーク部分で落ちていたのですが、これは不要な処理でした。。。(色々と試した時の残骸です(笑))

そんなわけで実行することができました。

変更前は下のような感じです。ハナっからグレースケール(笑)

でわでわ。。。



Java OpenCV 〜背景除去、輪郭を学習する準備4:コマンドで起動する実装〜

画面に入力した文字列(コマンド)で起動する処理をプログラムを変更しないでも追加できるように実装を変更します。

現状の実装

  1. Executeボタンを押下すると処理が動く
  2. 画面の上部中央にあるテキストふフィールドの値を取得
  3. 画面の下にあるClearボタンで表示したイメージを消す

シンプルに、ボタンを押下→Canvas部分にイメージを描画

という処理を行っています。Clearボタンを押下するとCanvasに描画したイメージがクリアされて何もなくなります。

次の実装

実装した時の動画を作成しました。

行っていることは、下の通りです。

  1. CommandIF(インターフェースクラス)を作成
  2. LearnOpenCvクラスにCommandIFを実装(impllements)
  3. 起動確認

Githubにて、実装の差分を見ることができます。

とりあえず、ここまでの実装では、動き的にはじめのものと変わりません。そのように作りました。
ポイントとしては、CommandIFを追加したので、CommandIFを実装したクラスなら、なんでも実行することができるようになったというところです。

クラスを動的に変更して見る

ここで追加するのが、Propertyファイルです。
処理内容としては下のような手順です。

  1. Propetiesクラスを使用してプロパティファイルを読み込む
  2. キー(コマンド)がプロパティファイル内にないときはエラーメッセージをコンソールに出力
  3. キー(コマンド)で起動するクラスの完全名を取得する
  4. 完全クラス名でクラスのインスタン種を取得する
  5. 取得したクラスはCommandIFを実装しているのでexecute()を実行する
  6. 結果をGraphicsContextクラスに描画する→JavaFXのCanvasに描画される

こんな感じです。

実行した結果は動画にしました。余計な音が入っています。

前半

後半

次回

OpenCVでの学習を開始します。ようやく準備ができました。

でわでわ。。。

Java OpenCV 〜背景除去、輪郭を学習する準備3:画像を表示する〜

JavaFXを使用してOpenCVで行なった処理を表示する仕組み(アプリ)を作成します。

前回、SceneBuilderを使用して作成した画面の土台になる部分に、コントローラーを追加しました。
これにより「ボタンを押下した時にXXXの処理をする〜」というような処理を実装することができます。

画像を描画する

そして、やっとOpenCVの処理にたどり着きました。
イメージファイルを読み込み表示する。。。ハローワールド的な処理ですが、まずはハローワールドです。

出力した結果はい下のような感じで出力することができました。

実装(成果物)

「FXMLファイル」

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:controller="zenryokuservice.opencv.fx.controller.TestingCvController"
        maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
        prefHeight="400.0" prefWidth="600.0"
        xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <top>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Label text="Input app no" />
            <TextField promptText="App No" />
            <Button mnemonicParsing="false" text="Execute" onAction="#clickExecute"/>
         </children>
      </HBox>
   </top>
   <center>
      <Canvas fx:id="testCanvas" height="200.0" width="200.0" BorderPane.alignment="CENTER" />
   </center>
   <bottom>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Button mnemonicParsing="false" text="Clear"  onAction="#clear"/>
         </children>
      </HBox>
   </bottom>
</BorderPane>

コントローラー

public class TestingCvController {

    @FXML
    private Canvas testCanvas;

    /** コンストラクタ */
    public TestingCvController() {
        this.testCanvas = new Canvas();
    }

    @FXML
    protected void clickExecute() {
        System.out.println("Helllo");
        URL url = getClass().getResource("/pipo-charachip007.png");
        Mat charactor = Imgcodecs.imread(url.getPath(), CvType.CV_8UC4);
        MatOfByte charaByte = new MatOfByte();
        Imgcodecs.imencode(".png", charactor, charaByte);
        try {
//          Image img = new Image(url.getPath().toString());
            BufferedImage buf = ImageIO.read(new ByteArrayInputStream(charaByte.toArray()));
            GraphicsContext g = testCanvas.getGraphicsContext2D();
            g.drawImage(SwingFXUtils.toFXImage(buf, null), buf.getWidth(), buf.getHeight());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @FXML
    public void setClosed() {
        // 現状は空実装
    }

    @FXML
    public void clear() {
        System.out.println("Clear");
        this.testCanvas.getGraphicsContext2D().clearRect(0, 0, this.testCanvas.getWidth(), this.testCanvas.getHeight());
    }
}

処理内容

FXMLで、アクションを指定しています。

<Button mnemonicParsing="false" text="Execute" onAction="#clickExecute"/>

この行で指定している#clickExevuteの部分でコントローラークラスの「TestingCvController#clickExevute()
を呼び出す」という意味になります。
同様に、<Button mnemonicParsing="false" text="Clear" onAction="#clear"/>の部分は「TestingCvController#clear()」を呼び出す。という命令を指定しています。

画像を表示する

下の処理が起動するメソッドになります。

@FXML
protected void clickExecute() {
    System.out.println("Helllo");
    URL url = getClass().getResource("/pipo-charachip007.png");
    Mat charactor = Imgcodecs.imread(url.getPath(), CvType.CV_8UC4);
    MatOfByte charaByte = new MatOfByte();
    Imgcodecs.imencode(".png", charactor, charaByte);
    try {
        BufferedImage buf = ImageIO.read(new ByteArrayInputStream(charaByte.toArray()));
        GraphicsContext g = testCanvas.getGraphicsContext2D();
        g.drawImage(SwingFXUtils.toFXImage(buf, null), buf.getWidth(), buf.getHeight());
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

ビルドパス上にあるフォルダより「pipo-charachip007.png"」を読み込み、SceneBuilderで作成したCanvasに書き込み処理を行っています。
上に表示した画像は指定のpngファイルを表示したものです。

ここまで来たので

現状としては、固定のイメージファイルを表示しているだけですが、ここからアプリっぽくしていきます。
変更点としては下の部分です。

  1. テキストボックスに入力したファイル名を表示する
  2. Clearボタンを押下したら、現在表示している画像を消す

とりあえずはここまで実装します。

LearnOpenCvクラスを作る

設計としては以下のような実装にしようと考えております。

  1. テキストフィールドに処理の名前(コマンド)を入力
  2. コマンドに対応したクラスのexecute()を起動する→なのでコマンドはいくらでも増やすことができる

具体的にどのように実現するか?

すでに実装してあるclickExcecute()の処理を修正して下のように変更する

  1. 入力値を受け取り、プロパティファイルのキーの有無をチェック
  2. プロパティファイルにキーがあれば、キーに紐付くクラスを取得、execte()を起動する
  3. 実行結果をCanvasに描画する

なので、execute()には引数としてGraphicsContextを渡してあげる必要があります。(描画するためです)

実装は次回にいたします。

でわでわ。。。



Java OpenCV 〜背景除去、輪郭を学習する準備2コントロラー追加〜

前回は、SceneBuilderで土台になる画面を作成しました。

今回は、この土台の細かい調整を行います。

Canvasを表示する

まずは、この画面のメイン部分になる「Canvas」を表示させて全体の雰囲気を見ます。

Canvasはプログラムで描画した画像を表示するためにしようする領域(コンテナ)になります。

まずは、このプログラムの問題点を修正します。

上のリンク先にはプログラムコードのGithubを参照しています。ちなみにこのプログラムの問題点は、以下の部分です。

OpenCvController controller = loader.getController();

この「OpenCvController」クラスは、今回作成したFXMLで登録していないクラスなので下のようにNullPointerExceptionで落ちます。

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at zenryokuservice.opencv.fx.Main$1.handle(Main.java:59)
    at zenryokuservice.opencv.fx.Main$1.handle(Main.java:1)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at com.sun.javafx.stage.WindowPeerListener.closing(WindowPeerListener.java:88)
    at com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:122)
    at com.sun.javafx.tk.quantum.GlassWindowEventHandler.run(GlassWindowEventHandler.java:40)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassWindowEventHandler.lambda$handleWindowEvent$423(GlassWindowEventHandler.java:151)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassWindowEventHandler.handleWindowEvent(GlassWindowEventHandler.java:149)
    at com.sun.glass.ui.Window.handleWindowEvent(Window.java:1266)
    at com.sun.glass.ui.Window.notifyClose(Window.java:1174)

コントローラーを登録する

コントローラーというのは、作成したFXMLには大まかに画面の表示部分とコントロール部分に分かれます。

上にあるのが画面の表示部分です。
前回作成したFXMLで出力されるのが、「表示部分」のみです。

出力されたもの(コード)は下のようになっています。

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <top>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Label text="Input app no" />
            <TextField promptText="App No" />
            <Button mnemonicParsing="false" text="Execute" />
         </children>
      </HBox>
   </top>
   <center>
      <Canvas height="200.0" width="200.0" BorderPane.alignment="CENTER" />
   </center>
   <bottom>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Button mnemonicParsing="false" text="Clear" />
         </children>
      </HBox>
   </bottom>
</BorderPane>

このルートになるコンテナクラス(=BorderPane)にコントローラークラスを追加します。

fx:controller="zenryokuservice.opencv.fx.controller.TestingCvController"

具体的には下のようになります。

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.canvas.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>

<BorderPane fx:controller="zenryokuservice.opencv.fx.controller.TestingCvController"
        maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
        prefHeight="400.0" prefWidth="600.0"
        xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
   <top>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Label text="Input app no" />
            <TextField promptText="App No" />
            <Button mnemonicParsing="false" text="Execute" onAction="#clickExecute"/>
         </children>
      </HBox>
   </top>
   <center>
      <Canvas height="200.0" width="200.0" BorderPane.alignment="CENTER" />
   </center>
   <bottom>
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" BorderPane.alignment="CENTER">
         <children>
            <Button mnemonicParsing="false" text="Clear" />
         </children>
      </HBox>
   </bottom>
</BorderPane>

コントローラー追加

次回は、以下のサイトを参考にFXMLの書き方を見て行きます。
このFXMLのチュートリアルプロパティのリファレンス(alignmentなど)があるので参考にして見て行きます。

でわでわ。。。

OpenCv 画像編集 〜プログラマ的デザイン手法〜

イントロダクション

目標達成プロジェクトを作成し、目標ブレークを行った時の目標のカテゴリ分けをわかりやすくするため(わかりづらくなったらごめんなさい)。

カテゴリをイメージにしました。やったことのない「デザイン(イメージのデザイン)」を四苦八苦しながらやりました。

正確には「イメージの部品」を作成しました。作成したものはGitにアップしました。

OpenCVで画像を重ねる

元にしたコードは以前やったものを持ってきました。環境のセットアップも見直しました。

ちょいと注意しなくてはいけないのが以下の点になります。

  1. 「画像ファイル名は日本語ではエラーになる」
  2. EclipseのNativeLibraryの設定

ここの記事見ておくと「は?意味わからん…」とならないと思われます。

そして実行して困ったのが、表示したものが「黒くなっている」ということでした。どうやら「0」が「透過=黒」になっている様で、Matクラスのチャンネル=要素数を4つにしなくてはいけない様です。

実行した結果

はこんな感じです。サイズが「60 x 60」なので小さいです。

実行したコードは下の様な感じです。Gitで完全なコードが観れます。

long start = System.currentTimeMillis();
///// 画像読込 /////
// レベル0カテゴリ
Mat src = Imgcodecs.imread(OpenCVTest9_Substract.class.getResource("/categories/" + "1_create.png").getPath(), Imgcodecs.IMREAD_UNCHANGED);
// レベル1カテゴリ
Mat cart = Imgcodecs.imread(OpenCVTest9_Substract.class.getResource("/categories/" + "A_design.png").getPath(), Imgcodecs.IMREAD_UNCHANGED);
// レベル2カテゴリ
Mat cart1 = Imgcodecs.imread(OpenCVTest9_Substract.class.getResource("/categories/" + "a_artifacts.png").getPath(), Imgcodecs.IMREAD_UNCHANGED);
System.out.println("*** First ****");
// 出力用、画像(行列を表すクラス)
Mat dst = new Mat();
// 元の画像に重ねていく
Core.addWeighted(src, 0.5, cart, 0.5, 0, dst);
Core.addWeighted(dst, 0.5, cart1, 0.5, 0, dst);
System.out.println("*** Second ****");
Imgcodecs.imwrite("/dst/1Aa.png", dst);
ViewFrame frame = new ViewFrame(dst);
System.out.println("実行時間: " + (System.currentTimeMillis() - start) + "ミリ秒");

シンプルに画像を読み込んで、重ねていく処理です。OpenCvは、参考書などを読むと無茶苦茶難しいですが、部分的に使うなら問題なく使えました。

そして、こいつを理解すれば機械学習も怖くない(笑)

でわでわ。。。

関連ページ