Color-By-Number / colorbynumber /simplify_image.py
Sagar Bharadwaj
Added image downsampling
db4d0c4
import cv2 as cv
import numpy as np
from .config import default_config
def _choose_closest_colors(image, color_list):
"""
Converts all colors in an image to the closest color in the color list.
Args:
image: Image in the RGB color space as a 3D array.
color_list: A list of tuples representing RGB values of allowed colors.
Returns:
A copy of the image with all colors replaced with the closest color in the list.
"""
width, height, channels = image.shape
image_copy = image.reshape((width, height, 1, channels)).copy()
color_list = np.array(color_list)
num_colors = color_list.shape[0]
color_list_broadcastable = color_list.reshape((1, 1, num_colors, 3))
norm_diff = ((image_copy - color_list_broadcastable)**2).sum(axis = -1)
indices_color_choices = norm_diff.argmin(axis = -1)
simplified_image = color_list[indices_color_choices.flatten(), :].reshape(image.shape)
# Adding 1 to indices_color_choices as so the first color is labeled as 1 and not 0.
indices_color_choices = indices_color_choices + 1
return simplified_image, indices_color_choices
def _kmeans_simplify_image(image, num_colors):
Z = image.reshape((-1,3))
# convert to np.float32
Z = np.float32(Z)
# define criteria, number of clusters(K) and apply kmeans()
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = num_colors
ret,label,center=cv.kmeans(Z,K,None,criteria,10,cv.KMEANS_RANDOM_CENTERS)
# Now convert back into uint8, and make original image
center = np.uint8(center)
res = center[label.flatten()]
res = res.reshape((image.shape))
simplified_image = res
indices_color_choices = label.reshape((image.shape[:2])) + 1
color_list = center
return simplified_image, indices_color_choices, color_list
def _denoise_image(image, h, denoise_type, blur_size = None):
if denoise_type == "fastNlMeansDenoisingColored":
denoised_image = cv.fastNlMeansDenoisingColored(
src = image.astype(np.uint8),
dst = None,
h = h,
hColor = h,
templateWindowSize = 7,
searchWindowSize = 21
)
elif denoise_type == "gaussianBlur":
kernel = (blur_size, blur_size)
denoised_image = cv.GaussianBlur(image, kernel, 0)
elif denoise_type == "blur":
kernel = (blur_size, blur_size)
denoised_image = cv.blur(image, kernel)
return denoised_image
def downsample_image(image):
"""
Downsample the image so the max dimension is 1000 pixels.
"""
max_dim = 1000
width, height = image.shape[:2]
if width > height:
new_width = max_dim
new_height = int(height * (new_width / width))
else:
new_height = max_dim
new_width = int(width * (new_height / height))
image = cv.resize(image, (new_height, new_width), interpolation = cv.INTER_AREA)
return image
def simplify_image(image,
color_list = None,
num_colors = None,
config = default_config,
):
"""
Converts all colors in an image to the closest color in the color list.
Denoises if required.
Args:
image: Image in the RGB color space as a 3D array.
color_list: A list of tuples representing RGB values of allowed colors.
Returns:
A copy of the image with all colors replaced with the closest color in the list.
"""
if config["denoise"] and (config["denoise_order"] == "before_simplify"):
image = _denoise_image(
image=image,
h = config["denoise_h"],
denoise_type = config["denoise_type"],
blur_size = config["blur_size"],
)
if color_list is None:
# Use kmeans to simplify the image to the specified number of colors.
simplified_image, indices_color_choices, color_list = _kmeans_simplify_image(image, num_colors)
else:
if config["apply_kmeans"]:
image, indices_color_choices, color_list_kmeans = _kmeans_simplify_image(image, len(color_list))
simplified_image, indices_color_choices = _choose_closest_colors(image, color_list)
if config["denoise"] and (config["denoise_order"] == "after_simplify"):
# Denoising after simplifying image as denoising original image need not reduce the
# number of colors islands and also removed some key features from the original image.
simplified_image = _denoise_image(
simplified_image,
h = config["denoise_h"],
denoise_type = config["denoise_type"],
blur_size = config["blur_size"],
)
# Simplying image again as denoising may have introduced new colors.
simplified_image, indices_color_choices = _choose_closest_colors(
simplified_image,
color_list
)
return simplified_image, indices_color_choices, color_list