From 2976682319a3f3080721fe7e6eb65ac54da74471 Mon Sep 17 00:00:00 2001 From: mmcwilliams Date: Sat, 25 Nov 2023 09:40:13 -0500 Subject: [PATCH] Perform closest color match and composite with selected colors --- py/.gitignore | 1 + py/pallete_schema.py | 37 +++++++++++++++++++------- py/posterize.py | 45 ++++++++++++++++++++++---------- py/{separator.py => separate.py} | 4 +-- 4 files changed, 62 insertions(+), 25 deletions(-) rename py/{separator.py => separate.py} (96%) diff --git a/py/.gitignore b/py/.gitignore index 84f50a4..5b2bf21 100644 --- a/py/.gitignore +++ b/py/.gitignore @@ -1,3 +1,4 @@ env __pycache__ *.png +output/* \ No newline at end of file diff --git a/py/pallete_schema.py b/py/pallete_schema.py index 7c1260f..7bc1fdd 100644 --- a/py/pallete_schema.py +++ b/py/pallete_schema.py @@ -43,16 +43,35 @@ class PalleteSchema : with open(filepath, 'w') as outfile : outfile.write(jsonstr) - def closest (self, comparison, space = 'BGR', pallete = None) : - p = pallete if pallete is not None else self.pallete - colors = normalize_colors(space, p) + def closest (self, comparison, space = 'BGR', colors = None) : + c = colors if colors is not None else self.colors + colors = normalize_colors(space, c) + if space == 'RGB' or space == 'BGR' : + closest, dist = closest_color_weighted_euclidean(colors, comparison, space) + else : + closest, dist = closest_color_euclidean(colors, comparison) + print(f'Color [{space}] {comparison} closest to {closest} [{dist}]') + return closest - def normalize_colors (self, space = 'BGR', pallete = None) : - colors = [] - p = pallete if pallete is not None else self.pallete - for color in p : - colors.append(convert_color(color['color'], color['space'], space)) - return colors + def closest_exclusive (self, comparisons, space = 'BGR', colors = None) : + c = colors if colors is not None else self.colors + colors = normalize_colors(space, c) + exclusive = [] + for comparison in comparisons : + if space == 'RGB' or space == 'BGR' : + closest, dist = closest_color_weighted_euclidean(colors, comparison, space) + else : + closest, dist = closest_color_euclidean(colors, comparision) + colors = remove_from_list(colors, closest) + exclusive.append(closest) + return exclusive + + def normalize_colors (self, space = 'BGR', colors = None) : + normalized = [] + c = colors if colors is not None else self.colors + for color in c : + normalized.append(convert_color(color['color'], color['space'], space)) + return normalized diff --git a/py/posterize.py b/py/posterize.py index a331125..964a664 100644 --- a/py/posterize.py +++ b/py/posterize.py @@ -2,13 +2,15 @@ from sklearn.cluster import MiniBatchKMeans import numpy as np import argparse import cv2 -from common import convert_color, closest_color, create_colored_image, remove_from_list, list_match +from common import convert_color, closest_color_weighted_euclidean, closest_color_euclidean, create_colored_image, remove_from_list, list_match +import os class Posterize: """Posterize an image and then find nearest colors to use""" colors = [] colors_dict = {} original_colors = [] + layers = [] pallete = None pallete_space = 'BGR' @@ -23,7 +25,7 @@ class Posterize: white = [255, 255, 255] - output = '' + output = None def __init__ (self, image, pallete, n_colors, output) : self.image = cv2.imread(image) @@ -31,6 +33,10 @@ class Posterize: self.pallete = pallete self.n_colors = n_colors + 1 self.output = output + + if not os.path.exists(self.output) : + print(f'Output directory {self.output} does not exist, creating...') + os.makedirs(self.output) self.flatten_pallete() self.posterize() @@ -53,7 +59,6 @@ class Posterize: self.image = bgrquant - cv2.imshow('image', bgrquant) cv2.waitKey(0) cv2.destroyAllWindows() @@ -61,25 +66,36 @@ class Posterize: def determine_colors (self): reshaped = self.image.reshape(-1, self.image.shape[2]) self.original_colors = np.unique(reshaped, axis=0) - white = closest_color(self.original_colors, [255, 255, 255]) + white, white_dist = closest_color_weighted_euclidean(self.original_colors, [255, 255, 255], 'BGR') blank = create_colored_image(self.w, self.h, [255, 255, 255]) composite = create_colored_image(self.w, self.h, [255, 255, 255]) + for i in range(self.n_colors) : if list_match(self.original_colors[i], white) : continue - mask = self.extract_color_mask(self.image, self.original_colors[i]) - closest = closest_color(self.colors, self.original_colors[i]) + original = self.original_colors[i] #BGR + mask = self.extract_color_mask(self.image, original) + original_normalized = convert_color(original, 'BGR', self.pallete_space) + if self.pallete_space == 'RGB' or self.pallete_space == 'BGR' : + closest, dist = closest_color_weighted_euclidean(self.colors, original_normalized, self.pallete_space) + else : + closest, dist = closest_color_euclidean(self.colors, original_normalized) self.colors = remove_from_list(self.colors, closest) name = self.match_color_name(closest) - - cv2.imwrite(f'{name}.png', mask) - - color_mat = create_colored_image(self.w, self.h, closest) - color_mask = cv2.bitwise_or(color_mat, color_mat, mask = (255-mask)) - - - + layer_name = f'{name}.png' + output_layer = os.path.join(self.output, layer_name) + cv2.imwrite(output_layer, mask) + self.layers.append({ + 'layer' : output_layer, + 'color' : closest, + 'space' : self.pallete_space + }) + #color_mat = create_colored_image(self.w, self.h, closest) + #color_mask = cv2.bitwise_or(color_mat, color_mat, mask = (255-mask)) + #color_mask = cv2.inRange(mask, np.array([0, 0, 0]), np.array([1, 1, 1])) + mask = cv2.bitwise_not(mask) + composite[mask > 0] = np.array(closest) cv2.imshow('image', composite) cv2.waitKey(0) @@ -97,3 +113,4 @@ class Posterize: def match_color_name (self, key) : return self.colors_dict[f'{key[0]},{key[1]},{key[2]}'] + diff --git a/py/separator.py b/py/separate.py similarity index 96% rename from py/separator.py rename to py/separate.py index 391cef0..a619263 100644 --- a/py/separator.py +++ b/py/separate.py @@ -10,7 +10,7 @@ parser.add_argument('colors', type=int, help='Number of colors to separate into' parser.add_argument('pallete', type=str, help='Pallete file') parser.add_argument('output', type=str, help='Output dir to write to') -class Separator : +class Separate : input = '' output = '' pallete = None @@ -33,5 +33,5 @@ class Separator : if __name__ == "__main__" : args = parser.parse_args() - Separator(args) + Separate(args)