Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
__pycache__/
venv/
stringArt/resources/string_pics/*
36 changes: 19 additions & 17 deletions stringArt/__main__.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@

import cv2 as cv
import time


from helpers.edgeImage import EdgeImg
from helpers.pinsImg import PinsImg
from helpers.pins_graph import PinsGraph
from helpers.image import Image
from helpers.stringImg import StringImg

from tests.test_graph import test_graph_neighbors
WINDOW_NAME = 'String Art'

def main():
start_time = time.time()
IMAGE_NAME = 'odri.jpg'
IMAGE_PATH = 'stringArt/resources/pics/' + IMAGE_NAME

WINDOW_NAME = 'String Art'
STRING_IMAGE_NAME = '{}.jpg'.format(time.strftime("%m.%d_%H:%M"))
STRING_IMAGE_PATH = 'stringArt/resources/string_pics/' + STRING_IMAGE_NAME

IMAGE_NAME = 'odri.jpg'
IMAGE_PATH = 'stringArt/resources/pics/' + IMAGE_NAME

STRING_IMAGE_NAME = '{}.jpg'.format(time.strftime("%m.%d_%H:%M"))
STRING_IMAGE_PATHE = 'stringArt/resources/string_pics/' + STRING_IMAGE_NAME
def main():

edge_img = EdgeImg(IMAGE_PATH)
edge_img.create_edge_img()

img = Image(IMAGE_PATH, WINDOW_NAME)
graph = PinsGraph(img)
pin_img = PinsImg(edge_img.edge_coords, edge_img.edge_img.shape)
pin_img.create_pin_img()

img.generate_string_img(graph)
graph = PinsGraph(pin_img.pin_arr, pin_img.distance)
graph.create_graph()

print('@@@@@@@ {}'.format(time.time()-start_time))
string_img = StringImg(graph.nodes, edge_img.img)
string_img.generate_string_img()

cv.imwrite(STRING_IMAGE_PATHE, img.img_out)
img.show_img(img.img_out, 0)
cv.imwrite(STRING_IMAGE_PATH, string_img.img_out)
string_img.show_img('String Image',string_img.img_out, 0)



Expand Down
34 changes: 34 additions & 0 deletions stringArt/helpers/edgeImage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import cv2 as cv
import numpy as np

class EdgeImg:

def __init__(self, image_path):
self.window_name = 'Edges Image'
self.img = cv.imread(image_path)
self.edge_img = None
self.edge_coords = None

def create_edge_img(self):
min_threshold = 40
max_threshold = 100
trackbar_name = 'Threshold'

cv.namedWindow(self.window_name)
cv.createTrackbar(trackbar_name, self.window_name, min_threshold, max_threshold, self.adjust_threshold)

self.adjust_threshold(min_threshold)
cv.waitKey()
cv.destroyAllWindows()
self.edge_coords = np.argwhere(self.edge_img == 255)


def adjust_threshold(self, low_threshold):
ratio = 3
kernel_size = 3

gray_img = cv.cvtColor(self.img, cv.COLOR_BGR2GRAY)
blur_img = cv.blur(gray_img, (3,3))
self.edge_img = cv.Canny(blur_img, low_threshold, low_threshold*ratio, kernel_size)

cv.imshow(self.window_name, self.edge_img)
117 changes: 0 additions & 117 deletions stringArt/helpers/image.py

This file was deleted.

34 changes: 34 additions & 0 deletions stringArt/helpers/pinsImg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import cv2 as cv
import numpy as np
import time

class PinsImg:
def __init__(self, edge_coords, shape):
self.edge_coords = edge_coords
self.shape = shape
self.window_name = 'Pins locations'
self.pin_arr = None


def create_pin_img(self):
trackbar_name = 'Pins distance'

cv.namedWindow(self.window_name)
cv.createTrackbar(trackbar_name, self.window_name, 15, 50, self.adjust_pin_distance)

self.adjust_pin_distance(15)
cv.waitKey()
cv.destroyAllWindows()

def adjust_pin_distance(self, distance):
start_time = time.time()
self.distance = distance
self.pin_arr = self.edge_coords[0::distance]
for x in range(0, self.shape[0]+1, distance):
for y in range(0, self.shape[1]+1, distance):
self.pin_arr = np.append(self.pin_arr, [[x,y]], axis=0)
pin_pic = np.zeros(self.shape, np.uint8)
for coords in self.pin_arr:
cv.circle(pin_pic, (coords[1], coords[0]), radius=0, color=(255, 255, 255), thickness=-1)
print('adjust pin time: {}'.format(time.time() - start_time))
cv.imshow(self.window_name, pin_pic)
32 changes: 19 additions & 13 deletions stringArt/helpers/pins_graph.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import math
import time

class PinsGraph:

def __init__(self, image):
def __init__(self, pin_arr, distance):
self.nodes = []
self.create_graph(image)
self.pin_arr = pin_arr
self.distance = distance


def create_graph(self, image):
for index, pin in enumerate(image.pin_arr):
def create_graph(self):
start_time = time.time()
for index, pin in enumerate(self.pin_arr):
self.nodes.append(PinNode(pin, index))
for i in range(len(self.nodes)-1):
pin = self.nodes[i]
for next_pin in self.nodes[i+1:]:
if pin.is_neighbor(next_pin, image.PIN_DISTANCE):
for index, pin in enumerate(self.nodes):
for next_pin in self.nodes[index+1:]:
# i like the naming of these methods, it's very clear what's going on
# I will keep this comment here for good luck
if pin.is_neighbor(next_pin, self.distance):
pin.add_neighbor(next_pin)
print('Create graph time: {}'.format(time.time()-start_time))

class PinNode:

def __init__(self, coords, id):
self.coords = (coords[1],coords[0])
self.id = id
self.coords = coords
self.neighbors = {}
self.strings = {}

Expand All @@ -33,9 +38,10 @@ def add_string(self, neighbor_node):
self.strings[neighbor_node.id] = neighbor_node
del neighbor_node.neighbors[self.id]
neighbor_node.strings[self.id] = self


def is_neighbor(self, next_pin, PIN_DISTANCE):
max_distance = PIN_DISTANCE * 3
distance = math.sqrt((self.coords[0] - next_pin.coords[0])**2 + (self.coords[1] - next_pin.coords[1])**2)

def is_neighbor(self, next_pin, pin_distance):
magic_number = 3
max_distance = (pin_distance * magic_number) ** 2 # ;)
distance = (self.coords[0] - next_pin.coords[0])**2 + (self.coords[1] - next_pin.coords[1])**2
return distance <= max_distance
57 changes: 57 additions & 0 deletions stringArt/helpers/stringImg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import cv2 as cv
import numpy as np
import random
from skimage.metrics import structural_similarity as ssim
import time

class StringImg:

def __init__(self, pin_nodes, img):
self.pin_nodes = pin_nodes
self.img = img
self.img_out = np.zeros(img.shape, np.uint8)

def generate_string_img(self): ########Working on it
start_time = time.time()
current_pin = random.choice(self.pin_nodes)
best_score = -1
prev_score = -1

counter = 1
alter_counter = 1

for _ in range(2000):
best_neighbor = None

for neighbor in list(current_pin.neighbors.values()): #Is this really the nicest way to delete while iterating? I wonder
mask = self.img_out.copy()
cv.line(mask, current_pin.coords, neighbor.coords, color=(255,255,255), thickness=1)
# this is probably what makes the whole thing slow... i think it should be possible to
# make it faster by only running it on a single channel (since this is all grayscale) and
# not running with full=True (the result of which is discarded anyway)...
score = ssim(self.img, mask, multichannel=True)
if score > best_score :
best_score = score
best_neighbor = neighbor
elif score < prev_score:
print('Delete: {} {}'.format(current_pin.coords, neighbor.coords))
del current_pin.neighbors[neighbor.id]
del neighbor.neighbors[current_pin.id]
if not best_neighbor:
print('@@@@@@@@@@@Alter root')
alter_counter += 1
best_neighbor = random.choice(list(current_pin.strings.values()))
else:
current_pin.add_string(best_neighbor)
print('Current pin: {} Best neighbor: {} counter: {}'.format(current_pin.coords, best_neighbor.coords, counter))
counter +=1
cv.line(self.img_out, current_pin.coords, best_neighbor.coords, color=(255,255,255), thickness=1)
self.show_img('String Image', self.img_out, 5)
current_pin = best_neighbor
prev_score = best_score
print('Number of alter roots: {}'.format(alter_counter))
print('String time: {}'.format(time.time()-start_time))

def show_img(self, window_name, img, time=0):
cv.imshow(window_name, img)
cv.waitKey(time)
Binary file added stringArt/resources/string_pics/img_out2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added stringArt/resources/string_pics/out.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 0 additions & 23 deletions stringArt/tests/test_graph.py

This file was deleted.