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图像,与原图一致

欢迎扫码关注本微信公众号,接下来会不定时更新更加精彩的内容,敬请期待~

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐