Skip to content

Instantly share code, notes, and snippets.

@tori29umai0123
Created June 1, 2024 02:37
Show Gist options
  • Save tori29umai0123/f4ed0fb1972a73cd42a9e620fa055763 to your computer and use it in GitHub Desktop.
Save tori29umai0123/f4ed0fb1972a73cd42a9e620fa055763 to your computer and use it in GitHub Desktop.
noLine2.py
#線画あり画像から、線画を取り除くスクリプト。線画を取り除きたい画像(image_dir)があれば動く。線画入力不要。
from PIL import Image, ImageFilter, ImageOps
from collections import defaultdict
from skimage import color as sk_color
from tqdm import tqdm
from skimage.color import deltaE_ciede2000, rgb2lab
import cv2
import numpy as np
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from PIL import Image
import os
def DoG_filter(image, kernel_size=0, sigma=1.0, k_sigma=2.0, gamma=1.5):
g1 = cv2.GaussianBlur(image, (kernel_size, kernel_size), sigma)
g2 = cv2.GaussianBlur(image, (kernel_size, kernel_size), sigma * k_sigma)
return g1 - gamma * g2
def XDoG_filter(image, kernel_size=0, sigma=1.4, k_sigma=1.6, epsilon=0, phi=10, gamma=0.98):
epsilon /= 255
dog = DoG_filter(image, kernel_size, sigma, k_sigma, gamma)
dog /= dog.max()
e = 1 + np.tanh(phi * (dog - epsilon))
e[e >= 1] = 1
return (e * 255).astype('uint8')
def binarize_image(image):
_, binarized = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
return binarized
def process_XDoG(image_path):
kernel_size=0
sigma=1.4
k_sigma=1.6
epsilon=0
phi=10
gamma=0.98
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
xdog_image = XDoG_filter(image, kernel_size, sigma, k_sigma, epsilon, phi, gamma)
binarized_image = binarize_image(xdog_image)
final_image = Image.fromarray(binarized_image)
return final_image
def replace_color(image, color_1, blur_radius=2):
data = np.array(image)
original_shape = data.shape
data = data.reshape(-1, 4)
color_1 = np.array(color_1)
matches = np.all(data[:, :3] == color_1, axis=1)
nochange_count = 0
mask = np.zeros(data.shape[0], dtype=bool)
while np.any(matches):
new_matches = np.zeros_like(matches)
match_num = np.sum(matches)
for i in tqdm(range(len(data))):
if matches[i]:
x, y = divmod(i, original_shape[1])
neighbors = [
(x, y-1), (x, y+1), (x-1, y), (x+1, y)
]
valid_neighbors = []
for nx, ny in neighbors:
if 0 <= nx < original_shape[0] and 0 <= ny < original_shape[1]:
ni = nx * original_shape[1] + ny
if not np.all(data[ni, :3] == color_1, axis=0):
valid_neighbors.append(data[ni, :3])
if valid_neighbors:
new_color = np.mean(valid_neighbors, axis=0).astype(np.uint8)
data[i, :3] = new_color
data[i, 3] = 255
mask[i] = True
else:
new_matches[i] = True
matches = new_matches
if match_num == np.sum(matches):
nochange_count += 1
if nochange_count > 5:
break
data = data.reshape(original_shape)
mask = mask.reshape(original_shape[:2])
result_image = Image.fromarray(data, 'RGBA')
blurred_image = result_image.filter(ImageFilter.GaussianBlur(radius=blur_radius))
blurred_data = np.array(blurred_image)
np.copyto(data, blurred_data, where=mask[..., None])
return Image.fromarray(data, 'RGBA')
def generate_distant_colors(consolidated_colors, distance_threshold):
consolidated_lab = [rgb2lab(np.array([color], dtype=np.float32) / 255.0).reshape(3) for color, _ in consolidated_colors]
max_attempts = 10000
for _ in range(max_attempts):
random_rgb = np.random.randint(0, 256, size=3)
random_lab = rgb2lab(np.array([random_rgb], dtype=np.float32) / 255.0).reshape(3)
if all(deltaE_ciede2000(base_color_lab, random_lab) > distance_threshold for base_color_lab in consolidated_lab):
return tuple(random_rgb)
return (128, 128, 128)
def consolidate_colors(major_colors, threshold):
colors_lab = [rgb2lab(np.array([[color]], dtype=np.float32)/255.0).reshape(3) for color, _ in major_colors]
i = 0
while i < len(colors_lab):
j = i + 1
while j < len(colors_lab):
if deltaE_ciede2000(colors_lab[i], colors_lab[j]) < threshold:
if major_colors[i][1] >= major_colors[j][1]:
major_colors[i] = (major_colors[i][0], major_colors[i][1] + major_colors[j][1])
major_colors.pop(j)
colors_lab.pop(j)
else:
major_colors[j] = (major_colors[j][0], major_colors[j][1] + major_colors[i][1])
major_colors.pop(i)
colors_lab.pop(i)
continue
j += 1
i += 1
return major_colors
def get_major_colors(image, threshold_percentage=0.01):
if image.mode != 'RGB':
image = image.convert('RGB')
color_count = defaultdict(int)
for pixel in image.getdata():
color_count[pixel] += 1
total_pixels = image.width * image.height
major_colors = [(color, count) for color, count in color_count.items() if (count / total_pixels) >= threshold_percentage]
return major_colors
def line_color(image, mask, new_color):
data = np.array(image)
data[mask, :3] = new_color
return Image.fromarray(data, 'RGBA')
def process_single_image(args):
filename, image_dir, output_dir = args
image_path = os.path.join(image_dir, filename)
output_path = os.path.join(output_dir, filename)
if not os.path.exists(output_path):
image = Image.open(image_path).convert('RGBA')
lineart = process_XDoG(image_path).convert('L')
replace_color_image = main(image, lineart)
replace_color_image.save(output_path)
return f"{filename} processed."
return f"{filename} skipped."
def process_images_concurrently(image_dir, output_dir, max_workers):
files = [f for f in os.listdir(image_dir) if f.endswith('.webp')]
args = [(filename, image_dir, output_dir) for filename in files]
with ThreadPoolExecutor(max_workers) as executor:
futures = [executor.submit(process_single_image, arg) for arg in args]
for future in as_completed(futures):
print(future.result())
def main(image, lineart):
lineart = lineart.point(lambda x: 0 if x < 200 else 255)
lineart = ImageOps.invert(lineart)
kernel = np.ones((3, 3), np.uint8)
lineart = cv2.dilate(np.array(lineart), kernel, iterations=1)
lineart = Image.fromarray(lineart)
mask = np.array(lineart) == 255
major_colors = get_major_colors(image, threshold_percentage=0.05)
major_colors = consolidate_colors(major_colors, 10)
new_color_1 = generate_distant_colors(major_colors, 100)
filled_image = line_color(image, mask, new_color_1)
replace_color_image = replace_color(filled_image, new_color_1, 2).convert('RGB')
# replace_color_imageをブラー処理
return replace_color_image
if __name__ == '__main__':
image_dir = 'D:/webp'
output_dir = 'D:/not_Line2'
max_workers = 12
if not os.path.exists(output_dir):
os.makedirs(output_dir)
process_images_concurrently(image_dir, output_dir, max_workers)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment