2017年2月17日金曜日

ヒストグラム均一化フィルタ(ToneEqualizationFilter)

今回はヒストグラムを均一化するフィルタを作成します。ヒストグラム均一化では、ヒストグラムの累積度数(輝度値0から画素数を累積したもの)のグラフの傾きが一定になるように変換する処理です。こうすることによって、コントラストが悪かったり、明るさが偏っている画像の全体的なバランスを改善することが可能になります。
http://www.mis.med.akita-u.ac.jp/~kata/image/equalize.html

  Tutorial1Activityクラスと同じ場所にToneEqualizationFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int tone[][]=ToneCalculator(imageArray);//①
        int length = width*height;
        int tonemap[][]=new int[3][256];
        tonemap[0]=tonemapping(tone[0],length);//②
        tonemap[1]=tonemapping(tone[1],length);
        tonemap[2]=tonemapping(tone[2],length);
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int alpha,red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            oimgArray[i]=255*16777216+
                    tonemap[0][red]*65536+tonemap[1][green]*256+tonemap[2][blue];
        }
        return oimgArray;
    }

 ①は前回同様に画像のヒストグラムを計算します。以下にメソッドを記載します。
   public int[][] ToneCalculator(int[] imageArray){
        int tone[][]=new int[3][256];
        int length = imageArray.length;
        int rgb;
        int red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            tone[0][red]+=1;
            tone[1][green]+=1;
            tone[2][blue]+=1;
        }
        return tone;
    }

 ②はヒストグラムの均一化を行います。以下にメソッドを記載します。
   public int[] tonemapping(int tone[], int all_pixel){
        int cumulative=0;
        int tonemap[]=new int[256];
        for(int i =0;i < 256;i++){
            cumulative+=tone[i];//③
            tonemap[i]=(cumulative*255)/all_pixel;//④
        }
        return tonemap;
    }
③はヒストグラムを左から累積した値を示します。
 ④では累積した値を全画素数で割ることで、現在の色の値(0~255)がどの値にマッピングされるかを計算します。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new ToneEqualizationFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。ヒストグラムの棒が長いほど、棒の左側に間隔が空くことがわかります。



割合レベル補正フィルタ(ToneRateMapFilter)

 今回はPhotoshopにおけるレベル補正に似た効果をもたらすフィルタを作成します。レベル補正とはコントラスト制御の一手法であり、ヒストグラムを見ながら画像全体の色調やカラーバランスおよび、シャドウ・ハイライトなどの調整を行えます。例えば、下の二つの図のうち上の図のような画像があるとします。この画像はヒストグラムが一つの山でできており、かつ中央付近にかたまっています。これを下の図の右のレベル補正図のように補正することで、メリハリのついた画像に変換することができます。
今回は、このような変換を自動で行うプログラムを作成します。考え方としては、第一に上限割合と下限割合のパラメータを設定し、ヒストグラムの中で左から下限割合までを黒にマッピングし、右から上限割合までを白にマッピングします。これにより、割合に応じて自動的にレベル補正が行われます。
 Tutorial1Activityクラスと同じ場所にToneRateMapFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
public int[] filter(int[] imageArray, int width, int height,int max,int  min) {
  int tone[][]=ToneCalculator(imageArray);//①
  int length = width*height;
  int min_rate=(length*min)/100;//②
  int max_rate=(length*max)/100;
  int tonemap[][]=new int[3][256];
  tonemap[0]=tonemapping(tone[0],max_rate,min_rate);//③
  tonemap[1]=tonemapping(tone[1],max_rate,min_rate);
  tonemap[2]=tonemapping(tone[2],max_rate,min_rate);
  int[] oimgArray = new int[imageArray.length];
  int rgb;
  int red,green,blue;
  for(int i =0;i < length;i++){
   rgb = imageArray[i];
   red = (rgb >> 16) & 0xff;
   green = (rgb >> 8) & 0xff;
   blue = (rgb) & 0xff;
   oimgArray[i]=255*16777216+
     tonemap[0][red]*65536+tonemap[1][green]*256+tonemap[2][blue];
  }
  return oimgArray;
 }

 ①では、赤緑青それぞれの色についてヒストグラムを計算します。以下にToneCalculatorメソッドを記載します。
 public int[][] ToneCalculator(int[] imageArray){
  int tone[][]=new int[3][256];
  int length = imageArray.length;
  int rgb;
  int red,green,blue;
  for(int i =0;i < length;i++){
   rgb = imageArray[i];
   red = (rgb >> 16) & 0xff;
   green = (rgb >> 8) & 0xff;
   blue = (rgb) & 0xff;
   tone[0][red]+=1;
   tone[1][green]+=1;
   tone[2][blue]+=1;
  }
  return tone;
 }

 ②は引数で与えられた上限と下限について、全画素数に対する上限割合および下限割合を計算しています。例えば、画像サイズが約200万画素(フルHD)で、下限が5%ならば、ヒストグラムにおいて左から10万が下限割合になります。
 ③はルックアップテーブルの作成を行います。以下に必要なメソッドを記載します。
        public int[] tonemapping(int tone[], int max_rate,int min_rate){
  double min=(double)getmin(tone,min_rate);//④
  double max=(double)getmax(tone,max_rate);//⑤
  double distance=max-min;//⑥
  double rate=255/distance;
  int tonemap[]=new int[256];
  for(int i =0;i < 256;i++){
   if(i <= min){
    tonemap[i]=0;
   }else if(i >= max){
    tonemap[i]=255;
   }else{
    tonemap[i]=(int)((i-min)*rate);
   }
  }
  return tonemap;
 }
 public int getmin(int tone[] , int min_rate){
  int min=0;
  int count=0;
  for(int i =0;i < 256;i++){
   count+=tone[i];
   if(count > min_rate){
    min=i;
    break;
   }
  }
  return min;
 }
 public int getmax(int tone[] , int max_rate){
  int max=255;
  int count=0;
  for(int i =0;i < 256;i++){
   count+=tone[i];
   if(count >= max_rate){
    max=i;
    break;
   }
  }
  return max;
 }

 ④では、③で得られた下限割合をもちいて、ヒストグラムの0~255の中で下限割合を満たすような値を求めます。⑤では上限割合を満たす値を求めます。
 ⑥以降は④⑤で得られた上限および下限をもちいて、上下限マッピングフィルタと同様にルックアップテーブルの作成を行います。ただし、上下限マッピングフィルタと異なり、赤緑青それぞれで異なる上限下限の値を用います。
 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new ToneRateMapFilter().filter(imageArray, imagewidth, imageheight, 95,5);
第四引数が上限パラメータを、第五引数が下限パラメータを示します。それぞれの単位はパーセントです。

 以下に実機での実行結果を示します。今回は分布変化がわかりやすいようにサイの画像を用いています。ヒストグラムからわかるように、中央に山のあるヒストグラムから左右に引き伸ばした山が得られています。このように、上限と下限を設定することで、それに対応したルックアップテーブルを自動で作成することができます。





シグモイド関数フィルタ(SigmoidToneFilter)

 今回はコントラスト変換の一手法であるシグモイド関数によるフィルタを作成します。シグモイド関数はS字を描く曲線で、次の式で表されます。
f(x)=1/(1+e-x)
これを変形すると以下の式になります。
f(x)=M/(1+e-α(x-M/2))
f(x):変換後の画素値, x:元の画素値, M:255, a:パラメータ)
この式に基づいてトーンカーブを作成すると以下のようなトーンカーブになります。
このようなトーンカーブを使用する場合、上下限マッピングフィルタと同様にコントラストが強調されます。
 
  Tutorial1Activityクラスと同じ場所にSigmoidToneFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height,double a) {
        if(a <= 0){
            return imageArray;//①
        }
        int table[]=new int[256];
        double e = Math.E;
        for(int i =0;i < 256;i++){
            table[i]=(int) (255.0/(1+Math.pow(e, -a*(((double)i)-127.0))));//②
            if(table[i] > 255){
                table[i]=255;
            }else if(table[i] < 0){
                table[i]=0;
            }
        }
        int length = width*height;
        int point=0;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            oimgArray[i]=255*16777216+
                    table[red]*65536+table[green]*256+table[blue];
        }
        return oimgArray;
    }

 ①は0以下のパラメータについては処理を行わないように設定しています。パラメータのとりうる値は0より大きい値のみです。
 ②はシグモイド関数の計算処理です。M=255として計算しています。
 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new SigmoidToneFilter().filter(imageArray, imagewidth, imageheight, 0.03);

第4引数がシグモイド関数のパラメータを示しています。パラメータは0より大きい値を入れます。
 以下に実機での実行結果を示します。パラメータは0.03を用いました。元画像よりコントラストが上がっているのがわかります。また、S字曲線の特性から、ヒストグラムが上限や下限付近に多く分布していることがわかります。



2017年2月13日月曜日

上下限マッピングフィルタ(HighLowMapFilter)

今回はトーンカーブ調整手法の一つである上下限マッピングフィルタを作成します。上下限マッピングフィルタでは値の上下限を決め、その範囲内を0-255に変換するフィルタです。トーンカーブは以下の図のようになります。



Tutorial1Activityクラスと同じ場所にHighLowMapFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
 public int[] filter(int[] imageArray, int width, int height, int max , int min) {
  double distance=max-min;//①
  double rate=255/distance;//②
  int table[]=new int[256];
  for(int i =0;i < 256;i++){
   if(i < min){
    table[i]=0;
   }else if(i > max){
    table[i]=255;
   }else{
    table[i]=(int)((i-min)*rate);//③
   }
  }
  int length = width*height;
  int[] oimgArray = new int[imageArray.length];
  int rgb;
  int red,green,blue;
  for(int i =0;i < length;i++){
   rgb = imageArray[i];
   red = (rgb >> 16) & 0xff;
   green = (rgb >> 8) & 0xff;
   blue = (rgb) & 0xff;
   oimgArray[i]=255*16777216+
     table[red]*65536+table[green]*256+table[blue];

  }
  imageArray=null;
  return oimgArray;
 }


 ①では上下限の幅を計算し、②で255をその幅で割ることで、倍率を計算します。
 ③では得られた倍率から、上下限の間の値について、新しい値を計算します。

最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new HighLowMapFilter().filter(imageArray, imagewidth, imageheight, 200,50);

第四引数が上限を、第五引数が下限を示します。
 以下に実機での実行結果を示します。上限は200、下限が50です。白飛びや黒つぶれが発生しますが、コントラスト比が上がっていることがわかります。

また、引数を変えることで以下のようなトーンカーブを得ることができます。
引数の上限を300、下限を-50に指定した場合の実機の画像が以下です。ヒストグラムの分布に上限と下限が現れ、コントラストが下がっていることがわかります。


上下限フィルタ(HighLowLightFilter)

今回はトーンカーブ調整の一手法である上下限フィルタを作成します、上下限フィルタでは、明るさの上限や下限を決め、それらを超えた値については上限や下限に変換します。トーンカーブで図化すると以下のようになります。


Tutorial1Activityクラスと同じ場所にHighLowLightFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height, int max , int min) {
        int table[]=new int[256];
        for(int i =0;i < 256;i++){
            if(i < min){//①
                table[i]=min;
            }else if(i > max){//②
                table[i]=max;
            }else{
                table[i]=i;
            }
        }
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            oimgArray[i]=255*16777216+
                    table[red]*65536+table[green]*256+table[blue];
        }
        return oimgArray;
    }

①と②では上限と下限を決め、それを超えた場合には上限や下限に値を変換しています。
最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new HighLowLightFilter().filter(imageArray, imagewidth, imageheight, 200,50);

第四引数が上限を、第五引数が下限を示します。

以下に実機での実行結果を示します。ヒストグラムから上限下限に多く分布していることがわかります。また、全体的にコントラストが落ちていることがわかります。



ガンマ補正フィルタ(GammaTransFilter)

 今回はガンマ補正フィルタを作成します。ガンマ補正フィルタはトーンカーブ調整を行うフィルタの一つです。トーンカーブとは画像補正機能の一つで、画像全体の色調やカラーを調整することが出来ます。補正前のデータ値(入力レベル)と補正後のデータ値(出力レベル)の変化を線で表したもので、その線を調整することで画像の色調を細かく補正します。詳しくは以下のサイトを参照してください。
https://www.gen-zo.com/technique/col_tone.html

トーンカーブはPhotoshopでも利用可能です。下にフォトショップでのトーンカーブ画面を表示します。
次に、ガンマ補正ですが、これは明るさを補正するための補正数値を示します。特徴は、モニタ、デジカメ等の画像の階調性を人間の見た目に最適な階調に補正します。その際の補正式はout = inγとなっています。(outやinは0~1の値)このγの値によって、補正の効果が変わります。ガンマ値が1より大きい時は暗くなり、ガンマ値が1より小さい時には明るくなります。ガンマ補正によるトーンカーブの例が下の図になります。

0から255の対角線が基準線を示し、それよりも上にある場合には画像が明るくなり、下にある場合には画像が暗くなります。図のように、ガンマ値が小さければ明るく、大きければ暗くなります。

ガンマ補正を用いた画像処理を実装します。 Tutorial1Activityクラスと同じ場所にGammaTransFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height,double gamma) {
        if(gamma<=0){
            gamma=1;//①
        }
        int table[]=new int[256];
        for(int i =0;i < 256;i++){
            table[i]=(int) (255*Math.pow((((double)i)/255),(1/gamma)));//②
            if(table[i] > 255){
                table[i]=255;
            }else if(table[i] < 0){
                table[i]=0;
            }
        }
        int length = width*height;
        int point=0;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int alpha,red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            oimgArray[i]=255*16777216+
                    table[red]*65536+table[green]*256+table[blue];
        }
        return oimgArray;
    }

 ①では0以下の引数について1に変換しています。ガンマのとれる値が0より大きい値でなければならないためです。
 ②ではガンマ値の計算を行っています。まず、ルックアップテーブルの現在の値を255で割り、0~1の範囲に変換します。その後、ガンマ値を用いた計算を行います。最後に255をかけて0~255の範囲に戻します。
 それ以降の処理はポスタリゼーションなどルックアップテーブルを用いるフィルタと同じです。
 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new GammaTransFilter().filter(imageArray, imagewidth, imageheight,1.5);

第4引数がガンマ値にかかわる値で、基本的に0より大きい値を入れます。②からわかるように、この引数が大きいと画像が明るくなり、小さいと画像が暗くなります。

 以下に実機での実行結果を示します。上の図が引数1.5のとき、下の図が引数0.5のときです。ヒストグラムが大きく変化していることがわかります。

2017年2月10日金曜日

ポスタリゼーションフィルタ(PosterizationFilter)

今回はポスタリゼーションフィルタを作成します。ポスタリゼーションとは、画像の階調(通常各色255段階)を落とすことでポスターを描いているような描写にする処理です。階調は任意の数を指定することができます。例えば、3段階の階調に変更する場合は、0~85までの値を0に、86~170までの値を127に、171~255までの値を255に変換します。詳しくは以下のサイトを参照してください。
http://www.sm.rim.or.jp/~shishido/post.html

Tutorial1Activityクラスと同じ場所にPosterizationFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height, int gradation) {
        int range=255/(gradation-1);
        int slash=255/gradation;
        int table[]=new int[256];
        int num=0;
        for(int i =0;i < 256;i++){
            num=i/slash;
            if(num==0){
                table[i]=0;
            }else if(num >= gradation-1){
                table[i]=255;
            }else{
                table[i]=range*num;
            }
        }
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int alpha,red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            alpha = (rgb >> 24) & 0xff;
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            oimgArray[i]=alpha*16777216+
                    table[red]*65536+table[green]*256+table[blue];
        }
        imageArray=null;
        return oimgArray;
    }

 ①は階調変換後の値を決めるための値の増加幅を計算しています。例えば、階調数が5ならば、63が増加幅になります。
 ②は階調変換の区間幅を指定します。階調数が5ならば51ごとの区間になります。
 ③は0~255の値のそれぞれについて、階調数ある区間のなかで、どの区間に相当するかを計算しています。階調数が5ならば、0~51は0番の区間、52~102は1番の区間になります。
 ④は0番の区間についてルックアップテーブルの値を0に設定しています。
 ⑤は階調数-1以上の区間についてルックアップテーブルの値を255に設定しています。
 ⑥ではそれら以外の区間について、ルックアップテーブルを区間番号×①の増加幅に設定しています。
 ⑦ではこれまでに得られたルックアップテーブルを用いて色の変換を行っています。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new PosterizationFilter().filter(imageArray, imagewidth, imageheight,5);

メソッドの第4引数が階調数を示します。
 以下に実機での実行結果を示します。階調数は5です。ポスターのように色がグラディエーションではなく段階になっていることがわかります。また、ヒストグラムも5本のバーで構成されていることがわかります。




YCrCb変換による彩度フィルタ(SaturationYCrCbFilter)

今回はYCbCr変換による彩度変換フィルタを作成します。YCbCrとは輝度信号Yと、2つの色差信号Cb(青)Cr(赤)を使って表現される色空間です。RGBと相互に変換することができます。詳しくは以下のリンクを参照してください。
http://www.wdic.org/w/WDIC/YCbCr

今回はまずYCbCrとRGBを相互変換するYCbCRTransクラスを作成します。 Tutorial1Activityクラスと同じ場所にYCbCRTransクラスを作成します。その後、以下のコードを入力します。
 public double[] RGBtoYCbCr(int rgb){
        double red = (double)((rgb >> 16) & 0xff)/255.0;//①
        double green = (double)((rgb >> 8) & 0xff)/255.0;
        double blue = (double)((rgb) & 0xff)/255.0;
        double Y=0.2990*red+0.5870*green+0.1140*blue;//②
        double Cb=-0.1690*red-0.3310*green+0.5000*blue;
        double Cr=0.5000*red-0.4190*green-0.0810*blue;
        double[] YCbCr=new double[]{Y,Cb,Cr};
        return YCbCr;
    }
    public int YCbCrtoRGB(double YCbCr[]){
        double dred=YCbCr[0]+1.4020*YCbCr[2];//③
        double dgreen=YCbCr[0]-0.3441*YCbCr[1]-0.7141*YCbCr[2];
        double dblue=YCbCr[0]+1.7720*YCbCr[1];
        int red=(int)(dred*255.0);//④
        if(red > 255){
            red=255;//⑤
        }else if(red < 0){
            red=0;
        }
        int green=(int)(dgreen*255.0);
        if(green > 255){
            green=255;
        }else if(green < 0){
            green=0;
        }
        int blue=(int)(dblue*255.0);
        if(blue > 255){
            blue=255;
        }else if(blue < 0){
            blue=0;
        }
        return 255*16777216+red*65536+green*256+blue;
    }

 ①はRGB値を0~1の範囲に変換しています。
 ②は①で変換したRGB値をYCbCrに変換しています。
 ③は②の逆変換で、YCbCr値をRGB値(0~1)に変換しています。
 ④はRGB値の範囲を0~255に変換しています。ただし、この時点では0未満や255より大きい値が入る可能性があります。
 ⑤は④の値を丸め、0~255の範囲に変換しています。

 次に、彩度変換を行うクラスを作成します。Tutorial1Activityクラスと同じ場所にSaturationYCbCrFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height, double value) {
        YCbCrTrans trans=new YCbCrTrans();
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        double YCbCr[]=new double[3];
        for(int i =0;i < length;i++){
            YCbCr=trans.RGBtoYCbCr(imageArray[i]);//⑥
            YCbCr[1]*=value;//⑦
            YCbCr[2]*=value;
            oimgArray[i]=trans.YCbCrtoRGB(YCbCr);//⑧
        }
        return oimgArray;
    }

 ⑥ではRGB値をYCbCr値に変換しています。
 ⑦ではCbとCrにメソッドの引数をかけることで、鮮やかさを変化させています。
 ⑧ではYCbCr値をRGB値に変換しています。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new SaturationYCbCrFilter().filter(imageArray, imagewidth, imageheight, 1.5);

第4引数が変化させる彩度の倍数を指定しています。
 以下に実機での実行結果を示します。HSVによる彩度変化のときと同様に鮮やかさが増していることがわかります。


HSV変換による彩度フィルタ(SaturationFilter)

今回はHSV変換を用いた彩度変換フィルタを作成します。前回(ヒートマップフィルタ)で作成したHSVTransクラスを使用しますので作成していない方は作成してください。基本的な考え方はHSVの各値の中でSの値を変化させることで彩度を変化させます。

 Tutorial1Activityクラスと同じ場所にSaturationFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height, int value) {
        HSVTrans trans=new HSVTrans();
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int hsv[]=new int[3];
        for(int i =0;i < length;i++){
            hsv=trans.RGBtoHSV(imageArray[i]);//①
            hsv[1]+=value;//②
            if(hsv[1] > 255){
                hsv[1]=255;//③
            }else if(hsv[1] < 0){
                hsv[1]=0;//③
            }
            oimgArray[i]=trans.HSVtoRGB(hsv);
        }
        return oimgArray;
    }

①ではRGB値を含むint型の値をHSV配列に変換します。
 ②ではHSVのなかでSに任意の値(メソッドの第4引数)を加えます。
 ③では②で変化したSの値が0~255の範囲となるように値をまるめます。
 ④ではHSVの値をRGBに戻します。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new SaturationFilter().filter(imageArray, imagewidth, imageheight, 30);

メソッドの第4引数が変化させる彩度の値を示します。

 以下に実機での実行結果を示します。元の画像から少し鮮やかさが増しているのがわかります。


2017年2月9日木曜日

ヒートマップフィルタ(HeatMapFilter)

 今回は疑似カラーマッピング技術であるヒートマップ風のフィルタを作成します。今回は作成に当たってHSV変換を行います。HSVとは色相(Hue)、彩度(Saturation・Chroma)、明度(Value・Lightness・Brightness)の三つの成分からなる色空間です。色相は色の種類を示し、0~360で表されます。彩度は色の鮮やかさを示し、0~100%で表されます。明度は色の明るさを示し、0~100%で表されます。これはRGB色空間の非線形変換であり、色の変換に用いられることもあります。
https://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93
RGBとHSVの相互変換のプログラム作成については次のサイトが参考になります。
http://hooktail.org/computer/index.php?RGB%A4%AB%A4%E9HSV%A4%D8%A4%CE%CA%D1%B4%B9%A4%C8%C9%FC%B8%B5
上記サイトでは彩度と明度が0~255で表されています。こちらもint型を用いるためにそのようにしたいと思います。(一部doubleキャストしていますが)
 ヒートマップフィルターにおいては、HSVのなかでHのみを変化させます。これにより色相の変化した画像となります。ただし、0から360まで変化させてしまうと色がほとんど元に戻ってしまうため、色相は0から240まで変化させます。

 まず、HSVとRGBの相互変換プログラムを作成します。Tutorial1Activityクラスと同じ場所にHSVTransクラスを作成します。その後、以下のコードを入力します。
    public int[] RGBtoHSV(int rgb){
        double red = (rgb >> 16) & 0xff;
        double green = (rgb >> 8) & 0xff;
        double blue = (rgb) & 0xff;
        red=red/255;
        green=green/255;
        blue=blue/255;
        double max;
        double min;
        int H;
        int S;
        int V;
        int result[]=new int[3];
        if(red > green){
            if(red > blue){
                max=red;
                if(green < blue){
                    min=green;
                }else{
                    min=blue;
                }
                H=(int) (60*(green-blue)/(max-min));
            }else{
                max=blue;
                min=green;
                H=(int) (60*(red-green)/(max-min)) +240 ;
            }
        }else{
            if(green > blue){
                max=green;
                if(red < blue){
                    min=red;
                }else{
                    min=blue;
                }
                H=(int) (60*(blue-red)/(max-min)) +120 ;
            }else{
                max=blue;
                min=red;
                H=(int) (60*(red-green)/(max-min)) +240 ;
            }
        }
        if(H < 0){
            H+=360;
        }
        H=H%360;
        S=(int)(((max-min)/max)*255);
        if(S > 255){
            S=255;
        }else if(S < 0){
            S=0;
        }
        V=(int)((max)*255);
        if(V > 255){
            V=255;
        }else if(V < 0){
            V=0;
        }
        result[0]=H;
        result[1]=S;
        result[2]=V;
        return result;
    }
    public int  HSVtoRGB(int hsv[]){
        //hsv H:0~360, S:0~255, V:0~255;
        int H=hsv[0];
        int S=hsv[1];
        int V=hsv[2];
        int red=0;
        int green=0;
        int blue=0;
        if(S==0){
            red=V;
            green=V;
            blue=V;
        }else{
            int I=(int)(H/60);
            double F=((double)H)/60 -I;
            int p=V*(255-S) / 255;
            int q=(int) (V*(255 - F*((double) S))/255);
            int t=(int) (V*(255-1*(1-F)*((double) S))/255);
            if(I==0){
                red=V;
                green=t;
                blue=p;
            }else
            if(I==1){
                red=q;
                green=V;
                blue=p;
            }else
            if(I==2){
                red=p;
                green=V;
                blue=t;
            }else
            if(I==3){
                red=p;
                green=q;
                blue=V;
            }else
            if(I==4){
                red=t;
                green=p;
                blue=V;
            }else
            if(I==5){
                red=V;
                green=p;
                blue=q;
            }
        }
        int result=255*16777216+red*65536+green*256+blue;
        return result;
    }

次にHeatMapFilterクラスを作成し、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        int y_val;
        int h;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            y_val = ( 2 * red + 4 * green + blue ) / 7;//①
            h = 240- y_val*240/255;//②
            int hsv[]=new int[]{h,255,255};//③
            oimgArray[i]=new HSVTrans().HSVtoRGB(hsv);//④
        }
        return oimgArray;
    }

 ①では輝度グレイスケールに変換しています。そして、②ではその値をまず0~240の範囲に変換してから、240から引くことによって逆転させています。これは、元画像での白色が赤色に対応するようにするためです。
 ③ではHSVからRGBに変換するための配列を用意します。HSVの順番ですが、SとVは最大値の255に設定します。
 ④ではHSVの値をRGB値に変換します。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new HeatMapFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。

モザイクフィルタ(MosaicFilter)

今回は画面全体にモザイクをかけるモザイクフィルターを作成します。モザイクフィルターでは、画面を指定のサイズ(m×m)に切り分けて、そのサイズの中での色の値の平均値をとり、平均値を新たな色の値とするという流れとなっています。
 Tutorial1Activityクラスと同じ場所にMosaicFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int size=8;//①
        int xnum=width/size;//②
        int xrest=width%size;
        int ynum=height/size;
        int yrest=height%size;
        int length = width*height;
        int rgb;
        int red,green,blue;

        int rgbarray[][]=new int[3][imageArray.length];
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            rgbarray[0][i]=red;//③
            rgbarray[1][i]=green;
            rgbarray[2][i]=blue;
        }
        int point=0;
        int rsum,gsum,bsum;
        int[] oimgArray = new int[imageArray.length];
        for(int y=0;y < ynum;y++){
            for(int x=0;x < xnum;x++){
                point=x*size+y*size*width;
                rsum=0;
                gsum=0;
                bsum=0;
                for(int yn=0;yn < size;yn++){
                    for(int xn=0;xn < size;xn++){
                        rsum+=rgbarray[0][point+xn+yn*width];//④
                        gsum+=rgbarray[1][point+xn+yn*width];
                        bsum+=rgbarray[2][point+xn+yn*width];
                    }
                }
                rsum=rsum/(size*size);
                gsum=gsum/(size*size);
                bsum=bsum/(size*size);
                for(int yn=0;yn < size;yn++){
                    for(int xn=0;xn < size;xn++){
                        oimgArray[point+xn+yn*width]=
                                255*16777216+ rsum*65536
                                        + gsum*256+bsum;//⑤
                    }
                }
            }
            if(xrest>0){
                for(int ysize=0;ysize < size; ysize++){
                    for(int rest=0;rest < xrest;rest++){
                        point=xnum*size+(y*size+ysize)*width+rest;
                        oimgArray[point]=imageArray[point];//⑥
                    }
                }
            }
        }
        if(yrest>0){
            for(int rest=0;rest < yrest;rest++){
                for(int x=0;x < width;x++){
                    point=x+(ynum*size+rest)*width;
                    oimgArray[point]=imageArray[point];//⑦
                }
            }
        }
        return oimgArray;
    }

 ①ではモザイクをかけるサイズを指定しています。
 ②では幅と高さを①のサイズで割り、画像の分割数を求めています。
 ③では一度赤緑青の色の値を格納しています。
 ④では①のサイズの領域について色の値の合計数をだし、その後平均値を出しています。
 ⑤では平均値を領域内のすべての画素に適用し、新しい色としています。
 ⑥と⑦では①のサイズが幅や高さを割り切れなかった時の処理を行っています。今回は元の色をそのまま用いるようにしています。

  最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new MosaicFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。上が①のサイズが4のときで、下が8のときです。

カラー版誤差拡散法(ErrorDiffusionColorFilter)

 今回はカラー版の誤差拡散法フィルタを作成します。考え方は前回の誤差拡散法と同様ですが、白黒の2色ではなく赤緑青がそれぞれ255または0のときの色を用います。そのため、全部で8色が使用されます。
 Tutorial1Activityクラスと同じ場所にErrorDiffusionColorFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
public int[] filter(int[] imageArray, int width, int height) {
        int length = width*height;
        int rgbarray[][]=new int[3][imageArray.length];
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        int y_val;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            rgbarray[0][i]=red;//①
            rgbarray[1][i]=green;
            rgbarray[2][i]=blue;
        }
        int point=0;
        int error=0;
        for(int i=0;i < 3;i++){
            for(int y=0;y < height; y++){
                for(int x=0;x < width;x++){
                    point=x+y*width;
                    if(rgbarray[i][point] > 127){//②
                        error=rgbarray[i][point]-255;
                        rgbarray[i][point]=255;
                    }else{
                        error=rgbarray[i][point];
                        rgbarray[i][point]=0;
                    }
                    if((x+1) < width){
                        rgbarray[i][point+1]+=error*7/16;//③
                    }
                    if((y+1) < height){
                        if((x-1) >= 0){
                            rgbarray[i][point-1+width]+=error*3/16;
                        }
                        rgbarray[i][point+width]+=error*5/16;
                        if((x+1) < width){
                            rgbarray[i][point+1+width]+=error*1/16;
                        }
                    }
                }
            }
        }

        for(int i =0;i < length;i++){
            oimgArray[i]=255*16777216+rgbarray[0][i]*65536+rgbarray[1][i]*256+rgbarray[2][i];
        }
        return oimgArray;
    }


①ではまず赤緑青の値を配列に格納しています。
②では赤緑青それぞれの色の値を127と比較し、0や255との差をエラー値として格納しています。
③ではフロイド-スタインバーグ・ディザリングの伝播方法に基づいてエラー値を伝播させます。

最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new ErrorDiffusionColorFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。8色で作製したにもかかわらず、実際の色をかなり再現できていることがわかります。

誤差拡散法(ErrorDiffusionFilter)

 今回はディザリング手法の一つである誤差拡散法フィルタを作成します。誤差拡散法は画素をある閾値で白黒に変換したとき、変換後の値と元の値
との誤差を次の画素の変換で使うものです。例えば、閾値が127のとき、200の画素は白(255)に変換されますが、誤差として-55が発生します。この誤差を次の画素の元の値に伝播させていきます。
伝播の方法はいくつかありますが、今回はフロイド-スタインバーグ・ディザリング(Floyd–Steinberg dithering)を用いたいと思います。フロイド-スタインバーグ・ディザリングでは以下の図のように誤差を伝播させます。
http://koujinz.cocolog-nifty.com/blog/2009/04/post-a316.html
先ほどの誤差-55ならば、一つ右の画素には -24が伝播されます。その他の誤差拡散法の手法については以下のページ(英語)を参照してください。
https://en.wikipedia.org/wiki/Dither

Tutorial1Activityクラスと同じ場所にErrorDiffusionFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        int y_val;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            y_val = ( 2 * red + 4 * green + blue ) / 7;//①
            oimgArray[i]=y_val;
        }
        int point=0;
        int error=0;
        for(int y=0;y < height; y++){
            for(int x=0;x < width;x++){
                point=x+y*width;
                if(oimgArray[point] > 127){//②
                    error=oimgArray[point]-255;//③
                    oimgArray[point]=255;
                }else{
                    error=oimgArray[point];//④
                    oimgArray[point]=0;
                }
                if((x+1) < width){
                    oimgArray[point+1]+=error*7/16;//⑤
                }
                if((y+1) < height){
                    if((x-1) >= 0){
                        oimgArray[point-1+width]+=error*3/16;
                    }
                    oimgArray[point+width]+=error*5/16;
                    if((x+1) < width){
                        oimgArray[point+1+width]+=error*1/16;
                    }
                }
            }
        }
        for(int i =0;i < length;i++){
            y_val = oimgArray[i];
            oimgArray[i]=255*16777216+y_val*65536+y_val*256+y_val;
        }
        return oimgArray;
    }

 ①ではまず画像をグレイスケール変換しています。
 ②ではグレイスケールの色の値を127と比較し、それより大きい場合には白(255)小さい場合には黒(0)に変換しています。 そして、白の場合には③で色の値ー255をエラー値として、黒の場合には④で色の値そのものをエラー値として取得します。
 ⑤以降では③でフロイド-スタインバーグ・ディザリングの図にもとづいてエラー値を伝播させます。
 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new ErrorDiffusionFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。白黒の二値であるにもかかわらず、グレイスケールにかなり近いように見えます。ただし、白や黒が連続しているところでは元画像にはなかった点が発生しています。

拡散フィルタ(DiffusionFilter)

 今回は拡散フィルタを作成します。拡散フィルタは色を拡散してすりガラス越しに見たような効果を出します。これは単純に各ピクセルをランダムに任意の範囲に拡散するだけです。

 Tutorial1Activityクラスと同じ場所にDiffusionFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int[] oimgArray = new int[imageArray.length];
        int xn,yn,xpoint,ypoint;
        for(int y=0;y < height;y++){
            for(int x=0;x < width;x++){
                xn=(int)(Math.random()*4.99) - 2;//①
                yn=(int)(Math.random()*4.99) - 2;//②
                xpoint=x+xn;//③
                if(xpoint < 0 || xpoint >= width){//④
                    xpoint=x;
                }
                ypoint=y+yn;//③
                if(ypoint < 0 || ypoint >= height){//④
                    ypoint=y;
                }
                oimgArray[x+y*width]=imageArray[xpoint+ypoint*width];//⑤
            }
        }
        return oimgArray;
    }

 ①と②では、x座標用およびy座標用の乱数を発生させています。乱数(0~1)に4.99をかけてからint型にキャストすることで0~4の整数乱数を生成し、-2することで-2~2の整数乱数に変換しています。
 得られた整数乱数を③でx座標およびy座標に加えます。そのうえで、④では計算して得られた座標が画像の枠内を超えていないかを調べます。超えた場合には元のx座標、y座標に戻します。
 ⑤では③で得られた座標の色を、当該座標(x+y*width)の色としています。これにより、周辺のランダムな位置の色を当該座標の色とすることができます。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new DiffusionFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。



2017年2月8日水曜日

ベイヤーディザフィルタ(BayerDitherFilter)

 今回はベイヤーディザフィルタを作成します。ベイヤーディザフィルタはディザリング手法の一つです。ベイヤーとはベイヤー型のパターンをさし、4×4のマトリックスを用いて処理を行います。以下にベイヤー型のパターンを載せます。

ベイヤーディザでは4×4のマトリックス内の輝度の平均値をとり、その平均値が上の図の右の各セルの値以上ならばそのセルを白色にするという処理を行います。例えば、160という値では下のような塗りつぶし方になります
このように白黒の数で範囲内の濃淡を表します。なお、平均値をとらず、各ピクセルの場所とマトリックスの場所の対応関係から変換する方法もあります。

 Tutorial1Activityクラスと同じ場所にBayerDitherFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
            int Bayer[] = new int[]{//①
                    0, 8, 2, 10,
                    12, 4, 14, 6,
                    3, 11, 1, 9,
                    15, 7, 13, 5
            };
            for (int i = 0; i < 16; i++) {
                Bayer[i] = Bayer[i] * 16 + 8;//②
            }
            int xn = width / 4;
            int yn = height / 4;

            int length = width * height;
            int[] oimgArray = new int[imageArray.length];
            int rgb;
            int red, green, blue;
            int y_val;
            for (int i = 0; i < length; i++) {
                rgb = imageArray[i];
                red = (rgb >> 16) & 0xff;
                green = (rgb >> 8) & 0xff;
                blue = (rgb) & 0xff;
                y_val = (2 * red + 4 * green + blue) / 7;
                oimgArray[i] = 255 * 16777216 + y_val * 65536 + y_val * 256 + y_val;
            }
            int pointer = 0;
            int x, y, xln, yln, sum, xy;
            for (y = 0; y < yn; y++) {
                for (x = 0; x < xn; x++) {
                    pointer = y * 4 * width + x * 4;
                    sum = 0;
                    for (yln = 0; yln < 4; yln++) {
                        for (xln = 0; xln < 4; xln++) {
                            sum += oimgArray[pointer + xln + yln * width]& 0xff;;//③
                        }
                    }
                    sum = sum / 16;//④
                    for (yln = 0; yln < 4; yln++) {
                        for (xln = 0; xln < 4; xln++) {
                            xy = xln + yln * 4;
                            if (sum >= Bayer[xy]) {//⑤
                                oimgArray[pointer + xln + yln * width] = 0xFFFFFFFF;
                            } else {
                                oimgArray[pointer + xln + yln * width] = 0xFF000000;
                            }


                        }
                    }
                }
            }
            return oimgArray;
    }

 ①ではベイヤー型の4×4のマトリックスを作成します。それを②では×16+8の計算をしています。
 ③では4×4の枠内で輝度の合計値を求めています。そして④で平均化しています。
 ⑤ではベイヤー型マトリックスの各セルと、④で得た輝度平均値を比較して、輝度平均値がセルの値以上ならば白に、そうでないならば黒に変換します。これによりベイヤー型のマトリックスに則った白黒の数を表現します。
最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new BayerDitherFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。白黒のみですが、前回のランダムディザ同様に濃淡が表現できていることがわかります。人物の顔の部分だけを拡大した図も載せます。


ランダムディザフィルタ(RandomDitherFilter)

 今回は二値化方法の一つであるランダムディザフィルタを作成します。ディザ、ディザリングとは制限された色数でそれ以上の色調を表現する技法です。今回は、黒(0)と白(255)からグレイスケール(0-255)のような画像を作成します。ランダムディザでは、まず画像を輝度グレイスケールに変換します。その後、ここで得た 0から255までの256階調を使って白にするか黒にするか確率計算を行います。例えば、得られた値が128のとき、128/255で約50%の確率になります。そのため、確率50%で白に、確率50%で黒になります。得られた値が64の場合は25%で白に、75%で黒になります。
http://blog.nariyu.jp/2015/01/canvas-image-effects/#fx-random-dither

 Tutorial1Activityクラスと同じ場所にRandomDitherFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        int y_val;
        double rand;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            y_val = ( 2 * red + 4 * green + blue ) / 7;
            rand=Math.random()*255;//①
            if(y_val>=rand){//②
                y_val=255;
            }else{
                y_val=0;
            }
            oimgArray[i]=255*16777216+y_val*65536+y_val*256+y_val;
        }
        return oimgArray;
    }

 ①では確率の計算用に0~1の乱数を発生させています。そして、②でピクセルの輝度が乱数以上ならば白に、乱数以下ならば黒にします。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new RandomDitherFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。白黒の二値画像ですが、Pタイル法などで2値画像にした時よりも人物の輪郭や髪の濃淡などがわかるようになっています。




ネガポジ反転(NegaPosiFilter)

今回はネガポジ反転フィルターを作成します。ネガポジ反転は画像の白を黒に、黒を白に反転することで、写真のネガのようにする変換です。処理としてはRGB各色の最大値である255から現在の色の値を引いて新しい色の値を得ます。

 Tutorial1Activityクラスと同じ場所にGrayscaleFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int table[]=new int[256];
        for(int i =0;i < 256;i++){
            table[i]=255-i;//①
        }
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            oimgArray[i]=255*16777216+
                    table[red]*65536+table[green]*256+table[blue];//②
        }
        return oimgArray;
    }

 ①ではRGBの色の値の範囲すべてについて、ネガポジ反転を行って得られる新しい値を配列に格納しています。このようにあらかじめ計算後の値を配列などに格納するとき、配列のことをルックアップテーブル(LUT)とよびます。ルックアップテーブルについて詳しくは以下のHPを参照してください。
http://imagingsolution.blog107.fc2.com/blog-entry-67.html
 ②ではルックアップテーブルに基づいてRGB各色をネガポジ反転しています。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new NegaPosiFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。ヒストグラムを見ると、これまでのヒストグラムから左右が反転していることがわかります。

セピア調変換(SepiaFilter)

今回は色合いをセピア調に変えるセピアフィルターを作成します。セピアとはイカ墨から作られる黒茶色の絵の具のことで、画像をセピア調にするということは、これで描かれたように見せるということである(らしいです)。セピア調変換には特定のアルゴリズムはありませんが、今回はセピアっぽく見える変換を行います。変換の方法としては、まず画像をグレイスケールに変換し、その後黒茶色っぽい色に変換するという流れです。

 Tutorial1Activityクラスと同じ場所にSepiaFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。

    public int[] filter(int[] imageArray, int width, int height) {
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        int y_val;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            y_val = ( 2 * red + 4 * green + blue ) / 7;//①
            red=(int)(y_val*240)/255;//②
            green=(int)(y_val*200)/255;
            blue=(int)(y_val*145)/255;
            oimgArray[i]=255*16777216+red*65536+green*256+blue;
        }
        return oimgArray;
    }

 ①ではRGB値を輝度グレイスケールに変換しています。その後②で輝度をRGBそれぞれについて指定の割合で変換します。この割合はよく使われている程度の認識で大丈夫でしょう。

 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。

imageArray=new SepiaFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。


2017年2月7日火曜日

グレイスケール変換(GrayscaleFilter)

 今回はグレイスケール変換について簡単に述べます。グレイスケールへの変換には複数の手法があります。一番簡単な手法では3色(red、green、blue)の平均値をとる方法があります。しかし、3色それぞれは同じ値でも明るさに違いがあり、一律に平均値をとるのでは本来の明るさが失われてしまいます。詳しくは以下のHPを参照してください。
http://daredemopc.blog51.fc2.com/blog-entry-877.html

ここでは、これまで同様輝度を用いたグレイスケール変換のプログラムを載せます。 Tutorial1Activityクラスと同じ場所にGrayscaleFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height) {
        int length = width*height;
        int[] oimgArray = new int[imageArray.length];
        int rgb;
        int red,green,blue;
        int y_val;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            y_val = ( 2 * red + 4 * green + blue ) / 7;//①
            oimgArray[i]=255*16777216+y_val*65536+y_val*256+y_val;//②
        }
        return oimgArray;
    }

①はこれまで同様の輝度計算です。輝度計算で得られた値を②でred、green、blueそれぞれに同じ値で格納します。
 最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray=new GrayscaleFilter().filter(imageArray, imagewidth, imageheight);

以下に実機での実行結果を示します。

二値画像の膨張処理(DilationFilter)

今回は二値画像の膨張処理を実装します。膨張処理とは、二値画像における処理方法のひとつで、各ピクセルについてそのピクセルと周囲のピクセルを調べ、一つでも白色があるならば白色に変換する処理です。そのため、処理の後は基本的に白色が増えます。具体的な処理などは前回同様以下のHPを参照してください。
http://imagingsolution.blog107.fc2.com/blog-entry-101.html

プログラムのコーディングに移ります。 Tutorial1Activityクラスと同じ場所にDilationFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height,int n) {
        int[] oimgArray = new int[imageArray.length];
        for(int nth=0;nth < n;nth++){
            int[] blackArray=new int[imageArray.length];
            for(int i =0;i < imageArray.length;i++){
                blackArray[i]=imageArray[i]& 0xff;
            }
            for (int x = 1; x < width-1; x++) {
                for (int y = 1; y < height-1; y++) {
                    int black=blackArray[x + y * width]+blackArray[x + y * width+1]+blackArray[x + y * width-1]
                            +blackArray[x + (y-1) * width]+blackArray[x + (y+1) * width]
                            +blackArray[x + (y-1) * width+1]+blackArray[x + (y-1) * width-1]
                            +blackArray[x + (y+1) * width+1]+blackArray[x + (y+1) * width-1];//①
                    if(black > 0){
                        oimgArray[x + y * width] = 0xFFFFFFFF;
                    }else{
                        oimgArray[x + y * width] = 0xFF000000;
                    }
                }
            }
            for (int i = 0; i < imageArray.length; i++) {
                if (oimgArray[i]==0) {
                    oimgArray[i] = 0xFFFFFFFF;
                }
            }
            imageArray=oimgArray;
        }
        return oimgArray;
    }

 前回と異なる場所は①の場所(前回の④)のみです。ここでは、あるピクセルとその周囲のピクセルについて、白黒の値の論理和を求めます。このとき、黒がいくつあっても白が一つでもあれば①の答えは0より大きくなります。逆にすべてのピクセルが黒の場合は答えは0になります。

Tutorial1ActivityクラスにDilationFilterを記入する際には必ず二値処理の後に加えます。今回は以下のような順番で記載しました。
imageArray =new PTileFilter().filter(imageArray, imagewidth, imageheight,50);
imageArray =new DilationFilter().filter(imageArray, imagewidth, imageheight, 2);
imageArray =new HistogramFilter().filter(imageArray, imagewidth, imageheight);


DilationFilterの第四引数は膨張回数を示しており、回数だけ膨張処理が繰り返されます。この例では2回繰り返されます。
 以下に実機での実行結果を示します。繰り返し回数は2です。Pタイル法フィルターの実行結果に比べて白が増えていることがわかります。ヒストグラムでも0(黒)のバーが短く、255(白)のバーが長くなっています。


二値画像の収縮処理(ErosionFilter)

 今回は二値画像の収縮処理を実装します。収縮処理とは、二値画像における処理方法のひとつで、各ピクセルについてそのピクセルと周囲のピクセルを調べ、一つでも黒色があるならば黒色に変換する処理です。そのため、処理の後は基本的に黒色が増えます。具体的な処理などは以下のHPを参照してください。
http://imagingsolution.blog107.fc2.com/blog-entry-101.html

プログラムのコーディングに移ります。 Tutorial1Activityクラスと同じ場所にErosionFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray, int width, int height,int n) {
        int[] oimgArray = new int[imageArray.length];
        for(int nth=0;nth < n;nth++){//①
            int[] blackArray=new int[imageArray.length];//②
            for(int i =0;i < imageArray.length;i++){
                blackArray[i]=imageArray[i]& 0xff;//③
            }
            for (int x = 1; x < width-1; x++) {
                for (int y = 1; y < height-1; y++) {
                    int black=blackArray[x + y * width]*blackArray[x + y * width+1]*blackArray[x + y * width-1]
                            *blackArray[x + (y-1) * width]*blackArray[x + (y+1) * width]
                            *blackArray[x + (y-1) * width+1]*blackArray[x + (y-1) * width-1]
                            *blackArray[x + (y+1) * width+1]*blackArray[x + (y+1) * width-1];//④
                    if(black > 0){//⑤
                        oimgArray[x + y * width] = 0xFFFFFFFF;
                    }else{
                        oimgArray[x + y * width] = 0xFF000000;
                    }
                }
            }
            for (int i = 0; i < imageArray.length; i++) {
                if (oimgArray[i]==0) {
                    oimgArray[i] = 0xFFFFFFFF;//⑥
                }
            }
            imageArray=oimgArray;//⑦
        }
        return oimgArray;
    }

 ①は収縮処理の繰り返し処理です。メソッドの第四引数の数だけ収縮処理を繰り返します。
 ②は 白黒の情報のみを持つ配列です。③によって入力用配列であるimageArrayから白(255)または黒(0)の情報を得て格納します。
 ④では、各ピクセルとその周囲のピクセルについて、白黒の値の論理積を求めます。このとき、白がいくつあっても黒が一つでもあれば④の答えは0となります。逆にすべてのピクセルが白の場合は答えは0より大きな値になります。
 ⑤では④の答えから0より大きい場合には出力用配列に白の情報を格納し、0であるならば黒の情報を格納します。
 ⑥では④で処理の行わなかった画像の一番外側について白の情報を格納します。
 ⑦では出力用配列を入力用配列にコピーし、繰り返し処理ができるようにしています。

 Tutorial1ActivityクラスにErosionFilterを記入する際には必ず二値処理の後に加えます。今回は以下のような順番で記載しました。
imageArray =new PTileFilter().filter(imageArray, imagewidth, imageheight,50);
imageArray =new ErosionFilter().filter(imageArray, imagewidth, imageheight, 2);
imageArray =new HistogramFilter().filter(imageArray, imagewidth, imageheight);

ErosionFilterの第四引数は収縮回数を示しており、回数だけ収縮処理が繰り返されます。この例では2回繰り返されます。
 以下に実機での実行結果を示します。繰り返し回数は2です。Pタイル法フィルターの実行結果に比べて黒が増えていることがわかります。ヒストグラムでも0(黒)のバーが長く、255(白)のバーが短くなっています。


Pタイル法フィルター(PTileFilter)

 今回は二値変換の方法の一つであるPタイル法について書きます。Pタイル法は画像の二値化したい領域が全画像の領域に占める割合を パーセント(%)で指定し二値化する手法です。Pタイル法については以下のHPをご覧ください。
http://imagingsolution.blog107.fc2.com/blog-entry-112.html

プログラムのコーディングを行います。  Tutorial1Activityクラスと同じ場所にPTileFilterクラスを作成します。その後、filterメソッドに以下のコードを入力します。
    public int[] filter(int[] imageArray,int width, int height, int threshould) {
        int tone[]=new int[256];
        int length=width*height;
        int border_length=length*threshould/100;//①
        int y_val = 0;
        int rgb = 0;
        int red = 0, green = 0, blue = 0;
        int[] oimgArray = new int[imageArray.length];
        for (int j = 0; j < imageArray.length; j++) {
            rgb = imageArray[j];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            y_val = ( 2 * red + 4 * green + blue ) / 7;//②
            oimgArray[j]=y_val;
            tone[y_val]++;//③
        }
        int total=0;
        int border=-1;
        for(int i =0;i < 256;i++){
            total+=tone[i];//④
            if(total>border_length){//⑤
                border=i;
                break;
            }
        }
        for (int j = 0; j < imageArray.length; j++) {
            if(oimgArray[j]>border){//⑥
                oimgArray[j] = 0xFFFFFFFF;
            }else{
                oimgArray[j] = 0xFF000000;
            }
        }
        return oimgArray;
    }

 ①は全ピクセル数について、閾値の割合に対応するピクセル数を取得しています。閾値が50ならば、全ピクセル数の50%に当たる数を取得します。
 ②はBinaryFilterによる二値化と同様に輝度を計算しています。
 ③は各輝度(0~255)について、出現回数を計算しています。
 ④は各輝度の出現回数を、0から順番に積み上げていきます。そして、⑤で①のピクセル数以上になったときに、その輝度を境界線として記録します。
 ⑥では 各ピクセルの輝度について境界線と比較し、高いならば白に、低いならば黒に色を変換します。

最後に、 Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。
imageArray =new PTileFilter().filter(imageArray, imagewidth, imageheight,50);

この時、第四引数が閾値(%)を示します。範囲を外れてもエラーは発生しませんが、基本的に閾値の範囲は0~100までとなります。なお、BinaryFilterについてはコメントアウトしておいてください。
 以下に実機での実行結果を示します。 閾値は50%です。BinaryFilterと比較して、白の割合が増えています。また、ヒストグラムを見るとわかりますが、BinaryFilterでは異なっていた0のバーと255のバーの長さが、Pタイル法ではほぼ同じになっています。

2017年2月6日月曜日

閾値二値化(BinaryFilter)

 今回は二値化処理のなかで、指定閾値で二値化を行うBinaryFilterを実装します。BinaryFilterでは、あるピクセルについて輝度を計算し、その輝度が指定値以上ならばそのピクセルを白に、指定値以下ならそのピクセルを黒に変換する処理です。輝度の計算方法は複数ありますが、今回は計算速度を考慮した輝度計算を行います。なお、RGB値を輝度に変換し、それを新たなRGB値にする処理はグレースケール処理となります。
 まずは、 前回同様Tutorial1Activityクラスと同じ場所に、BinaryFilterクラスを作成します。その後以下のfilterメソッドを記述します。

    public int[] filter(int[] imageArray,int width, int height,int threshould) {
        int y_val = 0;
        int rgb = 0;
        int red = 0, green = 0, blue = 0;
        int[] oimgArray = new int[imageArray.length];//①
        for (int j = 0; j < imageArray.length; j++) {
            rgb = imageArray[j];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            y_val = ( 2 * red + 4 * green + blue ) / 7;//②
           if (y_val > threshould) {//③
                oimgArray[j] = 0xFFFFFFFF;
            } else {
                oimgArray[j] = 0xFF000000;
            }
        }
        return oimgArray;
    }


 ①では、出力用に画像のint配列と同じ長さの空の配列を作成します。
②では輝度を計算します。輝度の計算ではもっと厳密な計算式がありますが、②の計算式では小数点計算を行わなくてすむため、計算速度が速いです。なお、輝度やグレイスケール化については以下のHPをご覧ください。
http://daredemopc.blog51.fc2.com/blog-entry-877.html
 ③では得られた輝度と閾値を比較し、閾値より高い場合にはピクセルを白(255,255,255)に、閾値以下ならばピクセルを黒(0,0,0)にします。

 最後に、Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);の下に以下の文を記入します。

      imageArray =new BinaryFilter().filter(imageArray, imagewidth, imageheight,120);

このとき、第4引数が閾値を示します。範囲を外れてもエラーは発生しませんが、基本的に閾値の範囲は0~254までとなります。
 以下に実機での実行結果を示します。閾値は120です。ヒストグラムからわかるように全てのピクセルが白または黒になっています。

Histogramフィルターの作成

今回からは画像処理の具体的な方法と実装について書いていきたいと思います。今回実装するのは画像のヒストグラムを表示するフィルターです。ヒストグラムは、各カラーの明るさのレベル別にピクセル数をグラフ化し、画像内のピクセル分布を示したものです。具体的には以下のURLをご覧ください。
https://helpx.adobe.com/jp/photoshop/using/viewing-histograms-pixel-values.html
Photoshop上では下の図のようにヒストグラムが表示されます。


 ヒストグラムを表示することで、画像処理によって色の分布がどのように変化するかを分析することができます。そのため、画像処理として最初に導入したいと思います。
 
 まず、Tutorial1Activityクラスと同じ場所に、HistogramFilterクラスを作成します。下の図のように選択してから、右クリックでNew→Java Classを選択します。
その後、クラスの内容を記入する画面では、下の図のようにHistogramFilterと記入し、OKを押します。
これでTutorial1Activityクラスと同じ場所に、HistogramFilterクラスが作成されました。
 次に、HistogramFilterクラスの内容を記入していきます。第一にfilterメソッドを作成します。比較的長いですが、これはred、green、blueそれぞれを分けて処理しているためです。各色で行っている処理は同じです。
filter
   public int[] filter(int[] imageArray, int width, int height) {
        int tone[][]=ToneCalculator(imageArray);//①
        int rate[][]=new int[3][256];
        int[] oimgArray =imageArray.clone();//②
        int red=0;
        int green=0;
        int blue=0;
        int maxr=0;
        int maxg=0;
        int maxb=0;
        //red--------------------------------------
        for(int i =0;i < 256;i++){
            if(maxr < tone[0][i]){
                maxr=tone[0][i];//③
            }
        }
        for(int i =0;i < 256;i++){
            rate[0][i]=(tone[0][i]*50)/maxr;//④
        }
        for(int i =0;i < 50;i++){
            oimgArray[(height-2-i -102)*width]=255*16777216;//⑤
            for(int n =0;n < 256;n++){
                oimgArray[(height-2-i-102)*width+n+1]=255*16777216+255*65536+255*256+255;//⑥
            }

        }
        for(int n =0;n < 256;n++){
            red=rate[0][n];
            if(red > 0){
                for(int rn=1;rn < red;rn++){
                    oimgArray[(height-1-rn-102)*width+n+1]=255*16777216+255*65536;//⑦
                }
            }
        }
        for(int n =0;n < 257;n++){
            oimgArray[(height-1-102)*width+n]=255*16777216;//⑧
        }
        //green--------------------------------------
        for(int i =0;i < 256;i++){
            if(maxg < tone[1][i]){
                maxg=tone[1][i];
            }
        }
        for(int i =0;i < 256;i++){
            rate[1][i]=(tone[1][i]*50)/maxg;
        }
        for(int i =0;i < 50;i++){
            oimgArray[(height-2-i-51)*width]=255*16777216;
            for(int n =0;n < 256;n++){
                oimgArray[(height-2-i-51)*width+n+1]=255*16777216+255*65536+255*256+255;
            }

        }
        for(int n =0;n < 256;n++){
            green=rate[1][n];
            if(green > 0){
                for(int gn=1;gn < green;gn++){
                    oimgArray[(height-1-gn-51)*width+n+1]=255*16777216+255*256;
                }
            }
        }
        for(int n =0;n < 257;n++){
            oimgArray[(height-1-51)*width+n]=255*16777216;
        }
        //blue--------------------------------------
        for(int i =0;i < 256;i++){
            if(maxb < tone[2][i]){
                maxb=tone[2][i];
            }
        }
        for(int i =0;i < 256;i++){
            rate[2][i]=(tone[2][i]*50)/maxb;
        }
        for(int i =0;i < 50;i++){
            oimgArray[(height-2-i)*width]=255*16777216;
            for(int n =0;n < 256;n++){
                oimgArray[(height-2-i)*width+n+1]=255*16777216+255*65536+255*256+255;
            }

        }
        for(int n =0;n < 256;n++){
            blue=rate[2][n];
            if(blue > 0){
                for(int bn=0;bn < blue;bn++){
                    oimgArray[(height-1-bn)*width+n+1]=255*16777216+255;
                }
            }
        }
        for(int n =0;n < 257;n++){
            oimgArray[(height-1)*width+n]=255*16777216;
        }
        return oimgArray;
    }

①では、3色256諧調分の配列を作り、画像のRGB各色の値について配列の対応する場所を増やしていく処理を行います。下にToneCalculatorメソッドを記載します。
    public int[][] ToneCalculator(int[] imageArray){
        int tone[][]=new int[3][256];
        int length = imageArray.length;
        int rgb;
        int red,green,blue;
        for(int i =0;i < length;i++){
            rgb = imageArray[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            tone[0][red]+=1;
            tone[1][green]+=1;
            tone[2][blue]+=1;
        }
        return tone;
    }

 ②では出力用にint配列をコピーしています。以降では赤色についてのみ述べます。
 ③では最も多く出現した色の値を求めています。この値がヒストグラム上での上限値となります。
 ④ではそれぞれの色の値(0~255)について、出現した色の値を最頻色の値で割ることで、最頻色の値に対する割合を出しています。さらに、50をかけることで0~50の51段階に変更しています。
 ⑤は画像上にヒストグラムを作る際の、左端の黒いバーを描写しています。
 ⑥は同様にヒストグラムの白い背景を描写しています。
 ⑦ではヒストグラムの描写を行っています。赤では下限位置から④でえられた各色の出現割合(0~50)の分だけ赤いピクセル(255,0,0)を描写しています。
 ⑧では下限を示す黒い横棒を描写しています。
 以上の操作を行うことにより、画像にヒストグラムを追加することができます。最後に、Tutorial1Activityクラスのint imageArray[]=ba2ia(ba);とba=ia2ba(imageArray);の間(前回の④と⑤の間)に以下の文を加えます。
 
imageArray=new HistogramFilter().filter(imageArray, imagewidth, imageheight);

今後、同じような文を同じ位置に加えていきますが、この文(HistogramFilter)は最後に記入することをお勧めします。画像処理後のヒストグラムを取得するためです。
 下に以上の編集を行った結果得られる画像を示します。画像の左下にヒストグラムが表示されます。この図ではRGB各色で白飛び(値が255)や黒つぶれ(値が0)が発生していることがわかります。

以上でヒストグラムの追加の説明を終わります。図のようにヒストグラムの画像に占める割合が大きいので、必要に応じて50段階ではなく30段階にすることや、横幅を256ではなく128にするなどの調整を行ってください。

Tutorial1Activityで画像処理を行うための編集2

前回に引き続き、Tutorial1Activityを編集し、細かい画像処理が可能な形に変更します。
今回は前回編集したonCameraFrameメソッドの中の、2つのImgproc.resizeおよびif文の間に以下の文を加えます。

        int imagewidth=mat.cols();//①
        int imageheight=mat.rows();//①
        byte[] ba=mat2ba(mat);//②
        mat = null;

        ba=ba4toba3(ba);//③
        int imageArray[]=ba2ia(ba);//④
        
        //ここで画像処理
        
        ba=ia2ba(imageArray);//⑤
        mat=ba2mat(imagewidth,imageheight,ba);//⑥
        ba=null;

 ①はそれぞれ、matから画像の幅と高さを取得しています。この値は実機によって異なる値です。また、リサイズしている場合にはリサイズ後の値が入ります。
 ②はmatをbyte配列に変換するメソッドです。詳しいメソッド内容は後述します。この時点でのbyte配列は、一つの画素・ピクセルを4個のbyteで表しており、配列上ではred、green、blue、alphaの順に並んでいます。
 ③ではこのバイト配列を変換し、一つのピクセルを3個のbyteで表すように変換します。ここではbyte配列からalphaを削除します。
 ④ではbyte配列をint配列に変換します。このint配列は一つの画素を一つのintで表しており、255*16777216を固定のalpha値、65536の倍数をred、256の倍数をgreen、1の倍数をblueで表しています。このint配列が、このブログで行う画像処理の基本的な処理対象となります。
 ⑤では画像処理した後のint配列を一度byte配列に戻します。この時点でのbyte配列は③と同じく一つのピクセルを3個のbyteで表しています。
 ⑥ではbyte配列をmatに変換しています。この際、必ず画像の幅と高さが必要になります。
 以上が今回の編集部分です。次に、各メソッドの内容を記載します。

mat2ba

    public static byte[] mat2ba(Mat mat){
        int dataSize=mat.cols()*mat.rows()*(int)mat.elemSize();
        byte[] data = new  byte[dataSize];
        mat.get(0, 0,data);
        mat=null;
        return data;
    }

ba4toba3
    public byte[] ba4toba3(byte ba[]){
        int length=ba.length;
        int slash=length/4;
        byte result[]=new byte[slash*3];
        for(int i =0;i < slash;i++){
            result[i*3]=ba[i*4];//r
            result[i*3+1]=ba[i*4+1];//g
            result[i*3+2]=ba[i*4+2];//b

        }
        ba=null;
        return result;
    }

ba2ia
    public static int[] ba2ia(byte ba[]){
        int length = ba.length/3;
        int result[]=new int[length];
        int r,g,b;
        for(int i =0;i < length;i++){
            r=ba[i*3]& 0xFF;
            g=ba[i*3+1]& 0xFF;
            b=ba[i*3+2]& 0xFF;
            result[i]=rgb2int(r, g, b);
        }
        ba=null;
        return result;
    }

ba2iaで使用しているrgb2int (r、g、bの値をint型に変換する)
    public static int rgb2int(int r,int g,int b){
        return 255*16777216+r*65536+g*256+b;
    }

ia2ba
    public static byte[] ia2ba(int ia[]){
        int length=ia.length;
        int red,green,blue;
        byte result[]=new byte[length*3];
        int rgb=0;
        for(int i =0;i < length;i++){
            rgb = ia[i];
            red = (rgb >> 16) & 0xff;
            green = (rgb >> 8) & 0xff;
            blue = (rgb) & 0xff;
            result[i*3]=(byte)red;
            result[i*3+1]=(byte)green;
            result[i*3+2]=(byte)blue;
        }
        ia=null;
        return result;
    }


ba2mat (CvTypeでorg.opencv.core.CVTypeのインポートが必要)
    public Mat ba2mat(int width,int height,byte pixels[]){
        Mat mat = new Mat(height,width, CvType.CV_8UC3);
        mat.put(0,0,pixels);
        return mat;
    }


以上が今回必要なメソッドです。実機で実行すると、これまでよりフレームレートの落ちたプレビューがみられるでしょうが、それ以外に変化はありません。

2017年2月5日日曜日

Tutorial1Activityで画像処理を行うための編集1

Tutorial1Activityを編集し、細かい画像処理が可能な形に変更します。

画面左のProjectタブからAndroid タブを選択し、OpenCVTutorial1CameraPreview→java→org.opencv.tutorial1→Tutorial1Activityを開きます。


このとき、前回でOpenCV Managerを使用しない設定にしている人は、 onCreateメソッドの先頭に以下の文を加えます。

System.loadLibrary("opencv_java3");

次に、97行目付近のonCameraFrameメソッドを編集します。
編集前は以下のようになっています。

public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        return inputFrame.rgba();
}

このメソッドはプレビューで表示される画像を返すメソッドで、inputFrameを受け取り、それをMat形式に変換したデータをreturnしています。
よって、inputFrameで得られたデータを画像処理し、最終的にMat形式に変換してあげることで、画像処理したプレビューが見られるようになります。
今回は、onCameraFrameメソッドを以下のように編集します。

    public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
        Mat mat=inputFrame.rgba();//①
        inputFrame=null;

        double resize=0.5;//②
        if(resize!=1){
            Imgproc.resize(mat, mat, new Size(),resize,resize, Imgproc.INTER_AREA);//③
        }


        if(resize!=1){
            Imgproc.resize(mat, mat, new Size(),1/resize, 1/resize, Imgproc.INTER_CUBIC);//④
        }
        return mat;//⑤
    }

①ではinputFrameをmat形式に変換します。②では画像処理するにあたり、リサイズして処理するか、そしてリサイズする倍率を指定しています。これは、プレビュー画像が大きいときに、そのままのサイズで画像処理を行うと非常に時間がかかってしまうことがあるためです。そのため、一度二分の一のサイズなどにリサイズしてから画像処理し、またリサイズしなおして表示するという流れが必要になる場合があります。このリサイズ倍率は実機でのプレビューサイズに応じて変える必要があります。目安として横幅が1920や1440の場合は0.5を、720や640の場合は1でリサイズしないほうがよいでしょう。なお、倍率の小数点以下が0.33など1を割り切れない値の場合、エラーが発生する恐れがあります。
③では縮小リサイズ処理を、④では拡大リサイズ処理を行っています。このときImgprocとSizeでクラスをインポートする必要があります。その際はImgprocやSizeにカーソルを当ててからAlt+Enterを押し、Import Classを選びましょう。また、Sizeでクラスをインポートするときは複数の選択肢が出ますが、org.opencv.coreを選びましょう。
 ⑤ではリサイズ処理したmatを返します。このmatが画像として実機のディスプレイに表示されます。

この時点で実機で実行し、エラーが発生しないことを確認しましょう。
下に実行結果の図を載せますが、気持ち若干のぼやけが生じます。




2017年2月4日土曜日

Android StudioにOpenCVをインストール

Android StudioにOpenCVをインストールします。
以下のサイトからOpenCV for Androidをダウンロードします。
http://opencv.org/

ダウンロードしたファイルを任意の場所に展開します。(Cドライブ直下等)
その後、Android Studio上で任意のプロジェクト(初期設定のプロジェクトで可)を開いた状態で、File→New→Import Moduleを選択し、以下のフォルダを選択します。(Cドライブ直下の場合)

C:\OpenCV-android-sdk\sdk\java

Module NameがopenCVLibrary320等となっていればOKです。
Nextを押し、設定を変えずにFinishを押します。

次に、OpenCV for Androidのサンプルプログラムをインポートします。
File→New→Import Projectを選択し、以下のフォルダを選択します。(Cドライブ直下の場合)

C:\OpenCV-android-sdk\samples\tutorial-1-camerapreview

必要に応じて、Import Destination Directoryを変更し、Nextを押し、次の画面でFinishを押します。

次に、build.gradleの設定をします。
以下の図のように、Project画面に切り替え→tutorial-1-camerapreview→openCVLibrary320→build.gradleの中で、 各SdkVersionを21以上に設定します。



また、tutorial-1-camerapreview→openCVTutorial1CameraPreview→build.gradleについても同様の設定を行います。

右上にSync Nowが表示されている場合クリックします。
そして、下のほうのMessages Gradle Syncの中に発生しているエラーの、Install missing platform(s) and sync projectをクリックします。

インストールが実行され、Finishを押します。
Syncが実行され、しばらくすると処理が終わります。

次に、実機にOpenCV Manager をインストールします。
図のようにPlayストアにてOpenCV Managerを検索し、インストールします。


OpenCV Managerをインストールしたら、実機とパソコンをつなぎ、上のメニューのRun→Run 'openCVTutorial1CameraPreview'を押し、図のように実機を選択した状態でOKを押します。





 起動すると実機の画面上にカメラのプレビューが映ります。
下の図はそのスクリーンショットです 。



以上で、OpenCVのインストールとサンプルプログラムの実行を終わります。
なお、OpenCV Managerをインストールしたくない場合や、できない環境の場合には以下のサイトを参考にしてください。
http://qiita.com/denjin-m/items/8b2f30b98ef4529b8f1f
Cppに関することは行わなくても大丈夫です。