Android java版OpenCV Mat中のピクセル操作


Android Java版OpenCVでMat中のピクセル操作する際は,get(), put()を使って1ピクセルずつ操作する例が多く示されている。
しかし,この操作はオーバーヘッドが大きく,多数のピクセルを操作するには不向きである。
C++版のOpenCVの解説ではポインタを使うと高速にピクセル操作が行えると書いてあるのに,
Java版では,ポインタが使えないので,それらしき記述が見当たらなかった。

get(), put()はもう少し柔軟な利用ができる。
get()でMat中のすべてのピクセルを得て,それらの値を操作変更し,put()を用いてもう一度Matに書き戻すという手法が高速なピクセ ル操作につながる。(C++版のOpenCVのポインタほどではないが)
画像の一部分だけを操作する場合では,submatを使って,操作対象部分を取り出してから,get().put()を使うとさらに高速化でき る。
get()では,用意した配列中に,Mat内のピクセルデータを配列の大きさに合わせてコピーする作業が行われる。
put()では,用意した配列中を,Mat内のピクセルデータに配列の大きさに合わせてコピーする作業が行われる。
Mat内すべてを取り出したり,書きだしたりするので,用意する配列の大きさはMat内のピクセルデータの大きさに合わせる必要がある。

・8bitのグレイスケール画像Mat
1ピクセルが1バイトで表されている,8bitのグレイスケール画像Matから,配列にデータを取り出すことを考えてみよう。
本来は2次元データだが,1次元の配列でデータを取り出す。
バイトデータは1行分つながり,それが行数分だけ,1列に並んでいる。行間の区切りのしるしは何もない。
例えば,8行12列(縦8横12)の画像では,左上のピクセルを(0,0)とすると,このピクセルは取り出した1次元配列の[0]にあたる。 (3,5)のピクセル(y=3,x=5)は,取り出した1次元配列の[3×12+5]にあたる。
*輝度は0が最低値(黒),255が最高値(白)となる。

・8bitRGBAカラー画像Mat
1ピクセルがRGBAの4バイト(順番は RGBA)で表されている,8bitRGBAカラー画像Matから,配列にデータを取り出すことを考えてみよう。
1ピクセルはRGBAの4バイトの塊で表されていて,この塊のデータが1行分つながり,それが行数分だけ,1列に並んでいる。塊間・行間の区切り のしるしは何もない。
例えば,8行12列(縦8横12)の画像では,左上のピクセルを(0,0)とすると,(0,0)にあるピクセルのRGBAの各値は取り出した1次元配列の [0],[1],[2],[3]にあたる。
(3, 5)のピクセル(y=3,x=5, 0から数えることに注意)のRGBAの各値は,取り出した1次元配列の[(3×12+5)×4+0], [(3×12+5)×4+1],[(3×12+5)×4+2],[(3×12+5)×4+3]にあたる。
*RGBAの各値は赤,緑,青の各輝度とそのピクセルの不透明度を意味している。
*各輝度は0が最低値,255が最高値となる。

・テスト
テストは,動作しているOpenCVのアプリ中にtestMat()を置いて,OpenCVの初期化が終わってから,この関数を呼び出すようにし た。
出力はLogcatを使用したため,あまりスマートではない。(実行結果内はLogcat特有の文字列が入っていたが,これは消去している。)

Matのメソッド
total():Mat中の総ピクセル数
channels():1ピクセル内の構成要素数
rows():Mat画像を構成している行数
cols():Mat画像を構成している列数

注意 javaのbyte型はsignedなので,-128から127までの値をとる。
プログラム中で値の大きさを比較する場合は,int型に型変換して,0xffとアンド演算してから行う必要がある。
  if (((int)return_buff[3]&0xff)<200)

・使用した関数testMat()

    private void testMat() {
        //まずはグレイスケールMatに関するテスト
        Mat mat = new Mat(2, 3, CvType.CV_8UC1); //縦幅2, 横幅3, CV_8UC1:8ビット、符号なし、1チャンネルのデータ
        byte[] raw_data = {1,2,3,4,5,6}; //ピクセル数は6個
        mat.put(0, 0, raw_data); // 書き込みのMat内のスタート位置は必ず(0,0)なので最初の2つの引数は0,0
        //この段階でmatは次のような2×3のピクセルの並びになっている。このような値の画像データはありえない
        // 1 2 3
        // 4 5 6
        Log.i("testMat","mat = "+mat.dump());
        long total = mat.total();
        int channels = mat.channels();
        int nRows=mat.rows();
        int nCols=mat.cols();
        Log.i("testMat","total, channels, nRows, nCols= "+total+", "+channels+", "+nRows+", "+nCols);

        byte[] return_buff= new byte[(int) (mat.total() * mat.channels())];
        mat.get(0, 0, return_buff); //mat内のすべてのデータの取得
                                                //取得時のMat内のスタート位置は必ず(0,0)なので最初の2つの引数は0,0
        Log.i("testMat","return_buff = "+ Arrays.toString(return_buff));
        return_buff[0]=11;
        Log.i("testMat","return_buff = "+Arrays.toString(return_buff));
        mat.put(0, 0, return_buff); //書き換えデータのmatへの書き込み
        byte[] return_buff2= new byte[(int) (mat.total() * mat.channels())];
        mat.get(0, 0, return_buff2); //matが書き換えられたことを確認するためすべてのデータの取得
        Log.i("testMat","return_buff2 = "+Arrays.toString(return_buff2));

        //次はRGBAの4値で1ピクセルを表すMatに関するテスト
        Mat matc = new Mat(2, 3, CvType.CV_8UC4); //縦幅2, 横幅3, CV_8UC4:8ビット、符号なし、4チャンネルのデータ
        byte[] raw_datac = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24};
        //ピクセル数は6個だが1つのピクセルは4つの値(RGBA)を持つので全データ数は24個になる
        //この段階でmatは次のような2×3のピクセルの並びになっている。このような値の画像データはありえない
        // [  1  2   3   4] [  5   6   7   8] [ 9 10 11 12]
        // [13 14 15 16] [17 18 19 20] [21 22 23 24]
        matc.put(0, 0, raw_datac);
        Log.i("testMat","matc = "+matc.dump());
        long totalc = matc.total();
        int channelsc = matc.channels();
        int nRowsc=matc.rows();
        int nColsc=matc.cols();
        Log.i("testMat","totalc, channelsc, nRowsc, nColsc= "+totalc+", "+channelsc+", "+nRowsc+", "+nColsc);

        byte[] return_buffc= new byte[(int) (matc.total() * matc.channels())];
        matc.get(0, 0, return_buffc);
        Log.i("testMat","return_buffc = "+Arrays.toString(return_buffc));
        double[] val;
        val=matc.get(0,0);  //(0,0)の位置のRGBAの値の取得
        Log.i("testMat","val = "+Arrays.toString(val));
        val=matc.get(0,1);  //(0,1)の位置のRGBAの値の取得
        Log.i("testMat","val = "+Arrays.toString(val));
        return_buffc[0]=111;
        Log.i("testMat","return_buffc = "+Arrays.toString(return_buffc));
        matc.put(0, 0, return_buffc);
        byte[] return_buff2c= new byte[(int) (matc.total() * matc.channels())];
        matc.get(0, 0, return_buff2c);
        Log.i("testMat","return_buff2c = "+Arrays.toString(return_buff2c));
        val=matc.get(0,0);  //(0,0)の位置のRGBAの値の取得 一ピクセルのみの取り出しはdouble型の配列を用意する
        Log.i("testMat","val = "+Arrays.toString(val));
        val=matc.get(0,1);  //(0,1)の位置のRGBAの値の取得 一ピクセルのみの取り出しはdouble型の配列を用意する
        Log.i("testMat","val = "+Arrays.toString(val));
        int dummy=0;
    }

・実行結果

グレースケールMatのテスト
mat = [  1,   2,   3;
       4,   5,   6]
total, channels, nRows, nCols= 6, 1, 2, 3
return_buff = [1, 2, 3, 4, 5, 6]
return_buff = [11, 2, 3, 4, 5, 6]
return_buff2 = [11, 2, 3, 4, 5, 6]

RGBAカラーMatのテスト
matc = [  1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12;
      13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24]
totalc, channelsc, nRowsc, nColsc= 6, 4, 2, 3
return_buffc = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
val = [1.0, 2.0, 3.0, 4.0]  ← R,G,B,Aの値
val = [5.0, 6.0, 7.0, 8.0]
return_buffc = [111, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
return_buff2c = [111, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
val = [111.0, 2.0, 3.0, 4.0]
val = [5.0, 6.0, 7.0, 8.0]