Двухмерная цветовая рампа (матрица 256×256), интерполированная из 4 угловых цветов

То, что я хочу достичь, – это программно создать двумерный цветовой рамп, представленный матрицей цветов 256х256. Ожидаемый результат можно увидеть на прилагаемом изображении. То, что у меня есть для начальной точки, – это 4 угла цвета матрицы, из которых должны быть интерполированы остальные 254 цвета между ними. Хотя у меня был некоторый успех для интерполяции цветов для одной оси, двумерный расчет дает мне некоторые плохие головные боли. Хотя изображение, похоже, имеет нелинейный цветовой градиент, я был бы доволен линейным.

Если бы вы могли дать мне несколько советов, как это сделать с помощью numpy или других инструментов, я буду более чем благодарен.

введите описание изображения здесь

Вот супер короткое решение, использующее функцию масштабирования от scipy.ndimage . Я определяю изображение 2×2 RGB с внутренними цветами (здесь случайными) и просто увеличиваю его до 256×256, order=1 делает интерполяцию линейной. Вот код:

 import numpy as np import matplotlib.pyplot as plt im=(np.random.rand(2,2,3)*255).astype(np.uint8) from scipy.ndimage.interpolation import zoom zoomed=zoom(im,(128,128,1),order=1) plt.subplot(121) plt.imshow(im,interpolation='nearest') plt.subplot(122) plt.imshow(zoomed,interpolation='nearest') plt.show() 

Вывод:

вывод из сценария

Вот очень короткий способ сделать это с помощью ImageMagick, который установлен на большинстве дистрибутивов Linux и доступен для OSX и Windows. Существуют также привязки Python. В любом случае, просто в командной строке создайте квадрат 2×2 с цветами в 4 углах вашего изображения, а затем пусть ImageMagick будет расширяться и интерполироваться до полного размера:

 convert \( xc:"#59605c" xc:"#ebedb3" +append \) \ \( xc:"#69766d" xc:"#b3b3a0" +append \) \ -append -resize 256x256 result.png 

введите описание изображения здесь

Первая строка делает пиксель 1×1 каждого из ваших верхних левых и верхних правых углов и добавляет два бок о бок. Вторая строка делает пиксель 1×1 каждого из ваших нижних и нижних правых углов и добавляет их рядом друг с другом. Окончательная строка добавляет нижний ряд ниже верхнего ряда и увеличивает интерполяцию до 256×256.

Если вы хотите лучше понять, что происходит, вот то же самое основное изображение, но расширенное с использованием ближайшего соседа, а не интерполяция:

 convert \( xc:"#59605c" xc:"#ebedb3" +append \) \ \( xc:"#69766d" xc:"#b3b3a0" +append \) \ -append -scale 20x20 result.png 

введите описание изображения здесь

Вот три способа сделать эту билинейную интерполяцию. Первая версия выполняет всю арифметику в чистом Python, вторая использует состав изображения PIL , третий использует Numpy для выполнения арифметики. Как и ожидалось, чистый Python значительно медленнее, чем другие подходы. Версия Numpy (которая была получена из кода, написанного Andras Deak ), почти так же быстро, как версия PIL для небольших изображений, но для больших изображений версия PIL заметно быстрее.

Я также пробовал использовать технологию масштабирования jadsq в PIL, но результаты были не очень хорошими – я подозреваю, что интерполяционный код PIL немного глючен.

Если вы хотите создать много этих билинейных изображений градиента того же размера, метод PIL имеет еще одно преимущество: после создания масок композиции вам не нужно перестраивать их для каждого изображения.

 #!/usr/bin/env python3 ''' Simple bilinear interpolation Written by PM 2Ring 2016.09.14 ''' from PIL import Image from math import floor import numpy as np def color_square0(colors, size): tl, tr, bl, br = colors m = size - 1 r = range(size) def interp_2D(tl, tr, bl, br, x, y): u0, v0 = x / m, y / m u1, v1 = 1 - u0, 1 - v0 return floor(0.5 + u1*v1*tl + u0*v1*tr + u1*v0*bl + u0*v0*br) data = bytes(interp_2D(tl[i], tr[i], bl[i], br[i], x, y) for y in r for x in r for i in (0, 1, 2)) return Image.frombytes('RGB', (size, size), data) # Fastest def color_square1(colors, size): #Make an Image of each corner color tl, tr, bl, br = [Image.new('RGB', (size, size), color=c) for c in colors] #Make the composition mask mask = Image.new('L', (size, size)) m = 255.0 / (size - 1) mask.putdata([int(m * x) for x in range(size)] * size) imgt = Image.composite(tr, tl, mask) imgb = Image.composite(br, bl, mask) return Image.composite(imgb, imgt, mask.transpose(Image.TRANSPOSE)) # This function was derived from code written by Andras Deak def color_square2(colors, size): tl, tr, bl, br = map(np.array, colors) m = size - 1 x, y = np.mgrid[0:size, 0:size] x = x[..., None] / m y = y[..., None] / m data = np.floor(x*y*br + (1-x)*y*tr + x*(1-y)*bl + (1-x)*(1-y)*tl + 0.5) return Image.fromarray(np.array(data, dtype = 'uint8'), 'RGB') color_square = color_square1 #tl = (255, 0, 0) #tr = (255, 255, 0) #bl = (0, 0, 255) #br = (0, 255, 0) tl = (108, 115, 111) tr = (239, 239, 192) bl = (124, 137, 129) br = (192, 192, 175) colors = (tl, tr, bl, br) size = 256 img = color_square(colors, size) img.show() #img.save('test.png') 

вывод

билинейный градиент


Просто для удовольствия, вот простая программа GUI, использующая Tkinter, которая может использоваться для генерации этих градиентов.

 #!/usr/bin/env python3 ''' Simple bilinear colour interpolation using PIL, in a Tkinter GUI Inspired by https://stackoverflow.com/q/39485178/4014959 Written by PM 2Ring 2016.09.15 ''' import tkinter as tk from tkinter.colorchooser import askcolor from tkinter.filedialog import asksaveasfilename from PIL import Image, ImageTk DEFCOLOR = '#d9d9d9' SIZE = 256 #Make the composition masks mask = Image.new('L', (SIZE, SIZE)) m = 255.0 / (SIZE - 1) mask.putdata([int(m * x) for x in range(SIZE)] * SIZE) maskt = mask.transpose(Image.TRANSPOSE) def do_gradient(): imgt = Image.composite(tr.img, tl.img, mask) imgb = Image.composite(br.img, bl.img, mask) img = Image.composite(imgb, imgt, maskt) ilabel.img = img photo = ImageTk.PhotoImage(img) ilabel.config(image=photo) ilabel.photo = photo def set_color(w, c): w.color = c w.config(background=c, activebackground=c) w.img = Image.new('RGB', (SIZE, SIZE), color=c) def show_color(w): c = w.color newc = askcolor(c)[1] if newc is not None and newc != c: set_color(w, newc) do_gradient() def color_button(row, column, initcolor=DEFCOLOR): b = tk.Button(root) b.config(command=lambda w=b:show_color(w)) set_color(b, initcolor) b.grid(row=row, column=column) return b def save_image(): filetypes = [('All files', '.*'), ('PNG files', '.png')] fname = asksaveasfilename(title="Save Image",filetypes=filetypes) if fname: ilabel.img.save(fname) print('Saved image as %r' % fname) else: print('Cancelled') root = tk.Tk() root.title("Color interpolation") coords = ((0, 0), (0, 2), (2, 0), (2, 2)) tl, tr, bl, br = [color_button(r, c) for r,c in coords] ilabel = tk.Label(root, relief=tk.SUNKEN) do_gradient() ilabel.grid(row=1, column=1) b = tk.Button(root, text="Save", command=save_image) b.grid(row=3, column=1) root.mainloop()