====== 【数値情報処理b】第6回 色空間 ======
===== 前回の課題 =====
[[ans5]]
===== 色と色空間 =====
__**電磁波**__は電場と磁場が波として伝わるものである。
電磁波は__**波長**__によって性質が異なる。
電磁波のうち、380 nm から 770 nm あたりの波長が人間の眼に見える光で、__**可視光**__と呼ばれる。
{{ wave.png?nolink |波の波長 }}
^ 波長 ^ 名称 ^
| 100μm 以上 | 電波 |
| 770nm から 100μm | 赤外線 |
| **380nm から 770nm** | **可視光** |
| 100nm から 380nm | 紫外線 |
| 0.01nm から 10nm | X線 |
| 0.01nm 以下 | ガンマ線 |
※ 1μm(マイクロメートル)= 10-6 m
※ 1nm(ナノメートル)= 10-9 m
可視光の__**色**__は波長と対応している。
波長によって色が異なって見えるのは、人間の眼で検出した光が脳内で波長に応じた色として知覚されるためである。
{{ spectrum.png?nolink |可視光の色と波長の関係 }}
色が波長と対応しているといっても、全ての色が単一の波長で再現できるわけではない。
波は重ね合わせができるので、普段我々が眼にしている光は様々な波長の光が重ね合わさって混ざった状態である。
例えば、白は、どの単一波長でも再現できないが、いくつかの波長の光を混ぜ合わせることで再現できる。
様々な色は 3 つか 4 つの基本となる色を混ぜ合わせて再現される。
基本となる色の選び方は様々ある。
__**色空間**__とは、この基本となる色を軸として数値的に構成した空間のことである。
===== 混色 =====
複数の色を混ぜ合わせることで様々な色を作ることを __**混色**__ という。
----
==== 光の 3 原色 ====
__**光の 3 原色**__とは、**赤(Red)**、**緑(Green)**、**青(Blue)** のRGB 3 色光を原色とするものである。
この 3 色光を混ぜ合わせて様々な色の光を生み出せる。
赤と緑を混ぜると黄、赤と青を混ぜるとマゼンタ、緑と青を混ぜるとシアンになり、赤と緑と青をすべて混ぜると白になる。
{{ primarycolors1.png?nolink |光の 3 原色}}
混ぜ合わせた色の明るさは、もとの色の明るさを単純に足した明るさになる。
このため、__**加法混色**__という。
----
==== 色の 3 原色 ====
__**色の 3 原色**__とは、**シアン(Cyan)**、**マゼンタ(Magenta)**、**黄(Yellow)** の 3 色を原色とするものである。
これは物体や塗料の色を表現するために使われる。
黄とシアンを混ぜると緑、黄とマゼンタを混ぜると赤、マゼンタとシアンを混ぜると青になり、シアンとマゼンタと黄をすべて混ぜると黒になる。
しかし、3 色を混ぜても実際はきれいな黒にならないので、印刷のインクではシアン、マゼンタ、黄のインクの他に黒を表現するために黒のインクを用いる。
この 4 色を使う方法を頭文字をとって CMYK という((K は Key plate の略で、黒インクで画像の輪郭や文字などの印刷するために使われていた印刷板に由来する。))。
{{ primarycolors2.png?nolink |色の 3 原色}}
色の 3 原色は光の 3 原色とは逆に色を混ぜ合わせるほど色が濃く暗くなっていくため、__**減法混色**__という。
----
==== 補色 ====
混ぜ合わせると無彩色(白、グレー、黒)になる色を__**補色**__という。
色の 3 原色は白から光の 3 原色のどれか 1 つを削った色であり、補色の関係にある。
例えば、青色光と黄色光を混ぜると白色光になる。
この関係は $(R,G,B)$ の画素値(範囲は $[0,1]$)で以下のようになる。
\[
\begin{array}{ccccc}
青 && 黄 && 白\\
(0,0,1)&+&(1,1,0)&=&(1,1,1)
\end{array}
\]
移項すると
\[
\begin{array}{ccccc}
白 && 青 && 黄\\
(1,1,1)&-&(0,0,1)&=&(1,1,0)
\end{array}
\]
となり、白から青を引けば黄になることが分かる。
靑と黄は補色の関係にある。
黄色の塗料に白色光を当てると、その塗料は白色光の中の青い波長の光を吸収して残りの光を反射するため、我々の眼には黄色に見える。
{{ reflected_light.png?nolink |反射光}}
一方、黒色の塗料に白色光を当てると、その塗料は白色光のほとんどを吸収してしまうため、反射する光がなくなって黒く見える。
黒色の塗料が吸収した光は、主に熱エネルギーになる。
次のプログラム negative_image.R は画像の色を補色で置き換える(色の反転)。
補色で置き換えた画像は、写真のフィルムでは__**ネガ**__((プリントした写真はネガの色を反転した__**ポジ**__になる。))に相当する。
# 補色
library(imager)
im1 <- boats
# 補色の計算
a <- array(im1, dim(im1))
im2 <- as.cimg(1 - a)
layout(t(1 : 2))
plot(im1, main = "元の画像")
plot(im2, main = "ネガ")
layout(1)
補色の計算は cimg 変数で行うと時間がかかるため、cimg 変数を配列に変換してから補色の計算をして cimg 変数に戻している。
===== 小テスト① =====
[[KMS>|Moodle Server(非公式)]]で第6回の小テスト①を受験しなさい。
===== GIMP の色空間 =====
GIMP で RGB の色空間で見るには、レイヤーの上にある **[チャンネル]** をクリックする。
GIMP では様々なモデルでチャンネル分解ができる。
メニューの **[色]**%%→%%**[色要素]**%%→%%**[チャンネル分解]** で行う。
選択できる主な色モデルは以下の通り。
* RGB
* HSV
* HSL
* CMYK
* YCbCr(何タイプかある)
GIMP で画像の色を補色に置き換えるには、メニューの **[色]**%%→%%**[階調の反転]** を選択する。
===== RGB 色空間 =====
RGB 色空間は光の 3 原色である**赤(R)**、**緑(G)**、**青(B)** で構成する色空間である。
テレビやディスプレイなどは拡大すると一般的に R, G, B の順序で画素が並んでおり、人間の眼ではこの 3 色が混ざり合って様々な色に見える。
{{ display_rgb.png?nolink |ディスプレイの画素}}
cimg 変数のカラーチャンネルは R,G,B の 3 つある。
次のプログラム primarycolors.R は光の 3 原色と色の 3 原色を円で描画するプログラムである。
# 光の3原色と色の3原色
library(imager)
# 黒で塗りつぶした画像
im1 <- imfill(300, 300, val = c(0, 0, 0))
# 円の半径
r <- 80
# 各カラーチャンネルにおいて1で塗りつぶした円の描画
R(im1) <- draw_circle(R(im1), 150, 110, r, t(1))
G(im1) <- draw_circle(G(im1), 110, 190, r, t(1))
B(im1) <- draw_circle(B(im1), 190, 190, r, t(1))
# 補色の計算
a <- array(im1, dim(im1))
im2 <- as.cimg(1 - a)
# 文字
im1 <- implot(im1, {
text(150, 90, "R", cex = 2, col = "black")
text( 90, 200, "G", cex = 2, col = "black")
text(210, 200, "B", cex = 2, col = "black")
})
im2 <- implot(im2, {
text(150, 90, "C", cex = 2, col = "black")
text( 90, 200, "M", cex = 2, col = "black")
text(210, 200, "Y", cex = 2, col = "black")
})
layout(t(1 : 2))
plot(im1, axes = FALSE, rescale = FALSE, main = "光の3原色")
plot(im2, axes = FALSE, rescale = FALSE, main = "色の3原色")
layout(1)
cimg 変数のカラーチャンネルは R,G,B の 3 つあり、加法混色になっている。
関数 ''R()'', ''G()'', ''B()'' はそれぞれ R, G, B のカラーチャンネルを抜き出す関数である。
関数 ''implot()'' は Plots タブにプロットする代わりに、cimg 変数の画像上にプロットするための関数である。
ここでは関数 ''text()'' を使って文字を描いている。
関数 ''draw_circle()'' で円を描くことができる。
> draw_circle(cimg変数, x, y, radius, color, opacity, filled)
^ 引数 ^ デフォルト ^ 機能 ^
| ''x'' | | 中心のx座標。 |
| ''y'' | | 中心のy座標。 |
| ''radius'' | | 半径。 |
| ''color'' | ''%%"white"%%'' | 色名かカラーチャンネルのベクトルで色を指定する。 |
| ''opacity'' | ''1'' | 不透明度で、0なら透明、1なら不透明にになる。 |
| ''filled'' | ''TRUE'' | ''TRUE'' なら円の内部を塗りつぶす。 |
12〜14行目では、それぞれのカラーチャンネルに個別に円を描くことで光の 3 原色を実現している。
18行目では、白から光の 3 原色を引いて補色を計算することで色の 3 原色を実現している。
RGB 色空間では数値をどう変えればどのような色になるかが直感的に分かりにくい。
そこで、人間に分かりやすい YCbCr や HSV などの色空間が考え出されている。
===== YCbCr色空間 =====
__**YCbCr色空間**__とは、輝度信号 Y、青成分の色差信号 Cb、赤成分の色差信号 Cr で構成される色空間である。
テレビ放送の信号の伝送やデジタル画像・動画の保存に使われる。
人間の眼は輝度の変化には敏感だが、色の変化には鈍感であるという特性があるので、輝度信号はそのままにして色差信号を間引くとデータを削減できる。
また、輝度信号はそのままモノクロ画像として使うことができる。
----
==== imagerの変換式 ====
imager では RGB と YCbCr の間の変換を行う関数として ''RGBtoYCbCr()'' と ''YCbCrtoRGB()'' がある。
これらの関数で使われている変換式は [[https://www.itu.int/rec/R-REC-BT.601/|ITU-R BT.601]] の **アナログ RGB 信号からデジタル YCbCr 信号への変換式** とほぼ同じである。
以下の変換式では $R, G, B$ の範囲 $[0,1]$、 $Y, C_{b}, C_{r}$ の範囲 $[0,255]$ とする。
**RGB %%→%% YCbCr の変換式** ''RGBtoYCbCr()''
\begin{align*}
r&=255R\\
g&=255G\\
b&=255B\\
Y&=\frac{66r+129g+25b+128}{256}+16\\
C_{b}&=\frac{-38r-74g+112b+128}{256}+128\\
C_{r}&=\frac{112r-94g-18b+128}{256}+128
\end{align*}
**YCbCr %%→%% RGB の変換式** ''YCbCrtoRGB()''
\begin{align*}
r&=\frac{298(Y-16)+409(C_{r}-128)+128}{256}\\
g&=\frac{298(Y-16)-100(C_{b}-128)-208(C_{r}-128)+128}{256}\\
b&=\frac{298(Y-16)+516(C_{b}-128)+128}{256}\\
R&=\frac{r}{255}\\
G&=\frac{g}{255}\\
B&=\frac{b}{255}
\end{align*}
次のプログラム ycbcr.R は boats を YCbCr に変換してそれぞれの信号をグレースケール画像として表示する。
# YCbCrへの変換
library(imager)
# 元の画像
im.rgb <- boats
# RGBからYCbCrに変換
im.ycbcr <- RGBtoYCbCr(im.rgb)
# [0,1]に規格化してY,Cb,Crを分割
il.ycbcr <- imsplit(im.ycbcr / 255, "c")
layout(t(1 : 4))
plot(im.rgb, main = "元の画像")
plot(il.ycbcr[[1]], rescale = FALSE, main = "Y")
plot(il.ycbcr[[2]], rescale = FALSE, main = "Cb")
plot(il.ycbcr[[3]], rescale = FALSE, main = "Cr")
layout(1)
----
==== JPEGフォーマットの変換式 ====
[[https://www.w3.org/Graphics/JPEG/jfif3.pdf|JPEGフォーマット]] では色空間として YCbCr が使われており、
変換式は [[https://www.itu.int/rec/R-REC-BT.601/|ITU-R BT.601]] の **デジタル RGB 信号からデジタル YCbCr 信号への変換式** とほぼ同じである。
以下の変換式では $R, G, B$ の範囲を $[0,1]$, $Y, C_{b}, C_{r}$ の範囲を $[0,255]$ としている。
=== RGB→YCbCr の変換 ===
\begin{eqnarray*}
r&=&255R\\
g&=&255G\\
b&=&255B\\
Y&=&0.299r+0.587g+0.114b\\
C_{b}&=&-0.1687r-0.3313g+0.5b+128\\
C_{r}&=&0.5r-0.4187g-0.0813b+128
\end{eqnarray*}
=== YCbCr→RGB の変換 ===
\begin{eqnarray*}
r&=&Y+1.402(C_{r}-128)\\
g&=&Y-0.34414(C_{b}-128)-0.71414(C_{r}-128)\\
b&=&Y+1.772(C_{b}-128)\\
R&=&\frac{r}{255}\\
G&=&\frac{g}{255}\\
B&=&\frac{b}{255}
\end{eqnarray*}
JPEG フォーマットでは輝度信号 Y はそのままで、色差信号 Cb, Cr の解像度を 1/4 や 1/9 にして間引くことでデータ量を圧縮している。
Cb, Cr の解像度を 1/4 にする場合、Cb, Cr それぞれで縦2行、横2列の 4 ピクセルの信号を平均し、その平均を4ピクセルの値とみなす。
これによってデータ量は $\left(1+\frac{1}{4}+\frac{1}{4}\right)\div 3=\frac{1}{2}$ 倍になる。
{{ jpeg_cbcr.png?nolink |Cbの間引き}}
次のプログラム reduce_choma.R は色差信号の解像度を変えて見比べるプログラムである。
簡単のために JPEG の変換式ではなく、''RGBtoYCbCr()'' と ''YCbCrtoRGB()'' を使用している。
# 色差信号の解像度を変えて間引く
library(imager)
# 色差信号の解像度を変える関数(解像度1/f^2倍)
reduce <- function(im.rgb, f) {
ux <- (width(im.rgb) %/% f) * f
uy <- (height(im.rgb) %/% f) * f
# 画像サイズをfの整数倍にしてRGBからYCbCrに変換
im.ycbcr <- RGBtoYCbCr(imsub(im.rgb, x <= ux, y <= uy))
# 色差信号の平均化
a <- array(im.ycbcr, dim(im.ycbcr))
for (x in seq(1, ux, f)) {
sx <- x : (x + (f - 1))
for (y in seq(1, uy, f)) {
sy <- y : (y + (f - 1))
for (cc in c(2, 3)) { # CbCrのみ
abar <- mean(a[sx, sy, 1, cc])
a[sx, sy, 1, cc] <- abar
}
}
}
# YCbCrからRGBに変換
return(YCbCrtoRGB(as.cimg(a)))
}
# 元の画像
im <- boats
# 色差信号の解像度1/f^2倍
f <- 2
# 間引いた画像
im.new <- reduce(im, f)
layout(t(1 : 2))
plot(im, rescale = FALSE, main = "元の画像")
plot(im.new, rescale = FALSE, main = "間引いた画像")
layout(1)
===== 小テスト② =====
[[KMS>|Moodle Server(非公式)]]で第6回の小テスト②を受験しなさい。
===== HSV色空間 =====
__**HSV色空間**__とは、色相 H(hue)、彩度 S(saturation)、明度 V(value)の3つの要素で構成される色空間である。
色の変化が直感的に分かりやすいことからCG作成などで利用される。
* 色相 H:色の種類を角度で表したもの。
* 彩度 S:色の濃さを表す。彩度が下がるほど色の鮮やかさが下がり、白黒に近づく。
* 明度 V:色の明るさを表す。明度が下がるほど暗くなる。
例えば、画像の明るさを変えたい場合、RGB 色空間から HSV 色空間に変換し、明度 V を調整した後、逆変換で RGB 色空間に戻す。
{{ hsvcylinder.png?nolink |HSV 色空間(円柱モデル)}}
----
==== 変換式 ====
imager では ''RGBtoHSV(im)'' と ''HSVtoRGB(im)'' が用意されている。
[[http://alvyray.com/Papers/CG/color78.pdf|Alvy Ray Smithの定義]]による。
$R, G, B, S, V$ の範囲は $[0,1]$ とする。
$H$ の範囲は $0^{\circ}\le H < 360^{\circ}$ とし、この範囲を超えたら $360^{\circ}$ を足し引きしてこの範囲にする。
例えば $H=360^{\circ}$ は $360^{\circ}$ を引いて $H=0^{\circ}$ にする。
=== RGB→HSV の変換 ===
\begin{align*}
V&=\mathrm{max}(R,G,B)\\
X&=\mathrm{min}(R,G,B)\\
S&=
\left\{
\begin{array}{ll}
0 & (R=G=B)\\
\frac{V-X}{V} & (それ以外)
\end{array}
\right.\\
H&=60^{\circ}\times
\left\{
\begin{array}{ll}
未定義 & (R=G=B)\\
\frac{G-B}{V-X} & (V=R)\\
2+\frac{B-R}{V-X} & (V=G)\\
4+\frac{R-G}{V-X} & (V=B)
\end{array}
\right.
\end{align*}
=== HSV→RGB の変換 ===
\begin{align*}
I&=\left\lfloor\frac{H}{60}\right\rfloor\\
F&=\frac{H}{60}-I\\
M&=V(1-S)\\
N&=V(1-SF)\\
K&=V\{1-S(1-F)\}\\
(R,G,B)&=
\left\{
\begin{array}{ll}
(V,K,M) &(I=0)\\
(N,V,M) &(I=1)\\
(M,V,K) &(I=2)\\
(M,N,V) &(I=3)\\
(K,M,V) &(I=4)\\
(V,M,N) &(I=5)
\end{array}
\right.
\end{align*}
ここで $\left\lfloor\frac{H}{60}\right\rfloor$ はガウス記号といい、$\frac{H}{60}$ を超えない最大の整数という意味である。
床関数ともいう。
次のプログラム hsv.R は H, S, V を変えると画像がどう変化するかを見るプログラムである。
# HSVで画像処理
library(imager)
cutoff <- function(x) {
x[x > 1] <- 1
x[x < 0] <- 0
return(x)
}
# 元の画像
im.rgb <- load.example("parrots")
# RGBからHSVへ変換
im.hsv <- RGBtoHSV(im.rgb)
# HSVの変換パラメータ
h.shift <- 0
s.scale <- 1
v.scale <- 1
# 色相 H
im.hsv[, , 1, 1] <- (im.hsv[, , 1, 1] + h.shift) %% 360
# 彩度 S
im.hsv[, , 1, 2] <- cutoff(im.hsv[, , 1, 2] * s.scale)
# 明るさ V
im.hsv[, , 1, 3] <- cutoff(im.hsv[, , 1, 3] * v.scale)
# HSVからRGBに変換
im.new <- HSVtoRGB(im.hsv)
layout(t(1 : 2))
plot(im.rgb, rescale= FALSE, main = "元の画像")
plot(im.new, rescale= FALSE, main = "新しい画像")
layout(1)
H, S, V を変えるパラメータは以下の通り。
* h.shift : H をシフトさせる角度
* s.scale : S の倍率
* v.scale : V の倍率
{{ hsv_shift_h.png?nolink |H をシフト}}
===== 定期試験 =====
* 日時:2025年7月24日(木)1限
* 場所:205教室
* Moodle で行う
* 持ち込み可
今までの小テストと課題に似た問題を出すので復習しておくように。
===== 授業アンケート =====
* 科目名:数値情報処理b
* 授業コード:1495
「er-学籍番号8桁@k.koeki-u.ac.jp」に空メールを送信すると返事のメールが届くので、後はその指示に従う。