vettorazi commited on
Commit
52cbb9c
1 Parent(s): 49e378b

copied local files. docker initial setup

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
+
9
+ COPY . .
10
+
11
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
compColors.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #This file generates Complementary Colours palletes using three different alghoritms:
2
+ #Adjacent (https://itsphbytes.wordpress.com/2016/09/25/getting-adjacent-colors-python-code/),
3
+ #Complementary (https://www.101computing.net/complementary-colours-algorithm/) and Gradient.
4
+ #The Gradient alghoritm is based on the code from https://stackoverflow.com/questions/22607043/python-generate-color-palette-from-image
5
+
6
+ import colorsys
7
+
8
+ def gradient_colors(color1, color2):
9
+ """
10
+ It takes two colors and returns a list of 5 colors that are
11
+ complementary to the two colors. Basically is a gradient between
12
+ the two colors.
13
+ """
14
+ if color1.startswith('#'):
15
+ color1 = color1[1:]
16
+ if color2.startswith('#'):
17
+ color2 = color2[1:]
18
+ # Convert hex colors to RGB
19
+ color1_rgb = tuple(int(color1[i:i+2], 16) for i in (0, 2, 4))
20
+ color2_rgb = tuple(int(color2[i:i+2], 16) for i in (0, 2, 4))
21
+
22
+ # Calculate the difference between the two colors
23
+ diff = [c2 - c1 for c1, c2 in zip(color1_rgb, color2_rgb)]
24
+
25
+ # Generate the palette
26
+ palette = []
27
+ for i in range(5):
28
+ r = color1_rgb[0] + diff[0] * i / 4
29
+ g = color1_rgb[1] + diff[1] * i / 4
30
+ b = color1_rgb[2] + diff[2] * i / 4
31
+ palette.append('#%02x%02x%02x' % (int(r), int(g), int(b)))
32
+
33
+ return palette
34
+
35
+
36
+ def complementary_colors(hexcolor):
37
+ # Check if the input starts with '#'. If it does, remove it.
38
+ if hexcolor.startswith('#'):
39
+ hexcolor = hexcolor[1:]
40
+
41
+ # Convert hex to RGB
42
+ r = int(hexcolor[0:2], 16)
43
+ g = int(hexcolor[2:4], 16)
44
+ b = int(hexcolor[4:6], 16)
45
+
46
+ # Calculate complementary colors
47
+ comp_r = 255 - r
48
+ comp_g = 255 - g
49
+ comp_b = 255 - b
50
+
51
+ # Convert RGB back to hex
52
+ comp_hex = "#{:02X}{:02X}{:02X}".format(comp_r, comp_g, comp_b)
53
+
54
+ return comp_hex
55
+
56
+
57
+ # Module to convert rgb to hex
58
+ def rgb_to_hex(rgb):
59
+ return '#%02X%02X%02X' % (rgb)
60
+
61
+ # Module to convert hex to rgb
62
+ def hex_to_rgb(value):
63
+ value = value.lstrip('#')
64
+ return tuple(int(value[i:i+2], 16) for i in (0, 2 ,4))
65
+
66
+ # This module gets the adjacent colors.
67
+ # They can be obtained by rotating the color by 30 and 360
68
+ # degrees.
69
+ def adjacent_colors(color):
70
+ # List to hold two colors
71
+ adjacent_colors = []
72
+
73
+ # Get the RGB
74
+ (r, g, b) = hex_to_rgb(color)
75
+
76
+ # Set the degree
77
+ d = 30/360
78
+
79
+ # Values has to be between 0 to 1
80
+ r = r / 255.0
81
+ g = g / 255.0
82
+ b = b / 255.0
83
+
84
+ # Convert to HLS using colorsys
85
+ h, l, s = colorsys.rgb_to_hls(r, g, b)
86
+
87
+ # Rotate the color
88
+ h = [(h+d) % 1 for d in (-d, d)]
89
+
90
+ # For both the colors, convert back to RGB and
91
+ # load to list
92
+ for hi in h:
93
+ r, g, b = colorsys.hls_to_rgb(hi, l, s)
94
+ r = int(r * 255.0)
95
+ g = int(g * 255.0)
96
+ b = int(b * 255.0)
97
+ hex_val = rgb_to_hex((r,g,b))
98
+
99
+ adjacent_colors.append(hex_val)
100
+ print(adjacent_colors)
101
+ return adjacent_colors
102
+
103
+
104
+
105
+
106
+ # # Test complementarty colors:
107
+ # color = "#771958"
108
+ # complementary = complementary_colors(color)
109
+ # print(f"Original color: {color}")
110
+ # print(f"Complementary color: {complementary}")
111
+
112
+ # # Test adjacent colors:
113
+ # print(f"Adjacent colors for {color}:")
114
+ # colors = adjacent_colors(color)
115
+
116
+ # # Test gradient colors:
117
+ # print(f"Gradient colors between {colors[0]} and {colors[1]}:")
118
+ # gradient = gradient_colors(colors[0], colors[1])
119
+ # for color in gradient:
120
+ # print(color)
dominantColors.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #This file finds X dominant colors of a given image, using k-means and different channels of the image,
2
+ #like RGB, and luminance.
3
+
4
+ import colorsys
5
+ import math
6
+ import numpy as np
7
+ from PIL import Image
8
+ from sklearn.cluster import MiniBatchKMeans
9
+ import compColors
10
+
11
+ PALETTE_WIDTH = 800
12
+ PALETTE_HEIGHT = 200
13
+ MAX_SIZE = 500,500
14
+
15
+ def make_palette(file, n_clusters):
16
+ im = Image.open(file)
17
+ im.thumbnail(MAX_SIZE)
18
+
19
+ pixels = np.array([im.getpixel((x,y)) for x in range(0, im.size[0]) for y in range(0, im.size[1])])
20
+ clt = MiniBatchKMeans(n_clusters = n_clusters, n_init = 1)
21
+ clt.fit(pixels)
22
+ return [[int(round(i)) for i in color] for color in clt.cluster_centers_]
23
+
24
+ def perceived_brightness (r, g, b):
25
+ """
26
+ Calculates perceived brightness for the given RGB values.
27
+ code from Darel Rex Finley (http://alienryderflex.com/hsp.html)
28
+ """
29
+ return math.sqrt((.299 * r * r) + (.587 * g * g) + (.114 * b * b))
30
+
31
+ def hsp_rank (r, g, b, mult=8):
32
+ """
33
+ Combines hue, saturation, and perceived brightness info for a
34
+ smoother sort.
35
+ """
36
+ lum = perceived_brightness(r, g, b)
37
+ h, s, v = colorsys.rgb_to_hsv(r, g, b)
38
+
39
+ return (h * lum * mult, s * lum * mult, s * mult)
40
+
41
+ def findDominantColors(imageFile, numberOfColors, showImage = True):
42
+ src = imageFile
43
+ n_clusters = numberOfColors
44
+
45
+ cluster_centers = make_palette(src, n_clusters)
46
+ cluster_centers.sort(key=lambda rgb: hsp_rank(*rgb))
47
+
48
+ pixels = []
49
+ dominantColors = []
50
+ for i in range(n_clusters):
51
+ color = tuple(cluster_centers[i])
52
+ dominantColors.append(color)
53
+ # print("Color #{}: {}".format(i+1, color))
54
+ for j in range(PALETTE_WIDTH//n_clusters * PALETTE_HEIGHT):
55
+ pixels.append(color)
56
+ if showImage:
57
+ img = Image.new('RGB',(PALETTE_HEIGHT,PALETTE_WIDTH))
58
+ img.putdata(pixels)
59
+ img.show()
60
+ return dominantColors
61
+
62
+ def rgb_to_hex(rgb):
63
+ return '#{:02x}{:02x}{:02x}'.format(rgb[0], rgb[1], rgb[2])
64
+
65
+ def hex_to_rgb(hex):
66
+ hex = hex.lstrip('#')
67
+ return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))
68
+
69
+ def create_color_palette(colors, palette_width=800, palette_height=200):
70
+ """
71
+ Receives a list of colors in hex format and creates a palette image
72
+ """
73
+ pixels = []
74
+ n_colors = len(colors)
75
+ for i in range(n_colors):
76
+ color = hex_to_rgb(colors[i])
77
+ for j in range(palette_width//n_colors * palette_height):
78
+ pixels.append(color)
79
+ img = Image.new('RGB', (palette_height, palette_width))
80
+ img.putdata(pixels)
81
+ img.show()
82
+
83
+
84
+ ##############pipeline for testing
85
+ # print("Dominant colors:")
86
+ # dominantColors=findDominantColors('./estampa-test.png', 10, False)
87
+ # dominantColorHex=[rgb_to_hex(color) for color in dominantColors]
88
+ # print(dominantColorHex)
89
+ # # ['#d54b18', '#555015', '#98680e', '#1f3919', '#da8007', '#a50637', '#b50406', '#d52350', '#e2090f', '#f8545c']
recolorLinearColorTransfer.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This script performs the linear color transfer step that
2
+ # leongatys/NeuralImageSynthesis' Scale Control code performs.
3
+ # https://github.com/leongatys/NeuralImageSynthesis/blob/master/ExampleNotebooks/ScaleControl.ipynb
4
+ # Standalone script by github.com/htoyryla, and github.com/ProGamerGov
5
+ #based on this: https://github.com/ProGamerGov/Neural-Tools
6
+
7
+ import numpy as np
8
+ import argparse
9
+ import imageio
10
+ from skimage import io,transform,img_as_float
11
+ from skimage.io import imread,imsave
12
+ from PIL import Image
13
+ from numpy import eye
14
+
15
+ parser = argparse.ArgumentParser()
16
+ parser.add_argument('-t', '--target_image', type=str, help="The image you are transfering color to. Ex: target.png", required=True)
17
+ parser.add_argument('-s', '--source_image', type=str, help="The image you are transfering color from. Ex: source.png", required=True)
18
+ parser.add_argument('-o', '--output_image', default='output.png', help="The name of your output image. Ex: output.png", type=str)
19
+ parser.add_argument('-m', '--mode', default='pca', help="The color transfer mode. Options are pca, chol, or sym.", type=str)
20
+ parser.add_argument('-e', '--eps', default='1e-5', help="Your epsilon value in scientific notation or normal notation. Ex: 1e-5 or 0.00001", type=float)
21
+ parser.parse_args()
22
+ args = parser.parse_args()
23
+
24
+
25
+ Image.MAX_IMAGE_PIXELS = 1000000000 # Support gigapixel images
26
+
27
+
28
+ def main():
29
+
30
+ target_img = imageio.v2.imread(args.target_image, pilmode="RGB").astype(float)/256
31
+ source_img = imageio.v2.imread(args.source_image, pilmode="RGB").astype(float)/256
32
+
33
+ output_img = match_color(target_img, source_img, mode=args.mode, eps=args.eps)
34
+ output_img = (output_img * 255).astype(np.uint8)
35
+ imsave(args.output_image, output_img)
36
+ # imsave(args.output_image, output_img)
37
+
38
+
39
+ def match_color(target_img, source_img, mode='pca', eps=1e-5):
40
+ '''
41
+ Matches the colour distribution of the target image to that of the source image
42
+ using a linear transform.
43
+ Images are expected to be of form (w,h,c) and float in [0,1].
44
+ Modes are chol, pca or sym for different choices of basis.
45
+ '''
46
+ mu_t = target_img.mean(0).mean(0)
47
+ t = target_img - mu_t
48
+ t = t.transpose(2,0,1).reshape(3,-1)
49
+ Ct = t.dot(t.T) / t.shape[1] + eps * eye(t.shape[0])
50
+ mu_s = source_img.mean(0).mean(0)
51
+ s = source_img - mu_s
52
+ s = s.transpose(2,0,1).reshape(3,-1)
53
+ Cs = s.dot(s.T) / s.shape[1] + eps * eye(s.shape[0])
54
+ if mode == 'chol':
55
+ chol_t = np.linalg.cholesky(Ct)
56
+ chol_s = np.linalg.cholesky(Cs)
57
+ ts = chol_s.dot(np.linalg.inv(chol_t)).dot(t)
58
+ if mode == 'pca':
59
+ eva_t, eve_t = np.linalg.eigh(Ct)
60
+ Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T)
61
+ eva_s, eve_s = np.linalg.eigh(Cs)
62
+ Qs = eve_s.dot(np.sqrt(np.diag(eva_s))).dot(eve_s.T)
63
+ ts = Qs.dot(np.linalg.inv(Qt)).dot(t)
64
+ if mode == 'sym':
65
+ eva_t, eve_t = np.linalg.eigh(Ct)
66
+ Qt = eve_t.dot(np.sqrt(np.diag(eva_t))).dot(eve_t.T)
67
+ Qt_Cs_Qt = Qt.dot(Cs).dot(Qt)
68
+ eva_QtCsQt, eve_QtCsQt = np.linalg.eigh(Qt_Cs_Qt)
69
+ QtCsQt = eve_QtCsQt.dot(np.sqrt(np.diag(eva_QtCsQt))).dot(eve_QtCsQt.T)
70
+ ts = np.linalg.inv(Qt).dot(QtCsQt).dot(np.linalg.inv(Qt)).dot(t)
71
+ matched_img = ts.reshape(*target_img.transpose(2,0,1).shape).transpose(1,2,0)
72
+ matched_img += mu_s
73
+ matched_img[matched_img>1] = 1
74
+ matched_img[matched_img<0] = 0
75
+ return matched_img
76
+
77
+
78
+ if __name__ == "__main__":
79
+ main()
recolorLumaConverterAlgo.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+
3
+ def hex_to_rgb(value):
4
+ value = value.lstrip('#')
5
+ length = len(value)
6
+ return tuple(int(value[i:i + length // 3], 16) for i in range(0, length, length // 3))
7
+
8
+ def luminance(color):
9
+ # Use the ITU-R BT.709 formula
10
+ r, g, b = color
11
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b
12
+
13
+ # def interpolate_color(color1, color2, factor):
14
+ # return tuple(int(c1 * (1 - factor) + c2 * factor) for c1, c2 in zip(color1, color2))
15
+
16
+ def quadratic_interpolate(factor):
17
+ #make the interpolation closer to the second color
18
+ return factor ** 2
19
+
20
+ def interpolate_color(color1, color2, factor):
21
+ adjusted_factor = quadratic_interpolate(factor)
22
+ return tuple(int(c1 * (1 - adjusted_factor) + c2 * adjusted_factor) for c1, c2 in zip(color1, color2))
23
+
24
+
25
+ def remap_image_colors(image_path, hex_palette):
26
+ # img = Image.open(image_path)
27
+ img = Image.fromarray(image_path)
28
+
29
+ rgb_palette = [hex_to_rgb(hex_code) for hex_code in hex_palette]
30
+
31
+ sorted_palette = sorted(rgb_palette, key=luminance)
32
+
33
+ img_rgb = img.convert("RGB")
34
+ pixels = img_rgb.load()
35
+
36
+ for y in range(img.height):
37
+ for x in range(img.width):
38
+ original_color = pixels[x, y]
39
+ lum = luminance(original_color)
40
+
41
+ # Find the closest palette colors by luminance (one darker, one brighter)
42
+ prev_color = sorted_palette[0]
43
+ next_color = sorted_palette[-1]
44
+ for i in range(1, len(sorted_palette)):
45
+ if luminance(sorted_palette[i]) > lum:
46
+ next_color = sorted_palette[i]
47
+ prev_color = sorted_palette[i-1]
48
+ break
49
+
50
+ # Interpolate between the two closest colors
51
+ lum_range = luminance(next_color) - luminance(prev_color)
52
+ if lum_range == 0:
53
+ mapped_color = prev_color
54
+ else:
55
+ factor = (lum - luminance(prev_color)) / lum_range
56
+ mapped_color = interpolate_color(prev_color, next_color, factor)
57
+
58
+ pixels[x, y] = mapped_color
59
+
60
+ img_rgb.save("result.jpg")
61
+
62
+ # hex_palette = ['#db5a1e', '#555115', '#9a690e', '#1f3a19', '#da8007', '#9a0633', '#b70406', '#d01b4b', '#e20b0f', '#f7515d']
63
+ # remap_image_colors('estampa.jpg', hex_palette)
recolorOTAlgo.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ import ot
4
+ from PIL import Image
5
+
6
+ def transfer_channel(source_channel, target_channel):
7
+ source_hist, _ = np.histogram(source_channel, bins=256, range=(0, 256))
8
+ target_hist, _ = np.histogram(target_channel, bins=256, range=(0, 256))
9
+
10
+ source_hist = source_hist.astype(np.float64) / source_hist.sum()
11
+ target_hist = target_hist.astype(np.float64) / target_hist.sum()
12
+
13
+ r = np.arange(256).reshape((-1, 1))
14
+ c = np.arange(256).reshape((1, -1))
15
+ M = (r - c) ** 2
16
+ M = M / M.max()
17
+
18
+ P = ot.emd(source_hist, target_hist, M)
19
+
20
+ transferred_channel = np.zeros_like(source_channel)
21
+ for i in range(256):
22
+ transferred_channel[source_channel == i] = P[i].argmax()
23
+
24
+ return transferred_channel
25
+
26
+ def optimal_transport_color_transfer(source, target):
27
+ source_lab = cv2.cvtColor(source, cv2.COLOR_BGR2Lab).astype(np.float64)
28
+ target_lab = cv2.cvtColor(target, cv2.COLOR_BGR2Lab).astype(np.float64)
29
+
30
+ # Transfer all channels L, a, and b
31
+ for ch in range(3):
32
+ source_lab[:, :, ch] = transfer_channel(source_lab[:, :, ch], target_lab[:, :, ch])
33
+
34
+ transferred_rgb = cv2.cvtColor(source_lab.astype(np.uint8), cv2.COLOR_Lab2BGR)
35
+ return transferred_rgb
36
+
37
+
38
+ def rgb_to_hex(rgb):
39
+ return '#{:02x}{:02x}{:02x}'.format(rgb[0], rgb[1], rgb[2])
40
+
41
+ def hex_to_rgb(hex):
42
+ hex = hex.lstrip('#')
43
+ return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))
44
+
45
+
46
+ def create_color_palette(colors, palette_width=800, palette_height=200):
47
+ """
48
+ Receives a list of colors in hex format and creates a palette image
49
+ """
50
+ pixels = []
51
+ n_colors = len(colors)
52
+ for i in range(n_colors):
53
+ color = hex_to_rgb(colors[i])
54
+ for j in range(palette_width//n_colors * palette_height):
55
+ pixels.append(color)
56
+ img = Image.new('RGB', (palette_height, palette_width))
57
+ img.putdata(pixels)
58
+ # img.show()
59
+ return img
60
+
61
+
62
+ # if __name__ == "__main__":
63
+ # source = cv2.imread("estampa-test.png")
64
+ # target = cv2.imread("color.png")
65
+ # transferred = optimal_transport_color_transfer(source, target)
66
+
67
+ # smooth = False#test with true to have a different result.
68
+ # if smooth:
69
+ # # Apply bilateral filtering
70
+ # diameter = 30 # diameter of each pixel neighborhood, adjust based on your image size
71
+ # sigma_color = 25 # larger value means colors farther to each other will mix together
72
+ # sigma_space = 25 # larger values means farther pixels will influence each other if their colors are close enough
73
+ # smoothed = cv2.bilateralFilter(transferred, diameter, sigma_color, sigma_space)
74
+ # cv2.imwrite("result_OTA.jpg", smoothed)
75
+ # else:
76
+ # cv2.imwrite("result_OTA.jpg", transferred)
77
+
78
+
79
+ def recolor(source, colors):
80
+ pallete_img = create_color_palette(colors)
81
+ palette_bgr = cv2.cvtColor(np.array(pallete_img), cv2.COLOR_RGB2BGR)
82
+ recolored = optimal_transport_color_transfer(source, palette_bgr)
83
+ smooth = True#test with true for different results.
84
+ if smooth:
85
+ # Apply bilateral filtering
86
+ diameter = 10 # diameter of each pixel neighborhood, adjust based on your image size
87
+ sigma_color = 25 # larger value means colors farther to each other will mix together
88
+ sigma_space = 15 # larger values means farther pixels will influence each other if their colors are close enough
89
+ smoothed = cv2.bilateralFilter(recolored, diameter, sigma_color, sigma_space)
90
+ recoloredFile = cv2.imwrite("result.jpg", smoothed, [cv2.IMWRITE_JPEG_QUALITY, 100])
91
+ return recoloredFile
92
+ else:
93
+ recoloredFile = cv2.imwrite("result.jpg", recolored)
94
+ return recoloredFile
95
+
96
+
97
+
recolorPaletteBasedTransfer.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #https://gfx.cs.princeton.edu/pubs/Chang_2015_PPR/index.php
2
+ import numpy as np
3
+ import cv2
4
+ from sklearn.cluster import KMeans
5
+ from PIL import Image
6
+
7
+ def extract_palette(image, n_colors=5):
8
+ """
9
+ Extracts dominant colors from the image using KMeans clustering.
10
+
11
+ Parameters:
12
+ - image: np.array, the input image
13
+ - n_colors: int, the number of colors to extract
14
+
15
+ Returns:
16
+ - palette: list, dominant colors in the image
17
+ """
18
+ # Reshape the image to be a list of pixels
19
+ pixels = image.reshape(-1, 3)
20
+
21
+ # Apply KMeans clustering to find the most dominant colors
22
+ kmeans = KMeans(n_clusters=n_colors)
23
+ kmeans.fit(pixels)
24
+
25
+ # Get the RGB values of the clusters' centers
26
+ palette = kmeans.cluster_centers_
27
+
28
+ return palette
29
+
30
+ def map_colors(source_img, source_palette, target_palette):
31
+ """
32
+ Maps colors from the source palette to the target palette.
33
+
34
+ Parameters:
35
+ - source_img: np.array, the source image
36
+ - source_palette: list, the source color palette
37
+ - target_palette: list, the target color palette
38
+
39
+ Returns:
40
+ - recolored_img: np.array, the recolored image
41
+ """
42
+ recolored_img = np.copy(source_img)
43
+
44
+ for i in range(source_img.shape[0]):
45
+ for j in range(source_img.shape[1]):
46
+ # Find the nearest color in the source palette
47
+ distances = np.linalg.norm(source_palette - source_img[i, j], axis=1)
48
+ closest_idx = np.argmin(distances)
49
+
50
+ # Replace with the corresponding color from the target palette
51
+ recolored_img[i, j] = target_palette[closest_idx]
52
+
53
+ return recolored_img
54
+
55
+ def palette_based_color_transfer(source_img, target_palette, n_colors=5):
56
+ """
57
+ Performs palette based color transfer.
58
+
59
+ Parameters:
60
+ - source_img: np.array, the source image
61
+ - target_palette: list, the target color palette
62
+ - n_colors: int, the number of colors in the source palette (default is 5)
63
+
64
+ Returns:
65
+ - recolored_img: np.array, the recolored image
66
+ """
67
+ # Convert the source image to RGB
68
+ source_img = cv2.cvtColor(source_img, cv2.COLOR_BGR2RGB)
69
+
70
+ # Extract the source palette
71
+ source_palette = extract_palette(source_img, n_colors)
72
+
73
+ # Perform color mapping
74
+ recolored_img = map_colors(source_img, source_palette, target_palette)
75
+
76
+ return recolored_img
77
+
78
+ def create_color_palette(colors, palette_width=800, palette_height=200):
79
+ """
80
+ Receives a list of colors in hex format and creates a palette image
81
+ """
82
+ pixels = []
83
+ n_colors = len(colors)
84
+ for i in range(n_colors):
85
+ color = hex_to_rgb(colors[i])
86
+ for j in range(palette_width//n_colors * palette_height):
87
+ pixels.append(tuple(color)) # Convert color to tuple
88
+ img = Image.new('RGB', (palette_height, palette_width))
89
+ img.putdata(pixels)
90
+ # img.show()
91
+ return img
92
+
93
+ def hex_to_rgb(hex_color):
94
+ return [int(hex_color[i:i+2], 16) for i in (1, 3, 5)]
95
+ # Example usage:
96
+ # source_img_path = "estampa.jpg"
97
+ # # target_palette = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0], [0, 255, 255]]
98
+ # hex_colors = ["#db5a1e", "#555115", "#9a690e", "#1f3a19", "#da8007", "#9a0633", "#b70406", "#d01b4b", "#e20b0f", "#f7515d"]
99
+
100
+ # target_palette = [hex_to_rgb(color) for color in hex_colors]
101
+ # recolored_img = palette_based_color_transfer(source_img_path, target_palette, 10)
102
+ # cv2.imwrite("recolored_image.jpg", cv2.cvtColor(recolored_img, cv2.COLOR_RGB2BGR))
103
+
104
+ def recolor(source, colors):
105
+ palette_img = create_color_palette(colors)
106
+ palette_bgr = cv2.cvtColor(np.array(palette_img), cv2.COLOR_RGB2BGR)
107
+
108
+ target_palette = [hex_to_rgb(color) for color in colors]
109
+ source_bgr = cv2.cvtColor(source, cv2.COLOR_RGB2BGR)
110
+ # No need to convert source to BGR, assume it's already in RGB
111
+ recolored = palette_based_color_transfer(source_bgr, target_palette, 10)
112
+ cv2.imwrite("result.jpg", cv2.cvtColor(recolored, cv2.COLOR_RGB2BGR))
113
+ return recolored
114
+
115
+
recolorReinhardAlgo.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from PIL import Image
4
+
5
+ def reinhards_color_transfer(source, target):
6
+ # Convert the images from the RGB to the Lab color space
7
+ source_lab = cv2.cvtColor(source, cv2.COLOR_BGR2Lab).astype(np.float64)
8
+ target_lab = cv2.cvtColor(target, cv2.COLOR_BGR2Lab).astype(np.float64)
9
+
10
+ # Compute mean and standard deviation for each channel in both images
11
+ l_mean_src, l_std_src = np.mean(source_lab[:, :, 0]), np.std(source_lab[:, :, 0])
12
+ a_mean_src, a_std_src = np.mean(source_lab[:, :, 1]), np.std(source_lab[:, :, 1])
13
+ b_mean_src, b_std_src = np.mean(source_lab[:, :, 2]), np.std(source_lab[:, :, 2])
14
+
15
+ l_mean_tar, l_std_tar = np.mean(target_lab[:, :, 0]), np.std(target_lab[:, :, 0])
16
+ a_mean_tar, a_std_tar = np.mean(target_lab[:, :, 1]), np.std(target_lab[:, :, 1])
17
+ b_mean_tar, b_std_tar = np.mean(target_lab[:, :, 2]), np.std(target_lab[:, :, 2])
18
+
19
+ # Subtract the means from the source image
20
+ source_lab[:, :, 0] -= l_mean_src
21
+ source_lab[:, :, 1] -= a_mean_src
22
+ source_lab[:, :, 2] -= b_mean_src
23
+
24
+ # Scale by the standard deviations
25
+ source_lab[:, :, 0] = (l_std_tar / l_std_src) * source_lab[:, :, 0]
26
+ source_lab[:, :, 1] = (a_std_tar / a_std_src) * source_lab[:, :, 1]
27
+ source_lab[:, :, 2] = (b_std_tar / b_std_src) * source_lab[:, :, 2]
28
+
29
+ # Add the target means
30
+ source_lab[:, :, 0] += l_mean_tar
31
+ source_lab[:, :, 1] += a_mean_tar
32
+ source_lab[:, :, 2] += b_mean_tar
33
+
34
+ # Clip pixel values to ensure they fall within the valid Lab range
35
+ source_lab[:, :, 0] = np.clip(source_lab[:, :, 0], 0, 255)
36
+ source_lab[:, :, 1] = np.clip(source_lab[:, :, 1], 0, 255)
37
+ source_lab[:, :, 2] = np.clip(source_lab[:, :, 2], 0, 255)
38
+
39
+ # Convert back to RGB
40
+ transferred_rgb = cv2.cvtColor(source_lab.astype(np.uint8), cv2.COLOR_Lab2BGR)
41
+ return transferred_rgb
42
+
43
+ def rgb_to_hex(rgb):
44
+ return '#{:02x}{:02x}{:02x}'.format(rgb[0], rgb[1], rgb[2])
45
+
46
+ def hex_to_rgb(hex):
47
+ hex = hex.lstrip('#')
48
+ return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))
49
+
50
+ def create_color_palette(colors, palette_width=800, palette_height=200):
51
+ """
52
+ Receives a list of colors in hex format and creates a palette image
53
+ """
54
+ pixels = []
55
+ n_colors = len(colors)
56
+ for i in range(n_colors):
57
+ color = hex_to_rgb(colors[i])
58
+ for j in range(palette_width//n_colors * palette_height):
59
+ pixels.append(color)
60
+ img = Image.new('RGB', (palette_height, palette_width))
61
+ img.putdata(pixels)
62
+ # img.show()
63
+ return img
64
+
65
+
66
+ # if __name__ == "__main__":
67
+ # source = cv2.imread("estampa.jpg")
68
+ # colors = ['#6b3d68', '#6d2055', '#695977', '#6b7988', '#6f9b9b']
69
+ # # Generate palette image
70
+ # palette_img = create_color_palette(colors)
71
+ # # Convert the palette image to BGR format
72
+ # palette_bgr = cv2.cvtColor(np.array(palette_img), cv2.COLOR_RGB2BGR)
73
+
74
+ # # Save the palette image
75
+ # # cv2.imwrite("palette.jpg", palette_bgr)
76
+
77
+ # target = palette_bgr#cv2.imread("palette.jpg")
78
+
79
+ # transferred = reinhards_color_transfer(source, target)
80
+ # cv2.imwrite("transferred_reinhard.jpg", transferred)
81
+
82
+
83
+ def recolor(source, colors):
84
+ palette_img = create_color_palette(colors)
85
+ palette_bgr = cv2.cvtColor(np.array(palette_img), cv2.COLOR_RGB2BGR)
86
+
87
+ recolored = reinhards_color_transfer(source, palette_bgr)
88
+ recoloredFile = cv2.imwrite("result.jpg", recolored)
89
+ return recolored
recolorReinhardV2Algo.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+
4
+ def color_transfer(source, target):
5
+ # Convert the images from RGB to L*a*b* color space
6
+ source_lab = cv2.cvtColor(source, cv2.COLOR_BGR2Lab)
7
+ target_lab = cv2.cvtColor(target, cv2.COLOR_BGR2Lab)
8
+
9
+ # Split the LAB image into L, A and B channels
10
+ source_l, source_a, source_b = cv2.split(source_lab)
11
+ target_l, target_a, target_b = cv2.split(target_lab)
12
+
13
+ # Compute mean and standard deviation for each channel in both images
14
+ l_mean_src, l_std_src, = source_l.mean(), source_l.std()
15
+ a_mean_src, a_std_src = source_a.mean(), source_a.std()
16
+ b_mean_src, b_std_src = source_b.mean(), source_b.std()
17
+
18
+ l_mean_tar, l_std_tar = target_l.mean(), target_l.std()
19
+ a_mean_tar, a_std_tar = target_a.mean(), target_a.std()
20
+ b_mean_tar, b_std_tar = target_b.mean(), target_b.std()
21
+
22
+ # Perform the color transfer
23
+ l_result = (source_l - l_mean_src) * (l_std_tar / l_std_src) + l_mean_tar
24
+ a_result = (source_a - a_mean_src) * (a_std_tar / a_std_src) + a_mean_tar
25
+ b_result = (source_b - b_mean_src) * (b_std_tar / b_std_src) + b_mean_tar
26
+
27
+ # Clip and reshape the channels
28
+ l_result = np.clip(l_result, 0, 255).astype(np.uint8)
29
+ a_result = np.clip(a_result, 0, 255).astype(np.uint8)
30
+ b_result = np.clip(b_result, 0, 255).astype(np.uint8)
31
+
32
+ # Merge the channels back
33
+ result_lab = cv2.merge([l_result, a_result, b_result])
34
+
35
+ # Convert the resulting image from L*a*b* to RGB color space
36
+ result = cv2.cvtColor(result_lab, cv2.COLOR_Lab2BGR)
37
+
38
+ return result
39
+
40
+ # Load source and target images
41
+ source_img = cv2.imread("estampa.jpg")
42
+ target_img = cv2.imread("cor.jpg")
43
+
44
+ # Perform color transfer
45
+ transferred_img = color_transfer(source_img, target_img)
46
+
47
+ # Return the transferred image for visualization
48
+ cv2.imwrite("result.jpg", transferred_img)
recolorTransferAlgo.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ from PIL import Image
4
+
5
+ def read_file(source_image, target_image):
6
+ s = cv2.cvtColor(source_image, cv2.COLOR_RGB2LAB)
7
+ t = cv2.cvtColor(np.array(target_image), cv2.COLOR_RGB2LAB)
8
+ return s, t
9
+
10
+ def get_mean_and_std(x):
11
+ x_mean, x_std = cv2.meanStdDev(x)
12
+ x_mean = np.hstack(np.around(x_mean,2))
13
+ x_std = np.hstack(np.around(x_std,2))
14
+ return x_mean, x_std
15
+
16
+ def color_transfer(source, target_image):
17
+ """
18
+ The primary goal of this algorithm is to adjust the colors of the source image
19
+ such that its color distribution looks like the distribution of the target image.
20
+ """
21
+ s, t = read_file(source, target_image)
22
+ s_mean, s_std = get_mean_and_std(s)
23
+ t_mean, t_std = get_mean_and_std(t)
24
+ height, width, channel = s.shape
25
+ for i in range(0,height):
26
+ for j in range(0,width):
27
+ for k in range(0,channel):
28
+ x = s[i,j,k]
29
+ x = ((x-s_mean[k])*(t_std[k]/s_std[k]))+t_mean[k]
30
+ # round or +0.5
31
+ x = round(x)
32
+ # boundary check
33
+ x = 0 if x<0 else x
34
+ x = 255 if x>255 else x
35
+ s[i,j,k] = x
36
+ s = cv2.cvtColor(s,cv2.COLOR_LAB2BGR)
37
+ cv2.imwrite('result.jpg',s)
38
+
39
+ def rgb_to_hex(rgb):
40
+ return '#{:02x}{:02x}{:02x}'.format(rgb[0], rgb[1], rgb[2])
41
+
42
+ def hex_to_rgb(hex):
43
+ hex = hex.lstrip('#')
44
+ return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4))
45
+
46
+ def create_color_palette(colors, palette_width=800, palette_height=200):
47
+ """
48
+ Receives a list of colors in hex format and creates a palette image
49
+ """
50
+ pixels = []
51
+ n_colors = len(colors)
52
+ for i in range(n_colors):
53
+ color = hex_to_rgb(colors[i])
54
+ for j in range(palette_width//n_colors * palette_height):
55
+ pixels.append(color)
56
+ img = Image.new('RGB', (palette_height, palette_width))
57
+ img.putdata(pixels)
58
+ # img.show()
59
+ return img
60
+
61
+ # colors = ['#d54b18', '#555015', '#98680e', '#1f3919', '#da8007', '#a50637', '#b50406', '#d52350', '#e2090f', '#f8545c']
62
+ # img = create_color_palette(colors)
63
+ # color_transfer('s2', img)
64
+
65
+ def recolor(source, colors):
66
+ palette_img = create_color_palette(colors)
67
+ palette_bgr = cv2.cvtColor(np.array(palette_img), cv2.COLOR_RGB2BGR)
68
+ source_bgr = cv2.cvtColor(source, cv2.COLOR_RGB2BGR)
69
+ recolored = color_transfer(source_bgr, palette_bgr)
70
+ return recolored
requirements.txt ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ annotated-types==0.5.0
2
+ anyio==3.7.1
3
+ certifi==2023.7.22
4
+ click==8.1.7
5
+ dnspython==2.4.2
6
+ email-validator==2.0.0.post2
7
+ exceptiongroup==1.1.3
8
+ fastapi==0.101.1
9
+ h11==0.14.0
10
+ httpcore==0.17.3
11
+ httptools==0.6.0
12
+ httpx==0.24.1
13
+ idna==3.4
14
+ imageio==2.31.1
15
+ itsdangerous==2.1.2
16
+ Jinja2==3.1.2
17
+ joblib==1.3.2
18
+ lazy_loader==0.3
19
+ MarkupSafe==2.1.3
20
+ networkx==3.1
21
+ numpy==1.25.2
22
+ opencv-python==4.8.0.76
23
+ orjson==3.9.5
24
+ packaging==23.1
25
+ Pillow==10.0.0
26
+ POT==0.9.1
27
+ pydantic==2.2.1
28
+ pydantic-extra-types==2.0.0
29
+ pydantic-settings==2.0.3
30
+ pydantic_core==2.6.1
31
+ python-dotenv==1.0.0
32
+ python-multipart==0.0.6
33
+ PyWavelets==1.4.1
34
+ PyYAML==6.0.1
35
+ scikit-image==0.21.0
36
+ scikit-learn==1.3.0
37
+ scipy==1.11.2
38
+ sniffio==1.3.0
39
+ starlette==0.27.0
40
+ threadpoolctl==3.2.0
41
+ tifffile==2023.8.12
42
+ typing_extensions==4.7.1
43
+ ujson==5.8.0
44
+ uvicorn==0.23.2
45
+ uvloop==0.17.0
46
+ watchfiles==0.19.0
47
+ websockets==11.0.3
server.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, Form
2
+ from fastapi.responses import StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from typing import List
5
+ import io
6
+ from PIL import Image, ImageOps
7
+ import numpy as np
8
+ import compColors
9
+ import dominantColors
10
+ import recolorReinhardAlgo
11
+ import recolorOTAlgo
12
+ import recolorTransferAlgo
13
+ import recolorLumaConverterAlgo
14
+ import recolorPaletteBasedTransfer
15
+
16
+ app = FastAPI()
17
+
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"],
21
+ allow_credentials=True,
22
+ allow_methods=["*"],
23
+ allow_headers=["*"],
24
+ )
25
+ @app.post("/dominantColor/")
26
+ async def dominant_color(file: UploadFile = File(...), num_colors: int = Form(...)):
27
+ """
28
+ Receive an image file and an integer and return the dominant color(s).
29
+ """
30
+ file_content = await file.read()
31
+ image_bytes = io.BytesIO(file_content)
32
+ im = Image.open(image_bytes)
33
+ dominantColorsRGB = dominantColors.findDominantColors(image_bytes, num_colors, False)
34
+ dominantColorsHex = [dominantColors.rgb_to_hex(color) for color in dominantColorsRGB]
35
+ return {"dominantColors": dominantColorsHex}
36
+
37
+ @app.post("/ColorPalettes/")
38
+ async def color_palettes(colors: str = Form(...)):
39
+ """
40
+ Receive an array of strings representing colors and return a color palette based on these colors.
41
+ """
42
+ #maybe this isn't necessary. converting the string to an array of strings
43
+ colors = [color.strip() for color in colors.split(',')]
44
+ #generate the first pallete, which is the complementary colors of the given colors
45
+ complementaryColors = []
46
+ for color in colors:
47
+ complementaryColors.append(compColors.complementary_colors(color))
48
+
49
+ #generate the second palette using the adjacent colors algorithm:
50
+ adjacentColors = []
51
+ for color in colors:
52
+ _adjcolors = compColors.adjacent_colors(color)
53
+ for _color in _adjcolors:
54
+ if _color not in adjacentColors:
55
+ adjacentColors.append(_color)
56
+ #generate the third palette using the gradient colors algorithm:
57
+ gradientColors = []
58
+ for i in range(len(colors)-1):
59
+ gradientColors.append(compColors.gradient_colors(colors[i], colors[i+1]))
60
+
61
+ #Fixing size of palletes to 5 colors:
62
+ complementaryColors = [complementaryColors[i:i + 5] for i in range(0, len(complementaryColors), 5)]
63
+ adjacentColors = [adjacentColors[i:i + 5] for i in range(0, len(adjacentColors), 5)]
64
+ colors = [colors[i:i + 5] for i in range(0, len(colors), 5)]
65
+
66
+ return {"inputColor": colors, "complementaryColors": complementaryColors, "adjacentColors": adjacentColors, "gradientColors": gradientColors}
67
+
68
+ @app.post("/recolor/")
69
+ async def recolor(file: UploadFile = File(...), colors: str = Form(...), model: str = Form(...)):
70
+ """
71
+ Receive an image file and an array of strings representing colors of a selected pallete and recolor an image.
72
+ """
73
+ method = model
74
+ invertColors = False
75
+ colors = [color.strip() for color in colors.split(',')]
76
+ file_content = await file.read()
77
+ image_bytes = io.BytesIO(file_content)
78
+ image = Image.open(image_bytes)
79
+ if invertColors:
80
+ image = ImageOps.invert(image)
81
+ image_np = np.array(image)
82
+
83
+ if method == "CCA":
84
+ #Characteristic Color Analysis
85
+ recolorReinhardAlgo.recolor(image_np, colors)
86
+ elif method == "OTA":
87
+ #Optimal Transport Algorithm transfer
88
+ recolorOTAlgo.recolor(image_np, colors)
89
+ elif method =="KMEANS":
90
+ #K-means clustering transfer
91
+ recolorTransferAlgo.recolor(image_np, colors)
92
+ elif method == "LUMA":
93
+ #Luma converter transfer
94
+ recolorLumaConverterAlgo.remap_image_colors(image_np, colors)
95
+ elif method == "palette":
96
+ #palette transfer
97
+ recolorPaletteBasedTransfer.recolor(image_np, colors)
98
+ img_file = open("./result.jpg", "rb")
99
+ return StreamingResponse(img_file, media_type="image/jpeg")
100
+
101
+
102
+
103
+
104
+ if __name__ == "__main__":
105
+ import uvicorn
106
+ uvicorn.run(app, host="0.0.0.0", port=8000)
107
+
108
+
109
+
110
+
111
+ #how to run:
112
+ #source env/bin/activate
113
+ #uvicorn server:app --reload
todo.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * add more two funcoes de troca de cor
2
+ *add invert colors
3
+ *check file type
4
+ *mask
5
+
6
+ **added:
7
+ recolor palette based transfer, recolor reinhard v2 and recolor linear transfer. => need to add to the backend.
8
+
9
+ `
10
+ source ./env/bin/activate
11
+ `
12
+ #how to run:
13
+ #source env/bin/activate
14
+ #uvicorn server:app --reload
15
+ `