數字圖像處理——RGB與HSV圖像互相轉換原理


01

RGB與HSV介紹

講RGB圖與HSV圖的互相轉換之前,我們先分別介紹一下這兩種圖像。

  • 首先是RGB圖像

RGB圖像是一種三通道圖像,通常用於表示彩色圖,它由相同行、列的紅(Red)、綠(Green)、藍(Blue)這三通道的數據組成。比如對於512行512列的RGB圖像,其紅通道為一張512*512灰度圖、綠通道為一張512*512灰度圖、藍通道為一張512*512灰度圖,三通道數據合起來構成了3*512*512的RGB圖像。

42ae1242bfadba48bb96ad81b0f2f6e1.png

我們視覺能看到的一些常見顏色,則由三通道數據對應坐標位置的三個像素值編碼組成。比如對於8位的RGB圖像,其每個像素值取值范圍是0~255,如果某一坐標點A處紅、綠、藍三通道的像素值依次為255,255,255,那么該點表示的顏色為白色,如果該處紅、綠、藍三通道的像素值依次為0,0,0,那么該點表示的顏色為黑色,又如果該處紅、綠、藍三通道的像素值為其它值,則其表示的顏色為其它顏色。所以理論上8位RGB圖可以表示的顏色種類數為255*255*255。‍

e3be5d2737b06fcc817edd05a75b28ad.png

RGB圖像是與硬件相對應的圖像,也即彩色相機圖像傳感器輸出的原始數據本身就包含了R、G、B三通道的數據,它將三通道圖像數據按照一定順序排列(拜爾陣列)輸出,上位機則通過USB或網絡接收傳感器傳來的數據並將其解析為RGB圖像。比如對於傳感器輸出的RG/GB格式拜爾陣列,其R、G、B數據在陣列中的排列如下圖:

fc12f6f027748dea923ef02379a01d8c.png

  • 其次是HSV圖像

RGB圖像與相機傳感器輸出的原始數據相對應,HSV圖像則與我們人類的直觀視覺更相符。HSV圖像也包含相同尺寸的三通道數據:H通道、S通道、V通道。下面分別介紹這三個通道:

H通道:H通道的像素值表示色調,取值范圍0~360,我們可以把這個取值范圍理解為角度,也即一個閉環的取值范圍,如下圖:

b30d0505548da0cdad471f34e948f79e.png

S通道:S通道的像素值表示圖像的飽和度。飽和度是指圖片彩色的純度——圖像的混合顏色越少,其飽和度越高,直觀看起來就越鮮艷鮮明、視覺效果越強烈;反之圖像的混合顏色越多,其飽和度越低,視覺效果越弱。

比如在所有可視色彩中純紅色的飽和度是最高的,也即純紅色看起來最鮮艷,但是如果在純紅色中混入其它顏色,那么其飽和度將會降低,這時看起來就沒那么鮮艷了。

S通道像素值的取值范圍是0~1,值越大表示飽和度越高。

V通道:V通道像素值表示圖像的明亮程度,取值范圍也是0~1,值越大表示越亮。

由以上介紹可知,RGB圖像與硬件輸出相對應,而HSV圖像則更符合人眼的直觀視覺,因此處理圖像時,往往先將RGB圖像轉換為HSV圖像,在HSV色彩空間對圖像進行處理,處理完畢后再將HSV圖像轉換為RGB圖像。


02

RGB與HSV的互相轉換原理

在講轉換原理之前,首先我們以8位彩色圖為例來明確一下RGB圖像、HSV圖像中各通道像素值的取值范圍,對於圖像中任意坐標點,其取值范圍如下:

a79e928300627ba992ff5323da4f9f17.png

然而在Opencv中,為了對HSV圖像進行可視化,通常將其像素值轉換到0~255之間:

0614979eb335cf4f7fb32c2c55fb0128.png

  • RGB轉HSV原理

轉換原理非常簡單,對於圖像中任意坐標點,其RGB顏色空間為(R,G,B),HSV顏色空間為(H,S,V),首先需要將R、G、B值轉換到0~1之間:

98de9dd25bb3c11409b553362f669a4b.png

然后計算H、S、V值:

4b963f5000db111f99a75809fa455fb1.png

如果計算得到的H值小於0,將該值再加上360,得到最終的H值:

00c17b2d3d7a4077d07555791d78c516.png

由於Opencv需要做HSV圖像的可視化,因此最后還需要將各個值轉換到0~255之間:

a025b7173973184c3478f76606beb211.png

  • HSV轉RGB原理

對於圖像中任意坐標點,其RGB顏色空間為(R,G,B),HSV顏色空間為(H,S,V)。首先將可視化圖像的H、S、V值分別轉換到0~360、0~1、0~1的范圍:

1fb9e1c490e4b1c328bf76cd7363651a.png

那么R、G、B的計算如以下公式,其中floor表示向下取整運算:

430b61b0e960f657f1eac34dd71be19c.png


03

基於Opencv的RGB與HSV互相轉換

Opencv提供了cvtColor函數,調用該函數可以非常方便地實現不同顏色空間的轉換。不過為了可視化,調用該函數得到的HSV圖像,其H、S、V三通道的取值范圍並不是0~360、0~1、0~1,而是經過轉換的0~180、0~255、0~255

void rgb_hsv(void)
{
  //讀取原圖像
  Mat img = imread("000000000902.bmp", CV_LOAD_IMAGE_COLOR);


  Mat img_hsv;
  cvtColor(img, img_hsv, CV_BGR2HSV);  //將RGB圖像轉換為HSV圖像


  Mat img_rgb;
  cvtColor(img_hsv, img_rgb, CV_HSV2BGR);   //將HSV圖像轉換為RGB圖像


  imshow("ori rgb", img);
  imshow("hsv", img_hsv);
  imshow("rgb", img_rgb);
  waitKey();
}

運行結果:

c340a6df3aa27b68a572682a543fb6e7.png

原圖

d4f6f1c445dc925c6cbb5d825c9e641c.png

HSV圖像

b0e38e0e323d24e4f33b8ba710b1d52d.png

將HSV圖像還原為RGB圖像,與原圖一致


04

使用C++自己實現HSV與RGB的互相轉換

為了加深上述轉換公式的理解,我們使用C++自己來實現轉換過程。

首先是RGB轉換為HSV的代碼:

void RGB2HSV(Mat img_rgb, Mat &img_hsv)
{
  img_hsv = Mat::zeros(img_rgb.size(), CV_8UC3);


  for (int i = 0; i < img_rgb.rows; i++)
  {
    Vec3b *p0 = img_rgb.ptr<Vec3b>(i);   //B--p[0]  G--p[1]  R--p[2]
    Vec3b *p1 = img_hsv.ptr<Vec3b>(i);   //B--p[0]  G--p[1]  R--p[2]


    for (int j = 0; j < img_rgb.cols; j++)
    {
      float B = p0[j][0] / 255.0;
      float G = p0[j][1] / 255.0;
      float R = p0[j][2] / 255.0;
      
      float V = (float)std::max({ B, G, R });     //B/G/R
      float vmin = (float)std::min({ B, G, R });
      float diff = V - vmin;


      float S, H;
      S = diff / (float)(fabs(V) + FLT_EPSILON);
      diff = (float)(60.0 / (diff + FLT_EPSILON));


      if (V == B)   //V=B
      {
        H = 240.0 + (R - G) * diff;
      }
      else if (V == G)  //V=G
      {
        H = 120.0 + (B - R) * diff;
      }
      else if (V == R)   //V=R
      {
        H = (G - B) * diff;
      }


      H = (H < 0.0) ? (H + 360.0) : H;


      p1[j][0] = (uchar)(H / 2);
      p1[j][1] = (uchar)(S * 255);
      p1[j][2] = (uchar)(V * 255);
    }
  }
}

首先是HSV轉換為RGB的代碼:

void HSV2BGR(Mat img_hsv, Mat &img_rgb)
{
  img_rgb = Mat::zeros(img_hsv.size(), CV_8UC3);


  for (int i = 0; i < img_rgb.rows; i++)
  {
    Vec3b *p0 = img_hsv.ptr<Vec3b>(i);   //B--p[0]  G--p[1]  R--p[2]
    Vec3b *p1 = img_rgb.ptr<Vec3b>(i);   //B--p[0]  G--p[1]  R--p[2]


    for (int j = 0; j < img_hsv.cols; j++)
    {
      float H = p0[j][0] * 2.0;
      float S = p0[j][1] / 255.0;
      float V = p0[j][2] / 255.0;
      
      float h = H / 60.0;            
      int i = floor(h);
      float f = h - i;         
      float p = V * (1 - S);
      float q = V * (1 - f * S);
      float t = V * (1 - (1 - f) * S);


      switch (i)
      {
      case 0:
        p1[j][2] = (uchar)(V * 255);
        p1[j][1] = (uchar)(t * 255);
        p1[j][0] = (uchar)(p * 255);
        break;
      case 1:
        p1[j][2] = (uchar)(q * 255);
        p1[j][1] = (uchar)(V * 255);
        p1[j][0] = (uchar)(p * 255);
        break;
      case 2:
        p1[j][2] = (uchar)(p * 255);
        p1[j][1] = (uchar)(V * 255);
        p1[j][0] = (uchar)(t * 255);
        break;
      case 3:
        p1[j][2] = (uchar)(p * 255);
        p1[j][1] = (uchar)(q * 255);
        p1[j][0] = (uchar)(V * 255);
        break;
      case 4:
        p1[j][2] = (uchar)(t * 255);
        p1[j][1] = (uchar)(p * 255);
        p1[j][0] = (uchar)(V * 255);
        break;
      default:
        p1[j][2] = (uchar)(V * 255);
        p1[j][1] = (uchar)(p * 255);
        p1[j][0] = (uchar)(q * 255);
        break;
      }
    }
  }
}

測試代碼:

void rgb_hsv(void)
{
  Mat img = imread("000000000902.bmp", CV_LOAD_IMAGE_COLOR);


  Mat img_hsv;
  //cvtColor(img, img_hsv, CV_BGR2HSV);
  RGB2HSV(img, img_hsv);


  Mat img_rgb;
  //cvtColor(img_hsv, img_rgb, CV_HSV2BGR);
  HSV2BGR(img_hsv, img_rgb);


  imshow("ori rgb", img);
  imshow("hsv", img_hsv);
  imshow("rgb", img_rgb);
  waitKey();
}

運行結果如下,可以看到,轉換結果跟調用Opencv函數的結果是一致的。

40785c627bcdfbb43c9ed50c4e92bbbb.png

HSV圖像

44bddb402fb0f42a3c407e286321cd7a.png

將HSV圖像還原為RGB圖像,與原圖一致

歡迎掃碼關注本微信公眾號,接下來會不定時更新更加精彩的內容,敬請期待~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM