みの屋.

プログラムを書いてる時に躓いたことやコードの進捗などをメモでまとめていく予定です.(ついでに日常のつぶやきもちらほらと……)

【Java】OpenCVで画像のアルファブレンディングしてみるお話

とある授業の課題で,2つの画像をアルファブレンディングすると言うものが出たのですが,そのうちの一つでめちゃくちゃ悩んだので解決法を.

今回作成に悩んだ画像はこちら.

f:id:Minoya:20171208160016p:plain:w300
ケーキのアルファブレンディング

一見簡単そう(OpenCVに一発でできる関数ありそう)とか思ってたのですが,そんな関数は探しても見つからず………
それなら誰かがネットに解決法を書いているだろうと探すもやっぱりどこにも落ちておらず………

結局自分でいろんなサイトさんを組み合わせて試行錯誤しながら完成までに数日かかってしまいました………

本当は隣の机で同じ課題のコードをPythonOpenCVで完成させていたのを横目に作業していたものの,やっぱり環境構築で意地はってJavaでやると決めたからには今回もがっつり意地はってしまいました………Pythonで書くと今回の私のコードより10行近く短縮されるらしいので「絶対Javaじゃないと嫌!」「Javaでないと困る!」という方でなければPythonの方も書きやすくてオススメです.

そして,私の検索スキルが弱いせいかもしれませんが,思ったより全くプログラムがヒットしなかったため, 今回やっと完成させたプログラムを忘れないように早速こっちにメモメモ.

アルゴリズム

今回のプログラムでは

  • 合成する画像を縦1ピクセルごとに分割する
  • 画像を合成する際にalpha値の比を右から左へグラデーションするように少しずつ変更(1ピクセルごとに1/widthだけalpha値を足していく)しながらfor文で処理を行う
  • 1ピクセルごとに処理したMatを再度連結して一つの画像にする

この3つの処理ができれば右から左へのアルファブレンディングが完成するはずなので,これを目標にプログラムしていきます

まずは,早速1番目と2番目の項目の実装です.

       int wid_main = img.cols();
        double wid = 0;

        for(int width=0; width<wid_main; width++){   //画像の横幅分for文を回す(img:画像1, img:画像2)
            Mat des = new Mat();
            Core.addWeighted(img.col(width), wid, img2.col(width), 1-(wid), 0.0, des);
            wid += 1.0/img.cols();
        }

ここはあっさり完成しました.
ここに処理した画像を一つの画像に連結し直すプログラムを書き加えれば完成です.

…………実は今回はここでがっつりつまづきました.
なんと,JavaOpenCVでは画像を一発で連結できる関数が無かったです

そこで,新規Matを用意し処理した縦1ピクセルのMatを順番に貼り付けてみたもののうまく行かず,また元の画像に処理済みのMatを順番に上書きしてみるもののコードが汚くなったばかりか画像も綺麗に仕上がらず.

結局OpenCVで画像を連結する際によく使われているhconcatという関数を見つけるまでに結構な時間がかかってしまいました.
しかし,やっと見つけたこのhconcat なぜか引数が(List, Mat)
てっきりこの関数は連結させたい画像2枚を引数に取ると思っていたのでまた混乱しました.

落ち着いてhconcatについてよくよく調べてみると,こちらのサイトさんに詳しい記述がありました. iwaki2009.blogspot.jp

以前は、2枚の画像を接続するのに使用したが、配列やVectorに格納されたMatを格納された枚異数だけ結合する機能もあることがわかった。 2枚の場合、配列の変形版で実現されている。

なるほど.hconcatを使う際には,Mat型を連結する順に格納したListが必要なようです.
やっと引数の意味がわかったところでMatをListに格納するための処理をソースコードに書き加えていきます.

       Mat[] array = new Mat[img.cols()];
        int wid_main = img.cols();
        double wid = 0;

        for(int width=0; width<wid_main; width++){
            Mat des = new Mat();
            Core.addWeighted(img.col(width), wid, img2.col(width), 1-(wid), 0.0, des);
            array[width] = des;
            wid += 1.0/img.cols();
        }

…………はい.実は先ほどListに格納しなければならない,と言ったものの,上記では配列にMatを格納しています.
実は使用したいList型のArrays.asList()は中身の追加ができないため,一度配列に格納してから最後に一括でListに起こしています.

ここまでできたところで,やっと配列をList型に変換し,hconcatを使用していきます.

       List<Mat> dest = Arrays.asList(array);
        Mat dst = new Mat();
        Core.hconcat(dest, dst);

これで,dstにList内のMatが全て連結された画像が格納されました.
あとは画像を出力して完成です!!
色々混乱することが多かったですが,やっぱりできた時の達成感は良いですね!

今回のプログラムの完全版は以下に示します.

import java.util.Arrays;
import java.util.List;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;

public class alpha_blending_gradation {
    public static void main(String[] args){
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        Mat img = Imgcodecs.imread("/Users/ユーザ名/dir/path/cake_Apple.jpg");
        Mat img2 = Imgcodecs.imread("/Users/ユーザ名/dir/path/cake_Marron.jpg");

        Mat[] array = new Mat[img.cols()];
        int wid_main = img.cols();
        double wid = 0;

        for(int width=0; width<wid_main; width++){
            Mat des = new Mat();
            Core.addWeighted(img.col(width), wid, img2.col(width), 1-(wid), 0.0, des);
            array[width] = des;
            wid += 1.0/img.cols();
        }

        List<Mat> dest = Arrays.asList(array);
        Mat dst = new Mat();
        Core.hconcat(dest, dst);
        Imgcodecs.imwrite("/Users/ユーザ名/dir/path/cake_alpha_blending_gradation3.png", dst);
    }
}