The video above was generated using neuralart.
The neural network takes position coordinates, distance to origin, and a vector z as inputs, and outputs a corresponding pixel intensity. The weights are randomly initialized. This configuration is described in more detail in the following blog posts and pages from studio otoro.
- Neural Network Generative Art in Javascript
- Generating Abstract Patterns with TensorFlow
- Neurogram
- Interactive Neural Network Art
The video frames are generated by transitioning the z input. In an earlier experiment, I tried varying z by transitioning along a straight line. The corresponding video is included in an earlier post. I also tried using a random walk, but found that there was too much back-and-forth motion this way.
The video above transitions z by walking along randomly generated cubic Bézier curves. The curves are connected, and the first handle of each curve has the same slope as the last handle of the preceding curve, in order to achieve smooth transitions across curves. Binary search is used so that the distance of each step along the curve is approximately fixed.
Here’s the source code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from __future__ import print_function | |
# Images can be converted to video with ffmpeg. | |
# > ffmpeg -pattern_type glob \ | |
# -i "*.png" \ | |
# -vcodec libx264 \ | |
# output.avi | |
import os | |
import sys | |
from PIL import Image | |
import neuralart | |
import numpy as np | |
RENDER_SEED = 10 | |
Z_SEED = 0 | |
DEVICE = "cpu" # 'cpu' for CPU, 'cuda' for GPU | |
ITERATIONS = 10000 | |
MIN_STEP_SIZE = .005 | |
MAX_STEP_SIZE = .006 | |
XRES = 2048 | |
YRES = 2048 | |
XLIM = np.array([-1.0, 1.0]) | |
YLIM = XLIM * (float(YRES) / XRES) | |
DEPTH = 9 | |
CHANNELS = 1 | |
OUTPUT_STD = 1.5 | |
HIDDEN_STD = 1.1 | |
Z_DIMS = 4 | |
Z_RANGE = (-1, 1) | |
RADIUS=True | |
if len(sys.argv) != 2: | |
sys.stderr.write("Usage: {} DIRECTORY\n".format(sys.argv[0])) | |
sys.exit(1) | |
directory = sys.argv[1] | |
if not os.path.exists(directory): | |
os.makedirs(directory) | |
rng = np.random.RandomState(seed=Z_SEED) | |
zfill = len(str(ITERATIONS – 1)) | |
M = np.array([ | |
[-1, 3, -3, 1], | |
[ 3, -6, 3, 0], | |
[-3, 3, 0, 0], | |
[ 1, 0, 0, 0] | |
]) | |
P0 = rng.uniform(*Z_RANGE, size=Z_DIMS) | |
P1 = rng.uniform(*Z_RANGE, size=Z_DIMS) | |
P2 = rng.uniform(*Z_RANGE, size=Z_DIMS) | |
P3 = rng.uniform(*Z_RANGE, size=Z_DIMS) | |
count = 0 | |
while count < ITERATIONS: | |
P0 = P3 | |
P1 = 2 * P3 – P2 | |
P2 = rng.uniform(*Z_RANGE, size=Z_DIMS) | |
P3 = rng.uniform(*Z_RANGE, size=Z_DIMS) | |
pos = P0 | |
lo = 0.0 | |
hi = 1.0 | |
while np.linalg.norm(P3 – pos) > MIN_STEP_SIZE: | |
if count >= ITERATIONS: | |
break | |
t = (lo + hi) / 2.0 | |
P = np.vstack((P0, P1, P2, P3)).T | |
C = P.dot(M).dot(np.array([t ** 3, t ** 2, t, 1])) | |
distance = np.linalg.norm(C – pos) | |
if distance < MIN_STEP_SIZE: | |
lo = t | |
continue | |
elif distance > MAX_STEP_SIZE: | |
hi = t | |
continue | |
pos = C | |
result = neuralart.render( | |
depth=DEPTH, | |
xres=XRES, | |
yres=YRES, | |
xlim=XLIM, | |
ylim=YLIM, | |
seed=RENDER_SEED, | |
channels=CHANNELS, | |
output_std=OUTPUT_STD, | |
hidden_std=HIDDEN_STD, | |
radius=RADIUS, | |
z=C, | |
device=DEVICE | |
) | |
file = os.path.join(directory, str(count).zfill(zfill) + ".png") | |
im = Image.fromarray(result.squeeze()) | |
im.save(file, "png") | |
count += 1 | |
lo = t | |
hi = 1.0 |