Дизеринг Флойда-Стейнберга - поистине волшебная техника. Предполагается, что это обманывает ваш глаз и мозг, заставляя вас думать, что вы видите больше, чем есть на самом деле.
В общем, дизеринг - это метод уменьшения цветового пространства изображения путем добавления искусственного шума. Ключевой идеей является то, что количество света в области должно оставаться примерно одинаковым.
Floyd-Steinberg использует неравномерное распределение ошибки квантования по окружающим пикселям. Это означает, что центральный пиксель округляется до 0 или 1. Затем остаточная ошибка добавляется к окружающим пикселям.
Все три изображения, которые вы можете найти в этой статье, были полутоновыми и размытыми. Все они состоят только из двухцветного шума. Остальным занимается ваш мозг.
А если хотите увидеть настоящие шедевры, попробуйте погуглить C64 artwork. Изображения обычно имеют 4, 8 или 16 цветов, но мы воспринимаем гораздо более широкую цветовую гамму только из-за примененного дизеринга.
Https://github.com/coells/100days
Https://notebooks.azure.com/coells/libraries/100days
алгоритм
def image_dither(path, black='#000000', white='#ffffff'): image_rgb = read_image(path) image_gray = grayscale(image_rgb) image_bw = floyd_steinberg(image_gray) show(layout([[ plot(image_gray, palette=gray(256)), plot(image_bw, palette=[black, white]) ]])) def floyd_steinberg(image): image = image.copy() distribution = np.array([7, 3, 5, 1], dtype=float) / 16 u = np.array([0, 1, 1, 1]) v = np.array([1, -1, 0, 1]) for y in range(image.shape[0] - 1): for x in range(image.shape[1] - 1): value = np.round(image[y, x]) error = image[y, x] - value image[y, x] = value image[y + u, x + v] += error * distribution image[:, -1] = 1 image[-1, :] = 1 return image def grayscale(image): height, width, _ = image.shape image = np.array(image, dtype=np.float32) / 255 image = image[:, :, 0] * .21 + \ image[:, :, 1] * .72 + \ image[:, :, 2] * .07 return image.reshape(height, width) def read_image(path, size=400): if path.startswith('https://'): image = Image.open(get(path, stream=True).raw) else: image = Image.open(path) width, height = image.size width, height = size, int(size * height / width) image = image.resize((width, height), Image.ANTIALIAS) data = image.getdata() assert data.bands in [3, 4], 'RGB or RGBA image is required' raw = np.array(data, dtype=np.uint8) return raw.reshape(height, width, data.bands) def plot(image, palette): y, x = image.shape plot = figure(x_range=(0, x), y_range=(0, y), plot_width=x, plot_height=y) plot.axis.visible = False plot.toolbar_location = None plot.min_border = 0 plot.image([np.flipud(image)], x=0, y=0, dw=x, dh=y, palette=palette) return plot
запустить
image_dither('./resource/day 96 - valinka.jpg')