前言
最近几个礼拜都在忙甄试资料,所以没甚么时间发文章,今日终于解脱所以发表一篇文章庆祝,废话不多说赶紧切入正题了。
色彩空间转换在影像处理当中佔了相当重要的位置,在我们使用亮度、色度等等有时候都会需要做色彩空间转换计算,因使用其它色彩空间可方便处理或压缩色彩加快达到目标。
这次介绍灰阶、HSV与YCbCr,三种色彩空间,主要使用实作方式让读者能更加了解特性。
前置作业
延伸上一篇程式码,将Write移到general,在色彩空间转换直接使用一维阵列处理,除了不用转为二维阵列之外,主要是因为她都是在处理一个像素,未来会讲到Block则用二维阵列可读性会较高。
1.建立一个全域的namespace让所有的cpp都能够呼叫使用,并将要宣告的别名都实现在general。
注:简易解说extern,主要用来让有include此标头档的文件能呼叫做使用。
标头档:general.h
#pragma once#ifndef GENERAL_H#define GENERAL_H#include <fstream>#include <string>#include <algorithm>typedef unsigned char UCHAE;typedef const unsigned char C_UCHAE;typedef const char C_CHAE;typedef unsigned __int32 UINT32;typedef const unsigned __int32 C_UINT32;typedef const __int32 C_INT32;typedef const double C_DOUBLE;namespace MNDT {extern void Write(C_UCHAE* str);extern char* LOG_FILE;}#endif // !GENERAL_H
实作档:general.cpp
#include "general.h"extern void MNDT::Write(C_UCHAE* str){std::fstream fwLog;fwLog.open(MNDT::LOG_FILE, std::ios::app);fwLog << str;fwLog.close();}extern char* MNDT::LOG_FILE = "D:\\Log.txt";
2.建立一个Library.h标头档和Library.cpp实作档,建立一个class名为Library,宣告一个色彩空间转换函数ChangeColor和一个清单ColerType。
ChangeColor参数:
标头档:Library.h
#pragma once#ifndef LIBRARY_H#define LIBRARY_H#include "general.h"enum ColerType{BGR2GRAY_8BIT,BGR2HSV,HSV2BGR,BGR2YCbCr,YCbCr2BGR,};class Library{public:Library();~Library(); /*ChangeColor Parameter:src= source of imagepur= purpose of imagewidth= Image's widthheight= Image's heighttype= change type*/void ChangeColor(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_UINT32 type);private:};#endif // !LIBRARY_H
实作档:Library.cpp
#include "Library.h"Library::Library(){}Library::~Library(){}void Library::ChangeColor(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_UINT32 type){switch (type){}}
灰阶影像
灰阶主要将RGB三个通道转为一个通道,主要使用YCbCr的Y(亮度)来做处理,为了加快运算所以将小数点转为整数,将现在数值乘上2^16次方,计算完后再使用位移这样就能较快速计算。
公式为[1]:
Y = R * 0.299 + G * 0.587 + B * 0.114。
步骤
建立函数BGR2Gray8Bit在Library.h和Library.cpp。ColorType加入BGR2GRAY_8BIT。标头档:加入Library.h的private
void BGR2Gray8Bit(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height);
实作档:函数加入Library.cpp、ChangeColor
ChangeColor:case ColerType::BGR2GRAY_8BIT:BGR2Gray8Bit(src, pur, width, height);break;
BGR2Gray8Bit:// 灰阶// R 0.299 ≈ 19595// G 0.587 ≈ 38469// B 0.114 ≈ 7472 (进位 反推回去比7471接近)// 为了快速运算(整数运算+位移) 先将数值左位移16次幂 65536void Library::BGR2Gray8Bit(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height){C_UCHAE* purEnd = pur + width * height + 1;while (pur < purEnd){C_UINT32 B = *src;src++;C_UINT32 G = *src;src++;C_UINT32 R = *src;src++;C_UCHAE pix = static_cast<UCHAE>((R * 19595 + G * 38469 + B * 7472) >> 16);*pur = pix;pur++;}}
YCbCr影像
[2]提到Y为亮度,Cb和Cr为色度,YCbCr还有许多用途若有兴趣可以前往维基百科观看一些用途,这里主要实作公式为主,这里也是为了加快速度先乘上2^8在位移回去。
公式为[2]:
Y = (66 * R + 129 * G + 25 * B + 128) >> 8 + 16。
Cb = (-38 * R - 74 * G + 112 * B + 128) >> 8 + 128。
Cr = (112 * R - 94 * G - 18 * B + 128) >> 8 + 128。YCbCr转RBG
R = (298 * (Y - 16) + 409 * (Cr - 128) + 128) >> 8
G = (298 * (Y - 16) - 100 * (Cb - 128) - 208 * (Cr - 128) + 128) >> 8
B = (298 * (Y - 16) + 516 * (Cr - 128) + 128) >> 8
因为后面会应用在图片上做为亮度与色度可调性,所以将BGR转YCbCr和YCbCr转BGR的单像素抓出来做函数。(应用为BGR转YCbCr调整再转回BGR所以拉出来会减少撰写程式的多寡)
步骤
标头档:加入Library.h的private
void BGR2YCbCr(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height);void SetBGR2YCbCr(int32_t* const ycbcr, C_INT32& B, C_INT32& G, C_INT32& R);void YCbCr2BGR(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height);void SetYCbCr2BGR(int32_t* const bgr, C_INT32& Y, C_INT32& Cb, C_INT32& Cr);
实作档:函数加入Library.cpp、ChangeColor
ChangeColor:case ColerType::BGR2YCbCr:BGR2YCbCr(src, pur, width, height);break;case ColerType::YCbCr2BGR:YCbCr2BGR(src, pur, width, height);break;
BGR2YCbCr、SetBGR2YCbCr:// BGR转YCbCr// Y = (66 * R + 129 * G + 25 * B + 128) >> 8 + 16// Cb = (-38 * R - 74 * G + 112 * B + 128) >> 8 + 128// Cr = (112 * R - 94 * G - 18 * B + 128) >> 8 + 128// 为了快速运算(整数运算+位移) 先将数值左位移16次幂 65536void Library::BGR2YCbCr(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height){C_UCHAE* purEnd = pur + width * 3 * height + 1;while (pur < purEnd){C_INT32 B = *src;src++;C_INT32 G = *src;src++;C_INT32 R = *src;src++;int32_t ycbcr[3];SetBGR2YCbCr(ycbcr, B, G, R);ycbcr[0] = (ycbcr[0] < 0 ? 0 : (ycbcr[0] > 255 ? 255 : ycbcr[0]));ycbcr[1] = (ycbcr[1] < 0 ? 0 : (ycbcr[1] > 255 ? 255 : ycbcr[1]));ycbcr[2] = (ycbcr[2] < 0 ? 0 : (ycbcr[2] > 255 ? 255 : ycbcr[2]));*pur = static_cast<UCHAE>(ycbcr[0]);pur++;*pur = static_cast<UCHAE>(ycbcr[1]);pur++;*pur = static_cast<UCHAE>(ycbcr[2]);pur++;}}void Library::SetBGR2YCbCr(int32_t* const ycbcr, C_INT32& B, C_INT32& G, C_INT32& R){*ycbcr = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;*(ycbcr + 1) = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;*(ycbcr + 2) = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;}
YCbCr2BGR、SetYCbCr2BGR:// YCbCr转BGR// R = (298 * (Y - 16) + 409 * (Cr - 128) + 128) >> 8// G = (298 * (Y - 16) - 100 * (Cb - 128) - 208 * (Cr - 128) + 128) >> 8// B = (298 * (Y - 16) + 516 * (Cr - 128) + 128) >> 8// 为了快速运算(整数运算+位移) 先将数值左位移16次幂 65536void Library::YCbCr2BGR(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height){C_UCHAE* purEnd = pur + width * 3 * height + 1;while (pur < purEnd){C_INT32 Y = *src;src++;C_INT32 Cb = *src;src++;C_INT32 Cr = *src;src++;int32_t bgr[3];SetYCbCr2BGR(bgr, Y, Cb, Cr);bgr[0] = (bgr[0] < 0 ? 0 : (bgr[0] > 255 ? 255 : bgr[0]));bgr[1] = (bgr[1] < 0 ? 0 : (bgr[1] > 255 ? 255 : bgr[1]));bgr[2] = (bgr[2] < 0 ? 0 : (bgr[2] > 255 ? 255 : bgr[2]));*pur = static_cast<UCHAE>(bgr[0]);pur++;*pur = static_cast<UCHAE>(bgr[1]);pur++;*pur = static_cast<UCHAE>(bgr[2]);pur++;}}void Library::SetYCbCr2BGR(int32_t* const bgr, C_INT32& Y, C_INT32& Cb, C_INT32& Cr){C_INT32 fixY = Y - 16;C_INT32 fixCb = Cb - 128;C_INT32 fixCr = Cr - 128;*bgr = (298 * fixY + 516 * fixCb + 128) >> 8;*(bgr + 1) = (298 * fixY - 100 * fixCb - 208 * fixCr + 128) >> 8;*(bgr + 2) = (298 * fixY + 409 * fixCr + 128) >> 8;}
HSV
HSV为一个360度色彩空间,[3]解释为H为色相、S为饱和度、V为明度,详细介绍至[3]观看。HSV公式较上面两个多,这里会推倒H转回去RGB公式。
HSV公式为:
RGB转HSV
MAX(R, G, B) MIN(R, G, B)
300° ~ 60° R 中间值 0°
60° ~ 180° G 中间值 120°
180° ~ 300° B 中间值 240°
H = 0, S = 0 条件:MAX == MIN
H 公式
60° * (G - B) / (MAX - MIN) 条件:if MAX == R && G >= B
60° * (G - B) / (MAX - MIN) + 360° 条件:if MAX == R && G < B
60° * (B - R) / (MAX - MIN) + 120° 条件:if MAX == G
60° * (R - G) / (MAX - MIN) + 240° 条件:if MAX == B
S 公式
(MAX - MIN) / MAX * 100(转百分比整数)
V 公式
MAX / 255 * 100(转百分比整数)
HSV转RGB
H = H * 2.0; // 修正到原先H
S = S / 255.0; // 修正到原先S
offset = H % 60° // 取得不满60度的剩余角度
MAX = V
MIN = V * (1 - S)
q = -(offset - 60°) * (V - MIN) / 60° + MIN;
t = offset * (V - MIN) / 60° + MIN;
position = H / 60 // 取得目前在六等分的哪个位置
R = V, G = t, B = MIN 条件:if position == 0
R = q, G = V, B = MIN 条件:if position == 1
R = MIN, G = V, B = t 条件:if position == 2
R = MIN, G = q, B = V 条件:if position == 3
R = t, G = MIN, B = V 条件:if position == 4
R = V, G = MIN, B = q 条件:if position == 5
H转回RGB推导
1.将H所有可能先列出来(6个角度区域),直接带入原先H公式并列出角度区域。
offset = (g - min) / (max - min) * 60 0~60
offset = (r - min) / (max - min) * 60 240~300
offset = (b - min) / (max - min) * 60 120~180
// 原先计算出来是负数才会产生此角度,因此原本角度为-60。
offset - 60 = (min - r) / (max - min) * 60 60~120
offset - 60 = (min - g) / (max - min) * 60 180~240
offset - 60 = (min - b) / (max - min) * 60 270~360
2.由上述可知道求rgb知道公式分为两种,因此推导2个公式即可,最后依照角度区域放入即可。
offset = (g - MIN) / (MAX - MIN) * 60
offset * (MAX - MIN) / 60 = g - MIN
offset * (MAX - MIN) / 60 + MIN = g
第一个公式为offset * (MAX - MIN) + MIN
offset - 60 = (MIN - r) / (MAX - MIN) * 60
(offset - 60) * (MAX - MIN) / 60 = MIN - r
-(offset - 60) * (MAX - MIN) / 60 + MIN = r
第二个公式为-(offset - 60) * (MAX - MIN) / 60 + MIN
因为后面会应用在图片上做为亮度与色度可调性,所以将BGR转HSV和HSV转BGR的单像素抓出来做函数。(应用为BGR转HSV调整再转回BGR所以拉出来会减少撰写程式的多寡)
步骤
标头档:加入Library.h的private
void BGR2HSV(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height);void SetBGR2HSV(double* const hsv, C_DOUBLE& B, C_DOUBLE& G, C_DOUBLE& R);void HSV2BGR(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height);void SetHSV2BGR(double* const bgr, C_DOUBLE& H, C_DOUBLE& S, C_DOUBLE& V);
实作档:函数加入Library.cpp、ChangeColor
ChangeColor: case ColerType::BGR2HSV:BGR2HSV(src, pur, width, height);break;case ColerType::HSV2BGR:HSV2BGR(src, pur, width, height);break;
BGR2HSV、SetBGR2HSVvoid Library::BGR2HSV(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height){C_UCHAE* purEnd = pur + width * 3 * height + 1;while (pur < purEnd){C_DOUBLE B = *src;src++;C_DOUBLE G = *src;src++;C_DOUBLE R = *src;src++;double hsv[3];SetBGR2HSV(hsv, B, G, R);*pur = static_cast<UCHAE>(hsv[0]);pur++;*pur = static_cast<UCHAE>(hsv[1]);pur++;*pur = static_cast<UCHAE>(hsv[2]);pur++;}}void Library::SetBGR2HSV(double* const hsv, C_DOUBLE& B, C_DOUBLE& G, C_DOUBLE& R){C_DOUBLE max = std::max(std::max(R, G), B);C_DOUBLE min = std::min(std::min(R, G), B);*(hsv + 2) = max;C_DOUBLE difference = max - min;if (max != min){if (max == R && G >= B){*hsv = (G - B) / difference;}else if (max == R && G < B){*hsv = (G - B) / difference + 6.0;}else if (max == G){*hsv = (B - R) / difference + 2.0;}else if (max == B){*hsv = (R - G) / difference + 4.0;}// 配合RGB正规化H和S*hsv *= 30.0;*(hsv + 1) = max == 0 ? 0 : (difference / max) * 255.0;}}
HSV2BGR、SetHSV2BGRvoid Library::HSV2BGR(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height){C_UCHAE* purEnd = pur + width * 3 * height + 1;while (pur < purEnd){C_DOUBLE H = static_cast<double>(*src);src++;C_DOUBLE S = static_cast<double>(*src);src++;C_DOUBLE V = static_cast<double>(*src);src++;double bgr[3];SetHSV2BGR(bgr, H, S, V);*pur = static_cast<UCHAE>(bgr[0]);pur++;*pur = static_cast<UCHAE>(bgr[1]);pur++;*pur = static_cast<UCHAE>(bgr[2]);pur++;}}void Library::SetHSV2BGR(double* const bgr, C_DOUBLE& H, C_DOUBLE& S, C_DOUBLE& V){C_DOUBLE fixH = H * 2.0;C_DOUBLE fixS = S / 255.0;C_UINT32 position = static_cast<int32_t>(fixH) / 60;C_DOUBLE offset = static_cast<int32_t>(fixH) % 60;// s = (max - min) / max,求minC_DOUBLE min = V * (1.0 - fixS);// -(f - 1) * (max - min) + min = g; 120.240.360C_DOUBLE q = -(offset - 60.0) * (V - min) / 60.0 + min;// f * (max - min) + min = g; 60.180.300C_DOUBLE t = offset * (V - min) / 60.0 + min;double R = 0;double G = 0;double B = 0;switch (position){case 0:R = V;G = t;B = min;break;case 1:R = q;G = V;B = min;break;case 2:R = min;G = V;B = t;break;case 3:R = min;G = q;B = V;break;case 4:R = t;G = min;B = V;break;case 5:R = V;G = min;B = q;break;}*bgr = B;*(bgr + 1) = G;*(bgr + 2) = R;}
使用空间处理影像
接着使用上述两种空间去做处理,主要用%来去计算,原理为单纯计算离0或255的距离以%做简单转换,C#内预设50%。
建立函数AdjustmentHSV、AdjustmentYCbCr在Library.h和Library.cpp。
标头档:加入Library.h的public
/*AdjustmentHSV Parameter:src= source of imagepur= purpose of imageH= H's %S= S's %V= V's %*/void AdjustmentHSV(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_INT32 H, C_INT32 S, C_INT32 V);/*AdjustmentYCbCr Parameter:src= source of imagepur= purpose of imageY= Y's %Cb= Cb's %Cr= Cr's %*/void AdjustmentYCbCr(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_INT32 Y, C_INT32 Cb, C_INT32 Cr);
实作档:加入函数到Library.cpp。
AdjustmentHSVvoid Library::AdjustmentHSV(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_INT32 H, C_INT32 S, C_INT32 V){C_UCHAE* purEnd = pur + width * 3 * height + 1;while (pur < purEnd){C_DOUBLE B = static_cast<double>(*src);src++;C_DOUBLE G = static_cast<double>(*src);src++;C_DOUBLE R = static_cast<double>(*src);src++;double hsv[3];double bgr[3];SetBGR2HSV(hsv, B, G, R);if (H < 0){hsv[0] = hsv[0] * static_cast<double>(100 + H) / 100.0;}else{hsv[0] = hsv[0] + (180 - hsv[0]) * static_cast<double>(H) / 100.0;}if (S < 0){hsv[1] = hsv[1] * static_cast<double>(100.0 + S) / 100.0;}else{hsv[1] = hsv[1] + (255.0 - hsv[1]) * static_cast<double>(S) / 100.0;}if (V < 0){hsv[2] = hsv[2] * static_cast<double>(100.0 + V) / 100.0;}else{hsv[2] = hsv[2] + (255.0 - hsv[2]) * static_cast<double>(V) / 100.0;}SetHSV2BGR(bgr, hsv[0], hsv[1], hsv[2]);*pur = static_cast<UCHAE>(bgr[0]);pur++;*pur = static_cast<UCHAE>(bgr[1]);pur++;*pur = static_cast<UCHAE>(bgr[2]);pur++;}}
AdjustmentYCbCrvoid Library::AdjustmentYCbCr(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_INT32 Y, C_INT32 Cb, C_INT32 Cr){C_UCHAE* purEnd = pur + width * 3 * height + 1;while (pur < purEnd){C_INT32 B = *src;src++;C_INT32 G = *src;src++;C_INT32 R = *src;src++;int32_t ycbcr[3];int32_t bgr[3];SetBGR2YCbCr(ycbcr, B, G, R);if (Y < 0){ycbcr[0] = static_cast<int32_t>(ycbcr[0] * static_cast<double>(100 + Y) / 100.0);}else{ycbcr[0] = static_cast<int32_t>(ycbcr[0] + (255 - ycbcr[0]) * static_cast<double>(Y) / 100.0);}if (Cb < 0){ycbcr[1] = static_cast<int32_t>(ycbcr[1] * static_cast<double>(100 + Cb) / 100.0);}else{ycbcr[1] = static_cast<int32_t>(ycbcr[1] + (255 - ycbcr[1]) * static_cast<double>(Cb) / 100.0);}if (Cr < 0){ycbcr[2] = static_cast<int32_t>(ycbcr[2] * static_cast<double>(100.0 + Cr) / 100.0);}else{ycbcr[2] = static_cast<int32_t>(ycbcr[2] + (255.0 - ycbcr[2]) * static_cast<double>(Cr) / 100.0);}SetYCbCr2BGR(bgr, ycbcr[0], ycbcr[1], ycbcr[2]);bgr[0] = (bgr[0] < 0 ? 0 : (bgr[0] > 255 ? 255 : bgr[0]));bgr[1] = (bgr[1] < 0 ? 0 : (bgr[1] > 255 ? 255 : bgr[1]));bgr[2] = (bgr[2] < 0 ? 0 : (bgr[2] > 255 ? 255 : bgr[2]));*pur = static_cast<UCHAE>(bgr[0]);pur++;*pur = static_cast<UCHAE>(bgr[1]);pur++;*pur = static_cast<UCHAE>(bgr[2]);pur++;}}
可视化
接着主要将刚刚实现的原始码可视化,首先建立C++的接口,在到C#导入,这上一篇有讲解过,因程式码不好贴上来,所以这里以色彩转换函数为例子,最后有副上完整的程式码。
1.MNDTLibrary.cpp加入后编译dll。
extern "C" MNDTLIBRARY_API void mndtChangeColor(C_UCHAE* src, UCHAE* pur, C_UINT32 width, C_UINT32 height, C_UINT32 type){Library lib;lib.ChangeColor(src, pur, width, height, type);}
2.MNDTLibrary.cs的namespace内加入清单(与C++清单一样)。
public enum ColerType : int { BGR2GRAY_8BIT = 0, BGR2HSV = 1, HSV2BGR = 2, BGR2YCbCr = 3, YCbCr2BGR = 4 }
3.MNDTLibrary.cs的class加入函数,这里分两种一个为8bit一个为24bit使用函数。
[DllImport(DLL_PATH)] unsafe private static extern void mndtChangeColor(IntPtr src, IntPtr pur, int width, int height, int type); public Bitmap Change8BitColor(Bitmap srcImage, ColerType type) { Bitmap purImage = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format8bppIndexed); Rectangle size = new Rectangle(0, 0, srcImage.Width, srcImage.Height); BitmapData srcData = srcImage.LockBits(size, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); BitmapData purData = purImage.LockBits(size, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); unsafe { IntPtr srcPtr = srcData.Scan0; IntPtr purPtr = purData.Scan0; mndtChangeColor(srcPtr, purPtr, srcImage.Width, srcImage.Height, (int)type); } purImage.Palette = _colorPalette; srcImage.UnlockBits(srcData); purImage.UnlockBits(purData); return purImage; } public Bitmap ChangeColor(Bitmap srcImage, ColerType type) { Bitmap purImage = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format24bppRgb); Rectangle size = new Rectangle(0, 0, srcImage.Width, srcImage.Height); BitmapData srcData = srcImage.LockBits(size, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); BitmapData purData = purImage.LockBits(size, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); unsafe { IntPtr srcPtr = srcData.Scan0; IntPtr purPtr = purData.Scan0; mndtChangeColor(srcPtr, purPtr, srcImage.Width, srcImage.Height, (int)type); } srcImage.UnlockBits(srcData); purImage.UnlockBits(purData); return purImage; }
4.图片转8bit灰阶呼叫範例
MNDTLibrary _lib = new MNDTLibrary();pic_pur.Image = _lib.Change8BitColor(_fileImage, ColerType.BGR2GRAY_8BIT);
结果图
MNDTVisualization C#程式码
结论
这大概算是把之前写的影像处理重新整理一次程式码,因为接着还要加入Adaboost和CNN等等东西,顺便作笔记让自己未来能回来翻翻。
在程式码部分效能不是最好的,有些地方为了精準的运算我都将它改成double,比较原先int或char运算效能就有差别了,但若不这样做图片损失会较严重,而有的地方还能简化,但可读性会降低许多,对于程式码或观念有误或更好方法欢迎下方留言提供。
参考文献
[1]WILLIE SONG.(2011) RGB 转灰阶公式 from: http://droidparadise.blogspot.com/2011/10/rgb_18.html.(2018.09.30)
[2]维基百科.(2018). YCbCr from: https://en.wikipedia.org/wiki/YCbCr.(2018.09.30)
[3]维基百科.(2018). HSL和HSV色彩空间 from: https://zh.wikipedia.org/wiki/HSL%E5%92%8CHSV%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4.(2018.09.30)