はじめに
画像を色調補正やフィルタリングなどの操作するには,表色系について知っておく必要があるので,ピクセルと色についてまとめた記事になります.
この記事で上げた表色系(色空間)以外にもたくさんありますが,良く使いそうな表色系を取り上げてます.
RGBとの変換処理はC/C++でのサンプルソース付きです.
表色系の種類
RGB表色系
ディスプレイで表示するときに必要.
各値は0~255の範囲で表現されている.
{\begin{align}
Color&=(r,g,b)\\
\\
White&=(255,255,255)\\
Black&=(0,0,0)\\
\\
Red&=(255,0,0)\\
Green&=(0,255,0)\\
Blue&=(0,0,255)\\
Yellow&=(255,255,0)\\
Cyan&=(0,255,255)\\
Magenta&=(255,0,255)\\
\end{align}
}
※ピクセルを再掲
HSV表色系
RGB表色系より感覚的に値を設定できる.
Hは0.0~360.0,SとVは0.0~1.0が取りうる範囲.
さらにHは角度を表してるため,360を超えている場合は,H-360と計算して範囲内に収めることが可能.
// ------------------------------------------------
// rgb2hsv
// rgb[] : R,G,Bの順番に値が格納されている
// hsv[] : H,S,Vの順番に値が格納される
void rgb2hsv(int rgb[3], double hsv[3]) {
int x = max(rgb[0], max(rgb[1], rgb[2]));
int n = min(rgb[0], min(rgb[1], rgb[2]));
hsv[2] = (double)x/255.0;
if(hsv[2]==0.0) {
hsv[1]=0.0;
hsv[0]=0.0;
return;
}
hsv[1] = (double)(x-n)/x;
if(hsv[1]==0.0) {
hsv[0]=0.0;
return;
}
double h = 0.0;
if(x==rgb[0]) h = 60.0*(rgb[1]-rgb[2])/(x-n);
else if(x==rgb[1]) h = 120.0+60.0*(rgb[2]-rgb[0])/(x-n);
else if(x==rgb[2]) h = 240.0+60.0*(rgb[0]-rgb[1])/(x-n);
if(h<0.0) h+=360.0;
if(h>360.0) h-=360.0;
hsv[0]=h;
}
// ------------------------------------------------
// hsv2rgb
// hsv[] : H,S,Vの順番
// rgb[] : R,G,Bの順番
void hsv2rgb(double hsv[3], int rgb[3]) {
double H = hsv[0];
double S = hsv[1];
double V = hsv[2];
if(H>=360.0) H-=360.0;
if(H<0.0) H+=360.0;
S = max(0.0, min(S, 1.0));
V = max(0.0, min(V, 1.0));
int h = ((int)floor(H/60.0));
double f = H/60.0 - (double)h;
if(!(h&1)) f=1-f;
double p = V*(1.0-S);
double q = V*(1.0-f*S);
double r=0.0, g=0.0, b=0.0;
// switch文でもOK
if(h==0) { r=V; g=q; b=p; }
else if(h==1) { r=q; g=V; b=p; }
else if(h==2) { r=p; g=V; b=q; }
else if(h==3) { r=p; g=q; b=V; }
else if(h==4) { r=q; g=p; b=V; }
else if(h==5) { r=V; g=p; b=q; }
rgb[0] = (int)(r*255);
rgb[1] = (int)(g*255);
rgb[2] = (int)(b*255);
}
YCbCr表色系
JPEGの圧縮時にこの表色系を使用する.
CbとCrのチャンネルは少し削って(間引き)劣化させても,人間の目は劣化したと認識されにくいため,圧縮時に利用.
Y値はグレースケール値として使用することが多い.(安定かつ手軽で良い)
似たものに,YUV表色系やYPbPr表色系があり,厳密な切り分けがない模様.
SDTVではYCbCr表色系,HDTVではYPbPr表色系を使っているようだ.(YPbPr表色系は後述)
Cb値とCr値は負数もとるため,ディスプレイ表示させたいときは128のオフセット値を足しておく.
// ------------------------------------------------
// rgb2ycbcr
// rgb[] : R,G,Bの順番に値が格納されている
// ycbcr[] : Y,Cb,Crの順番に値が格納される
void rgb2ycbcr(int rgb[3], double ycbcr[3], double offset = 128.0) {
ycbcr[0] = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
ycbcr[1] =-0.169 * rgb[0] - 0.332 * rgb[1] + 0.500 * rgb[2] + offset;
ycbcr[2] = 0.500 * rgb[0] - 0.419 * rgb[1] - 0.081 * rgb[2] + offset;
}
// ------------------------------------------------
// ycbcr2rgb
// ycbcr[] : Y,Cb,Crの順番
// rgb[] : R,G,Bの順番
void ycbcr2rgb(double ycbcr[3], int rgb[3], double offset = 128.0) {
double cb = ycbcr[1] - 128.0;
double cr = ycbcr[2] - 128.0;
int r = (int)(ycbcr[0] + 1.402 * cr);
int g = (int)(ycbcr[0] - 0.344 * cb - 0.714 * cr);
int b = (int)(ycbcr[0] + 1.772 * cb);
rgb[0] = max(0, min(r, 255));
rgb[1] = max(0, min(g, 255));
rgb[2] = max(0, min(b, 255));
}
YPbPr表色系
こちらはHDTV向け.
変換方法はYCbCr表色系と同じで係数が違うだけ.
// ------------------------------------------------
// rgb2ypbpr
// rgb[] : R,G,Bの順番に値が格納されている
// ypbpr[] : Y,Pb,Prの順番に値が格納される
void rgb2ypbpr(int rgb[3], double ypbpr[3], double offset = 128.0) {
ypbpr[0] = 0.213 * rgb[0] + 0.715 * rgb[1] + 0.072 * rgb[2];
ypbpr[1] =-0.115 * rgb[0] - 0.385 * rgb[1] + 0.500 * rgb[2] + offset;
ypbpr[2] = 0.500 * rgb[0] - 0.454 * rgb[1] - 0.046 * rgb[2] + offset;
}
// ------------------------------------------------
// ypbpr2rgb
// ypbpr[] : Y,Pb,Prの順番
// rgb[] : R,G,Bの順番
void ypbpr2rgb(double ypbpr[3], int rgb[3], double offset = 128.0) {
double pb = ypbpr[1] - 128.0;
double pr = ypbpr[2] - 128.0;
int r = (int)(ypbpr[0] + 1.579 * pr);
int g = (int)(ypbpr[0] - 0.187 * pb - 0.468 * pr);
int b = (int)(ypbpr[0] + 1.856 * pb);
rgb[0] = max(0, min(r, 255));
rgb[1] = max(0, min(g, 255));
rgb[2] = max(0, min(b, 255));
}
その他の表色系
XYZ表色系やLa*b*表色系などもある.
終わりに
以前良く使っていたRGBとHSV,輝度値Yなどの変換式を記載しました.
XYZ表色系やLa*b*表色系はそのうち…