I'm working on a program that allows you to free-form crop an image and simultaneously show you the resultant image. The following code achieves this.
import numpy as np
import cv2
def crop(pts):
global res
pts = np.array(pts)
mask = np.zeros((height, width), dtype=np.uint8)
cv2.fillPoly(mask, [pts], (255))
res = cv2.bitwise_and(img,img,mask = mask)
rect = cv2.boundingRect(pts) # returns (x,y,w,h) of the rect
cropped = res[rect[1]: rect[1] + rect[3], rect[0]: rect[0] + rect[2]]
cv2.imshow('res', res)
def mouse_callback(event, x, y, flags, params):
global selecting, pts, img
if event == cv2.EVENT_LBUTTONDOWN:
selecting = True
elif event == cv2.EVENT_MOUSEMOVE:
if selecting == True:
pts.append((x, y))
crop(pts)
print((x,y))
elif event == cv2.EVENT_LBUTTONUP:
selecting = False
res = None
selecting = False
pts = []
img = cv2.imread("girl.png", -1)
height = img.shape[0]
width = img.shape[1]
cv2.namedWindow('image')
cv2.setMouseCallback('image', mouse_callback)
cv2.imshow('image', img)
cv2.waitKey(0)
cv2.imwrite("res.png", res)
My challenge is to encapsulate the above code into an interface as follows:
So far I have followed a tutorial to achieve something similar:
# import the necessary packages
from tkinter import *
from PIL import Image
from PIL import ImageTk
from tkinter import filedialog as tkf
import cv2
def select_image():
# grab a reference to the image panels
global panelA, panelB
# open a file chooser dialog and allow the user to select an input
# image
path = tkf.askopenfilename()
# ensure a file path was selected
if len(path) > 0:
# load the image from disk, convert it to grayscale, and detect
# edges in it
image = cv2.imread(path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edged = cv2.Canny(gray, 50, 100)
# OpenCV represents images in BGR order; however PIL represents
# images in RGB order, so we need to swap the channels
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# convert the images to PIL format...
image = Image.fromarray(image)
edged = Image.fromarray(edged)
# ...and then to ImageTk format
image = ImageTk.PhotoImage(image)
edged = ImageTk.PhotoImage(edged)
# if the panels are None, initialize them
if panelA is None or panelB is None:
# the first panel will store our original image
panelA = Label(image=image)
panelA.image = image
panelA.pack(side="left", padx=10, pady=10)
# while the second panel will store the edge map
panelB = Label(image=edged)
panelB.image = edged
panelB.pack(side="right", padx=10, pady=10)
# otherwise, update the image panels
else:
# update the pannels
panelA.configure(image=image)
panelB.configure(image=edged)
panelA.image = image
panelB.image = edged
# initialize the window toolkit along with the two image panels
root = Tk()
panelA = None
panelB = None
# create a button, then when pressed, will trigger a file chooser
# dialog and allow the user to select an input image; then add the
# button the GUI
btn = Button(root, text="Select an image", command=select_image)
btn.pack(side="bottom", fill="both", expand="yes", padx="10", pady="10")
# kick off the GUI
root.mainloop()
What I have not managed to accomplish is implement the mouse event in tkinter, as the function takes in a window name - but I only want it to get applied to the image. How can I achieve this?
Instead of bind your command to your image, bind it to the tk.Label that contains the image:
import tkinter as tk
from PIL import Image
from PIL import ImageTk
root = tk.Tk()
ima = Image.open('test.png')
imag = ImageTk.PhotoImage(ima)
lab = tk.Label(root, image=imag)
lab.pack()
lab.bind('<Button-1>', *function)
root.mainloop()
Explaination
Tkinter provides a powerful mechanism to let you deal with events
yourself. For each widget, you can bind Python functions and methods
to events.
(...)
Events are given as strings, using a special event syntax:
Related
I found on the internet this python code for counting people with the Open CV library. I would need to open the window that opens with CV2, inside a Tkinter window in order to then add the commands for the settings.
This is the code from GitHub: https://github.com/Gupu25/PeopleCounter
How can I make the two OpenCv windows open inside a Tkinter window?
Here is a minimal example of opening a video in a tkinter window with OpenCV's VideoCapture object:
from tkinter import NW, Tk, Canvas, PhotoImage
import cv2
def photo_image(img):
h, w = img.shape[:2]
data = f'P6 {w} {h} 255 '.encode() + img[..., ::-1].tobytes()
return PhotoImage(width=w, height=h, data=data, format='PPM')
def update():
ret, img = cap.read()
if ret:
photo = photo_image(img)
canvas.create_image(0, 0, image=photo, anchor=NW)
canvas.image = photo
root.after(15, update)
root = Tk()
root.title("Video")
cap = cv2.VideoCapture("video.mp4")
canvas = Canvas(root, width=1200, height=700)
canvas.pack()
update()
root.mainloop()
cap.release()
To display 2 OpenCV videos, simply make a few adjustments, and use the np.hstack() method or the np.vstack() method, depending on whether you want your videos to be displayed side by side horizontally or vertically:
from tkinter import NW, Tk, Canvas, PhotoImage
import cv2
import numpy as np
def photo_image(img):
h, w = img.shape[:2]
data = f'P6 {w} {h} 255 '.encode() + img[..., ::-1].tobytes()
return PhotoImage(width=w, height=h, data=data, format='PPM')
def update():
ret1, img1 = cap1.read()
ret2, img2 = cap2.read()
if ret1:
photo = photo_image(np.hstack((img1, img2)))
canvas.create_image(0, 0, image=photo, anchor=NW)
canvas.image = photo
root.after(15, update)
root = Tk()
root.title("Video")
cap1 = cv2.VideoCapture("video1.mp4")
cap2 = cv2.VideoCapture("video2.mp4")
canvas = Canvas(root, width=1200, height=700)
canvas.pack()
update()
root.mainloop()
cap.release()
I had created the program with the combination of tkinter and cv2 for turning color image to black and white but I am not able to understand how to add the button that will save the black and white image. How to do it?
# importing the Packages
import cv2 # importing cv2 package for converting the colored imaged to gray or vice versa
import tkinter as tk # importing tkinter package for creating Gui and its instance io created is tk
from tkinter import filedialog as fd # importing the filedialog package from tkinter to open the file default file explorer
from PIL import Image, ImageTk # Python imaginary library for performing operation on images
root = tk.Tk()
root.title("Color to blackNwhite Convertor")
root.config(bg='#FFFAFA')
def selectimage():
global panelA,panelB #used two global variables to create two panels
location = fd.askopenfilename()
if len(location) > 0:
image = cv2.imread(location)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# convert the images to PIL format...
image = Image.fromarray(image)
gray = Image.fromarray(gray)
# ...and then to ImageTk format
image = ImageTk.PhotoImage(image)
gray = ImageTk.PhotoImage(gray)
if panelA is None or panelB is None:
panelA = tk.Label(image=image)
panelA.image = image
panelA.pack(side="left", padx=10, pady=10)
panelB = tk.Label(image=gray)
panelB.image = gray
panelB.pack(side="right", padx=10, pady=10)
else:
panelA.configure(image=image)
panelB.configure(image=gray)
panelA.image = image
panelB.image = gray
panelA = None
panelB = None
btn = tk.Button(root, text="Select an image",font=('arial',10,'bold'),bg="cyan", command=selectimage)
btn.config(height=5,width=15)
btn.pack(side="bottom", fill="both", expand="yes", padx="10", pady="10")
# kick off the GUI
root.mainloop()
You should keep the reference of the image returned by Image.fromarray(gray), then create a button to trigger a callback to save the gray image using save() on the gray image:
# importing the Packages
import cv2 # importing cv2 package for converting the colored imaged to gray or vice versa
import tkinter as tk # importing tkinter package for creating Gui and its instance io created is tk
from tkinter import filedialog as fd # importing the filedialog package from tkinter to open the file default file explorer
from PIL import Image, ImageTk # Python imaginary library for performing operation on images
root = tk.Tk()
root.title("Color to blackNwhite Convertor")
root.config(bg='#FFFAFA')
def selectimage():
global panelA,panelB #used two global variables to create two panels
global gray_image ### for saving reference to the gray image
location = fd.askopenfilename()
if len(location) > 0:
image = cv2.imread(location)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# convert the images to PIL format...
image = Image.fromarray(image)
gray_image = Image.fromarray(gray) ### save the gray image
# ...and then to ImageTk format
image = ImageTk.PhotoImage(image)
gray = ImageTk.PhotoImage(gray_image) ### use 'gray_image' instead of 'gray'
if panelA is None or panelB is None:
panelA = tk.Label(image=image)
panelA.image = image
panelA.pack(side="left", padx=10, pady=10)
panelB = tk.Label(image=gray)
panelB.image = gray
panelB.pack(side="right", padx=10, pady=10)
else:
panelA.configure(image=image)
panelB.configure(image=gray)
panelA.image = image
panelB.image = gray
panelA = None
panelB = None
gray_image = None
bframe = tk.Frame(root)
bframe.pack(side="bottom", fill="both", expand="yes")
btn = tk.Button(bframe, text="Select an image",font=('arial',10,'bold'),bg="cyan", command=selectimage)
btn.config(height=5,width=15)
btn.pack(side="left", fill="x", expand=1)
### function to save the gray image
def save_image():
if gray_image:
# ask the filename to be used as the output
filename = fd.asksaveasfilename()
if filename:
gray_image.save(filename) # save the gray image
tk.Button(bframe, text="Save image", font="arial 10 bold", bg="gold", width=15, height=5, command=save_image).pack(side="left", fill="x", expand=1)
# kick off the GUI
root.mainloop()
You may want to create a postscript file :
canvas.postscript(file="file_name.ps", colormode='color')
then use Pillow to save the postscript file as a png :
from PIL import Image
myImage = Image.open("file_name.ps")
myImage.save('imageName.png', 'png')
from PIL import Image, ImageTk
import numpy as np
import tkinter as tk
import time
def put_pixel(row, col):
pixels[row, col] = [255, 255, 255]
width = 20
height = 10
pixels = np.full((height, width, 3), 0, dtype=np.uint8)
root = tk.Tk()
root.geometry("300x300")
root.configure(background='grey')
img = ImageTk.PhotoImage(Image.open("maze.png"))
panel = tk.Label(root, image=img)
panel.pack(side = "top")
b = tk.Button(root, text="Sure!")
b.pack(side="bottom", fill="both")
for i in range(1, width-2, 2):
put_pixel(5, i)
time.sleep(2)
img = Image.fromarray(pixels, 'RGB')
panel.configure(image=img)
panel.image = img
root.mainloop()
The script just adds white pixels on a black image. But I want it to be animated to see the adding of every pixel step by step. So I tried to update an image in a label after every single pixel being added. But I got an error
_tkinter.TclError: image "" doesn't exist
If I don't use loop and just put an image into a label it works fine. How do I fix that?
You need to convert the PIL.Image into a tkinter.PhotoImage:
img = Image.fromarray(pixels, 'RGB')
tkimg = ImageTk.PhotoImage(img)
panel.configure(image=tkimg)
panel.image = img
tkinter.Label will only accept this type, not PIL images.
I'm using tkinter and I have a "sprite sheet" and I want to cut it into multiple images. I tried PIL:
img = Image.open("test.png").convert("RGBA")
img2 = img.crop([300,300,350,350])
image = ImageTk.PhotoImage(img2)
win = tk.Tk()
label = tk.Label(win, image = image)
label.pack()
but on my window, there is only an empty white rectangle and I don't understand why. Moreover I tried img2.show() just to make shure that img2 wasn't empty and it wasn't.
Here is your code, with a few changes. Note the call to Tk() at the top, and mainloop() at the bottom. The other modification is that it obtains the width and height of the image and then crops 25% from each of the four sides to leave the middle 50% of the image.
#!/usr/bin/python
from tkinter import *
from PIL import ImageTk,Image
root = Tk()
img = Image.open("test.png").convert("RGBA")
w, h = img.size
left = w/4
right = 3*w/4
upper = h/4
lower = 3*h/4
img2 = img.crop([ left, upper, right, lower])
image = ImageTk.PhotoImage(img2)
label = Label(root, image = image)
label.pack()
root.mainloop()
I am using python 3, opencv, and tkinter to create a GUI where I can upload an image and then display the image in a panel next to the final image which is the stacked version of all images in the same folder as the inputted image. The stacker() function simply stacks the images in the folder.
For some reason, when running this program, the image that is supposed to be in the panelA is displaying, while the image from panelB is not.
How can I display both images- the input and the output?
Also is there any way to speed up this code?
def select_image():
# grab a reference to the image panels
global panelA, panelB
# open a file chooser dialog and allow the user to select an input
# image
path = tkinter.filedialog.askopenfilename()
# ensure a file path was selected
if len(path) > 0:
# load the image from disk, convert it to grayscale, and detect
# edges in it
image = cv2.imread(path)
edged = stacker(os.path.dirname(os.path.dirname(path)))
# OpenCV represents images in BGR order; however PIL represents
# images in RGB order, so we need to swap the channels
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# convert the images to PIL format...
image = Image.fromarray(image)
edged = Image.fromarray(edged)
# ...and then to ImageTk format
image = ImageTk.PhotoImage(image)
edged = ImageTk.PhotoImage(edged)
# if the panels are None, initialize them
if panelA is None or panelB is None:
# the first panel will store our original image
panelA = tk.Label(image=image)
panelA.image = image
panelA.pack(side="left", padx=10, pady=10)
# while the second panel will store the edge map
panelB = tk.Label(image=edged)
panelB.image = edged
panelB.pack(side="right", padx=10, pady=10)
# otherwise, update the image panels
else:
# update the pannels
panelA.configure(image=image)
panelB.configure(image=edged)
panelA.image = image
panelB.image = edged
# initialize the window toolkit along with the two image panels
root = tk.Tk()
panelA = None
panelB = None
print("done")
# create a button, then when pressed, will trigger a file chooser
# dialog and allow the user to select an input image; then add the
# button the GUI
btn = tk.Button(root, text="Select an image", command=select_image)
print("done1")
btn.pack(side="bottom", fill="both", expand="yes", padx="10", pady="10")
print("done2")
# kick off the GUI
root.mainloop()
print("done3")
Your code works as intended, but both the images are probably not displaying due to the size of image. I would suggest a resize on the image.
edged = cv2.resize(edged, dsize=(500, 600), interpolation=cv2.INTER_CUBIC)
The reason both images may not be displayed is due to the size of the images. I would recommend using the resize function to ensure the images display regardless of the input image size.
I have provided a code snippet of how it should look below.
edged = cv2.resize(edged, dsize=(500, 600), interpolation=cv2.INTER_CUBIC)