動画の解説を参照
今回は実写の画像を元にして、色数が少なく輪郭の線がある漫画のような画像を作成します。
そのために、第5回に紹介した「メディアンフィルタ」を使って細部の構造を潰します。
import cv2
import numpy as np
from google.colab import files
file = files.upload()
filename = next(iter(file))
img = cv2.imread(filename)
h, w = img.shape[:2]
new_w = 800
new_h = int(h * (new_w / w))
img1 = cv2.resize(img, (new_w, new_h))
img1 = cv2.medianBlur(img1, 9)
cv2.imwrite('ex1.png', img1)
元画像の選び方についての注意
実写から漫画的な画像を作るのが目的なので、元画像は実写 (風) のものにしてください。実写でも極端に色数が少ないものはNGです。
また、輪郭の線を取り出すために、背景に対して人物や建物などのオブジェクトの境界が明確なものにしてください。
例えばこのような画像です (出力画像もこのページに掲載されているので、課題の元画像としてこの画像は使わないでください)
参考 :
haconana (AI生成された画像の無料DLサイト)
上記の太枠の説明の条件を満たす元画像を元にして、横幅が800ピクセルでぼかしの入った画像 ex1.pngを出力してください。
例
動画の解説を参照
第7回で紹介した Canny法を使えば、画像から太さ1の輪郭線を取り出せます。
ただし、そのままでは漫画的な輪郭線としては細すぎて使えません。
OpenCVのdilate関数を使えば、白い線を太くできます。
以下のコードの3行目は太さを5にするものです。
img2_1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
img2_1 = cv2.Canny(img2_1, ?, ?)
img2_5 = cv2.dilate(img2_1, np.ones((5, 5), np.uint8), iterations=1)
cv2.imwrite('ex2_1.png', img2_1)
cv2.imwrite('ex2_5.png', img2_5)
動画の解説を参照
一般的なカラー画像は、赤・緑・青の成分の明るさが0~255の256段階で、\(256^3=(2^8)^3=2^{24}=16,777,216\) 通りの色があります。
これを赤・緑・青それぞれ2段階 (0か255)にすると以下の8色になります。
| 赤 |
緑 |
青 |
色 |
| 0 |
0 |
0 |
黒 |
| 255 |
0 |
0 |
赤 |
| 0 |
255 |
0 |
緑 |
| 0 |
0 |
255 |
青 |
| 255 |
255 |
0 |
黄 |
| 255 |
0 |
255 |
マゼンタ |
| 0 |
255 |
255 |
シアン |
| 255 |
255 |
255 |
白 |
元のそれぞれの色成分 (0~255) を128で割った値の整数部分は、元の値が0~127なら 0, 128~255なら 1 になります。
それに255をかければ、「128より暗いところは0, そうでなければ255」が得られます。
それぞれの色成分について二値化を行うようなものです。
img3_2 = np.uint8(img1 / 128) * 255
同様に、それぞれの色成分を4段階にすると \(4^3=(2^2)^3=2^{6}=64\) 色になります。
それぞれの色成分は、0~255の値を64で割った整数部分 0~3 に 255/3 をかけることで得られます。
img3_4 = np.uint8(img1 / 64) * np.uint8(255 / 3)
さきほどの画像と比べると「使える絵の具」が増えるため、元の画像の色に近くなります。
img3_2 = np.uint8(img1 / 128) * 255
img3_4 = np.uint8(img1 / 64) * np.uint8(255 / 3)
cv2.imwrite('ex3_2.png', img3_2)
cv2.imwrite('ex3_4.png', img3_4)
課題1で作成した画像を元にして、赤・緑・青の各成分を2段階に減色した画像 ex3_2.png, 4段階に減色した画像 ex3_4.png を出力してください。
※ ex3_2.png よりも ex3_4.png の方が元の画像の色に近くなるはずです。
動画の解説を参照
前項の方法でそれぞれ8色、64色に減色した画像を作りましたが、色数を増やしてもそれほど元画像の色には近くなりません。
これは、「使わない色の絵の具」を増やしてしまったせいです。
k平均法 (
参考) では、画像に存在するピクセルを色空間内に配置してその塊 (クラスター) の中心を「絵の具」として使います。
結果として元画像で多く使われている色に近い結果が得られます。
下のコードの
?が減色後の色数です。
from sklearn.cluster import KMeans
img4 = img1.copy()
model = KMeans(n_clusters=?)
X = img4.reshape((new_w * new_h, 3))
model.fit(X)
KMeans(n_clusters=8, random_state=0)
Y = model.predict(X)
centers = np.zeros(model.cluster_centers_.shape, dtype='uint8')
for i in range(0, model.cluster_centers_.shape[0]):
temp = centers[i] = model.cluster_centers_[i]
for j in range(0, model.cluster_centers_.shape[1]):
centers[i][j] = int(temp[j])
img4 = np.empty((0, 3), dtype=np.uint8)
for i in range(0, Y.shape[0]):
img4 = np.append(img4, np.array([centers[Y[i]]]), axis=0)
img4 = img4.reshape((new_h, new_w, 3))
cv2.imwrite('ex4.png', img4)
下の画像はk平均法でそれぞれ8色, 64色に減色したものです。前項の同じ色数の2つの画像と比べると、元画像に近い色になっていることがわかります。
動画の解説を参照
前項の方法で得た減色画像 (img4) に、第2項で得た太枠の画像 (img2_5) を合成すれば、漫画のような画像ができます。
そのために、まずグレースケール画像である img2_5 から、カラーの img4 と同じ形式の画像 img_frame を作ります (色は白黒のままで、形式を変換するだけです)。
img_frame = cv2.cvtColor(img2_5, cv2.COLOR_GRAY2BGR)
枠の色が白なので、これらをそのまま合成するとこのようになってしまいます。
そこで、「img4 をベースとして、img_frameの同じ位置の点を調べ、その点が白なら出力画像のその位置を指定の明るさの灰色にし、そうでなければなにもしない」という処理を行います。
ここで使用するwhere関数の第3引数はベースとする画像、第1引数は変更を行う条件、第2引数は条件を満たしたときに変更する色です。
img5 = np.where(img_frame == 255, ?, img4)
第2引数を 128, 0 にするとそれぞれこのような画像ができます。
img_frame = cv2.cvtColor(img2_5, cv2.COLOR_GRAY2BGR)
img5 = np.where(img_frame == 255, ?, img4)
cv2.imwrite('ex5.png', img5)
動画の解説を参照
今回作成したノートブックを「imgprc2024@gmail.com」と共有してください。
※ 第2回で共有した、画像共有用の「画像処理」フォルダの中に「第8回」フォルダを作り、その中に今回の元画像と出力画像を入れてください。
※ 課題の再提出の際は、ノートブックの再度の共有はせずにチャットで連絡してください。