kopia lustrzana https://github.com/halfmonty/StringArtGenerator
pruning
rodzic
f21602234e
commit
d55d212301
193
stringart.py
193
stringart.py
|
@ -1,193 +0,0 @@
|
|||
import collections
|
||||
import math
|
||||
import os
|
||||
import cv2
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
MAX_LINES = 4000
|
||||
N_PINS = 36*8
|
||||
MIN_LOOP = 20 # To avoid getting stuck in a loop
|
||||
MIN_DISTANCE = 20 # To avoid very short lines
|
||||
LINE_WEIGHT = 15 # Tweakable parameter
|
||||
FILENAME = "h2.jpg"
|
||||
SCALE = 25 # For making a very high resolution render, to attempt to accurately gauge how thick the thread must be
|
||||
HOOP_DIAMETER = 0.625 # To calculate total thread length
|
||||
|
||||
tic = time.perf_counter()
|
||||
|
||||
img = cv2.imread(FILENAME, cv2.IMREAD_GRAYSCALE)
|
||||
|
||||
# Didn't bother to make it work for non-square images
|
||||
assert img.shape[0] == img.shape[1]
|
||||
length = img.shape[0]
|
||||
|
||||
def disp(image):
|
||||
cv2.imshow('image', image)
|
||||
cv2.waitKey(0)
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
# Cut away everything around a central circle
|
||||
X,Y = np.ogrid[0:length, 0:length]
|
||||
circlemask = (X - length/2) ** 2 + (Y - length/2) ** 2 > length/2 * length/2
|
||||
img[circlemask] = 0xFF
|
||||
breakpoint()
|
||||
pin_coords = []
|
||||
center = length / 2
|
||||
radius = length / 2 - 1/2
|
||||
|
||||
# Precalculate the coordinates of every pin
|
||||
for i in range(N_PINS):
|
||||
angle = 2 * math.pi * i / N_PINS
|
||||
pin_coords.append((math.floor(center + radius * math.cos(angle)),
|
||||
math.floor(center + radius * math.sin(angle))))
|
||||
|
||||
line_cache_y = [None] * N_PINS * N_PINS
|
||||
line_cache_x = [None] * N_PINS * N_PINS
|
||||
line_cache_weight = [1] * N_PINS * N_PINS # Turned out to be unnecessary, unused
|
||||
line_cache_length = [0] * N_PINS * N_PINS
|
||||
|
||||
print("Precalculating all lines... ", end='', flush=True)
|
||||
|
||||
for a in range(N_PINS):
|
||||
for b in range(a + MIN_DISTANCE, N_PINS):
|
||||
x0 = pin_coords[a][0]
|
||||
y0 = pin_coords[a][1]
|
||||
|
||||
x1 = pin_coords[b][0]
|
||||
y1 = pin_coords[b][1]
|
||||
|
||||
d = int(math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0)*(y1 - y0)))
|
||||
|
||||
#d = max(abs(y1-y0), abs(x1-x0)) inf-norm
|
||||
|
||||
# A proper (slower) Bresenham does not give any better result *shrug*
|
||||
xs = np.linspace(x0, x1, d, dtype=int)
|
||||
ys = np.linspace(y0, y1, d, dtype=int)
|
||||
|
||||
line_cache_y[b*N_PINS + a] = ys
|
||||
line_cache_y[a*N_PINS + b] = ys
|
||||
line_cache_x[b*N_PINS + a] = xs
|
||||
line_cache_x[a*N_PINS + b] = xs
|
||||
line_cache_length[b*N_PINS + a] = d
|
||||
line_cache_length[a*N_PINS + b] = d
|
||||
|
||||
|
||||
print("done")
|
||||
|
||||
error = np.ones(img.shape) * 0xFF - img.copy()
|
||||
|
||||
img_result = np.ones(img.shape) * 0xFF
|
||||
lse_buffer = np.ones(img.shape) * 0xFF # Used in the unused LSE algorithm
|
||||
|
||||
result = np.ones((img.shape[0] * SCALE, img.shape[1] * SCALE), np.uint8) * 0xFF
|
||||
line_mask = np.zeros(img.shape, np.float64) # XXX
|
||||
|
||||
line_sequence = []
|
||||
pin = 0
|
||||
line_sequence.append(pin)
|
||||
|
||||
thread_length = 0
|
||||
|
||||
last_pins = collections.deque(maxlen = MIN_LOOP)
|
||||
|
||||
for l in range(MAX_LINES):
|
||||
|
||||
if l % 100 == 0:
|
||||
print("%d " % l, end='', flush=True)
|
||||
|
||||
img_result = cv2.resize(result, img.shape, interpolation=cv2.INTER_AREA)
|
||||
|
||||
# Some trickery to fast calculate the absolute difference, to estimate the error per pixel
|
||||
diff = img_result - img
|
||||
mul = np.uint8(img_result < img) * 254 + 1
|
||||
absdiff = diff * mul
|
||||
print(absdiff.sum() / (length * length))
|
||||
|
||||
max_err = -math.inf
|
||||
best_pin = -1
|
||||
|
||||
# Find the line which will lower the error the most
|
||||
for offset in range(MIN_DISTANCE, N_PINS - MIN_DISTANCE):
|
||||
test_pin = (pin + offset) % N_PINS
|
||||
if test_pin in last_pins:
|
||||
continue
|
||||
|
||||
xs = line_cache_x[test_pin * N_PINS + pin]
|
||||
ys = line_cache_y[test_pin * N_PINS + pin]
|
||||
|
||||
# Simple
|
||||
# Error defined as the sum of the brightness of each pixel in the original
|
||||
# The idea being that a wire can only darken pixels in the result
|
||||
line_err = np.sum(error[ys,xs]) * line_cache_weight[test_pin*N_PINS + pin]
|
||||
'''
|
||||
|
||||
# LSE Unused
|
||||
goal_pixels = img[ys, xs]
|
||||
old_pixels = lse_buffer[ys, xs]
|
||||
new_pixels = np.clip(old_pixels - LINE_WEIGHT * line_cache_weight[test_pin*N_PINS + pin], 0, 255)
|
||||
|
||||
line_err = np.sum((old_pixels - goal_pixels) ** 2) - np.sum((new_pixels - goal_pixels) ** 2)
|
||||
#LSE
|
||||
'''
|
||||
|
||||
if line_err > max_err:
|
||||
max_err = line_err
|
||||
best_pin = test_pin
|
||||
|
||||
line_sequence.append(best_pin)
|
||||
|
||||
xs = line_cache_x[best_pin * N_PINS + pin]
|
||||
ys = line_cache_y[best_pin * N_PINS + pin]
|
||||
weight = LINE_WEIGHT * line_cache_weight[best_pin*N_PINS + pin]
|
||||
|
||||
'''
|
||||
#LSE
|
||||
old_pixels = lse_buffer[ys, xs]
|
||||
new_pixels = np.clip(old_pixels - weight, 0, 255)
|
||||
|
||||
lse_buffer[ys, xs] = new_pixels
|
||||
#LSE
|
||||
'''
|
||||
|
||||
# Subtract the line from the error
|
||||
line_mask.fill(0)
|
||||
line_mask[ys, xs] = weight
|
||||
error = error - line_mask
|
||||
error.clip(0, 255)
|
||||
|
||||
# Draw the line in the result
|
||||
cv2.line(result,
|
||||
(pin_coords[pin][0] * SCALE, pin_coords[pin][1] * SCALE),
|
||||
(pin_coords[best_pin][0] * SCALE, pin_coords[best_pin][1] * SCALE),
|
||||
color=0, thickness=4, lineType=8)
|
||||
|
||||
x0 = pin_coords[pin][0]
|
||||
y0 = pin_coords[pin][1]
|
||||
|
||||
x1 = pin_coords[best_pin][0]
|
||||
y1 = pin_coords[best_pin][1]
|
||||
|
||||
# Calculate physical distance
|
||||
dist = math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0)*(y1 - y0))
|
||||
thread_length += HOOP_DIAMETER / length * dist
|
||||
|
||||
last_pins.append(best_pin)
|
||||
pin = best_pin
|
||||
|
||||
img_result = cv2.resize(result, img.shape, interpolation=cv2.INTER_AREA)
|
||||
|
||||
diff = img_result - img
|
||||
mul = np.uint8(img_result < img) * 254 + 1
|
||||
absdiff = diff * mul
|
||||
|
||||
print(absdiff.sum() / (length * length))
|
||||
|
||||
print('\x07')
|
||||
toc = time.perf_counter()
|
||||
print("%.1f seconds" % (toc - tic))
|
||||
|
||||
cv2.imwrite(os.path.splitext(FILENAME)[0] + "-out.png", result)
|
||||
|
||||
with open(os.path.splitext(FILENAME)[0] + ".json", "w") as f:
|
||||
f.write(str(line_sequence))
|
Ładowanie…
Reference in New Issue