Introducir el análisis de imágenes/video al implementar: Conversión de la imagen a un foto-mosaico.
En el campo de las imágenes y la fotografía, un fotomosaico es una imagen usualmente una fotografía que ha sido dividida en secciones rectangulares (usualmente del mismo tamaño), tal como es compuesto un mosaico tradicional, con la característica de que cada elemento del mosaico es reemplazado por otra fotografía con colores promedios apropiados al elemento de la imagen original. Cuando es vista en detalle, los píxeles individuales se ven como la imagen principal, sin embargo al verla como un todo, es posible apreciar que la imagen está compuesta por cientos de miles de imágenes.
El CIE Lab* (CIELAB) es el modelo cromático usado normalmente para describir todos los colores que puede percibir el ojo humano. Fue desarrollado específicamente con este propósito por la Commission Internationale d'Eclairage (Comisión Internacional de la Iluminación), razón por la cual se abrevia CIE. Los asteriscos () que siguen a cada letra forman parte del nombre, ya que representan L, a* y b*, de L, a y b.
Los tres parámetros en el modelo representan la luminosidad de color (L, L=0 rendimientos negro y L=100 indica blanca), su posición entre rojo y verde (a, valores negativos indican verde mientras valores positivos indican rojo) y su posición entre amarillo y azul (b*, valores negativos indican azul y valores positivos indican amarillo).
El proceso de creación del mosaico está dividido principalmente en 2 fases, la primera es una fase previa de creación del dataset de imágenes utilizadas en el mosaico y la segunda es el proceso como tal de reemplazo de los pixeles de la imagen por otras imágenes que aproximen el color de cada pixel.
Para la primera parte se utilizó un script en Python para descargar imágenes aleatorias de una API publica llamada Pixabay en la cual se permite utilizar un filtro por el color de la imagen, el funcionamiento del script es simple, hace una serie de peticiones a la API (una por cada color) solicitando 20 imágenes del color deseado, luego toma las imágenes de vista previa (una imagen de menor resolución) y las almacena con el código hexadecimal del color dominante de las imágenes, en total se descargaron 277 imágenes.
Una vez ya se tiene el dataset de imágenes listo, viene la segunda parte, en esta fase se lee la imagen que se desea transformar a mosaico y se le baja la resolución por cuestión de rendimiento, una vez tenga una resolución baja se reemplaza cada pixel de la imagen por la imagen del dataset con el color dominante más aproximado al color del pixel, para esto se utilizo la representación CIELAB del color, debido a que esta permite hallar correctamente la "distancia" entre 2 colores.
1linklet img, images;
2linklet pixel_size;
3linklet newImage = {pixels: [], height: 0, width: 0}
4linklet files = ["043431.jpg","060505.jpg","061709.jpg","062627.jpg","070504.jpg","07090d.jpg","090605.jpg","09090b.jpg","0b0806.jpg","0b2b36.jpg","0c0d0e.jpg","0e0d0c.jpg","0e3947.jpg","0f0e12.jpg","100f0d.jpg","101010.jpg","103a0e.jpg","121812.jpg","160e10.jpg","163c3a.jpg","182d2c.jpg","1d2318.jpg","222327.jpg","238e92.jpg","25bbbc.jpg","262626.jpg","272c26.jpg","274141.jpg","274c3a.jpg","282124.jpg","2a2a2a.jpg","2a5e4a.jpg","2f3841.jpg","2f6a89.jpg","303b3f.jpg","303c1e.jpg","31233f.jpg","31aa72.jpg","331f44.jpg","343737.jpg","3572d2.jpg","36312f.jpg","368785.jpg","383a3f.jpg","3a2b20.jpg","3a4324.jpg","3c3c3c.jpg","3e3320.jpg","3e8163.jpg","3f3f3f.jpg","3f511f.jpg","40576e.jpg","405d72.jpg","41413b.jpg","414141.jpg","434343.jpg","453f38.jpg","473328.jpg","474735.jpg","476695.jpg","476931.jpg","484d57.jpg","49652e.jpg","4a2c54.jpg","4b4b4b.jpg","4c296d.jpg","4c4c4c.jpg","4d5460.jpg","4d5b17.jpg","4e3f34.jpg","4e563d.jpg","4f2e27.jpg","506e33.jpg","515046.jpg","515f66.jpg","51817a.jpg","524122.jpg","525a45.jpg","55423d.jpg","584f5b.jpg","599b9d.jpg","5a6345.jpg","5b5650.jpg","5b8db8.jpg","5c783b.jpg","5f673f.jpg","60c1e3.jpg","614f42.jpg","616366.jpg","657546.jpg","685435.jpg","685941.jpg","685f56.jpg","68676d.jpg","68809e.jpg","699853.jpg","6ac2e0.jpg","6b5b4b.jpg","6b5b76.jpg","6c4b55.jpg","6d726a.jpg","6e7846.jpg","6e7e8e.jpg","6eadca.jpg","6f18cc.jpg","6f34b7.jpg","6f588a.jpg","6f6f6f.jpg","6f7172.jpg","6fc1b9.jpg","7090a1.jpg","726f65.jpg","747474.jpg","775885.jpg","775c96.jpg","77804f.jpg","77c2c2.jpg","785b89.jpg","787878.jpg","7b6958.jpg","7c7374.jpg","7d6953.jpg","7ed47e.jpg","818a87.jpg","8197b1.jpg","835330.jpg","83aeab.jpg","855bbb.jpg","888988.jpg","89573c.jpg","8a6d69.jpg","8b8787.jpg","8d4d6c.jpg","8d69cd.jpg","8d8a8a.jpg","8eaca3.jpg","908e8d.jpg","939393.jpg","94898a.jpg","955a41.jpg","9d948f.jpg","9e713c.jpg","9f9d9b.jpg","a03638.jpg","a0a7a1.jpg","a0bdbc.jpg","a3688b.jpg","a44543.jpg","a6262e.jpg","a69075.jpg","a7908a.jpg","a81c48.jpg","a8b4b9.jpg","a9528f.jpg","aa0e0b.jpg","aaaaaa.jpg","ac8ac2.jpg","accbce.jpg","af6118.jpg","afafaf.jpg","b17bcd.jpg","b24e78.jpg","b461a0.jpg","b682a3.jpg","b6b7b8.jpg","b82825.jpg","b8b8b8.jpg","b8c1c0.jpg","b92825.jpg","ba723b.jpg","ba9dbd.jpg","baa388.jpg","bababa.jpg","bc7298.jpg","be5e77.jpg","beadb9.jpg","bfbfbf.jpg","c06c80.jpg","c08b3f.jpg","c11010.jpg","c2addb.jpg","c3396d.jpg","c49766.jpg","c4b348.jpg","c51f29.jpg","c5b148.jpg","c696b1.jpg","c6b6bb.jpg","c6d0da.jpg","c8b9a7.jpg","c9c9c9.jpg","ca2031.jpg","cb2b21.jpg","cbbb4e.jpg","ccd9d9.jpg","cd6664.jpg","ce9fb1.jpg","cf4837.jpg","cfa1f2.jpg","d07065.jpg","d13a3a.jpg","d2d3d0.jpg","d2d7df.jpg","d34d4b.jpg","d393b7.jpg","d48b1d.jpg","d56457.jpg","d5cec2.jpg","d62428.jpg","d677ed.jpg","d79828.jpg","d7c2b9.jpg","d886a0.jpg","d8afca.jpg","d8cab2.jpg","d8d2d2.jpg","d8dce6.jpg","da448e.jpg","dacdd4.jpg","dad6d9.jpg","db8704.jpg","dbc1d8.jpg","dc9437.jpg","dcb7ce.jpg","dd9ecc.jpg","dddcdc.jpg","de4446.jpg","dea06b.jpg","dea458.jpg","dedede.jpg","dfb383.jpg","e1ce4b.jpg","e1d5c1.jpg","e1ded9.jpg","e4d233.jpg","e4dfd9.jpg","e5e5e8.jpg","e6cabd.jpg","e6d9da.jpg","e8c549.jpg","e8c641.jpg","e8d18a.jpg","e8dcd6.jpg","e9b44c.jpg","e9e8e9.jpg","ea8531.jpg","eabfcd.jpg","eac00b.jpg","eadb0c.jpg","eae12c.jpg","eae77f.jpg","ebc497.jpg","ebe7e1.jpg","ec8e15.jpg","edae29.jpg","edca79.jpg","ede8e1.jpg","eee8c4.jpg","efd83b.jpg","efde3a.jpg","f0bd0d.jpg","f0f7fa.jpg","f13a40.jpg","f19d2a.jpg","f1e6bf.jpg","f4bd38.jpg","f5f2ea.jpg","f697b9.jpg","f7c038.jpg","f7c604.jpg","f88e3f.jpg","f8c13a.jpg","f8ebc6.jpg","f9c941.jpg","fac43a.jpg","faeab6.jpg","fbdc04.jpg"]
5linklet RGBtoXYZ_RtoX = [];
6linklet RGBtoXYZ_GtoX = [];
7linklet RGBtoXYZ_BtoX = [];
8linklet RGBtoXYZ_RtoY = [];
9linklet RGBtoXYZ_GtoY = [];
10linklet RGBtoXYZ_BtoY = [];
11linklet RGBtoXYZ_RtoZ = [];
12linklet RGBtoXYZ_GtoZ = [];
13linklet RGBtoXYZ_BtoZ = [];
14linklet slider;
15link
16linkfunction preload() {
17link // TODO: find a way to use fs library :|
18link for(var i = 0; i < 256; i++){ //i from 0 to 255
19link r = parseFloat(i/255) ; //r from 0 to 1
20link
21link if (r > 0.04045 )
22link r = Math.pow((r+0.055)/1.055 , 2.4);
23link else
24link r = r/12.92;
25link
26link r = r * 100
27link
28link var ref_X = 95.047;
29link var ref_Y = 100.000;
30link var ref_Z = 108.883;
31link
32link RGBtoXYZ_RtoX[i] = r * 0.4124/ref_X;
33link RGBtoXYZ_GtoX[i] = r * 0.3576/ref_X;
34link RGBtoXYZ_BtoX[i] = r * 0.1805/ref_X;
35link RGBtoXYZ_RtoY[i] = r * 0.2126/ref_Y;
36link RGBtoXYZ_GtoY[i] = r * 0.7152/ref_Y;
37link RGBtoXYZ_BtoY[i] = r * 0.0722/ref_Y;
38link RGBtoXYZ_RtoZ[i] = r * 0.0193/ref_Z;
39link RGBtoXYZ_GtoZ[i] = r * 0.1192/ref_Z;
40link RGBtoXYZ_BtoZ[i] = r * 0.9505/ref_Z;
41link }
42link images = {}
43link for (var t = 0; t < files.length; t++) {
44link const file = files[t];
45link images[file] = loadImage("/vc/docs/sketches/assets/mosaic_images/" + file)
46link }
47link img = loadImage("/vc/docs/sketches/lenna.png")
48link}
49link
50linkfunction setup() {
51link // Slider to control pixel_size
52link slider = createSlider(0, 8, 1, 1);
53link pixel_size = Math.pow(2, slider.value())
54link slider.position(150, 552);
55link slider.style('width', '40%');
56link slider.input(() => {
57link pixel_size = Math.pow(2, slider.value())
58link newImage = preProcessImage()
59link for (var k = 0; k < newImage.pixels.length; k++) {
60link const [R,G,B] = newImage.pixels[k];
61link const filename = getClosestColor(R,G,B)
62link image(images[filename], (k % newImage.width) *pixel_size, int(k / newImage.height) *pixel_size, pixel_size, pixel_size);
63link }
64link redraw()
65link })
66link let div = createDiv('Pixel size:');
67link div.style('font-size', '18px');
68link div.position(220, 532);
69link
70link createCanvas(512, 562)
71link newImage = preProcessImage()
72link for (var k = 0; k < newImage.pixels.length; k++) {
73link const [R,G,B] = newImage.pixels[k];
74link const filename = getClosestColor(R,G,B)
75link image(images[filename], (k % newImage.width) *pixel_size, int(k / newImage.height) *pixel_size, pixel_size, pixel_size);
76link }
77link}
78link
79linkfunction draw() {
80link noLoop()
81link}
82link
83linkfunction preProcessImage(){
84link var new_pixels = []
85link pixelDensity(1)
86link img.loadPixels()
87link
88link for (let h = 0; h < img.height; h+=pixel_size) {
89link for (let w = 0; w < img.width; w+=pixel_size) {
90link new_pixels.push([0,0,0])
91link
92link for (let j = 0; j < pixel_size; j++) {
93link const wihi = 4 * ((h + j) * img.width + w)
94link const wfhi = 4 * ((h + j) * img.width + (w + pixel_size))
95link
96link for (let i = wihi; i < wfhi; i+=4) {
97link const [R,G,B] = [img.pixels[i], img.pixels[i + 1], img.pixels[i + 2]] // get pixel rgb
98link new_pixels[new_pixels.length -1][0] = new_pixels[new_pixels.length -1][0] + R
99link new_pixels[new_pixels.length -1][1] = new_pixels[new_pixels.length -1][1] + G
100link new_pixels[new_pixels.length -1][2] = new_pixels[new_pixels.length -1][2] + B
101link }
102link }
103link
104link new_pixels[new_pixels.length -1][0] = int(new_pixels[new_pixels.length -1][0] / (pixel_size * pixel_size))
105link new_pixels[new_pixels.length -1][1] = int(new_pixels[new_pixels.length -1][1] / (pixel_size * pixel_size))
106link new_pixels[new_pixels.length -1][2] = int(new_pixels[new_pixels.length -1][2] / (pixel_size * pixel_size))
107link }
108link }
109link return {pixels:new_pixels, height: img.height/pixel_size, width: img.width/pixel_size}
110link}
111link
112linkfunction getClosestColor(R,G,B){
113link var minDiff = 10000
114link var closest
115link var [L,a,b] = RGBtoLAB(R,G,B)
116link
117link for (let i = 0; i < files.length; i++) {
118link const color = files[i].replace(".jpg","")
119link var cr = parseInt(color.substring(0,2), 16)
120link var cg = parseInt(color.substring(2,4), 16)
121link var cb = parseInt(color.substring(4), 16)
122link
123link var [cL,ca,cb] = RGBtoLAB(cr,cg,cb)
124link
125link var diff = Math.sqrt( Math.pow(L - cL, 2) + Math.pow(a - ca, 2) + Math.pow(b - cb, 2) )
126link if (diff < minDiff){
127link minDiff = diff
128link closest = files[i]
129link }
130link }
131link return closest
132link}
133link
134linkfunction RGBtoLAB(r,g,b){
135link //RGBtoXYZ
136link var x = RGBtoXYZ_RtoX[r] + RGBtoXYZ_GtoX[g] + RGBtoXYZ_BtoX[b];
137link var y = RGBtoXYZ_RtoY[r] + RGBtoXYZ_GtoY[g] + RGBtoXYZ_BtoY[b];
138link var z = RGBtoXYZ_RtoZ[r] + RGBtoXYZ_GtoZ[g] + RGBtoXYZ_BtoZ[b];
139link
140link if (x > 0.008856)
141link x = Math.cbrt(x);
142link else
143link x = (7.787 * x) + 0.13793103448275862;
144link
145link if (y > 0.008856)
146link y = Math.cbrt(y);
147link else
148link y = (7.787 * y) + 0.13793103448275862;
149link
150link if (z > 0.008856)
151link z = Math.cbrt(z);
152link else
153link z = (7.787 * z) + 0.13793103448275862;
154link
155link L = (116 * y) - 16;
156link a = 500 * (x - y);
157link b = 200 * (y - z);
158link
159link return [L,a,b];
160link}
1linkimport json
2linkimport requests
3linkimport shutil
4linkfrom time import sleep
5linkfrom colorthief import ColorThief
6linkfrom tqdm import tqdm
7linkfrom os import rename
8link
9link
10linkdef to_hex(number):
11link res = ""
12link aux = hex(int(number))[2:]
13link return ("0" * (2-len(aux))) + aux
14link
15linkkey = "YOUR API KEY"
16linkimage_type = "photo"
17linkper_page = 20
18linkpage = 1
19linkorientation = "horizontal"
20linkbase_url = f'https://pixabay.com/api/?key={key}&image_type={image_type}&per_page={per_page}&page={page}&orientation={orientation}&> > colors='
21link
22linkcolors = ["grayscale", "transparent", "red", "orange", "yellow", "green", "turquoise", "blue", "lilac", "pink", "white", "gray", > > "black", "brown" ]
23linkfor color in colors:
24link URL = base_url + color
25link res = requests.get(URL)
26link res_data = json.loads(res.text)
27link for image in tqdm(res_data['hits']):
28link # save the image
29link url = image['previewURL']
30link name = './data/' + str(image['id']) + '.jpg'
31link r = requests.get(url, stream=True)
32link if r.status_code == 200:
33link with open(name, 'wb') as f:
34link r.raw.decode_content = True
35link shutil.copyfileobj(r.raw, f)
36link
37link # Get the dominant color of the image
38link color_thief = ColorThief(name)
39link dominant_color = color_thief.get_color(quality=1)
40link dom = ""
41link for x in dominant_color:
42link dom += to_hex(x)
43link rename(name, './data/' + dom + '.jpg')