File size: 5,059 Bytes
ff2a834
f490375
 
54cb6c5
f490375
ff2a834
f490375
 
 
 
 
 
 
 
 
 
 
b4734e1
 
 
 
 
 
 
 
 
 
 
dc882c7
 
 
ff2a834
 
7fef374
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ff2a834
 
db4d0c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ff2a834
7fef374
 
54cb6c5
ff2a834
 
 
 
 
 
 
 
 
 
 
 
7fef374
 
 
 
 
 
 
 
 
 
 
 
 
4112803
 
7fef374
ff2a834
7fef374
 
ff2a834
 
7fef374
 
 
 
 
 
ff2a834
 
7fef374
 
 
 
ff2a834
7fef374
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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