2010. 12. 16.
2017.  4.25. HSVへの変換式を訂正
石立 喬

Visual C++ 2010 Expressを用いた易しい画像処理(3)

―原画像のRGBデータをHSV(HSB)表示に変換して加工する―


  「易しい画像処理(2)」に引き続き、ピクセル値を個別に単独で変換する、もう一つの処理について紹介する。コンピュータのモニタやTVなど、発光色の表示にはRGB表色系が用いられるが、人間の感覚に近いのは、HSB(HSV)系、HSL系などの表色系である。
 原画像を、人間の見た目で精度良く加工するには、RGBデータを一旦HSB(HSV)表示などに変換してから、その系上で処理を行い、再びRGBに逆変換するのが良い。大抵の画像ソフトには、この機能が備わっている。
 この種の色空間の変換は、単に画像を補正するのみでなく、最近では、画像から特徴を抽出して、画像理解や画像認識を行うのに広く使われている。この画像理解への応用については、別途述べる。
表色系(色空間)
 RGBによる色の表現を、より人の感覚に近づけた色空間(表色系)の主なものに、HSV(HSBとも言う)系とHSL系があり、さらにHSI系もある。これらのいずれの色空間にも色相があり、その変換結果はほぼ共通している(HSI系は一部異なる)。しかし、彩度や明度の表現は、かなり異なるので、注意を要する。
◎HSV系
 HSVは、Hue(色相)、 Saturation(彩度)、 Value(明度)の意味で、Mac OS X、Photoshop、Illustratorなどに採用されている。Value(明度)の代わりにBrightness(明度)と呼ぶHSBもあるが、内容的には同一である(BはBlueと解釈される恐れがある)。R、G、Bのいずれか一つの値のみが大きい場合でも明度が上がるので、人の感じる明るさとは異なる場合がある。
◎HSL系
 HSLは、Hue(色相)、 Saturation(彩度)、 Lightness(明度)の意味で、Microsoft Window、PaintShopProなどに採用されている。HSLの順序を一部変えて、HLSと呼ぶ場合もあるが、同一である。R、G、Bの値の最大と最小の平均で明るさを決めるので、人の感じる明るさに近い。Lightnessに似たLuminanceもあるが、これはTV用である。
◎ HSI系
 HSIは、Hue(色相)、 Saturation(彩度)、 Intensity(輝度)の意味で、画像理解(Computer Vision)の分野に採用されている。R、G、Bの値の平均で明るさを決めるので、人の感じる明るさに近い。
 Hue(色相)以外の計算式を比較して下表に示す。
  HSV(HSB) HSL(HLS) HSI
Value,Brightness,
Lightness,
Intensityなど(明るさ)
 Value(Brightness)=max Lightness=(max+min)/2 Intensity=(R+G+B)/3
Saturation(彩度) (max-min)/max Lightnessが0.5以下の場合
 (max-min)/(max+min)
Lightnessが0.5を超える場合
 (max-min)/(2-(max+min))
 1-min/Intensity

RGBからHSVへ、HSVからRGBへ変換するメソッド
 下表に示すように、.NET Frameworkには、Color構造体からHSL系に変換するメソッドがあるが、RGB系への逆変換のメソッドは無い。HSL系へ変換する目的を、画像補正ではなく、画像理解(Computer Vision)に限れば、これでも十分なのかも知れない。一方、Javaは、RGB系とHSB系(JavaではHSVと呼ばない)相互間の変換メソッドが用意されている。
 ここではHSV系を使うことにし、.NETの既存のメソッドを用いず、変換メソッドをJavaに準じて自作した。自作したメソッドを下表の右に示してある。
 Javaでは、hue、saturation、brightnessのすべてについて0.0〜1.0の範囲としている(hueについては、自動的にこの範囲に収められる)が、.NETではhueは0.0〜360.0、saturationとbrightnessは0.0〜1.0であるので、自作メソッドは、.NETに倣った。
 変換式は、「HSV色空間−Wikipedia」を参考にした。

  .NET Framework 4 Java Standard Edition 6 自作メソッド
ColorからHSLへ float GetHue()
float GetSaturation()
float GetBrightness()
   
RGBからHSV(HSB)へ   Float[] RGBtoHSB(int r,int g,int b,
float[] hsbvals)
Array<float>^ RGBToHSV(int r,int g,int b)
HSV(HSB)からRGBへ   int HSBtoRGB(float hue,float saturation,
float brightness)
HSV(HSB)からRGBColorへ Color getHSBColor(float h,float s,float b) Color HSVToRGBColor(float hue,
float saturation,float value)

 .NET Frameworkの場合、GetBrightness()のように、Brightnessの名称を使っているので、当然HSB系であると思っていたが、良く調べてみるとHSL系であり、GetLightness()と、別の名称を使えば混乱がなかったのに残念である。



プログラム

private: System::Void Form1_Paint(System::Object^ sender,
                  System::Windows::Forms::PaintEventArgs^ e) {

   int WIDTH=320,HEIGHT=240;
   int X0=10,Y0=10;
   int X1=WIDTH+20,Y1=10;
   int X2=10,Y2=HEIGHT+20;
   int X3=WIDTH+20,Y3=HEIGHT+20;

   Color color_src,color_dst1,color_dst2,color_dst3;

   Bitmap^ bmap_dst1=gcnew Bitmap(WIDTH,HEIGHT);
   Bitmap^ bmap_dst2=gcnew Bitmap(WIDTH,HEIGHT);
   Bitmap^ bmap_dst3=gcnew Bitmap(WIDTH,HEIGHT);

   Byte r,g,b;

   array<float>^ hsv= gcnew array<float>(3);

   Graphics^ gr=e->Graphics;

   //原画像のビットマップを生成する
   Bitmap^ bmap_src=gcnew Bitmap("sample.JPG");

   //原画像を左上に描画する
   gr->DrawImage(bmap_src,X0,Y0,WIDTH,HEIGHT);

   //原画像bmapをRGBに分解し、HSVに変換し、補正して新しい画像bmap_dstを生成する
   for(int j=0;j<HEIGHT;j++)
      for(int i=0;i<WIDTH;i++){
         color_src=bmap_src->GetPixel(i,j);
         r=color_src.R;      //RGBを取得する
         g=color_src.G;
         b=color_src.B;
         hsv=RGBToHSV(r,g,b);   //HSVに変換する

         color_dst1=HSVToRGBColor(hsv[0],hsv[1],hsv[2]+0.3f);   //明度の増加
         bmap_dst1->SetPixel(i,j,color_dst1);
         color_dst2=HSVToRGBColor(hsv[0],hsv[1]+0.3f,hsv[2]);   //彩度の増加
         bmap_dst2->SetPixel(i,j,color_dst2);
         color_dst3=HSVToRGBColor(hsv[0]+40.0f,hsv[1],hsv[2]);   //色相の変更
         bmap_dst3->SetPixel(i,j,color_dst3);

      }

   gr->DrawImage(bmap_dst1,X1,Y1);   //明度の増加を右上に
   gr->DrawImage(bmap_dst2,X2,Y2);   //彩度の増加を左下に
   gr->DrawImage(bmap_dst3,X3,Y3);   //色相の変更を右下に

}

//RGBデータをHSV(HSB)系に変換して配列化するメソッド
private: array<float>^ RGBToHSV(int r,int g,int b){

   array<float>^ hsv= gcnew array<float>(3);
   float hue,saturation,value;

   int max=Math::Max(Math::Max(r,g),b);  //r,g,bの最大を求める
   int min=Math::Min(Math::Min(r,g),b);  //r,g,bの最小を求める
   int diff=max-min;

   if(diff!=0){
      if(max==r){
          hue=60*(g-b)/(float)diff;
          if(hue < 0) hue += 360;
      }
      if(max==g) hue=60*(b-r)/(float)diff+120;
      if(max==b) hue=60*(r-g)/(float)diff+240;
   }
   else
      hue=0;

   if(max!=0)
      saturation=diff/(float)max;
   else
      saturation=0;

   value=max/255.0f;

   hsv[0]=hue;
   hsv[1]=saturation;
   hsv[2]=value;

   return hsv;

}

//HSBデータからRGB系を経由してColor構造体を生成するメソッド
private: Color HSVToRGBColor(float hue,float saturation,float value){

   int r,g,b;
   int h;
   float f;
   int v,p,q,t;

   if(hue>360) hue-=360;
   else if(hue<0) hue+=360;
   if(saturation>1) saturation=1.0f;
   else if(saturation<0) saturation=0.0f;
   v=(int)(255*value);
   if(v>255) v=255;
   else if(v<0) v=0;

   if(saturation==0){
      r=v;
      g=v;
      b=v;
   }
   else{
      h=(int)(hue/60);
      f=hue/60-h;
      p=(int)(v*(1-saturation));
      if(p<0) p=0;
      else if(p>255) p=255;
      q=(int)(v*(1-f*saturation));
      if(q<0) q=0;
      else if(q>255) q=255;
      t=(int)(v*(1-(1-f)*saturation));
      if(t<0) t=0;
      else if(t>255) t=255;

      switch(h){
         case 0:r=v; g=t; b=p; break;
         case 1:r=q; g=v; b=p; break;
         case 2:r=p; g=v; b=t; break;
         case 3:r=p; g=q; b=v; break;
         case 4:r=t; g=p; b=v; break;
         case 5:r=v; g=p; b=q; break;
      };
   }

   return Color::FromArgb(r,g,b);

}


得られた結果
 図で、左上が原画像、右上が明度増加、左下が彩度増加、右下が色相の変更である。



「Visual C++ の勉強部屋」(目次)へ