kopia lustrzana https://github.com/halfmonty/StringArtGenerator
pruning
rodzic
721582a091
commit
ae81e4d9f0
193
stringart.js
193
stringart.js
|
@ -1,193 +0,0 @@
|
|||
var collections = require('collections');
|
||||
var math = require('math');
|
||||
var os = require('os');
|
||||
var cv2 = require('cv2');
|
||||
var np = require('numpy');
|
||||
var time = require('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 = 'leki-lig.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];
|
||||
|
||||
function 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;
|
||||
|
||||
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.push((math.floor(center + radius * math.cos(angle)),
|
||||
math.floor(center + radius * math.sin(angle))));
|
||||
}
|
||||
line_cache_y = [null] * N_PINS * N_PINS;
|
||||
|
||||
line_cache_x = [null] * 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;
|
||||
|
||||
console.log('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 = Number(math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0)*(y1 - y0)));
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('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.push(pin);
|
||||
|
||||
thread_length = 0;
|
||||
|
||||
last_pins = collections.deque(maxlen = MIN_LOOP);
|
||||
|
||||
for (l in range(MAX_LINES)) {
|
||||
|
||||
if (l % 100 == 0) {
|
||||
console.log('%d ' % l, end='', flush=true);
|
||||
}
|
||||
img_result = cv2.resize(result, img.shape, Numbererpolation=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;
|
||||
console.log(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];
|
||||
'\n' +
|
||||
' # LSE Unused\n' +
|
||||
' goal_pixels = img[ys, xs]\n' +
|
||||
' old_pixels = lse_buffer[ys, xs]\n' +
|
||||
' new_pixels = np.clip(old_pixels - LINE_WEIGHT * line_cache_weight[test_pin*N_PINS + pin], 0, 255)\n' +
|
||||
' line_err = np.sum((old_pixels - goal_pixels) ** 2) - np.sum((new_pixels - goal_pixels) ** 2)\n' +
|
||||
' #LSE\n' +
|
||||
' ';
|
||||
|
||||
if (line_err > max_err) {
|
||||
max_err = line_err;
|
||||
best_pin = test_pin;
|
||||
}
|
||||
line_sequence.push(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];
|
||||
|
||||
'\n' +
|
||||
' #LSE\n' +
|
||||
' old_pixels = lse_buffer[ys, xs]\n' +
|
||||
' new_pixels = np.clip(old_pixels - weight, 0, 255)\n' +
|
||||
' lse_buffer[ys, xs] = new_pixels\n' +
|
||||
' #LSE\n' +
|
||||
' ';
|
||||
|
||||
// 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.push(best_pin);
|
||||
pin = best_pin;
|
||||
|
||||
img_result = cv2.resize(result, img.shape, Numbererpolation=cv2.INTER_AREA);
|
||||
|
||||
diff = img_result - img;
|
||||
mul = np.uint8(img_result < img) * 254 + 1;
|
||||
absdiff = diff * mul;
|
||||
|
||||
console.log(absdiff.sum() / (length * length));
|
||||
|
||||
console.log('\x07');
|
||||
toc = time.perf_counter();
|
||||
console.log('%.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