前回の続き。DAZ3D studio の画像をアニメ塗りにする画像処理の試行です。
色々確かめながら良い方法を探っています。
前回からの修正
リストから一番近い値をとる関数は uint8 同士の比較で問題があった(マイナス時にアンダーフローする)ので修正。
リスト内のベクトルから一番近いベクトルを返す関数を追加。
元画像
今回は前回のモデルと、ちょっと色合いの多いモデルを用意した。
また特定の方向から光を当てるのをやめて環境光にした。
改めてRGBベクトルとHSVベクトルをクラスタリング。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
import os import cv2 import numpy as np import argparse def getN(n, data): if isinstance(n, (np.uint8, np.float32, int, float)): return data[np.abs(np.asarray(data) - np.float32(n)).argmin()] elif isinstance(n, (np.ndarray, list)): idx = ( ((np.asarray(data) - np.asarray(n).astype(np.float32)) ** 2) .sum(axis=1) .argmin() ) return data[idx] else: print("\033[31m --- ERROR \033[0m") def hsv2rgb(h, s, v): return cv2.cvtColor(cv2.merge((h, s, v)), cv2.COLOR_HSV2BGR) def hsv2rgba(h, s, v, mask): return cv2.merge((hsv2rgb(h, s, v), mask)) def main(): parser = argparse.ArgumentParser() parser.add_argument("file", type=str) parser.add_argument("k", type=int) args = parser.parse_args() print(f"input = {args.file}, k = {args.k:02}") output_dir = "output/" + os.path.basename(args.file) + f"/rgb_hsv/" os.makedirs(output_dir, exist_ok=True) img = cv2.imread(args.file, cv2.IMREAD_UNCHANGED) mask = img[:, :, 3] img = img[:, :, :3] hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # RGB => HSV k = args.k """ RGBのクラスタリング """ criteria = cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 10, 1.0 _, _, centers_rgb = cv2.kmeans( img[(mask != 0)].astype(np.float32), k, None, criteria, attempts=10, flags=cv2.KMEANS_RANDOM_CENTERS, ) clst_rgb = centers_rgb.astype(np.uint8) """ HSVのクラスタリング """ _, _, centers_hsv = cv2.kmeans( hsv[(mask != 0)].astype(np.float32), k, None, criteria, attempts=10, flags=cv2.KMEANS_RANDOM_CENTERS, ) clst_hsv = centers_hsv.astype(np.uint8) """ 再構築 """ imgx = np.zeros(img.shape, dtype="uint8") hsvx = np.zeros(img.shape, dtype="uint8") for i in range(img.shape[0]): # y for j in range(img.shape[1]): # x if mask[i][j] != 0: imgx[i][j] = getN(img[i][j], clst_rgb) hsvx[i][j] = getN(hsv[i][j], clst_hsv) cv2.imwrite(output_dir + f"rgb_k={k:02}.png", cv2.merge((imgx, mask))) cv2.imwrite( output_dir + f"hsv_k={k:02}.png", cv2.merge((cv2.cvtColor(hsvx, cv2.COLOR_HSV2BGR), mask)), ) if __name__ == "__main__": main() |
RGBだと明るさの影響が強く、HSVだと色合いで分離できているように思えます。
RGBだと素材、HSVだとテクスチャとも考えられる。
方針
やりたいことからするとHSVの方が向いていますが、Hueの値がクラスタリング向きでないです。
色相(H)は0-180の値で0と180の色が同じになっています。
0と179がほとんど変わらないのに値として離れているのが問題。
この影響を他に与えないために、色相 ( Hue ) だけ先にクラスタリングして、色相ごとにSV空間のクラスタリングをしてみます。
色相をどれくらい分けるか
HSVはこんな感じで表現される。
彩度 (S) が小さい、また明度 (V) が小さい場合は少し他と分ける必要があるので次のように色相 (H) 分離テスト。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
RANGE_S = (25, 256) RANGE_V = (25, 256) print(h[mask != 0].shape) print( h[ (mask != 0) & (RANGE_S[0] < s) & (s < RANGE_S[1]) & (RANGE_V[0] < v) & (v < RANGE_V[1]) ].shape ) MAX_SIZE = h[ (mask != 0) & (RANGE_S[0] < s) & (s < RANGE_S[1]) & (RANGE_V[0] < v) & (v < RANGE_V[1]) ].size print(MAX_SIZE) # return """ hueのクラスタリング """ k = args.hk criteria = cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS, 10, 1.0 _, labels, centers_h = cv2.kmeans( h[ (mask != 0) & (RANGE_S[0] < s) & (s < RANGE_S[1]) & (RANGE_V[0] < v) & (v < RANGE_V[1]) ].astype(np.float32), k, None, criteria, attempts=10, flags=cv2.KMEANS_RANDOM_CENTERS, ) clst_h = centers_h.ravel().astype(np.uint8) for ki in range(k): print( f"{ki:02} - {np.count_nonzero(labels == ki):9} - {np.count_nonzero(labels == ki)/(MAX_SIZE/k):2.5}" ) colors = [] for i in range(k): c = np.uint8([i * 180 / k, 255, 255]) colors.append(c) hx = np.zeros(v.shape, dtype="uint8") sx = np.zeros(v.shape, dtype="uint8") vx = np.zeros(v.shape, dtype="uint8") sum = 0 for i, r_v in enumerate(v): # y for j, r_c in enumerate(r_v): # x if ( (mask[i][j] != 0) & (RANGE_S[0] < s[i][j]) & (s[i][j] < RANGE_S[1]) & (RANGE_V[0] < v[i][j]) & (v[i][j] < RANGE_V[1]) ): sum += 1 idx = np.abs(clst_h - np.float32(h[i][j])).argmin() hx[i][j] = colors[idx][0] sx[i][j] = colors[idx][1] vx[i][j] = colors[idx][2] |
わかりやすくするために色はクラスタごとに分けている。
1枚目は K=4 、2枚目は K=5 くらいがよさそうです。
SVクラスタリング
あとはHUEごとにSVベクトルを k を変えながらクラスタリングする。
合成
k=1の画像をベースにk=2,4の画像をぼかして不透明度50%でオーバーレイ。
元画像の輝度を50%で影を補完して、ハイパスとコピーフィルターで輪郭をパリッとさせます。
いい感じです。
とりあえず満足いくレベルにはなってきたので一旦ここまで。
Thank you very much for the information https://www.gate.io/de/price/utopia-genesis-foundation-uop/sar