SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Javaで学ぶグラフィックス処理

ヒストグラムの拡張・平坦化によるカラー画像の補正

ヒストグラムを補正して画像を鮮明にする


  • X ポスト
  • このエントリーをはてなブックマークに追加

プログラムの概要

 画面描画時の主な動作(paint()の内容)は、次の通りです。

  1. 選択ボックスの指定に従って、画像ファイルを読み込み、原画像とする。
  2. 原画像を表示する。
  3. 原画像をRGBに分解しL(輝度、luminosity)を計算する。
  4. 原画像のRGBLの各ヒストグラムを求め、表示する。
  5. 上記Lヒストグラムの統計量(minmidmax)を求めて、線と数値で表示する。
  • ヒストグラム拡張の場合
    1. 原画像のminが0に、maxが255になるように、比例関係式を用いてRGBL各ヒストグラムを拡張する。
    2. 比例関係式を用いて原画像のRGBを補正する。
    3. 比例関係式を用いて統計量を求める。
  • ヒストグラム平坦化の場合
    1. 原画像のLヒストグラムから換算表(Look Up Table)を作成し、それを用いてRGBL各ヒストグラムを平坦化する。
    2. 換算表を用いて原画像のRGBを補正する。
    3. 換算表を用いて統計量を求める。
  1. 補正後のRGBから補正画像を生成し、補正画像を表示する。
  2. 補正後のRGBL各ヒストグラムを表示する。
  3. 補正後のLヒストグラムの統計量(minmidmax)を表示する。

プログラム

Hist.java(一部抜粋)
import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;

public class Hist extends Applet 
   implements ItemListener,ActionListener{

   // ……中略……

   public void init(){
      // ……中略……
   }

   // ----------------------- GUI関係メソッド ------------------------

   //チョイスが変更になった場合のメソッド
   public void itemStateChanged(ItemEvent ie){
      // ……中略……
   }
      
   //ボタンをクリックしたときのメソッド
   public void actionPerformed(ActionEvent ae){
      // ……中略……
   }

   // ------------------------- 共通メソッド -------------------------

   //画像ファイルを読み込みImage画像にするメソッド
   public Image readImageFile(String filename){
      // ……中略……
   }

   //Image画像をRGBL値に分解するメソッド
   private void getRGBLFromImage(Image img, int[] red,
      int[] green, int[] blue, int[] lum){

      Color color;
      int[] rgb=new int[SIZE];

      PixelGrabber grabber=
         new PixelGrabber(img,0,0,WIDTH,HEIGHT,rgb,0,WIDTH);
      try{
         grabber.grabPixels();
      } catch(InterruptedException e){}

      for(int i=0;i<SIZE;i++){
         color=new Color(rgb[i]);
         red[i]=color.getRed();
         green[i]=color.getGreen();
         blue[i]=color.getBlue();
         lum[i]=(3*red[i]+6*green[i]+blue[i])/10;   //NTSC方式に近似
      }

   }

   //RGBL値のヒストグラムを個別にとるメソッド
   private void createHistogram(int[] rgb, int[] hist){

      for(int i=0;i<LEVEL;i++)
         hist[i]=0;
      for(int i=0;i<SIZE;i++)
         hist[rgb[i]]++;

   }

   //ヒストグラムデータを描画するメソッド
   private void drawHistogram(int x, int y, int[] red_hist,
      int[] green_hist, int[] blue_hist, int[] lum_hist){
      // ……中略……
   }

   //ヒストグラムの統計量(min、mid、max)を求めるメソッド
   private void getStatisticsFromHistogram(
      int[] hist, int[] levels){

      int percent1=SIZE/100;
      int percent50=SIZE/2;
      int percent99=SIZE/100*99;

      int i=0;
      int sum=0;

      do{
         sum+=hist[i++];
      } while(sum<percent1);
      levels[0]=i-1;      //min
      do{
         sum+=hist[i++];
      } while(sum<percent50);
      levels[1]=i-1;      //mid
      do{
         sum+=hist[i++];
      } while(sum<percent99);
      levels[2]=i-1;      //max

   }

   //ヒストグラムの統計量(min、max、mid)を線と数値で表示するメソッド
   private void drawStatistics(int x, int y,
      // ……中略……
   }

   //R、G、B成分からImage画像を生成するメソッド
   private Image createImageFromRGB(
      int[] red, int[] green, int[] blue){

      // ……中略……

   }

   // ----------------- ヒストグラム拡張関係メソッド -----------------

   //ヒストグラムを拡張するメソッド
   private void spreadHistogram(
      int min, int max, int[] src_hist, int[] dest_hist){

      int i,j;
      double ratio=255.0/(max-min);

      for(i=0;i<LEVEL;i++)
         dest_hist[i]=0;
      for(i=0;i<LEVEL;i++){
         if(i<min) dest_hist[0]+=src_hist[i];
         else if(i>max) dest_hist[LEVEL-1]+=src_hist[i];
         else{
            j=(int)((i-min)*ratio);
            dest_hist[j]=src_hist[i];
         }
      }

   }

   //拡張ヒストグラムに応じてRGB成分を変換するメソッド
   private void spreadRGB(int min, int max, int[] src, int[] dest){
   
      double ratio=255.0/(max-min);
      for(int i=0;i<SIZE;i++){
         if(src[i]<min) dest[i]=0;
         else if(src[i]>max) dest[i]=255;
         else dest[i]=(int)((src[i]-min)*ratio);
      }

   }

   // ---------------- ヒストグラム平坦化関係メソッド ----------------

   //原画像のヒストグラムから変換表(LookUpTable)を作成するメソッド
   private void generateLookUpTable(int[] hist, int[] table){

      int i,j=0;
      int sum=0;
      int average=SIZE/LEVEL+1;

      for(i=0;i<LEVEL;i++){
         sum+=hist[i];
         j+=sum/average;
         sum %=average;
         table[i]=j;
      }

   }

   //ヒストグラムを平坦化するメソッド
   private void equalizeHistogram(
      int[] table, int[] src_hist, int[] dest_hist){

      for(int i=0;i<LEVEL;i++)
         dest_hist[i]=0;
      for(int i=0;i<LEVEL;i++)
         dest_hist[table[i]]+=src_hist[i];

   }

   //平坦化ヒストグラムに応じてRGB成分を変換するメソッド
   private void equalizeRGB(int[] table, int[] src, int[] dest){

      for(int i=0;i<SIZE;i++)
         dest[i]=table[src[i]];

   }

   public void paint(Graphics g){

      String proccess=null;

      // ---------------------- 原画像の処理 -------------------------

      //画像ファイルを読み込む
      img_src=readImageFile(filename);

      //原画像を左上に表示する
      g.drawImage(img_src,X0,Y0,null);

      //Image画像をR,G,B,Lに分解する
      getRGBLFromImage(img_src,src_red,src_green,src_blue,src_lum);

      //R,G,B,Lそれぞれのヒストグラムを求める
      createHistogram(src_red,src_red_hist);
      createHistogram(src_green,src_green_hist);
      createHistogram(src_blue,src_blue_hist);
      createHistogram(src_lum,src_lum_hist);

      //原画像のヒストグラムを右上に表示する
      drawHistogram(X1,Y0,
         src_red_hist,src_green_hist,src_blue_hist,src_lum_hist);

      int[] mmm_levels=new int[3];

      //ヒストグラムのmin、max、midを求める
      getStatisticsFromHistogram(src_lum_hist,mmm_levels);

      min_level=mmm_levels[0];
      mid_level=mmm_levels[1];
      max_level=mmm_levels[2];
               
      //ヒストグラムのmin、max、midを表示する
      drawStatistics(X1,Y0,min_level,mid_level,max_level);

      g.drawString("原画像",X1+270,Y0+20); 

      // ------------------ ヒストグラム拡張の場合 -------------------

      if(!equal_flag){     

         //ヒストグラムを拡大する
         spreadHistogram(
            min_level,max_level,src_red_hist,dest_red_hist);
         spreadHistogram(
            min_level,max_level,src_green_hist,dest_green_hist);
         spreadHistogram(
            min_level,max_level,src_blue_hist,dest_blue_hist);
         spreadHistogram(
            min_level,max_level,src_lum_hist,dest_lum_hist);

         //拡大ヒストグラムに応じてRGB成分を変換する
         spreadRGB(min_level,max_level,src_red,dest_red);
         spreadRGB(min_level,max_level,src_green,dest_green);
         spreadRGB(min_level,max_level,src_blue,dest_blue);

         //補正ヒストグラムの統計量を求める
         dest_min_level=0;
         dest_mid_level=
            (int)((mid_level-min_level)*255.0/(max_level-min_level));
         dest_max_level=255;

         //ヒストグラム拡大結果であることを表示する
         proccess="拡張結果";

      }

       // ---------------- ヒストグラム平坦化の場合 ------------------

      if(equal_flag){

         //LookUpTableを生成する
         generateLookUpTable(src_lum_hist,table);

         //ヒストグラム平坦化を行う
         equalizeHistogram(table,src_red_hist,dest_red_hist);
         equalizeHistogram(table,src_green_hist,dest_green_hist);
         equalizeHistogram(table,src_blue_hist,dest_blue_hist);
         equalizeHistogram(table,src_lum_hist,dest_lum_hist);

         //平坦化ヒストグラムに応じてRGB成分を変換する
         equalizeRGB(table,src_red,dest_red);
         equalizeRGB(table,src_green,dest_green);
         equalizeRGB(table,src_blue,dest_blue);

         //補正ヒストグラムの統計量を求める
         dest_min_level=table[min_level];
         dest_mid_level=table[mid_level];
         dest_max_level=table[max_level];

         //ヒストグラム平坦化結果であることを表示する
         proccess="平坦化結果";

      }

      // ----------- 共通(補正画像とヒストグラムの表示)-------------

      //R,G,B成分からImage画像を生成する
      img_dest=createImageFromRGB(dest_red,dest_green,dest_blue);

      //補正画像を左下に表示する
      g.drawImage(img_dest,X0,Y1,null);

      //補正画像のヒストグラムを右下に表示する
      drawHistogram(X1,Y1,
         dest_red_hist,dest_green_hist,dest_blue_hist,dest_lum_hist);

      //ヒストグラムの補正方法を表示する
      g.drawString(proccess,X1+270,Y1+20);

      //ヒストグラムの統計量(min、max、mid)を表示する
      drawStatistics(X1,Y1,
         dest_min_level,dest_mid_level,dest_max_level);

   }

}

プログラムの使い方

 アプレットを実行すると、上方右側に原画像選択用のドロップダウン選択ボックスと、補正方式切り替え用のボタンが表示されます。その下には、原画像とそのヒストグラム、レベルの最小値、中央値、最大値が示され、さらに下には、補正画像とそのヒストグラム、レベルの最小値、中央値、最大値が示されます。

 起動時には、補正方式が「ヒストグラム拡張」に設定されており、ボタンには[→ヒストグラム平坦化]と表示されています。これをクリックすると、「ヒストグラム平坦化」に変わります。原画像は、デフォルトで「暗い画像」になっていて、「明るい画像」「明暗の画像」に切り替えることができます。

プログラムの実行結果

 図4は実行結果の一例を示したもので、原画像には「明暗の画像」が選ばれています。この場合には、「ヒストグラム平坦化」が適しています。原画像と比べて、ヒストグラムが広がっていて、補正画像は、暗い部分が明るくなり、明るい部分は、逆に少し暗くなっていることが分かります。逆光の写真の補正に適しています。

図4 実行結果の例
図4 実行結果の例

まとめ

 ヒストグラムを用いたカラー画像の補正は、色相のズレが生じるとされています。輝度データを基にして自動的にヒストグラム補正を行い、カラー情報は無視しましたが、色彩が強調された特殊な画像を除き、一般的な自然画像に対しては、十分使用できる結果が得られました。

参考資料

 この記事は、筆者のウエブサイト『Visual C++ 6.0を用いた易しい画像処理(4)-原画像のヒストグラムを求め、ヒストグラムを変換して新しい画像を描く-(ただし、現在は Visual C++ 2005 EXpress Edition 版に改定)』を全面的に改良し、Java言語に書き直して、分かりやすく加筆したものです。

 市販ソフトによるヒストグラムの補正方法を解説した書籍で比較的詳しいのは、次の2冊です。

  1. 早川廣行のPhotoshop CSプロフェッショナル講座 基本編』 早川廣行 著、毎日コミュニケーションズ、2004年8月
  2. Adobe Photoshop CS2パーフェクトマスター』 KUMIKO 著、秀和システム、2005年9月
修正履歴

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Javaで学ぶグラフィックス処理連載記事一覧

もっと読む

この記事の著者

石立 喬(イシダテ タカシ)

1955年東京工大卒。同年、NECへ入社し、NEC初のコンピュータの開発に参画。磁気メモリ、半導体メモリの開発、LSI設計などを経て、1989年帝京大学理工学部教授。情報、通信、電子関係の教育を担当。2002年定年により退職し現在に至る。2000年より、Webサイト「Visual C++の勉強部屋」を公開。...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/214 2006/11/06 19:08

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング