
抹茶もなかさんの自動ベタ塗りを試してみた
15ページ分もベタ塗りするの無理…(死)

と言う事でどうしても自動ベタ塗り機能が欲しくて、抹茶もなかさんの公開している自動ベタ塗りを試してみました
https://zenn.dev/mattyamonaca/articles/f3320e0e170d80
コードとか読めない!

と言うことでチャットGPTに聞いてみた所、コントロールネットのユニットを2個付ければ結構線画に忠実なベタ塗り機能が作れるということらしい。
ということで早速やってみます。
まず線画を用意して…

ポジティブプロンプトに自分で作ったベタ塗りlolaと、キャラクター専用のベタ塗りLola、月須和・那々 (2vXpSwA7)さんの作った<lora:noline-16oa:1>,no lineart,flat color,を入れる。あとは髪の色とか服の色を入れます。
https://note.com/2vxpswa7/n/n046c3e928f1e
negativepromptには
line art,lineart,shadow,
を入れます。
で、抹茶もなかさん制作のline2lineを入れるのですが、5ギガのコントロールネットは私の環境じゃstablediffusionが壊れる(anytest で壊れたことがある)ので断念。
なのでlineartで代用する事にします。
Denoising strength 7.5
unit1は
control net
lineart
control weight 1.2
unit2は
control net
lineart
control weight 0.3
ステップ数は30
でやってみます。
すると…出来た!


かなり線画に忠実!
これをチャットGPTにヤレと指示して、1枚の生成画像を色ごとにレイヤー分割しました。
コードはこちら。
フォルダとか色々入力しないと動かないと思います。
私もチャットGPTで作っただけの素人なんで、分かる人は適当に試してみて下さい。
import cv2
import numpy as np
from collections import defaultdict
import os
# 入力フォルダと出力フォルダのパスを指定
input_folder = r"W:\\input"
output_folder = r"W:\\output"
if not os.path.exists(output_folder):
os.makedirs(output_folder)
# 入力フォルダ内のすべての画像ファイルを処理
for filename in os.listdir(input_folder):
if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
input_path = os.path.join(input_folder, filename)
# 画像を読み込む
image = cv2.imread(input_path, cv2.IMREAD_UNCHANGED) # 透過情報も保持
if image is None:
print(f"読み込み失敗: {input_path}")
continue
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # OpenCVはBGRなのでRGBに変換
# 画像の形状を取得
h, w, c = image.shape
pixels = image.reshape((-1, 3))
# K-meansクラスタリングで色をグループ化
K = 6 # 色の数(適宜調整)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1.0)
_, labels, centers = cv2.kmeans(np.float32(pixels), K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
# 各色ごとのマスクを作成
masks = defaultdict(lambda: np.zeros((h, w, 4), dtype=np.uint8))
for i, label in enumerate(labels.flatten()):
x, y = i % w, i // w
color = tuple(map(int, centers[label])) # 色
masks[label][y, x] = (*color, 255) # RGBAにして透明度を255
# 各マスクを1px縮小してマスク外を削除
kernel = np.ones((3, 3), np.uint8)
for i, mask in masks.items():
mask_rgb = cv2.cvtColor(mask, cv2.COLOR_RGBA2RGB)
mask_gray = cv2.cvtColor(mask_rgb, cv2.COLOR_RGB2GRAY)
_, mask_binary = cv2.threshold(mask_gray, 1, 255, cv2.THRESH_BINARY)
mask_eroded = cv2.erode(mask_binary, kernel, iterations=1)
mask_filled = cv2.bitwise_and(mask_rgb, mask_rgb, mask=mask_eroded)
mask_filled = cv2.cvtColor(mask_filled, cv2.COLOR_RGB2RGBA)
mask_filled[:, :, 3] = mask_eroded # アルファチャンネルを更新
masks[i] = mask_filled
# 各マスクを1px拡張してベタ塗り
for i, mask in masks.items():
mask_rgb = cv2.cvtColor(mask, cv2.COLOR_RGBA2RGB)
mask_gray = cv2.cvtColor(mask_rgb, cv2.COLOR_RGB2GRAY)
_, mask_binary = cv2.threshold(mask_gray, 1, 255, cv2.THRESH_BINARY)
mask_dilated = cv2.dilate(mask_binary, kernel, iterations=1)
mask_filled = np.zeros_like(mask_rgb)
mask_filled[mask_dilated == 255] = centers[i] # 拡張された部分を元の色で塗りつぶす
mask_filled = cv2.cvtColor(mask_filled, cv2.COLOR_RGB2RGBA)
mask_filled[:, :, 3] = mask_dilated # アルファチャンネルを更新
masks[i] = mask_filled
# ゴミ取り機能
for i, mask in masks.items():
mask_gray = cv2.cvtColor(mask, cv2.COLOR_RGBA2GRAY)
_, mask_binary = cv2.threshold(mask_gray, 1, 255, cv2.THRESH_BINARY)
num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(mask_binary, connectivity=8)
sizes = stats[1:, -1]
min_size = 200 # ゴミとみなす最小サイズ(適宜調整)
mask_cleaned = np.zeros_like(mask_binary)
for j in range(1, num_labels):
if sizes[j - 1] >= min_size:
mask_cleaned[labels == j] = 255
mask_filled = cv2.bitwise_and(mask, mask, mask=mask_cleaned)
masks[i] = mask_filled
# 隙間塗りつぶし機能
for i, mask in masks.items():
mask_gray = cv2.cvtColor(mask, cv2.COLOR_RGBA2GRAY)
_, mask_binary = cv2.threshold(mask_gray, 1, 255, cv2.THRESH_BINARY)
mask_filled = cv2.morphologyEx(mask_binary, cv2.MORPH_CLOSE, kernel, iterations=2)
mask_filled = cv2.cvtColor(mask_filled, cv2.COLOR_GRAY2RGBA)
mask_filled[:, :, :3] = mask[:, :, :3] # 元の色を保持
mask_filled[:, :, 3] = mask[:, :, 3] # アルファチャンネルを保持
masks[i] = mask_filled
# 各マスクをレイヤーとして保存
for i, mask in masks.items():
output_path = os.path.join(output_folder, f"{os.path.splitext(filename)[0]}_layer_{i}.png")
cv2.imwrite(output_path, cv2.cvtColor(mask, cv2.COLOR_RGBA2BGRA)) # OpenCVはBGRAで保存
print(f"Saved: {output_path}")
で、線画と合成してみると・・・
抹茶もなかさんが言ってた通りあんまり線画とあってない!

でももう少しで出来そうな雰囲気ですね。