Task03 Opencv框架实现色彩空间转换
一、前言
说到色彩,我不禁想起白居易的一首诗,也是自己非常喜欢的一首诗:
暮
江
吟
暮江吟
暮江吟
一
道
残
阳
铺
水
中
,
半
江
瑟
瑟
半
江
红
。
一道残阳铺水中,半江瑟瑟半江红。
一道残阳铺水中,半江瑟瑟半江红。
可
怜
九
月
初
三
夜
,
露
似
真
珠
月
似
弓
。
可怜九月初三夜,露似真珠月似弓。
可怜九月初三夜,露似真珠月似弓。这首诗营造了这样一种意象:傍晚的太阳快要落山,柔和的晚霞照射到江面,江水在夕阳的照射下,一半碧绿,一半艳红,可爱的是九月初三的夜晚,江岸两边青草和树叶上的白露好像晶莹的珍珠,一轮弯月像张开的弓箭静静的挂在天边,在这傍晚和月夜交替中,就好像身临其境亲自感受这一片宁静安逸的多彩生活,多么美妙的颜色啊!
言归正传,色彩对于我们的生活如此重要,于是,从古至今,无论是诗人、文学家、画家还是艺术家等等都对自然界的颜色倾注了自己大量的精力,创造了一个多彩的艺术世界。从研究层面上来说,人和其他动物感知物体颜色基本上是由物体反射光的性质决定,比如说,自然光下人眼看到绿色的树叶,是因为树叶吸收自然光除绿色的部分而反射绿色的缘故!虽然人的大脑对颜色的感知和理解过程(生理和心理作用)还没有被完全的研究透彻!但是,随着计算机科学的发展,在过去的几十年时间里色彩已经成功的应用到了数字图像的世界中去,用颜色去传输和表达我们的生活和情感,下面我就搜集的一些资料和阅览的大佬博客和大家分享自己学习图像颜色和图像转换的一些知识。
二、几种常用彩色模型
2.1彩色空间概念
彩色模型也叫彩色空间、彩色系统,用多个相互独立的色彩属性(三原色、亮度、对比度、饱和度等)联合表达色彩的坐标模型,常用的彩色模型有RGB、YUV、CMYK、HSV等。
现有的彩色模型不是面向硬件的(彩色电视、打印机),就是面向应用的。在数字图像处理中,最通用的面向硬件的模型是RGB(红、绿、蓝)模型,主要用于彩色摄像机和监视器等,CMY(青、粉红、黄)和CMYK(青、粉红、黄、黑)主要是面向彩色打印机的,HSI(色调、饱和度、亮度)主要应用工业图像采集设备,
2.2RGB
RGB模型也称为加色法混色模型。它是以R、G、B三原色光互相叠加来实现混色的方法,适合于显示器等发光体的显示。人类视觉系统能感知的颜色都可以用红、绿、蓝三种基色光按照不同的比例混合,例如:白色=100%红色+100%绿色+100%蓝色;黄色=100%红色+100%绿色+0%蓝色等。该模型基于三维笛卡尔坐标系,具体描述如图:
从黑色(0,0,0)到白色(1,1,1),若沿三维立方体对角线取值,可得到灰度级色彩,其RGB三色值相等。
在RGB彩色模型中表示的图像由R、G、B三个8比特分量图像构成,三幅图像在屏幕上混合生成一幅合成的24比特彩色图像,也即是全彩色图像,与上图对应的24位彩色立方图可表示为:
2.3CMYK
CMYK是最常用于印刷的颜色模型。也称为“四色处理”,它代表青色,洋红色,黄色和键(或黑色),并且是减色颜色模型,而不是像RGB这样的加色颜色模型。其色彩空间构成为:
- Cyan青:范围0-100%(大多数应用中)
- Magenta品红:范围0-100%(大多数应用中)
- Yellow黄:范围0-100%(大多数应用中)
- blacK黑:范围0-100%(大多数应用中)
模型图表示如下:
与RGB颜色模型一样,CMYK是原色(青色,品红色,黄色和黑色)的组合,这可能是他们唯一的共同点。CMYK能呈现的颜色少,向其他模型转换时会存在颜色差异,但是为什么要用这种模型?为什么要使用黑色?这是为了实用目的,Wikipedia上说:
- 为了提高打印质量、减少莫尔条纹,文本通常以黑色打印,还有些细节(如衬线);如果使用三种墨水印刷文本,就需要对三层颜色的图像进行极其精确的对齐。
- 青色、品红色和黄色颜料相组合,很难产生纯黑色。
- 使用黑色墨(而不是使用三色组合),可以显着节省成本,黑色墨水通常更便宜。
2.4YUV
YUV色彩模型是一种主要用于电视系统以及模拟视频领域的颜色编码方法,Y表示亮度也就是灰度值,“U”和“V” 表示的则是色度,用来描述影像色彩及饱和度,用于指定像素的颜色。亮度信息Y与色度信息U、V相互独立。没有UV通道,可只显示Y通道的灰度分量图。
从RGB色彩模型到YUV色彩模型存在以下简单的转换关系:
反之,从YUV到RGB的转换关系如下:
2.5YCbCr
YCbCr和YUV一样是一种用于电视系统编码的颜色模型,使用较少。在Wiki上的定义为:
YCbCr或Y’CbCr是色彩空间的一种,通常会用于影片中的影像连续处理,或是数字摄影系统中。Y’和Y是不同的,Y就是所谓的流明(luminance),表示光的浓度且为非线性。Y’为颜色的亮度(luma)成分、而Cb和Cr则为蓝色和红色的浓度偏移量成分。
YCbCr不是一种绝对色彩空间,是YUV压缩和偏移的版本。YCbCr的Y与YUV中的Y含义一致,Cb和Cr与UV同样都指色彩,Cb指蓝色色度(RGB输入信号蓝色部分与RGB信号亮度值之同的差异),Cr指红色色度(RGB输入信号红色部分与RGB信号亮度值之间的差异),在应用上很广泛,JPEG、MPEG、DVD、摄影机、数字电视等皆采此一格式。因此一般俗称的YUV大多是指YCbCr。
2.6HSV
HSV颜色空间是一种用色调(H)、饱和度(S)、亮度(V)联合表示的颜色模型。其色彩空间由3部分组成:
- Hue(色调): 颜色种类,如红、蓝、黄,范围0-360° ,每个值对应着一种颜色。
- Saturation(饱和度): 颜色的强或是颜色的丰满程度,范围0-100%,0表示没有颜色,100表示强烈的颜色,降低饱和度其实就是在颜色中增加灰色的分量。
- Value(亮度值): 颜色的亮度,范围0-100%,0总是黑色, 100时根据饱和度,可能为白色或饱和度更低的颜色。
它是一种比价直观的颜色模型,在许多图像编辑工具中应用比较广泛,如Photoshop。但不适合使用在光照模型,许多光线混合运算、光强运算等都无法直接使用HSV来实现。
由于H、S分量代表了色彩分信息,不同HS值在表示颜色时有较大差异,所以该模型可用于颜色分割。
模型可表示为:
2.7HSI
2.8Lab
2.9色彩变换公式
彩色变换通用公式可表示为:
g
(
x
,
y
)
=
T
(
f
(
x
,
y
)
)
g(x,y)=T(f(x,y))
g(x,y)=T(f(x,y))
其中,f(x,y)表示输入彩色图像,g(x,y)表示转换或处理后的输出彩色图像。T是在(x,y)空间域上对f的一个算子,所以与灰度变换类似,颜色变换本质上是一个单像素映射的过程。
三、基于OpenCV的代码实现
3.1cvtColor函数
在OpenCV中图像颜色空间的转换可通过cvtColor函数实现,具体的函数形式为:
void cv::cvtColor(cv::InputArray src, //原始图像
cv::OutputArray dst, //目标图像
int code, //转换标识码
int dstCn=0 //目标图像通道数,默认为0
)
常见颜色空间转换标识码(Code)如下所示:
- CV_BGR2RGB、CV_RGB2BGR(在RGB或BGR色彩空间之间转换)
- CV_RGB2GRAY、CV_GRAY2RGB(RGB色彩空间与灰度空间之间转换)
- CV_RGB2YUV、CV_YUV2RGB(RGB色彩空间与YUV空间之间转换)
- CV_RGB2YCrCb、CV_YCrCb2RGB(RGB色彩空间与YCrCb空间之间转换)
- CV_RGB2HSV、CV_HSV2RGB(RGB色彩空间与HSV空间之间转换)
- CV_RGB2Lab、CV_Lab2RGB(RGB色彩空间与CIE LAB空间之间转换
- CV_RGB2Luv、CV_Luv2RGB(RGB色彩空间与CIE Luv空间之间转换)
- CV_RGB2HLS、CV_HLS2RGB(RGB色彩空间与HLS空间之间转换)
C++代码实现:
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>
// 宏定义
using namespace std;
using namespace cv;
int main()
{
Mat srcImage=imread("C:/Users/Administrator/Desktop/beauty.jpg");
if(srcImage.empty())
{
printf("Load failture...");
return -1;
}
Size dsize=Size(round(srcImage.cols*0.2),round(srcImage.rows*0.2));
resize(srcImage,srcImage,dsize,0,0,INTER_LINEAR); //缩小一下
Mat dst_rgb2gray,dst_rgb2yuv,dst_rgb2hsv,dst_rgb2lab,dst_rgb2ycrcb;
Mat dst_rgb2hsv_full;
cvtColor(srcImage,dst_rgb2gray,CV_RGB2GRAY);
cvtColor(srcImage,dst_rgb2yuv,CV_RGB2YUV);
cvtColor(srcImage,dst_rgb2hsv,CV_RGB2HSV);
cvtColor(srcImage,dst_rgb2lab,CV_RGB2Lab);
cvtColor(srcImage,dst_rgb2ycrcb,CV_RGB2YCrCb);
imshow("srcImage",srcImage);
imshow("rgb2gray",dst_rgb2gray);
imshow("rgb2yuv",dst_rgb2yuv);
imshow("rgb2hsv",dst_rgb2hsv);
imshow("rgb2lab",dst_rgb2lab);
imshow("rgb2ycrcb",dst_rgb2ycrcb);
waitKey(0);
return 0;
}
效果图展示:
3.2单像素映射实现RGB2HSV
RGB到HSV颜色空间转换公式如下图所示:
C++代码实现:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat RGB2HSV(Mat& src) {
int row = src.rows;
int col = src.cols;
Mat dst(row, col, CV_32FC3); //Mat dst(row, col, src.type())
//遍历图像所有像素点
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
//归一化处理
float b = src.at<Vec3b>(i, j)[0] / 255.0;
float g = src.at<Vec3b>(i, j)[1] / 255.0;
float r = src.at<Vec3b>(i, j)[2] / 255.0;
//计算通道最大、最小值
float minn = min(r, min(g, b));
float maxx = max(r, max(g, b));
dst.at<Vec3f>(i, j)[2] = maxx; // V通道
float delta = maxx - minn;
float h, s;
if (maxx != 0) {
s = delta / maxx;
}
else {
s = 0;
}
if (r == maxx) {
h = (g - b) / delta;
}
else if (g == maxx) {
h = 2 + (b - r) / delta;
}
else if (b==maxx) {
h = 4 + (r - g) / delta;
}
else{
h = 0;
}
h *= 60;
if (h < 0)
h += 360;
dst.at<Vec3f>(i, j)[0] = h; // H通道
dst.at<Vec3f>(i, j)[1] = s; // S通道
}
}
return dst;
}
int main(){
cv::Mat src = cv::imread("C:/Users/Administrator/Desktop/beauty.jpg");
if (src.empty()){
return -1;
}
cv::Mat dst, dst1, dst2;
Size dsize=Size(round(0.3*src.cols),round(0.3*src.rows));
resize(src,src,dsize,0,0,INTER_LINEAR); //缩放一下
//opencv:CV_RGB2HSV实现转换
cv::cvtColor(src, dst1, CV_RGB2HSV); //RGB2HSV
//单像素操作实现RGB2HSV
dst = RGB2HSV(src); //RGB2HSV
cv::namedWindow("srcImage", CV_WINDOW_AUTOSIZE);
imshow("srcImage", src);
cv::namedWindow("RGB2HSV", CV_WINDOW_AUTOSIZE);
imshow("RGB2HSV", dst);
cv::namedWindow("Opencv_RGB2HSV", CV_WINDOW_AUTOSIZE);
imshow("Opencv_RGB2HSV", dst1);
cv::waitKey(0);
return 0;
}
显示效果:
分析一下:OpenCV自带的转换函数,更偏向低(红色)Hue值,而单像素操作的色调整体上要均匀些,说明自带函数后台实现方式还是有很大差别的。
上面的分析有误,不是OpenCV自带的函数底层代码实现的差异,具体看下图解释:
OpenCV中cvtColor(InputArray src, OutputArray dst,int code)函数有两种code参数实现RGB图像到HSV图像的转换:
- CV_RGB2HSV(H通道值范围0-180)
- CV_RGB2HSV_FULL(H通道值范围0-360)
只有使用code=CV_RGB2HSV_FULL时,才能实现RGB图像到HSV图像色调全范围(0-360)映射!
3.3位运算实现RGB2YUV
由于在计算机中,乘法运算相比于移位运算的效率太低,因此,可以将把所有乘法、除法都改成移位运算。如何将常数乘法改成移位运算?这里给个例子:Y=Y*11可以改为:Y=(Y<<3)+(Y<<1)+Y。 那么我们就可以将任何一种颜色转换公式首先转换成int型整数相乘,然后分解为位移运算去实现,以RGB2YUV为例。
1.转换公式为:
Y
=
0.299
R
+
0.587
G
+
0.114
B
Y = 0.299R + 0.587G + 0.114B
Y=0.299R+0.587G+0.114B
U
=
−
0.147
R
−
0.289
G
+
0.436
B
U = -0.147R - 0.289G + 0.436B
U=−0.147R−0.289G+0.436B
V
=
0.615
R
−
0.515
G
−
0.100
B
V = 0.615R - 0.515G - 0.100B
V=0.615R−0.515G−0.100B2.扩展颜色区间:两边同乘以256
256
Y
=
76.544
R
+
150.272
G
+
29.184
B
256Y = 76.544R + 150.272G + 29.184B
256Y=76.544R+150.272G+29.184B
256
U
=
−
37.632
R
−
73.984
G
+
111.616
B
256U = -37.632R - 73.984G + 111.616B
256U=−37.632R−73.984G+111.616B
256
V
=
157.44
R
−
131.84
G
−
25.6
B
256V = 157.44R - 131.84G - 25.6B
256V=157.44R−131.84G−25.6B3.四舍五入:浮点运算转整形运算(有精度损失)
256
Y
=
77
R
+
150
G
+
29
B
256Y = 77R + 150G + 29B
256Y=77R+150G+29B
256 U = − 38 R − 74 G + 112 B 256U = -38R - 74G + 112B 256U=−38R−74G+112B
256 V = 158 R − 132 G − 26 B 256V = 158R - 132G - 26B 256V=158R−132G−26B
4.归一化处理:两边同除以256,即右移8位(>>8),可得YUV通道值:
Y
=
(
77
R
+
150
G
+
29
B
)
>
>
8
Y = (77R + 150G + 29B) >> 8
Y=(77R+150G+29B)>>8
U = ( − 38 R − 74 G + 112 B ) > > 8 U = (-38R - 74G + 112B) >> 8 U=(−38R−74G+112B)>>8
V = ( 158 R − 132 G − 26 B ) > > 8 V = (158R - 132G - 26B) >> 8 V=(158R−132G−26B)>>8
5.将上式乘法运算全部转化为位移运算,得
Y
=
(
(
R
<
<
6
)
+
(
R
<
<
3
)
+
(
R
<
<
2
)
+
R
+
(
G
<
<
7
)
+
(
G
<
<
4
)
+
(
G
<
<
2
)
+
(
G
<
<
1
)
+
(
B
<
<
4
)
+
(
B
<
<
3
)
+
(
B
<
<
2
)
+
B
)
>
>
8
Y = ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8
Y=((R<<6)+(R<<3)+(R<<2)+R+(G<<7)+(G<<4)+(G<<2)+(G<<1)+(B<<4)+(B<<3)+(B<<2)+B)>>8
U = ( − ( ( R < < 5 ) + ( R < < 2 ) + ( R < < 1 ) ) − ( ( G < < 6 ) + ( G < < 3 ) + ( G < < 1 ) ) + ( ( B < < 6 ) + ( B < < 5 ) + ( B < < 4 ) ) ) > > 8 U = (-((R << 5) + (R << 2) + (R << 1)) - ((G << 6) + (G << 3) + (G << 1)) + ((B << 6) + (B << 5) + (B << 4))) >> 8 U=(−((R<<5)+(R<<2)+(R<<1))−((G<<6)+(G<<3)+(G<<1))+((B<<6)+(B<<5)+(B<<4)))>>8
V = ( ( R < < 7 ) + ( R < < 4 ) + ( R < < 3 ) + ( R < < 2 ) + ( R < < 1 ) − ( ( G < < 7 ) + ( G < < 2 ) ) − ( ( B < < 4 ) + ( B < < 3 ) + ( B < < 1 ) ) ) > > 8 V = ((R << 7) + (R << 4) + (R << 3) + (R << 2) + (R << 1) - ((G << 7) + (G << 2)) - ((B << 4) + (B << 3) + (B << 1))) >> 8 V=((R<<7)+(R<<4)+(R<<3)+(R<<2)+(R<<1)−((G<<7)+(G<<2))−((B<<4)+(B<<3)+(B<<1)))>>8
C++代码实现RGB2YUV:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
//位运算实现RGB2YUV
Mat rgb2yuv(Mat& src){
Mat dst(src.rows,src.cols, CV_8UC3); //初始化dst矩阵
//Mat dst(src.size(),src.type());
for(int i=0;i<src.rows;i++){
for(int j=0;j<src.cols;j++){
dst.at<Vec3b>(i,j)[0]=( (src.at<Vec3b>(i,j)[2] <<6 ) + (src.at<Vec3b>(i,j)[2] <<3 ) + (src.at<Vec3b>(i, j)[2] << 2) + src.at<Vec3b>(i, j)[2] +
(src.at<Vec3b>(i, j)[1] << 7) + (src.at<Vec3b>(i, j)[1] << 4) + (src.at<Vec3b>(i, j)[1] << 2) + (src.at<Vec3b>(i, j)[1] << 1) +
(src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 3) + (src.at<Vec3b>(i, j)[0] << 2) + src.at<Vec3b>(i, j)[0]) >> 8;
dst.at<Vec3b>(i,j)[1]=(-((src.at<Vec3b>(i, j)[2] << 5) + (src.at<Vec3b>(i, j)[2] << 2) + (src.at<Vec3b>(i, j)[2] << 1)) -
((src.at<Vec3b>(i, j)[1] << 6) + (src.at<Vec3b>(i, j)[1] << 3) + (src.at<Vec3b>(i, j)[1] << 1)) +
((src.at<Vec3b>(i, j)[0] << 6) + (src.at<Vec3b>(i, j)[0] << 5) + (src.at<Vec3b>(i, j)[0] << 4))) >> 8;
dst.at<Vec3b>(i,j)[2]=( (src.at<Vec3b>(i, j)[2] << 7) + (src.at<Vec3b>(i, j)[2] << 4) + (src.at<Vec3b>(i, j)[2] << 3) + (src.at<Vec3b>(i, j)[2] << 2) + (src.at<Vec3b>(i, j)[2] << 1) -
((src.at<Vec3b>(i, j)[1] << 7) + (src.at<Vec3b>(i, j)[1] << 2)) -
((src.at<Vec3b>(i, j)[0] << 4) + (src.at<Vec3b>(i, j)[0] << 3) + (src.at<Vec3b>(i, j)[0] << 1))) >> 8;
}
}
return dst;
}
int main()
{
Mat srcImage=imread("C:/Users/Administrator/Desktop/beauty.jpg");
if(srcImage.empty())
{
printf("Load failture...");
return -1;
}
Size dsize=Size(round(srcImage.cols*0.2),round(srcImage.rows*0.2));
resize(srcImage,srcImage,dsize,0,0);
Mat dstImage,dstImage1;
//位运算实现RGB2YUV
double t1_beg=(double)getTickCount();
dstImage=rgb2yuv(srcImage);
double t1_end=(double)getTickCount();
double time1=((t1_end-t1_beg)*1000)/((double)getTickFrequency());
cout<<"bit_RGB2YUV time: "<<time1<<"ms"<<endl;
//OpenCV函数实现RGB2YUV
double t2_beg=(double)getTickCount();
cvtColor(srcImage,dstImage1,CV_RGB2YUV);
double t2_end=(double)getTickCount();
double time2=((t2_end-t2_beg)*1000)/((double)getTickFrequency());
cout<<"OpenCV_RGB2YUV time: "<<time2<<"ms"<<endl;
imshow("srcImage",srcImage);
imshow("bit_RGB2YUV",dstImage);
imshow("opencv_RGB2YUV",dstImage1);
cvWaitKey(0);
return 0;
}
实现效果:
运行时间:
位运算:21.575ms
OpenCV自带函数:0.929ms
时间和效果上还是有一些差距的,后续再调试看看效果。
参考博客:
【1】常见颜色模型介绍
【2】YUV色彩模型与RGB色彩模型详解
【3】OpenCV图像处理专栏一 | 盘点常见颜色空间互转