URL上の画像をJavaCV (OpenCV)で読み込む

JavaでOpenCVを使う必要があったので、JavaのラッパーライブラリであるJavaCVを利用してみました。

GitHub - bytedeco/javacv: Java interface to OpenCV, FFmpeg, and more
Java interface to OpenCV, FFmpeg, and more. Contribute to bytedeco/javacv development by creating an account on GitHub.

その際に、URL上の画像を読み込んで加工するのに少々ハマったので、その対応メモです。

やりたいこと

URL上の画像パスから画像をMat型で読み込んで、OpenCVで加工したいのです。

ハマったポイント

URLから画像を読み込む機能はOpenCVにはなさそうだったので、JavaのImageIOクラスを使ってURLから画像を読み込んで、それをOpenCVのMatに変換してやろうと思いました。

当初はImageIOクラスで読み込んだ画像データはint配列として取り出せるので、それをバイト配列に変換してからOpenCVのMatで読み込めばいけるだろうと思ったんですが、こんな感じでなんだか色味がおかしくなってしまいました。

ちなみに元画像は↓のOpenCVのサンプル画像を利用させてもらってます。

https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/fruits.jpg

どうせBGRとして読み込むべきところをRGBとして読み込んじゃってるとかそんなんだろうと思ってcvtColorで変換かけてみましたが、赤っぽくなるだけで元画像と同じ色味にはなりませんでした。

原因

原因は、ImageIOクラスから取り出したint配列はARGBの順で並んでいるため、その順のままMatに変換してしまうとおかしな色になってしまうことにあります。

なので、この問題を解決するためには、ARGBの並び順をMatで読みこめるBGRAの順に並び変えないといけないです。

そういった変換にはcvtColorを使いたいところですが、残念ながらOpenCVにはARGBをBGRAなどに変換する処理は実装されていません。

そのため、ARGBからBGRAに変換する部分は自前で実装する必要があります。

実装

上記のARGBからBGRAに変換する部分を自前実装して、URLから画像を読み込んでMat型に格納するサンプルコードがこちらになります。

public class Sample {
    public static void main(String[] args) throws IOException {
        // URLから画像を取得
        URL url = new URL("https://raw.githubusercontent.com/opencv/opencv/4.x/samples/data/fruits.jpg");
        BufferedImage bufferedImage = ImageIO.read(url);

        // 各画素のARGB色情報を抽出
        // intは4byteなので、各byteがそれぞれARGBの各要素に対応している
        int[] argbArray = bufferedImage.getRGB(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), null, 0, bufferedImage.getWidth());

        // int配列をbyte配列に変換する
        // その際、Mat型で読み込めるようにARGBの並びをBGRAに変換する
        byte[] bgraArray = new byte[argbArray.length * 4];
        for (int i = 0; i < argbArray.length; i++) {
            bgraArray[i * 4 + 0] = (byte) ((argbArray[i] >> 0) & 0xFF); // B
            bgraArray[i * 4 + 1] = (byte) ((argbArray[i] >> 8) & 0xFF); // G
            bgraArray[i * 4 + 2] = (byte) ((argbArray[i] >> 16) & 0xFF); // R
            bgraArray[i * 4 + 3] = (byte) ((argbArray[i] >> 24) & 0xFF); // A
        }

        // Mat型として読み込み
        Mat image = new Mat(bufferedImage.getHeight(), bufferedImage.getWidth(), CV_8UC4, new BytePointer(bgraArray));
        imwrite("result.jpg", image);
    }
}

上記コードを実行すると、無事元画像と同じものがresult.jpgとして保存されます。

参考

How to convert between Mat and byte[] · Issue #888 · bytedeco/javacv
This is different from opencv's API. I'm not familiar with it.

コメント