cv2.cvtColorで [rec709(sRGB)] ⇔ [XYZ] は用意されているが、
任意のマトリクスを指定するような機能は用意されていないと思われる
そのためマトリクス適用は自分で行う必要がある。
色変換のシンプルな例をテストした。
マトリクスの転置が必要なこととチャネル順がBGRであることに注意
カラーのarrayを直接入力して試した時にマトリクスは転置しないとだめだと気が付いたが、画像に対して同じような処理をしたら結果が変わってしまった。
openCVでは画像読み込みするとRGBではなくてBGRで読み込まれる。
単純な処理しかしない場合はこのまま進めても問題ないがr,g,bごとに違う処理をするときは順番に注意しなければいけない。
sRGBの原色(赤青緑白)をXYZに変換する
sRGBの原色をXYZやxyの色空間に変換した場合は下の値になれば正解
sRGB | XYZ | xy | |
---|---|---|---|
赤 | 1, 0, 0 | 0.4125, 0.2127, 0.0193 | 0.64, 0.33 |
緑 | 0, 1, 0 | 0.3576, 0.7152, 0.1192 | 0.30, 0.60 |
青 | 0, 0, 1 | 0.1804, 0.0722, 0.9503 | 0.15, 0.06 |
白 | 1, 1, 1 | 0.9505, 1.0000, 1.0888 | 0.3127, 0.3290 |
sRGBからXYZに変換するマトリクスを適用(数値入力の場合)
- マトリクスの転置を行うこと(最初から転置したマトリクスの書き方をしてれば問題ないが)
- .Tで転置
import numpy as np
#4色(赤青緑白)を設定してこの色を変換する
linearColor = np.array([
[1,0,0],#red
[0,1,0],#grenn
[0,0,1],#blue
[1,1,1],#white
])
#sRGBからXYZに変換するマトリクス (https://en.wikipedia.org/wiki/SRGB)
sRGB_to_XYZ_Matrix = np.matrix([[0.4124,0.3576,0.1805],[0.2126,0.7152,0.0722],[0.0193,0.1192,0.9505]])
#マトリクスを適用する。この時マトリクスを転置すること
XYZ = linearColor * sRGB_to_XYZ_Matrix.T
"""
XYZ
matrix([[ 0.4124, 0.2126, 0.0193],
[ 0.3576, 0.7152, 0.1192],
[ 0.1805, 0.0722, 0.9505],
[ 0.9505, 1. , 1.089 ]])
"""
ちなみにxyYの値は
XYZの合計で割ったものがxyz。xyYのYはXYZのYと同じ
#色域を表す場合に一般的に使われるxyYに変換する。https://en.wikipedia.org/wiki/SRGB の値と同じxyYになっていれば問題ない
xyz = XYZ/np.sum(XYZ, axis=1) #X,Y,Zの合計で割ったものが小文字xyz
xyY = np.c_[xyz[:,0],xyz[:,1],XYZ[:,1]] #xyY
"""
xyY
matrix([[ 0.6400745 , 0.32997051, 0.2126 ],
[ 0.3 , 0.6 , 0.7152 ],
[ 0.15001662, 0.06000665, 0.0722 ],
[ 0.31271591, 0.32900148, 1. ]])
"""
このXYZを画像として表示する場合
- マトリクスを適用するとタイプがarrayからmatrixになるので注意。必要に応じて.Aでarrayに戻せる
- openCVで表示や画像の保存をする場合はRGBではなくBGRの順になっている必要がある
#XYZ画像表示
import cv2
XYZ = XYZ.A.reshape(1,4,3) #高さ1、幅4ピクセルのカラー画像の形式に整える
XYZ = XYZ.astype(np.float32) #float64だと次のBGR2RGB変換が出来ないのでfloatt32に変更
XYZ = cv2.cvtColor(XYZ,cv2.COLOR_BGR2RGB) #BGRに変換
img = cv2.resize(XYZ, (4*50,1*50),interpolation=cv2.INTER_NEAREST) #50倍の大きさの画像にする
cv2.imshow("XYZ",(img)) #表示
cv2.waitKey(0)
cv2.destroyAllWindows()
openCVで画像として読み込むとうまくいかない
このような画像を用意して読み込む。(縦1、横4ピクセルでarrayに入力したものと同じ値の画像を用意)
リニアのfloat32のexr画像を使った。
8bit画像だと255で割ったり場合によってはデガンマしないといけないから。
原因は下のように画像読み込みをするとBGRの順になってしまうから。
#4色(赤青緑白)の画像を読み込む
linearColor = cv2.imread('srgbPrimary.exr',3)
"""
linearColor
array([[[ 0., 0., 1.],
[ 0., 1., 0.],
[ 1., 0., 0.],
[ 1., 1., 1.]]], dtype=float32)
"""
この状態でマトリクスを適用するともちろん正しくないものになる
BGRのまま進めても問題ない場合もあるが、チャネル単位で処理を変えたりマトリクスを掛ける場合は順番が大事。
とりあえずRGBの順に変換にしてしまうのが手っ取り早い。
cv2.COLOR_BGR2RGBで簡単に変換できる。
また、float64だとcv2.COLOR_BGR2RGBに対応していないみたいなので、float32変換した。
sRGBからXYZに変換するマトリクスを適用(画像読み込みの場合)
import numpy as np
import cv2
#4色(赤青緑白)の画像を読み込む
linearColor = cv2.imread('srgbPrimary.exr',3)
h,w, _ = linearColor.shape #もとの画像の解像度取得
linearColor = cv2.cvtColor(linearColor,cv2.COLOR_BGR2RGB) #BGRをRGBに変換
linearColor = linearColor.reshape(h*w,3) #マトリクスを適用しやすいように縦*横のarrayを一行に変換する
#sRGBからXYZに変換するマトリクス (https://en.wikipedia.org/wiki/SRGB)
sRGB_to_XYZ_Matrix = np.matrix([[0.4124,0.3576,0.1805],[0.2126,0.7152,0.0722],[0.0193,0.1192,0.9505]])
#マトリクスを適用する。この時マトリクスを転置すること
XYZ = linearColor * sRGB_to_XYZ_Matrix.T
#XYZ画像表示
XYZ = XYZ.A #タイプをmatrixからarrayに変換
XYZ = XYZ.reshape(h,w,3)
XYZ = XYZ.astype(np.float32)
XYZ = cv2.cvtColor(XYZ,cv2.COLOR_RGB2BGR)
img = cv2.resize(XYZ, (w*50,h*50),interpolation=cv2.INTER_NEAREST)
cv2.imshow("XYZ_Color",(img))
cv2.waitKey(0)
cv2.destroyAllWindows()
ここでも同じようなことが書いてある
https://stackoverflow.com/questions/35167381/fastest-way-to-apply-color-matrix-to-rgb-image-using-opencv-3-0