最近幾個禮拜都在忙甄試資料,所以沒甚麼時間發文章,今日終於解脫所以發表一篇文章慶祝,廢話不多說趕緊切入正題了。
色彩空間轉換在影像處理當中佔了相當重要的位置,在我們使用亮度、色度等等有時候都會需要做色彩空間轉換計算,因使用其它色彩空間可方便處理或壓縮色彩加快達到目標。
這次介紹灰階、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 image
pur = purpose of image
width = Image's width
height = Image's height
type = 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。
步驟
標頭檔:加入Library.h的private
void BGR2Gray8Bit(C_UCHAE* src, UCHAE* pur
, C_UINT32 width, C_UINT32 height);
實作檔:函數加入Library.cpp、ChangeColor
case ColerType::BGR2GRAY_8BIT:
BGR2Gray8Bit(src, pur, width, height);
break;
// 灰階
// R 0.299 ≈ 19595
// G 0.587 ≈ 38469
// B 0.114 ≈ 7472 (進位 反推回去比7471接近)
// 為了快速運算(整數運算+位移) 先將數值左位移16次冪 65536
void 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++;
}
}
[2]提到Y為亮度,Cb和Cr為色度,YCbCr還有許多用途若有興趣可以前往維基百科觀看一些用途,這裡主要實作公式為主,這裡也是為了加快速度先乘上2^8在位移回去。
公式為[2]:
因為後面會應用在圖片上做為亮度與色度可調性,所以將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
case ColerType::BGR2YCbCr:
BGR2YCbCr(src, pur, width, height);
break;
case ColerType::YCbCr2BGR:
YCbCr2BGR(src, pur, width, height);
break;
// 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次冪 65536
void 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;
}
// 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次冪 65536
void 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為一個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
case ColerType::BGR2HSV:
BGR2HSV(src, pur, width, height);
break;
case ColerType::HSV2BGR:
HSV2BGR(src, pur, width, height);
break;
void 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;
}
}
void 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,求min
C_DOUBLE min = V * (1.0 - fixS);
// -(f - 1) * (max - min) + min = g; 120.240.360
C_DOUBLE q = -(offset - 60.0) * (V - min) / 60.0 + min;
// f * (max - min) + min = g; 60.180.300
C_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 image
pur = purpose of image
H = 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 image
pur = purpose of image
Y = 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。
void 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++;
}
}
void 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);
結果圖
這大概算是把之前寫的影像處理重新整理一次程式碼,因為接著還要加入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)