import tkinter as tk
from PIL import Image, ImageTk
import cv2
import ImageTracer
from datetime import datetime
from mathutils import between
isCameraOn = False
[docs]def lights_camera_action():
"""
Sets up connection to the webcam and starts showing the webcam feed in the tkinter window.
"""
global cam, isCameraOn
cam = cv2.VideoCapture(0)
isCameraOn = True
webcam_stream()
[docs]def webcam_stream():
"""
Shows the webcam feed in the tkinter window.
"""
global frame, isCameraOn
if isCameraOn:
_, frame = cam.read()
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
webcam.imgtk = imgtk
webcam.configure(image=imgtk)
webcam.after(1, webcam_stream)
[docs]def stopWebcamStream():
"""
Stops webcam stream.
"""
global isCameraOn, root
isCameraOn = False
cam.release()
[docs]def takePicture():
"""
Stops the webcam stream and opens the cropping GUI on the last frame from the webcam feed.
"""
global frame
now = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
cv2.imwrite(f"../output_images/image_{now}.jpg", frame)
stopWebcamStream()
cropLabel = tk.Label(window, text='Crop image by mouse selection')
cropLabel.grid(row=0)
canvas = tk.Canvas(window, width=frame.shape[1], height=frame.shape[0])
canvas.create_image(0, 0, anchor=tk.NW, image=webcam.imgtk)
canvas.grid(row=1)
p1: tuple | None = None
p2: tuple | None = None
crop_box_id = None
has_rect = False
move_start: tuple | None = None
def mouse_down(event):
"""
Handles the mouse being pressed down.
"""
nonlocal p1, has_rect, move_start
if has_rect and between(p1[0], p2[0], event.x) and between(p1[1], p2[1], event.y):
move_start = event.x, event.y
else:
has_rect = False
reset_rect()
p1 = event.x, event.y
def update_rect():
"""
Updates the rectangle being drawn to screen.
"""
nonlocal p1, p2, crop_box_id
if p1 and p2:
if crop_box_id:
canvas.coords(crop_box_id, *p1, *p2)
else:
crop_box_id = canvas.create_rectangle(*p1, *p2, width=1)
def mouse_move(event):
"""
Updates the rectangle being drawn to screen as the mouse moves.
"""
nonlocal p1, p2, has_rect, move_start
if has_rect:
dx, dy = event.x - move_start[0], event.y - move_start[1]
p1 = p1[0] + dx, p1[1] + dy
p2 = p2[0] + dx, p2[1] + dy
move_start = event.x, event.y
else:
p2 = event.x, event.y
update_rect()
def mouse_up(event):
"""
Handles the mouse being released.
"""
nonlocal p2, has_rect
if not has_rect:
p2 = event.x, event.y
has_rect = True
def reset_rect(event=None):
"""
Resets the rectangle on screen.
"""
nonlocal p1, p2, has_rect, crop_box_id
p1 = p2 = None
has_rect = False
canvas.delete(crop_box_id)
crop_box_id = None
canvas.bind("<Button-1>", mouse_down)
canvas.bind("<B1-Motion>", mouse_move)
canvas.bind("<ButtonRelease-1>", mouse_up)
root.bind('<Escape>', reset_rect)
webcam.destroy()
def doTrace():
"""
Opens a window with the options to edit the minimum stroke length and the animation speed.
Once the "run" button is clicked, the image opens up in a new window and an animation plays where all the edges are traced.
"""
global frame
window.destroy()
root.unbind('<Escape>')
if p1 and p2:
frame = frame[min(p1[1], p2[1]):max(p1[1], p2[1]), min(p1[0], p2[0]):max(p1[0], p2[0])]
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
root.geometry(f'300x300+{int(screen_width / 2 - 150)}+{int(screen_height / 2 - 150)}')
titleLabel = tk.Label(root, text="Image Tracer")
titleLabel.config(font=("Arial", 15))
titleLabel.pack(pady=(10, 0))
tk.Label(root, text="Minimum stroke length").pack(pady=(10, 0))
minStrokeLengthSlider = tk.Scale(root, from_=0, to=1000, length=250, orient='horizontal')
minStrokeLengthSlider.set(50)
minStrokeLengthSlider.pack()
tk.Label(root, text="Animation speed").pack()
speedSlider = tk.Scale(root, from_=0, to=100, length=250, orient='horizontal')
speedSlider.set(100)
speedSlider.pack()
tk.Button(root, text='Run', command=lambda: cv2.imwrite(f'../output_images/trace_{now}.jpg',
ImageTracer.run(frame, minStrokeLengthSlider.get(),
speedSlider.get())),
width=10).pack(pady=(20, 20))
tk.Label(root, text="Press Q to stop animation").pack()
btnPicture.configure(text='Trace!', command=doTrace)
btnPicture.grid(row=2)
if __name__ == '__main__':
root = tk.Tk()
root.title("Artificial Picasso")
window = tk.Frame(root)
window.grid()
webcam = tk.Label(window)
webcam.grid()
btnPicture = tk.Button(window, width=20, text="Take Picture", font="none 12", command=takePicture)
btnPicture.grid()
lights_camera_action()
root.mainloop()