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本のバーで構成されていることがわかります。