概要
ドット絵とはレトロゲーム等に見られる描画方法で, 低スペックのゲーム機内でそれっぽいものを表現する手法です.
現在ではゲーム内の表現手法の一つとして確立しています.
入れた画像をドット絵調にするコードを書きました.
ドット絵の特徴
ドット絵の特徴として
- 低画素
- 色数が絞ってある(カラーパレット)
- わかりやすい画調
が挙げられます.
それぞれに対し, 「画像の縮小」, 「K-means」, 「輪郭描画+ハイコントラスト」で対応させます.
プログラムの流れ
背景削除
まず用意した画像の背景を削除します.
powerpointの「背景を削除」という機能が便利で, よく使っています.
一応OpenCVにもcv2.grabCut
という背景を削除する関数が用意されています.
アルファチャンネル(透過情報)込みの状態で読み込むためには
img_original = cv2.imread(PATH_IMG, -1)
とすればOKです.
低画素 - 画像の縮小
まずは, 画像を縮小させ, 画素が少ない状態にします.
img_small = cv2.resize(img, dsize=size_img)
画像がカクカクになったのがわかるかと思います.
色数を絞る - K-means
画像一枚に使われる色数を絞ります.
頻度が多い色はひとまとまりにして同じ色に上書きするイメージです.
そのようなクラスタリングを実現させるために, 教師なしクラスタリング手法の一つであるK-meansを行います.
各画素がBGRではられた3次元空間上の点とみなし, その距離の近いものにまとめます
OpenCV内にもK-meansがあるようですが, 今回はscikit-learnで色を絞っていきます.
透過度が高い箇所が悪さしないように, ちょっと複雑なコードになっています.
#透過二値変換
is_invisible = (img_small[:,:,3]<128)
img_small[is_invisible==True] = np.array([0,0,0,0],dtype=np.uint8)
img_small[:,:,3][is_invisible==False] = 255
#カラーパレット削減
pred = KMeans(n_clusters=NUMBER_PALETTE-1).fit_predict(img_small[:,:,:3][is_invisible==False])
colors = np.zeros((NUMBER_PALETTE-1,4))
for i in range(NUMBER_PALETTE-1):
if len(img_small[is_invisible==False][pred==i])>0:
colors[i] = np.average(img_small[is_invisible==False][pred==i],axis=0)
else:
colors[i] = np.array([0,0,0,0])
img_palette = img_small.copy()
img_palette[is_invisible==False] = np.array( [colors[i] for i in pred] ,dtype=np.uint8)
わかりやすい画調 - 輪郭描画・ハイコントラスト
輪郭取得はCanny法が有名で, それっぽい感じになるのでオススメ
ハイコントラストは画像の色を線形変換すればOKです.
# コントラスト
img_tmp = (img_palette[:,:,:3].astype(int)-128)*contrast+128+offset
img_tmp = (np.clip(img_tmp,0,255)+0.5).astype(np.uint8)
# 輪郭
img_canny_bgr = cv2.Canny(img_small[:,:,:3],c_min,c_max)
img_canny_a = cv2.Canny(img_small[:,:,3],c_min,c_max)
img_canny = (img_canny_bgr==255)+(img_canny_a==255)
#表示
img_res[:,:,:3] = img_tmp
img_res[img_canny] = COLOR_OUTLINE
imgShow(bool*img_res,size=4)