Skip to content

neumann-mlucas/StringArt.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

StringArt.jl

David by Michelangelo

This script implements a simplified version of the String Art greedy algorithm. Given an image, it attempts to create a similar representation using a single thread wound around a circle of nails, effectively constructing an image using only lines. This technique is probably most well-know for the artistic works of Petros Vrllis.

Most implementations often require high-contrast images, and still the results can vary significantly from one image to another. In this version, I've tweaked the algorithm parameters to enhance the contrast and detail in the final output. While this adjustment impacts performance, it remains usable.

Additionally, the script features a command-line interface (CLI) with various tweakable parameters, option flags, RGB color mode and you can also save the GIF animation with easy. Feel free to explore these options to customize the output according to your preferences, if you want to reuse the code or call it somewhere else, you should look at StringArt.run.

Useful Resources:

Algorithm

  1. Setup:
  • Load the source image and create an empty output image.
  • Calculate pin positions and all possible lines between 2 pins.
  1. Iteration Step:
  • Choose a pin (P).
  • Create all possible lines connecting P to the other pins in the circle.
  • Calculate the error between each lines and the source image.
  • Find the line (L) that gives the minimum error.
  • Update the output image adding L, and the source image subtracting L.
  • Set the pin to be opposite point in the line L.

Line Generating Function: One-pixel-width lines (or any square/stair-like lines) do not yield good results. Experimentation with different line functions is essential here. I ended up choosing to apply the Gaussian Blur Kernel to the line. It's simple, and it works (also, it eliminates the need to fine-tune other parameters).

Line Pixel Strength: Opt for low line pixel values to create nuanced shades of gray in the output image.

Choose Pin: Randomizing the pin position periodically (every N steps) tends to give better results.

Error Function: Arguably the most critical part of the algorithm. You should minimize the error here and not any other metric (I lost a lot of time doing that...). The best function that I found was the squared difference between the source image and the line (but the performance takes a considerable hit here).

Excluding Already Visited Lines: While excluding used lines each iteration improves performance, it results in a more diffuse and noisy image. In this implementation, visited lines are retained. If you prefer the noisy style, just uncomment the lines with filter!.

Requirements

The Libraries:

  • ArgParse
  • Images
  • Logging
  • Memoize

Usage

# basic
$ julia -O3 -t 8 main.jl -i [input image] -o [output image]

# suggested
$ julia -O3 -t 8 main.jl -s 720 --steps 2000 -i [input image] -o [output image]

# alter the image resolution
$ julia -O3 -t 8 main.jl -s 800 -i [input image] -o [output image]

# RGB color mode
$ julia -O3 -t 8 main.jl --color -i [input image] -o [output image]

#  RGB color mode with custom colors
$ julia -O3 -t 8 main.jl --color --colors "#FFFF33,#33FFFF" -i [input image] -o [output image]

# Saves GIF output
$ julia -O3 -t 8 main.jl --gif -i [input image] -o [output image]

Debugging Utilities

# plot pins used in the image
$ julia utils.jl -f plot_pins -i [input image] -o [output image]

# plot color channel for a given color and input image
$ julia utils.jl -f plot_colors --colors "#FF0000" -i [input image] -o [output image]

Parameters

usage: main.jl -i INPUT [-o OUTPUT] [-s SIZE] [-n PINS]
               [--steps STEPS] [--gif] [--color] [--verbose] [-h]

optional arguments:
  -i, --input INPUT    input image path
  -o, --output OUTPUT  output image path whiteout extension (default:
                       "output")
  -s, --size SIZE      output image size in pixels (type: Int64,
                       default: 512)
  -n, --pins PINS      number of pins to use in canvas (type: Int64,
                       default: 180)
  --steps STEPS        number of algorithm iterations (type: Int64,
                       default: 1000)
  --gif                Save output as a GIF
  --color              RGB mode
  --colors             HEX code of colors to use in RGB mode
  --verbose            verbose mode
  -h, --help           show this help message and exit

keep the number of pins bellow 250 and the image size bellow 1000.

the number of iteration steps is dependent on the image size. For size between 500 and 800, 2000 iteration is more than enough.

GIF mode with custom colors RGB mode is not supported

Gallery

David by Michelangelo

Albert Einstein

Eye

RGB Mode

Birth of Venus

Lady with an Ermine

The Fallen Angel

Animation

David Gif


TODO

  • GIF mode
  • Optimize Memory Usage
  • Enhance Image Contrast and Remove Background
  • Support Custom Colors in GIF mode
  • Port Code to the GPU

About

String Art Generator written in Julia

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages