第9回 様々なフィルタ処理

本題に入る前に、必ず 連絡 の動画を見てください。
Google Colabへのリンク

1. リサイズ

動画の解説を参照

今回は画像の合成・鮮鋭化 (くっきりさせる)・セピア化 (古いフィルム写真のような色にする)・エンボス化 (浮彫のような画像を作る) の4種類の処理を行います。
「2. 合成」では2枚の元画像、「3. 鮮鋭化」「4. セピア化」「5. エンボス化」では元画像のうち最初の1枚の方を使います。

ここでは2枚の画像を元にして横幅が800ピクセルになるようにリサイズします。
ただし、次項の「合成」では2枚の画像を合成するため、両者のサイズが同じになるように縦のサイズを決めます。
import cv2
import numpy as np
from google.colab import files

img1 = [None, None]

for i in range(2):
  file = files.upload()
  filename = next(iter(file))
  img1[i] = cv2.imread(filename)

h, w = img1[0].shape[:2]
new_w = 800
new_h = int(h * (new_w / w))
img1[0] = cv2.resize(img1[0], (new_w, new_h))
img1[1] = cv2.resize(img1[1], (new_w, new_h))

cv2.imwrite('ex1_0.png', img1[0])
cv2.imwrite('ex1_1.png', img1[1])
元画像0の選び方についての注意
エンボス化の処理では、元画像の明るい部分に対して暗い部分が浮き出て見えるような画像ができます。
その際に、「上から」「左から」「左上から」光が当たっているように見える3種類のものを作ります。
その結果を確認するため、元画像には周囲より暗い部分があり、その上と左の境界線がほぼ水平・鉛直なものを使ってください。

※ これは「長方形の黒い板」があるので条件を満たしています。この画像になんとなく影響されて城の画像を使う人が多いですが、一般には日本の城は元画像には向きません
元画像1の選び方についての注意
上記のコードでは元画像1がどのようなアスペクト比でも、出力されるサイズは元画像0と同じになるようにしてあります。
2枚の画像が「横長」と「縦長」だったりすると、元画像1から作られる画像は大きく歪みます。
元画像0


元画像1


ex1_1.pngがこうなってしまう

2枚の元画像はアスペクト比 (縦横比) が同じものを使うのがベストです。
すくなくとも「縦横のどちらが長いか」は2枚の画像で同じにしておくのがお勧めです。

課題1

上記の太枠の説明の条件を満たす2枚の画像を元にして、横幅を800ピクセルにリサイズした画像 ex1_0.png, ex1_1.png を出力してください。

2. 合成

動画の解説を参照

addWeited関数を使うと、2枚の画像を合成できます。
第2, 第4引数で両者の割合を指定できます。
以下のコードでは 7:3 の比率になります。
img2 = cv2.addWeighted(img1[0], 0.7, img1[1], 0.3, 0)

こうすると 5:5 (つまり 1:1)
img2 = cv2.addWeighted(img1[0], 0.5, img1[1], 0.5, 0)

こうすると 3:7 の比率になります。
img2 = cv2.addWeighted(img1[0], 0.3, img1[1], 0.7, 0)

ratio = ?
img2 = cv2.addWeighted(img1[0], ratio, img1[1], 1 - ratio, 0)
cv2.imwrite('ex2.png', img2)

課題2

課題1で作成した画像を合成した画像 ex2.png を出力してください。
※ 第2, 第4引数は出力結果を見ながら ratio に代入する値を 0~1 の範囲で調整してください (0や1にすると一方の画像と全く同じになるのでNG)。

3. 鮮鋭化

動画の解説を参照

鮮鋭化は画像をくっきりさせる処理です。
第5回の平滑化、つまりぼやけさせる処理の逆の処理です。
元画像を A, ぼやけた画像を B とすれば、「ぼかす」という処理は A に
B - A
を足す処理にあたります。くっきりさせるには、元画像 A にその逆
A - B
を足せばいいので、できる画像は
A × 2 - B
です。これは、前項の合成の関数 addWeightedで実行できます。
つまり、A と B を「2:-1」で混ぜるということです。
img3_blured = cv2.blur(img1[0], (7, 7))
img3_sharpened = cv2.addWeighted(img1[0], 2.0, img3_blured, -1.0, 0)
cv2.imwrite('ex3.png', img3_sharpened)

課題3

課題1で作成した 0番目の画像を鮮鋭化した画像 ex3.png を出力してください。
※ ex1_0.pngと交互に表示して、くっきりしていることを確認してください。

4. セピア化

動画の解説を参照

セピア色とは、イカ墨に由来する茶褐色 (#6B4A2B) の色で、昔の白黒写真が時間の経過で変色したものを指すこともあります。
通常のカラーの写真の各ピクセルの R, G, B の成分に対して
\( \begin{pmatrix} 0.393 & 0.769 & 0.189 \\ 0.349 & 0.686 & 0.168 \\ 0.272 & 0.534 & 0.131 \end{pmatrix} \begin{pmatrix} R \\ G \\ B \end{pmatrix} = \begin{pmatrix} R' \\ G' \\ B' \end{pmatrix} \)
という計算で得られる (R', G', B') の色を作ると、セピア色の画像が得られます。
上記の式のように、画像処理では色成分を説明するとき R, G, B の順に書くのが通例ですが、OpenCVでは逆の B, G, R の順です。
そのため、計算に使う行列もこのように逆順になります。
\( \begin{pmatrix} 0.131 & 0.534 & 0.272 \\ 0.168 & 0.686 & 0.349 \\ 0.189 & 0.769 & 0.393 \end{pmatrix} \begin{pmatrix} B \\ G \\ R \end{pmatrix} = \begin{pmatrix} B' \\ G' \\ R' \end{pmatrix} \)
sepia_matrix = np.array([
  [0.131, 0.534, 0.272],
  [0.168, 0.686, 0.349],
  [0.189, 0.769, 0.393]])
この行列とそれぞれの点の色 (B, G, R) の積はdot関数で計算できます。
sepia = np.dot(sepia_matrix, img4[y, x])
計算の結果、それぞれの色成分が0~255の範囲に収まらない可能性もあるので、clip関数でその範囲に収まるように修正します。
img4[y, x] = np.clip(sepia, 0, 255)
まとめると、以下のコードで全体をセピア化できます。
sepia_matrix = np.array([
  [0.131, 0.534, 0.272],
  [0.168, 0.686, 0.349],
  [0.189, 0.769, 0.393]])

img4 = img1[0].copy()
for y in range(new_h):
    for x in range(new_w):
        sepia = np.dot(sepia_matrix, img4[y, x])
        img4[y, x] = np.clip(sepia, 0, 255)

cv2.imwrite('ex4.png', img4)

課題4

課題1で作成した 0番目の画像をセピア化した画像 ex4.png を出力してください。

5. エンボス化

動画の解説を参照

エンボス (emboss) とは浮彫加工のことで、画像処理ではこの用語は画像の明度の情報をもとにして浮彫のような画像を作ることを意味します。
グレースケール化した画像に以下のようなマスクをかけ、
0 0 0
1 0 -1
0 0 0
それを明度とした画像をつくるとこのようになります。
第7回のX方向のソーベルフィルタと似ていますが、絶対値を取らないので左が右より明るいところだけが白くなります。
このフィルタで得られる値は負になることもあります (右が左より明るいところ)。

そこで得られた値の画像全体の最小値・最大値を \(V_{min}\), \(V_{max}\) として、それぞれのピクセルでの計算値 \(V\) から
\( \begin{eqnarray} V'=\frac{V-V_{min}}{V_{max}-V_{min}}\times 255 \end{eqnarray} \)
を求めれば、\(V'\) は0~255の範囲に収まります。
これを明度として使うとこのような浮彫のような画像になります。

マスクをこのように変えて
0 1 0
0 0 0
0 -1 0
同様の処理を行うと、上から光が当たっているような結果になります。

このようなマスクでは
1 0 0
0 0 0
0 0 -1
左上から光が当たっているような結果になります。
mask0 = np.array([[0, 0, 0], [1, 0, -1], [0, 0, 0]])
mask1 = np.array([[0, 1, 0], [0, 0, 0], [0, -1, 0]])
mask2 = np.array([[1, 0, 0], [0, 0, 0], [0, 0, -1]])
mask = [mask0, mask1, mask2]
img5 = [None, None, None]
for i in range(3):
  img_gray = cv2.cvtColor(img1[0], cv2.COLOR_RGB2GRAY)
  img5[i] = cv2.filter2D(img_gray, cv2.CV_32F, mask[i])
  min_value = np.min(img5[i])
  max_value = np.max(img5[i])
  img5[i] = (img5[i] - min_value) / (max_value - min_value) * 255
  img5[i] = img5[i].astype(np.uint8)
  cv2.imwrite(f'ex5_{i}.png', img5[i])

課題5

課題1で作成した 0番目の画像をエンボス化した画像 ex5_0.png, ex5_1.png, ex5_2.png を出力してください。
※ それぞれ、暗い部分が盛り上がっていて、左・上・左上から光が当たっているような結果になります。

提出

動画の解説を参照

今回作成したノートブックを「imgprc2024@gmail.com」と共有してください。
※ 第2回で共有した、画像共有用の「画像処理」フォルダの中に「第9回」フォルダを作り、その中に今回の元画像と出力画像を入れてください。
※ 課題の再提出の際は、ノートブックの再度の共有はせずにチャットで連絡してください。