Optie研

パソコンで絵や動画を作る方法について

Python3 & OpenCV で画像処理を学ぶ[1] 〜 色空間を工学的に理解する

 今回はあまりPython3 & OpenCVしません。色彩工学に関する基礎的な話がメインになります。

1. はじめに

 本記事は、私が「色空間についての工学的な理解を概略的に得ること」を目標とします。そのためにまず、可視光線と色認知の関係や色の三色性についての基本を復習します。次に、色の三色性に基づいて「色の見え」を定量評価する指標としてのCIE-XYZ表色系について触れ、xy色度図L*a*b*表色系、およびこれらとRGB空間などとの関係も解説します。次に、RGB空間からHSI空間への変換について軽く述べたのちに、OpenCVでの色空間の変換を取り扱います。

 また、本記事に限りませんが、文献資料などを調べつつ自分の理解をまとめるという形を取っており、あまり厳密でない説明や曖昧な説明を含むかと思われます。もし仮に、「説明のための便宜の範疇を超えて、明らかに間違っている内容」を発見し、かつそれを指摘することが有益だと考えられる場合、ご連絡いただけると幸いです。

2. 色の基本のおさらい

 色空間の話に入る前に、まず「色」とはどのようなものだったかをおさらいしましょう。

2.1 可視光線と人が見る「色」

 通常私達が見ている「色」や「光」の物理的な正体は、電磁波 ということになります。電磁波というのは電波やX線などをも含むのですが、その中でも波長が 380nm 780nm のもの(=可視光線)は人の眼で知覚することができて、そこから私たちは「色」を見ることができる、ということです。

f:id:Optie_f:20180218155126p:plain

(様々な波長を持つ電磁波のイメージ。軸が大嘘ついてますが……)

 つまり、「色」や「光」という何か特別なものが物理的世界に存在するわけではなくて、あくまで可視光線という特定の電磁波が視細胞上で化学変化を起こし、これを脳が解釈した結果として、我々は「色」を見ている……ということになります。参考までに、ニュートンがプリズムを用いた分光実験によって「光線に色は付いていない(“The rays are not coloured”)」ことを証明しているので、導出に興味のある方は調べてみてください。

 以下、「可視光線域の電磁波」を指して「光」と呼ぶことと、「色」という言葉が、「その色を知覚させる波長の電磁波」という物理的な側面と「結果として人間が見る色」という心理的な側面を併せ持つことに注意してください。

2.2. 色の三色性

f:id:Optie_f:20180218170528p:plain

可視光線スペクトル。
出展 : 可視光線 - Wikipedia

 可視光線の各波長が、それぞれの色に対応しています。
 この中で、互いに独立した3つの単色光をうまく選んで混ぜ合わせると、他の任意の色と同じ色として知覚させられることが知られています。つまり例えば、500nm付近()の単色光と700nm付近()の単色光を混ぜ合わせた混色光は、人間にとっては575nm付近()の単色光と同じように見えるということです。+の混色光との単色光は、物理的には別物であるにも関わらずです。(色の三色性)

 これは、人の視細胞上には色を弁別する受容体が三種類しかないことに起因しています。つまり、(//)波長に対して反応しやすい三種類の受容体が視細胞上に多数存在し、それぞれがどれだけ刺激されて反応したかという比率の情報によって色を判別しているため、その反応のあり方が同一になれば、 物理的には異なる光であっても、人の眼には同じ色として見えるということです。このことから、混色時に使われる三色のことを原刺激と呼びます。
 関連して、いわゆる太陽や蛍光灯などの白く見える光とは、(誤解を恐れずに言えば、)これらの三種類の受容体がバランスよく反応するような、複数の波長の光を含む可視光線だと考えることができます。

f:id:Optie_f:20180218170808p:plain

太陽光に含まれる波長ごとの強さを示す分光エネルギー分布。可視光線領域が最も高くなっている。
出展 : 太陽光 - Wikipedia

 任意の色が三色だけの混合で表せるという性質が色の三色性ですが、これを利用した代表的なものが、ディスプレイのRed, Green, Blueや、印刷のCyan, Magenta, Yellow(+K)ですね。RGBは闇にR,G,Bの光を足していったもの(=加法混色)で、CMYは光にC,M,Yフィルタを通したもの、すなわち白色光からR,G,Bを引き去ったものになります(=減法混色)。「R,G,Bを引き去る」とは、例えば白色光をYellowフィルタに通すと黄色く見えますが、これは白色光がフィルターを通るときにBlue成分が吸収されて無くなった結果そう見える、ということです。

f:id:Optie_f:20180218172937g:plain

RGBの加法混色と、CMYの減法混色。CMYKのKとはKey Plateの略で、C,M,Yのみでは完全な黒にならないために追加されるインク。
出展 : 色の3原色 (T. Fujiwara)

3. XYZ表色系 - 色の見えを規定する

 光は電磁波であって色は認知上の産物であること、三色の混色で任意の色を表現できることがわかりました。以上の事実を踏まえて、「色」を定量的な指標で表すことを考えましょう。以下、色を表示するための体系である表色系について触れます。

3.1 CIE-XYZ表色系

 さて、先ほど「任意の単色は、三色(=三刺激)の混合によって等色させられる」ことを述べました。この事実から、実験によって「ある色を等色するのに必要な三刺激値」を求めることができそうです(等色実験)。実際そのようにして、「ある波長 λ の光に対して、等色に必要な三刺激の値を返す三組の関数」である等色関数を構成することができます。

f:id:Optie_f:20180218171049p:plain

CIE-XYZ等色関数
出展 : CIE 1931 色空間 - Wikipedia

 そうして、国際照明委員会(CIE)により、可視光線380 ~ 780nmで定義された等色関数の組が CIE-XYZ表色系 です。実験では、三刺激(RGB)の値は部分的に負の値をとる*1ようなのですが、そのような関数は実用上問題があるので、全て正になるように調整されています。また、CIE-XYZ表色系では、波長ごとの人間の「明るさ」の知覚(比視感度曲線)も$\overline{y}(\lambda)$で近似できるように設定されており、$Y$だけで色の明るさを規定できます。*2

 実際の光は特定波長のみを持つ単色光というわけではなく、しばしば複数の波長を持つ合成光(太陽光など)であるわけですが、どのような色光であっても、その光が含む波長を示す分光エネルギー分布 $L(\lambda)$ が与えられれば、以下のような積分で三刺激値を求めることができます。*3

$ X = \int_{380}^{780} \overline{x}(\lambda)L(\lambda) d\lambda $

$ Y = \int_{380}^{780} \overline{y}(\lambda)L(\lambda) d\lambda $

$ Z = \int_{380}^{780} \overline{z}(\lambda)L(\lambda) d\lambda $

3.2 xy色度図

 次に、こうして得られる三刺激値 $X,Y,Z$ の相対比 $x,y,z$ を考えます。すなわち、 $X,Y,Z$ それぞれの刺激値は、三刺激値全体 $X+Y+Z$ の中でどれくらいの割合を占めるか?というようなことを考えます。(例えば、 刺激値 $X $ の大きさが全体に占める割合が80%なら、$(x,y,z)=(0.8, 0.1, 0,1)$ のような形になります)そうすると、刺激値の相対比 $x,y,z$ は、以下のような式で表されることがわかると思います。

$ x = \dfrac{X}{X+Y+Z} $

$ y = \dfrac{Y}{X+Y+Z} $

$ z = \dfrac{Z}{X+Y+Z} $

また、これらの式とその意味( $x,y,z$ が相対比であること)から、

$ x + y + z = 1 $

であることもわかるかと思います。つまり、$ z=1-x-y $ なので、$x,y$ の値が決まれば $z$ もただ一つの値に決まるということです。このことから $(x,y)$ を二次元座標で表現すると、「明るさを無視した、三刺激値の比率と色の対応」の図を作ることができ、それが以下のxy色度図となります。$(x,y)=(1,0),(0,1)$の二点を結ぶ対角線の右上側は、$x+y$だけですでに1を超える領域のため存在しないことに注意してください。($x,y,z$は非負なので)

f:id:Optie_f:20180218171630p:plain

xy色度図。
出展 : CIE 1931 色空間 - Wikipedia

 ここには、人間の知覚できる(明るさの違いを除いた)全ての色が、歪んだ楕円を切断したような領域の中に表されています。このような形状になる理由は、例えば$(x,y)=(1.0,0,0)$となるような光は存在しない、すなわち $X$ の刺激値だけを上げるような光は物理的に存在せず、光が$X$の刺激値を上げるならば必ず隣接する $Y$ にも影響するからであり、したがって、この形状は物理的に取りうる $(x,y)$ の領域であるということができます。
 ディスプレイに表示する色の観点では、LEDの三原色R,G,Bの発色の割合を $ (r,g,b)=(1.0,0.0,0.0),(0.0,1.0,0.0),(0.0,0.0,1.0) $ のようにしたとき、その三点を三刺激値 $(X,Y,Z)$ およびその比率 $(x,y)$ に対応させてこのxy色度図にプロットし、それを結んだ三角形がディスプレイに表示できる色の範囲ということになります。

3.3 CIE-L*a*b*表色系

 前節で、等色実験に基づいて光の波長と三刺激値の対応を与えるCIE-XYZ表色系について見ましたが、$(X,Y,Z)$ の値をそのまま三次元座標として捉えた空間は、等色関数の重なりなどの影響から、色空間としては歪んでおり、そのままでは色に対して何か操作を考えるには困難がありそうです。
 そこで、$X,Y,Z$ に適当な変換を施して、我々の「空間」の直感に合う直交座標の三次元色空間を再現する目的で設計されたのが、以下のような CIE-L*a*b表色系 です。

f:id:Optie_f:20180218172103j:plain

L*a*b*表色系の図。
出展 : Lab 表色系 (T. Fujiwara)

  $(X,Y,Z) \rightarrow (L^*, a^*, b^*) $ とするための具体的な変換は、以下のようなものです。

$ L^* = 116 \left( \dfrac{Y}{Y_0} \right)^{ \frac{1}{3} } - 16 $

$ a^* = 500 \left( \left( \dfrac{X}{X_0} \right)^{\frac{1}{3}} - \left( \dfrac{Y}{Y_0} \right)^{\frac{1}{3}} \right) $

$ b^* = 200 \left( \left( \dfrac{Y}{Y_0} \right)^{\frac{1}{3}} - \left( \dfrac{Z}{Z_0} \right)^{\frac{1}{3}} \right) $

 ここで $ X_0, Y_0, Z_0 $ は、対象と同一の照明条件の下の標準白色に対する三刺激値です。要するに、照明によって色の見え方が違うため、その照明における「白色」の三刺激値をもとに基準化をしているということです。
 式はやや複雑ですが、$a^*$ はCIE-XYZ図のグラフ的に $X$ と重なる $Y$ の影響を引いて排除したもの、すなわち(負値を含む)赤成分で、$b^*$ も同様に黄成分だと考えればよいと思います。$L^*$ は明るさです。

 CIE-L*a*b*表色系の具体的なメリットとしては、二色 $ (L^*_1, a^*_1, b^*_1 ), ( L^*_2, a^*_2, b^*_2 ) $ の色の差 $\Delta$ を、以下のように三次元空間における普通の距離(ユークリッド距離)として簡単に計算できることです。

$ \Delta = \sqrt{ \left( L^*_1 - L^*_2 \right)^2 + \left( a^*_1 - a^*_2 \right)^2 + \left( b^*_1 - b^*_2 \right)^2 } $

3.4 XYZ表色系とRGB値との関係

 さて、我々が馴染み深いのは(XYZなどではなく)RGB画像データであるわけですが、いわゆる $(R,G,B)$ と先程までのXYZ表色系などとはどのような関係にあるのでしょうか。
 察しがついているかもしれませんが、 $(R,G,B)$ の値それ自体は具体的な色を表しているわけではなくて、ディスプレイなどによる出力を経て初めて具体的な色になります。したがって、モニターがデータとして $(R,G,B)$ の値を受け取ってどのように発光するか(実際の色の見え=XYZ表色系 にどう対応づけられる発光になるのか)などといったことによって、データとしてのRGB値が表す具体的な色は異なってきます。
  そこで、 $(R,G,B) \rightarrow (X,Y,Z)$ という具体的な対応づけを規格化したものが、sRGBやAdobe RGBなどだということになります。これは、前述のxy色度図において三角形で表されるものです。モニターの発色による $(R,G,B) \rightarrow (X,Y,Z)$ の対応と、sRGBなどの規格が定める $(R,G,B) \rightarrow (X,Y,Z)$ の対応とどれだけ近いかが、「正しい発色」を考える上で問題になっていることです。

4. RGBとHSI

 RGBや、色相,彩度,明度からなるHSI(HLSとも)の概念は周知であると思います。 R,G,Bの三軸による表現はあまり人間にとって直観的ではないので、代わりに色相,彩度,明度というわかりやすい属性で色を扱おうという話ですね。
 しかしながら、その相互変換の方法はいくらか存在するようです。六角錐モデル、双六角錐モデル、円柱モデルなどがありますが、今回はその中でも、人間の直感により近いという意味で精密とされる双六角錐モデルについて以下で解説します。

4.1. 双六角錐モデルによるRGB-HSI変換

はじめに、双六角錐モデルの模式図を以下に示します。次に、数式的な解説を行うので、見比べながら確認してください。

f:id:Optie_f:20180218172402p:plain

双六角錐モデルの模式図。白/黒に近い色は彩度が低いという点が特徴である。他のモデルでは、白と原色の赤などが同じ彩度1.0とされることもある。
出展 : 色モデル (T. Fujiwara)

 まず、明度 $I$ は以下のようになります。

$ I_{max} = max\{R,G,B\} $

$ I_{min} = min\{R,G,B\} $

$ I = \dfrac{I_{max} + I_{min}}{2} $

こちらは大丈夫でしょうか。$(R,G,B)$ の中で一番大きい値と一番小さい値の平均が明度であると言っていますね。

 次に、彩度 $S$ です。

$ S = \left\{ \begin{array}{ll} \dfrac{I_{max} - I_{min}}{I_{max} + I_{min}} & (I \leqq 0.5) \\ \dfrac{I_{max} - I_{min}}{2-(I_{max} + I_{min})} & (I < 0.5) \end{array} \right. $

$R=G=B$ のときの色がグレースケール、すなわち $I_{max} = I_{min}$ となり彩度 $S=0$ となること。グレーを境に、明度が上がって白に近づくほど、また明度が下がって黒に近づくほど、分母が大きくなり彩度は小さくなること。また、彩度が最大になるのは、$I_{max} = 1$ かつ $I_{min} = 0$ , すなわち $I=0.5$ なるときであることを確認してください。

 最後に、色相 $H$ です。 $H$ は角度であることに注意して、

$r = \dfrac{I_{max}-R}{I_{max} - I_{min}}$

$g = \dfrac{I_{max}-G}{I_{max} - I_{min}}$

$b = \dfrac{I_{max}-B}{I_{max} - I_{min}}$

とおくと、これらの文字を使って、以下のように表されます。

$ H = \left\{ \begin{array}{ll} \dfrac{\pi}{3}(b-g) & (R=I_{max}のとき) \\ \dfrac{\pi}{3}(2+r-b) & (G=I_{max}のとき) \\ \dfrac{\pi}{3}(4+g-r) & (B=I_{max}のとき) \end{array} \right. $

 例えば、 $R=I_{max}$ であるときは色相として赤成分が一番強いということに注意すると、係数の $\dfrac{\pi}{3}$ および定数項と場合分けの条件から、円周をR,G,Bを中心に三等分して、それぞれの支配領域の中で残り二色の比率を考えているものと見ることができそうです。
$I_{max} = I_{min}$ (グレースケール)であるとき、分母がゼロとなるので色相は定義されません。

5. OpenCVによる色変換実験

 ようやくOpenCVの話です。が、OpenCVでは cvtColor(image, flag) 関数の第二引数に「何から何への変換か」を示すflag (例: cv2.COLOR_RGB2GRAY ) を与えるだけで変換がされたimageが返ってきます。
 以下、もぺもぺを用いて実験してみましょう。各色空間での値をRGBの値であると言い張ってmatplotlibに表示させピクセル配列の一部と共に表示しました。

from matplotlib import pyplot as plt
import numpy as np
import cv2

%matplotlib inline


IMAGE_PATH = '../0.IntroToOpenCV/mopemope_title.jpg'

img_gbr = cv2.imread(IMAGE_PATH, 1)
img_rgb = cv2.cvtColor(img_gbr, cv2.COLOR_BGR2RGB)

img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
img_xyz = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2XYZ)
img_lab = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2LAB)
img_hls = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HLS)

spaces = [img_rgb, img_gray, img_xyz, img_lab, img_hls]
names = ['RGB', 'Grayscale', 'XYZ', 'L*a*b*', 'HLS']

for space, name in zip(spaces, names):
    plt.title('mopemope' + name)
    if name=='Grayscale':
        # GRAYの場合(width, height)の二次元配列となるため、三次元配列に変換しておく
        space = cv2.cvtColor(space, cv2.COLOR_GRAY2RGB)
    plt.imshow(space)
    plt.show()
    print('----- part of pixels -----')
    print(space[0])
    print('====================')

f:id:Optie_f:20180218155130p:plain

    ----- part of pixels -----
    [[217 255 216]
     [217 255 216]
     [217 255 216]
     ..., 
     [255 255 255]
     [255 255 255]
     [255 255 255]]
    ====================

f:id:Optie_f:20180218155138p:plain

    ----- part of pixels -----
    [[239 239 239]
     [239 239 239]
     [239 239 239]
     ..., 
     [255 255 255]
     [255 255 255]
     [255 255 255]]
    ====================

f:id:Optie_f:20180218155141p:plain

    ----- part of pixels -----
    [[220 244 240]
     [220 244 240]
     [220 244 240]
     ..., 
     [242 255 255]
     [242 255 255]
     [242 255 255]]
    ====================

f:id:Optie_f:20180218155144p:plain

    ----- part of pixels -----
    [[246 109 143]
     [246 109 143]
     [246 109 143]
     ..., 
     [255 128 128]
     [255 128 128]
     [255 128 128]]
    ====================

f:id:Optie_f:20180218155150p:plain

    ----- part of pixels -----
    [[ 59 236 255]
     [ 59 236 255]
     [ 59 236 255]
     ..., 
     [  0 255   0]
     [  0 255   0]
     [  0 255   0]]
    ====================

また、変換に使うflagですが、flags = [i for i in dir(cv2) if i.startswith('COLOR_')] のようにして探すことができます。 以下では、デジタル完結の作業にはあまり関係がないと思われるベイヤーフィルターを弾いて、冒頭6文字 'COLOR_' を除いたものを変換元ごとに表示するコードです。

import re

flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
flags = [flag for flag in flags if not re.match(r'.*(Bayer|BAYER).*',flag)]


A=''

for flag in flags:
    if A == re.split(r'\w(2)\D',flag)[0]:
        print(flag[6:], end=', ')
    else:
        A = re.split(r'\w(2)\D',flag)[0]
        print('\n')
        print(flag[6:], end=', ')

以下、その出力です。

    BGR2BGR555, BGR2BGR565, BGR2BGRA, BGR2GRAY, BGR2HLS, BGR2HLS_FULL, BGR2HSV, BGR2HSV_FULL, BGR2LAB, BGR2LUV, BGR2Lab, BGR2Luv, BGR2RGB, BGR2RGBA, BGR2XYZ, BGR2YCR_CB, BGR2YCrCb, BGR2YUV, BGR2YUV_I420, BGR2YUV_IYUV, BGR2YUV_YV12, 
    
    BGR5552BGR, BGR5552BGRA, BGR5552GRAY, BGR5552RGB, BGR5552RGBA, 
    
    BGR5652BGR, BGR5652BGRA, BGR5652GRAY, BGR5652RGB, BGR5652RGBA, 
    
    BGRA2BGR, BGRA2BGR555, BGRA2BGR565, BGRA2GRAY, BGRA2RGB, BGRA2RGBA, BGRA2YUV_I420, BGRA2YUV_IYUV, BGRA2YUV_YV12, 
    
    COLORCVT_MAX, 
    
    GRAY2BGR, GRAY2BGR555, GRAY2BGR565, GRAY2BGRA, GRAY2RGB, GRAY2RGBA, 
    
    HLS2BGR, HLS2BGR_FULL, HLS2RGB, HLS2RGB_FULL, 
    
    HSV2BGR, HSV2BGR_FULL, HSV2RGB, HSV2RGB_FULL, 
    
    LAB2BGR, LAB2LBGR, LAB2LRGB, LAB2RGB, 
    
    LBGR2LAB, LBGR2LUV, LBGR2Lab, LBGR2Luv, 
    
    LRGB2LAB, LRGB2LUV, LRGB2Lab, LRGB2Luv, 
    
    LUV2BGR, LUV2LBGR, LUV2LRGB, LUV2RGB, 
    
    Lab2BGR, Lab2LBGR, Lab2LRGB, Lab2RGB, 
    
    Luv2BGR, Luv2LBGR, Luv2LRGB, Luv2RGB, 
    
    M_RGBA2RGBA, 
    
    RGB2BGR, RGB2BGR555, RGB2BGR565, RGB2BGRA, RGB2GRAY, RGB2HLS, RGB2HLS_FULL, RGB2HSV, RGB2HSV_FULL, RGB2LAB, RGB2LUV, RGB2Lab, RGB2Luv, RGB2RGBA, RGB2XYZ, RGB2YCR_CB, RGB2YCrCb, RGB2YUV, RGB2YUV_I420, RGB2YUV_IYUV, RGB2YUV_YV12, 
    
    RGBA2BGR, RGBA2BGR555, RGBA2BGR565, RGBA2BGRA, RGBA2GRAY, RGBA2M_RGBA, RGBA2RGB, RGBA2YUV_I420, RGBA2YUV_IYUV, RGBA2YUV_YV12, RGBA2mRGBA, 
    
    XYZ2BGR, XYZ2RGB, 
    
    YCR_CB2BGR, YCR_CB2RGB, 
    
    YCrCb2BGR, YCrCb2RGB, 
    
    YUV2BGR, YUV2BGRA_I420, YUV2BGRA_IYUV, YUV2BGRA_NV12, YUV2BGRA_NV21, YUV2BGRA_UYNV, YUV2BGRA_UYVY, YUV2BGRA_Y422, YUV2BGRA_YUNV, YUV2BGRA_YUY2, YUV2BGRA_YUYV, YUV2BGRA_YV12, YUV2BGRA_YVYU, YUV2BGR_I420, YUV2BGR_IYUV, YUV2BGR_NV12, YUV2BGR_NV21, YUV2BGR_UYNV, YUV2BGR_UYVY, YUV2BGR_Y422, YUV2BGR_YUNV, YUV2BGR_YUY2, YUV2BGR_YUYV, YUV2BGR_YV12, YUV2BGR_YVYU, YUV2GRAY_420, YUV2GRAY_I420, YUV2GRAY_IYUV, YUV2GRAY_NV12, YUV2GRAY_NV21, YUV2GRAY_UYNV, YUV2GRAY_UYVY, YUV2GRAY_Y422, YUV2GRAY_YUNV, YUV2GRAY_YUY2, YUV2GRAY_YUYV, YUV2GRAY_YV12, YUV2GRAY_YVYU, YUV2RGB, YUV2RGBA_I420, YUV2RGBA_IYUV, YUV2RGBA_NV12, YUV2RGBA_NV21, YUV2RGBA_UYNV, YUV2RGBA_UYVY, YUV2RGBA_Y422, YUV2RGBA_YUNV, YUV2RGBA_YUY2, YUV2RGBA_YUYV, YUV2RGBA_YV12, YUV2RGBA_YVYU, YUV2RGB_I420, YUV2RGB_IYUV, YUV2RGB_NV12, YUV2RGB_NV21, YUV2RGB_UYNV, YUV2RGB_UYVY, YUV2RGB_Y422, YUV2RGB_YUNV, YUV2RGB_YUY2, YUV2RGB_YUYV, YUV2RGB_YV12, YUV2RGB_YVYU, 
    
    YUV420P2BGR, YUV420P2BGRA, YUV420P2GRAY, YUV420P2RGB, YUV420P2RGBA, 
    
    YUV420SP2BGR, YUV420SP2BGRA, YUV420SP2GRAY, YUV420SP2RGB, YUV420SP2RGBA, 
    
    YUV420p2BGR, YUV420p2BGRA, YUV420p2GRAY, YUV420p2RGB, YUV420p2RGBA, 
    
    YUV420sp2BGR, YUV420sp2BGRA, YUV420sp2GRAY, YUV420sp2RGB, YUV420sp2RGBA, 
    
    mRGBA2RGBA, 

6. おわりに

 本記事では、可視光線から復習し、等色実験から色の見えを定量的に規定するXYZ表色系やxy色度図、それを元にしたL*a*b*色空間や、RGB&HSIの変換と工学的な立ち位置を概観することができたと思います。また、変換の数式的な理解をざっくり得られたことで、各色空間の間の関係も掴みやすくなったのではないかと思います。
 次回↓は、具体的な画像を用いて、画像の統計量に関して取り扱おうと思います。

optie.hatenablog.com

また、一応考えている今後の予定としては、以下の通りです。

[done] 0. 導入
[done] 1. 色空間 
2. 画像の統計量
3. 画素ごとの濃淡変換
4. 空間フィルタリング
5. 周波数フィルタリング
6. テクスチャ・領域処理
7. パターンと図形検出
8. パターン認識

 省略・割り込み・入れ替えなど起こるかもしれませんが、概念的には「画素ベースの操作」→「画素の位置関係ベースの操作」→「画素の周波数ベースの操作」→「特徴量抽出」→「パターン認識」という流れで学習を進めることがが念頭にあります。今回もそうなのですが、理論的な教科書としては暫定的に「ディジタル画像処理」(CG-ARTS協会)に従いつつ、OpenCVによる実験を行いながら学んでいく感じになると思います。

7. 参考文献

*1:一般に光を足し合わせると明るくなる=彩度が落ちるので、高彩度の Cyan などは Red をマイナスしないと物理的には等色できない、といったことがあるようです。

*2:というよりもともと、波長ごとに「明るさ」の感じ方を示す比視感度曲線は、緑成分=中波長付近で最大になるような形をしており、中波長付近に反応するM錐体細胞の感度曲線とほぼ一致しているようです。これは、物理的に同じ強さの光であれば、緑色を最も明るく感じることを示唆しています。グラフ上の三刺激の中でも $\overline{y}(\lambda)$ の最大値が最も低いのも、緑色を最も強く感じるために、刺激値としては少ない量で済むことを表していると考えることもできそうです。

*3:補足 : 「入力光の中の ある波長 $\lambda$ nmの強さに対して、その刺激値を求める」という計算を、入力光に含まれる 380 ~ 780nm の範囲の波長全てに対して行い、それらを足し合わせるというイメージです。結果として、入力光に対する刺激値を得ます