使用 Tkinter 显示和编辑 opencv 图像
Display and EDIT opencv image using Tkinter
我有一个简单的 GUI 应用程序来显示来自所选相机的图像和转换后的图像。要进行转换,我需要在图像上选择 4 个点,所以我想通过单击来选择它们。但为了做到这一点,图片必须是 opencv 图像。所以我的想法是“冻结”来自相机的图像,然后对其进行编辑(例如通过函数 set_default_points() 在其上绘制 4 个圆圈)。
这是我的代码
(目前主要的函数是show_frame(), freeze_camera() 和 set_default_points())
from tkinter import *
from tkinter import ttk
import cv2
import numpy as np
from PIL import Image, ImageTk
# ----- SET -----
GREY = "#D8D8D8"
BLUE = "#81F7F3"
AQUA = "#9CC3D5"
ICE = "#C7D3D4"
canvas_width = 500
canvas_height = 600
def camera_amount():
'''Returns int value of available camera devices connected to the host device
from url: https://www.codegrepper.com/code-examples/python/how+to+count+how+many+cameras+you+have+with+python
'''
camera = 0
while True:
if (cv2.VideoCapture(camera).grab()) is True:
camera = camera + 1
else:
cv2.destroyAllWindows()
return camera
# ----- default start images -----
img1 = cv2.imread("d1.png")
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
resized1 = cv2.resize(img1, (canvas_width, canvas_height))
img2 = cv2.imread("ipm.png")
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
resized2 = cv2.resize(img2, (canvas_width, canvas_height))
# ----- Application ------
class App(Tk):
def __init__(self):
super().__init__()
self.title("Image TOP-DOWN Tranformation")
self.minsize(width=1200, height=700)
self.config(padx=5, pady=5, bg=ICE)
im1 = Image.fromarray(resized1)
im2 = Image.fromarray(resized2)
self.org_img = ImageTk.PhotoImage(im1)
self.transf_img = ImageTk.PhotoImage(im2)
self.canvas1 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.org_image_container = self.canvas1.create_image(250, 300, anchor="center", image=self.org_img)
self.canvas1.grid(padx=10, pady=10, row=1, rowspan=8, column=0)
self.canvas2 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.trf_image_container = self.canvas2.create_image(250, 300, anchor="center", image=self.transf_img)
self.canvas2.grid(padx=10, pady=10, row=1, rowspan=8, column=1)
# ----- Labels -----
self.label1 = Label(text="Original image")
self.label1.grid(row=0, column=0)
self.label2 = Label(text="Transformed image")
self.label2.grid(row=0, column=1)
self.label3 = Label(text="Functionalities")
self.label3.grid(row=0, column=2)
self.move_label = Label(text="Move points")
self.move_label.grid(row=8, column=2, columnspan=3)
# ----- Buttons -----
self.selected_camera = StringVar()
self.camera_cb = ttk.Combobox(self, textvariable=self.selected_camera)
self.camera_cb.grid(padx=5, pady=5, row=1, column=2, columnspan=3)
amount_of_cameras = camera_amount()
self.camera_cb['values'] = [i for i in range(amount_of_cameras)]
self.camera_cb.current(0)
self.change_camera = Button(text="Change Camera", command=self.choose_camera)
self.change_camera.grid(padx=5, pady=5, row=2, column=2, columnspan=3)
self.freeze_cam = Button(text="Freeze camera", command=self.freeze_camera)
self.freeze_cam.grid(padx=5, pady=5, row=3, column=2, columnspan=3)
self.default_points = Button(text="Set default points", command=self.set_default_points)
self.default_points.grid(padx=5, pady=5, row=4, column=2, columnspan=3)
self.transform_button = Button(text="Transform", command=self.transform)
self.transform_button.grid(padx=5, pady=5, row=5, column=2, columnspan=3)
self.clear_button = Button(text="CLEAR", command=self.clear)
self.clear_button.grid(padx=5, pady=5, row=6, column=2, columnspan=3)
self.save_button = Button(text="SAVE", command=self.save)
self.save_button.grid(padx=5, pady=5, row=7, column=2, columnspan=3)
self.up_butt = Button(text="↑", command=self.up)
self.up_butt.grid(padx=5, pady=5, row=9, column=3)
self.down_butt = Button(text="↓", command=self.down)
self.down_butt.grid(padx=5, pady=5, row=10, column=3)
self.left_butt = Button(text="←", command=self.left)
self.left_butt.grid(padx=5, pady=5, row=10, column=2, columnspan=2)
self.right_butt = Button(text="→", command=self.right)
self.right_butt.grid(padx=5, pady=5, row=10, column=4)
# ----- fields -----
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.imgtk = None
self.after_id = None
self.resized11 = None
self.input_points = []
self.output_points = []
def choose_camera(self):
self.freeze_camera()
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.show_frame()
# po upakowaniu w klasę po prostu zmienić jedno pole, a w funkcji odpowiedzialnej za wyświetlanie kamery dac ifa
def freeze_camera(self):
self.canvas1.after_cancel(self.after_id)
# now, the image on canvas is freezed,
# it means, that the picture is last imgtk from show_frame function,
# but it's not like cv2 image, and it cannot be editable
# I tried example like this, but it doesn't work
# pil_image = PIL.Image.open('Image.jpg').convert('RGB')
# open_cv_image = numpy.array(pil_image)
# # Convert RGB to BGR
# open_cv_image = open_cv_image[:, :, ::-1].copy()
def set_default_points(self):
default_points = [[448, 609], [580,609], [580,741], [448,741]]
for pts in default_points:
cv2.circle(self.imgtk, pts, 5, (0, 0, 255), -2)
pass
def transform(self):
pass
def clear(self):
pass
def save(self):
pass
def up(self):
pass
def down(self):
pass
def left(self):
pass
def right(self):
pass
def draw_circle(self, event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDBLCLK:
cv2.circle(self.imgtk, (x, y), 5, (255, 0, 0), -2)
self.input_points.append([x, y])
if event == cv2.EVENT_RBUTTONDBLCLK:
cv2.circle(self.imgtk, (x, y), 5, (0, 0, 255), -2)
self.output_points.append([x, y])
def show_frame(self):
"""
https://www.tutorialspoint.com/how-to-show-webcam-in-tkinter-window
"""
img11 = self.cap.read()[1]
cv2image = cv2.cvtColor(img11, cv2.COLOR_BGR2RGB)
self.resized11 = cv2.resize(cv2image, (canvas_width, canvas_height))
im11 = Image.fromarray(self.resized11)
self.imgtk = ImageTk.PhotoImage(im11)
self.canvas1.imgtk = self.imgtk
self.canvas1.itemconfig(self.org_image_container, image=self.imgtk)
self.after_id = self.canvas1.after(10, self.show_frame)
if __name__ == "__main__":
app = App()
app.show_frame()
app.mainloop()
问题是当我尝试使用函数 set_default_points 编辑那张冻结的图片时,我发生了这个错误:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Python_versions\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "D:\DUCKIETOWN\aplikacja_do_transformacji_obrazu\APP.py", line 146, in set_default_points
cv2.circle(self.imgtk, pts, 5, (0, 0, 255), -2)
cv2.error: OpenCV(4.5.5) :-1: error: (-5:Bad argument) in function 'circle'
> Overload resolution failed:
> - img is not a numpy array, neither a scalar
> - Expected Ptr<cv::UMat> for argument 'img'
编辑:看来,实现我的目标的唯一方法是使用 opencv 图像在分离的 opencv window 上选择转换点,然后将其上传到我的 tkinter 显示 window.
这里是更正后的代码:
import tkinter.messagebox
from tkinter import *
from tkinter import ttk
import cv2
import numpy as np
from PIL import Image, ImageTk
import transform
# ----- SET -----
GREY = "#D8D8D8"
BLUE = "#81F7F3"
AQUA = "#9CC3D5"
ICE = "#C7D3D4"
canvas_width = 500
canvas_height = 600
def camera_amount() -> int:
'''Returns int value of available camera devices connected to the host device
from url: https://www.codegrepper.com/code-examples/python/how+to+count+how+many+cameras+you+have+with+python
'''
camera = 0
while True:
if (cv2.VideoCapture(camera).grab()) is True:
camera = camera + 1
else:
cv2.destroyAllWindows()
return camera
# ----- default start images -----
img1 = cv2.imread("d1.png")
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
resized1 = cv2.resize(img1, (canvas_width, canvas_height))
img2 = cv2.imread("ipm.png")
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
resized2 = cv2.resize(img2, (canvas_width, canvas_height))
# ----- Application ------
class App(Tk):
def __init__(self):
super().__init__()
self.title("Image TOP-DOWN Tranformation")
self.minsize(width=1200, height=700)
self.config(padx=5, pady=5, bg=ICE)
im1 = Image.fromarray(resized1)
im2 = Image.fromarray(resized2)
self.org_img = ImageTk.PhotoImage(im1)
self.transf_img = ImageTk.PhotoImage(im2)
self.canvas1 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.org_image_container = self.canvas1.create_image(250, 300, anchor="center", image=self.org_img)
self.canvas1.grid(padx=10, pady=10, row=1, rowspan=8, column=0)
self.canvas2 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.trf_image_container = self.canvas2.create_image(250, 300, anchor="center", image=self.transf_img)
self.canvas2.grid(padx=10, pady=10, row=1, rowspan=8, column=1)
# ----- Labels -----
self.label1 = Label(text="Original image")
self.label1.grid(row=0, column=0)
self.label2 = Label(text="Transformed image")
self.label2.grid(row=0, column=1)
self.label3 = Label(text="Functionalities")
self.label3.grid(row=0, column=2)
self.move_label = Label(text="Move points")
self.move_label.grid(row=8, column=2, columnspan=3)
# ----- Buttons -----
self.selected_camera = StringVar()
self.camera_cb = ttk.Combobox(self, textvariable=self.selected_camera)
self.camera_cb.grid(padx=5, pady=5, row=1, column=2, columnspan=3)
amount_of_cameras = camera_amount()
self.camera_cb['values'] = [i for i in range(amount_of_cameras)]
self.camera_cb.current(0)
self.change_camera = Button(text="Change Camera", command=self.choose_camera)
self.change_camera.grid(padx=5, pady=5, row=2, column=2, columnspan=3)
self.freeze_cam = Button(text="Freeze camera", command=self.freeze_camera)
self.freeze_cam.grid(padx=5, pady=5, row=3, column=2, columnspan=3)
self.default_points = Button(text="Set default points", command=self.set_default_points)
self.default_points.grid(padx=5, pady=5, row=4, column=2, columnspan=3)
self.transform_button = Button(text="Transform", command=self.transform)
self.transform_button.grid(padx=5, pady=5, row=5, column=2, columnspan=3)
self.clear_button = Button(text="CLEAR", command=self.clear)
self.clear_button.grid(padx=5, pady=5, row=6, column=2, columnspan=3)
self.save_button = Button(text="SAVE", command=self.save)
self.save_button.grid(padx=5, pady=5, row=7, column=2, columnspan=3)
self.up_butt = Button(text="↑", command=self.up)
self.up_butt.grid(padx=5, pady=5, row=9, column=3)
self.down_butt = Button(text="↓", command=self.down)
self.down_butt.grid(padx=5, pady=5, row=10, column=3)
self.left_butt = Button(text="←", command=self.left)
self.left_butt.grid(padx=5, pady=5, row=10, column=2, columnspan=2)
self.right_butt = Button(text="→", command=self.right)
self.right_butt.grid(padx=5, pady=5, row=10, column=4)
# ----- fields -----
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.imgtk = None
self.after_id = None
self.resized11 = None
self.ipm_matrix = None
self.input_points = []
self.output_points = []
self.ipm_matrixes = [] # list of saved ipm_matrixes which satisfied us
def choose_camera(self) -> None:
self.freeze_camera(display=False)
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.clear()
def freeze_camera(self, display: bool = True) -> None:
self.canvas1.after_cancel(self.after_id)
if display:
self.resized11 = cv2.cvtColor(self.resized11, cv2.COLOR_BGR2RGB)
cv2.namedWindow("image")
cv2.setMouseCallback("image", self.draw_circle)
while True:
cv2.imshow("image", self.resized11)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
def set_default_points(self) -> None:
default_points = [[448, 609], [580,609], [580,741], [448,741]]
for pts in default_points:
cv2.circle(self.resized11, pts, 5, (0, 0, 255), -2)
self.canvas1.itemconfig(self.org_image_container, image=self.resized11)
def transform(self) -> None:
if len(self.input_points) == 4 and len(self.output_points) == 4:
ordered_pts = transform.order_points(np.array(self.input_points, dtype=np.float32))
ordered_out_pts = transform.order_points(np.array(self.output_points, dtype=np.float32))
self.ipm_matrix = cv2.getPerspectiveTransform(ordered_pts, ordered_out_pts)
self.transf_img = cv2.warpPerspective(self.resized11, self.ipm_matrix, self.resized11.shape[:2][::-1])
self.transf_img = cv2.cvtColor(self.transf_img, cv2.COLOR_BGR2RGB)
self.transf_img = Image.fromarray(self.transf_img)
warpedtk = ImageTk.PhotoImage(self.transf_img)
self.canvas2.warpedtk = warpedtk
self.canvas2.itemconfig(self.trf_image_container, image=warpedtk)
else:
tkinter.messagebox.showwarning("Warning", "Choose right points to transformation!")
def clear(self) -> None:
self.output_points = []
self.input_points = []
self.show_frame()
def save(self) -> None:
# add to self.ipm_matrixes tuple (camera number, ipm_matrix)
self.ipm_matrixes.append((int(self.selected_camera.get()), self.ipm_matrix))
answer = tkinter.messagebox.askyesno("Save", "Do you want to save your all ipm matrixes to file?")
if answer:
self.safe_to_file()
pass
def up(self) -> None:
pass
def down(self) -> None:
pass
def left(self) -> None:
pass
def right(self) -> None:
pass
def draw_circle(self, event, x, y, flags, param) -> None:
if event == cv2.EVENT_LBUTTONDBLCLK:
cv2.circle(self.resized11, (x, y), 5, (255, 0, 0), -2)
self.input_points.append([x, y])
if event == cv2.EVENT_RBUTTONDBLCLK:
cv2.circle(self.resized11, (x, y), 5, (0, 0, 255), -2)
self.output_points.append([x, y])
def show_frame(self) -> None:
"""
https://www.tutorialspoint.com/how-to-show-webcam-in-tkinter-window
"""
img11 = self.cap.read()[1]
cv2image = cv2.cvtColor(img11, cv2.COLOR_BGR2RGB)
self.resized11 = cv2.resize(cv2image, (canvas_width, canvas_height))
im11 = Image.fromarray(self.resized11)
self.imgtk = ImageTk.PhotoImage(im11)
self.canvas1.imgtk = self.imgtk
self.canvas1.itemconfig(self.org_image_container, image=self.imgtk)
self.after_id = self.canvas1.after(10, self.show_frame)
def safe_to_file(self) -> None:
with open("saved_conf.txt", "a") as file:
res = ""
for elem in self.ipm_matrixes:
res += f"Camera {elem[0]}: {elem[1]}\n"
res += "\n\n"
file.write(res)
if __name__ == "__main__":
app = App()
app.show_frame()
app.mainloop()
但是如果有人有不同的想法,我怎么能在一个单一的 tkinter 中做到这一点 window 我将不胜感激。
如有任何帮助,我将不胜感激。
迈克尔
在 show_frame() 中,您使用 opencv 读取帧作为 Mat,然后使用 self.imgtk = ImageTk.PhotoImage(im11) 将其转换为 PIL 图像。
然后当您调用 set_default_points() 以使用 cv2.circle(self.imgtk, ... ) 时,您再次需要 Mat。
编辑:
您需要使用opencv格式(Mat)进行opencv操作,然后当您要显示结果时,Tkinter需要PIL格式。
opencv_image --> opencv_operation --> ImageTk.PhotoImage() --> Tkinter 显示
我有一个简单的 GUI 应用程序来显示来自所选相机的图像和转换后的图像。要进行转换,我需要在图像上选择 4 个点,所以我想通过单击来选择它们。但为了做到这一点,图片必须是 opencv 图像。所以我的想法是“冻结”来自相机的图像,然后对其进行编辑(例如通过函数 set_default_points() 在其上绘制 4 个圆圈)。
这是我的代码
(目前主要的函数是show_frame(), freeze_camera() 和 set_default_points())
from tkinter import *
from tkinter import ttk
import cv2
import numpy as np
from PIL import Image, ImageTk
# ----- SET -----
GREY = "#D8D8D8"
BLUE = "#81F7F3"
AQUA = "#9CC3D5"
ICE = "#C7D3D4"
canvas_width = 500
canvas_height = 600
def camera_amount():
'''Returns int value of available camera devices connected to the host device
from url: https://www.codegrepper.com/code-examples/python/how+to+count+how+many+cameras+you+have+with+python
'''
camera = 0
while True:
if (cv2.VideoCapture(camera).grab()) is True:
camera = camera + 1
else:
cv2.destroyAllWindows()
return camera
# ----- default start images -----
img1 = cv2.imread("d1.png")
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
resized1 = cv2.resize(img1, (canvas_width, canvas_height))
img2 = cv2.imread("ipm.png")
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
resized2 = cv2.resize(img2, (canvas_width, canvas_height))
# ----- Application ------
class App(Tk):
def __init__(self):
super().__init__()
self.title("Image TOP-DOWN Tranformation")
self.minsize(width=1200, height=700)
self.config(padx=5, pady=5, bg=ICE)
im1 = Image.fromarray(resized1)
im2 = Image.fromarray(resized2)
self.org_img = ImageTk.PhotoImage(im1)
self.transf_img = ImageTk.PhotoImage(im2)
self.canvas1 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.org_image_container = self.canvas1.create_image(250, 300, anchor="center", image=self.org_img)
self.canvas1.grid(padx=10, pady=10, row=1, rowspan=8, column=0)
self.canvas2 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.trf_image_container = self.canvas2.create_image(250, 300, anchor="center", image=self.transf_img)
self.canvas2.grid(padx=10, pady=10, row=1, rowspan=8, column=1)
# ----- Labels -----
self.label1 = Label(text="Original image")
self.label1.grid(row=0, column=0)
self.label2 = Label(text="Transformed image")
self.label2.grid(row=0, column=1)
self.label3 = Label(text="Functionalities")
self.label3.grid(row=0, column=2)
self.move_label = Label(text="Move points")
self.move_label.grid(row=8, column=2, columnspan=3)
# ----- Buttons -----
self.selected_camera = StringVar()
self.camera_cb = ttk.Combobox(self, textvariable=self.selected_camera)
self.camera_cb.grid(padx=5, pady=5, row=1, column=2, columnspan=3)
amount_of_cameras = camera_amount()
self.camera_cb['values'] = [i for i in range(amount_of_cameras)]
self.camera_cb.current(0)
self.change_camera = Button(text="Change Camera", command=self.choose_camera)
self.change_camera.grid(padx=5, pady=5, row=2, column=2, columnspan=3)
self.freeze_cam = Button(text="Freeze camera", command=self.freeze_camera)
self.freeze_cam.grid(padx=5, pady=5, row=3, column=2, columnspan=3)
self.default_points = Button(text="Set default points", command=self.set_default_points)
self.default_points.grid(padx=5, pady=5, row=4, column=2, columnspan=3)
self.transform_button = Button(text="Transform", command=self.transform)
self.transform_button.grid(padx=5, pady=5, row=5, column=2, columnspan=3)
self.clear_button = Button(text="CLEAR", command=self.clear)
self.clear_button.grid(padx=5, pady=5, row=6, column=2, columnspan=3)
self.save_button = Button(text="SAVE", command=self.save)
self.save_button.grid(padx=5, pady=5, row=7, column=2, columnspan=3)
self.up_butt = Button(text="↑", command=self.up)
self.up_butt.grid(padx=5, pady=5, row=9, column=3)
self.down_butt = Button(text="↓", command=self.down)
self.down_butt.grid(padx=5, pady=5, row=10, column=3)
self.left_butt = Button(text="←", command=self.left)
self.left_butt.grid(padx=5, pady=5, row=10, column=2, columnspan=2)
self.right_butt = Button(text="→", command=self.right)
self.right_butt.grid(padx=5, pady=5, row=10, column=4)
# ----- fields -----
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.imgtk = None
self.after_id = None
self.resized11 = None
self.input_points = []
self.output_points = []
def choose_camera(self):
self.freeze_camera()
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.show_frame()
# po upakowaniu w klasę po prostu zmienić jedno pole, a w funkcji odpowiedzialnej za wyświetlanie kamery dac ifa
def freeze_camera(self):
self.canvas1.after_cancel(self.after_id)
# now, the image on canvas is freezed,
# it means, that the picture is last imgtk from show_frame function,
# but it's not like cv2 image, and it cannot be editable
# I tried example like this, but it doesn't work
# pil_image = PIL.Image.open('Image.jpg').convert('RGB')
# open_cv_image = numpy.array(pil_image)
# # Convert RGB to BGR
# open_cv_image = open_cv_image[:, :, ::-1].copy()
def set_default_points(self):
default_points = [[448, 609], [580,609], [580,741], [448,741]]
for pts in default_points:
cv2.circle(self.imgtk, pts, 5, (0, 0, 255), -2)
pass
def transform(self):
pass
def clear(self):
pass
def save(self):
pass
def up(self):
pass
def down(self):
pass
def left(self):
pass
def right(self):
pass
def draw_circle(self, event, x, y, flags, param):
if event == cv2.EVENT_LBUTTONDBLCLK:
cv2.circle(self.imgtk, (x, y), 5, (255, 0, 0), -2)
self.input_points.append([x, y])
if event == cv2.EVENT_RBUTTONDBLCLK:
cv2.circle(self.imgtk, (x, y), 5, (0, 0, 255), -2)
self.output_points.append([x, y])
def show_frame(self):
"""
https://www.tutorialspoint.com/how-to-show-webcam-in-tkinter-window
"""
img11 = self.cap.read()[1]
cv2image = cv2.cvtColor(img11, cv2.COLOR_BGR2RGB)
self.resized11 = cv2.resize(cv2image, (canvas_width, canvas_height))
im11 = Image.fromarray(self.resized11)
self.imgtk = ImageTk.PhotoImage(im11)
self.canvas1.imgtk = self.imgtk
self.canvas1.itemconfig(self.org_image_container, image=self.imgtk)
self.after_id = self.canvas1.after(10, self.show_frame)
if __name__ == "__main__":
app = App()
app.show_frame()
app.mainloop()
问题是当我尝试使用函数 set_default_points 编辑那张冻结的图片时,我发生了这个错误:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Python_versions\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "D:\DUCKIETOWN\aplikacja_do_transformacji_obrazu\APP.py", line 146, in set_default_points
cv2.circle(self.imgtk, pts, 5, (0, 0, 255), -2)
cv2.error: OpenCV(4.5.5) :-1: error: (-5:Bad argument) in function 'circle'
> Overload resolution failed:
> - img is not a numpy array, neither a scalar
> - Expected Ptr<cv::UMat> for argument 'img'
编辑:看来,实现我的目标的唯一方法是使用 opencv 图像在分离的 opencv window 上选择转换点,然后将其上传到我的 tkinter 显示 window.
这里是更正后的代码:
import tkinter.messagebox
from tkinter import *
from tkinter import ttk
import cv2
import numpy as np
from PIL import Image, ImageTk
import transform
# ----- SET -----
GREY = "#D8D8D8"
BLUE = "#81F7F3"
AQUA = "#9CC3D5"
ICE = "#C7D3D4"
canvas_width = 500
canvas_height = 600
def camera_amount() -> int:
'''Returns int value of available camera devices connected to the host device
from url: https://www.codegrepper.com/code-examples/python/how+to+count+how+many+cameras+you+have+with+python
'''
camera = 0
while True:
if (cv2.VideoCapture(camera).grab()) is True:
camera = camera + 1
else:
cv2.destroyAllWindows()
return camera
# ----- default start images -----
img1 = cv2.imread("d1.png")
img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
resized1 = cv2.resize(img1, (canvas_width, canvas_height))
img2 = cv2.imread("ipm.png")
img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
resized2 = cv2.resize(img2, (canvas_width, canvas_height))
# ----- Application ------
class App(Tk):
def __init__(self):
super().__init__()
self.title("Image TOP-DOWN Tranformation")
self.minsize(width=1200, height=700)
self.config(padx=5, pady=5, bg=ICE)
im1 = Image.fromarray(resized1)
im2 = Image.fromarray(resized2)
self.org_img = ImageTk.PhotoImage(im1)
self.transf_img = ImageTk.PhotoImage(im2)
self.canvas1 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.org_image_container = self.canvas1.create_image(250, 300, anchor="center", image=self.org_img)
self.canvas1.grid(padx=10, pady=10, row=1, rowspan=8, column=0)
self.canvas2 = Canvas(width=500, height=600, bg=ICE, highlightthickness=2)
self.trf_image_container = self.canvas2.create_image(250, 300, anchor="center", image=self.transf_img)
self.canvas2.grid(padx=10, pady=10, row=1, rowspan=8, column=1)
# ----- Labels -----
self.label1 = Label(text="Original image")
self.label1.grid(row=0, column=0)
self.label2 = Label(text="Transformed image")
self.label2.grid(row=0, column=1)
self.label3 = Label(text="Functionalities")
self.label3.grid(row=0, column=2)
self.move_label = Label(text="Move points")
self.move_label.grid(row=8, column=2, columnspan=3)
# ----- Buttons -----
self.selected_camera = StringVar()
self.camera_cb = ttk.Combobox(self, textvariable=self.selected_camera)
self.camera_cb.grid(padx=5, pady=5, row=1, column=2, columnspan=3)
amount_of_cameras = camera_amount()
self.camera_cb['values'] = [i for i in range(amount_of_cameras)]
self.camera_cb.current(0)
self.change_camera = Button(text="Change Camera", command=self.choose_camera)
self.change_camera.grid(padx=5, pady=5, row=2, column=2, columnspan=3)
self.freeze_cam = Button(text="Freeze camera", command=self.freeze_camera)
self.freeze_cam.grid(padx=5, pady=5, row=3, column=2, columnspan=3)
self.default_points = Button(text="Set default points", command=self.set_default_points)
self.default_points.grid(padx=5, pady=5, row=4, column=2, columnspan=3)
self.transform_button = Button(text="Transform", command=self.transform)
self.transform_button.grid(padx=5, pady=5, row=5, column=2, columnspan=3)
self.clear_button = Button(text="CLEAR", command=self.clear)
self.clear_button.grid(padx=5, pady=5, row=6, column=2, columnspan=3)
self.save_button = Button(text="SAVE", command=self.save)
self.save_button.grid(padx=5, pady=5, row=7, column=2, columnspan=3)
self.up_butt = Button(text="↑", command=self.up)
self.up_butt.grid(padx=5, pady=5, row=9, column=3)
self.down_butt = Button(text="↓", command=self.down)
self.down_butt.grid(padx=5, pady=5, row=10, column=3)
self.left_butt = Button(text="←", command=self.left)
self.left_butt.grid(padx=5, pady=5, row=10, column=2, columnspan=2)
self.right_butt = Button(text="→", command=self.right)
self.right_butt.grid(padx=5, pady=5, row=10, column=4)
# ----- fields -----
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.imgtk = None
self.after_id = None
self.resized11 = None
self.ipm_matrix = None
self.input_points = []
self.output_points = []
self.ipm_matrixes = [] # list of saved ipm_matrixes which satisfied us
def choose_camera(self) -> None:
self.freeze_camera(display=False)
self.cap = cv2.VideoCapture(int(self.selected_camera.get()))
self.clear()
def freeze_camera(self, display: bool = True) -> None:
self.canvas1.after_cancel(self.after_id)
if display:
self.resized11 = cv2.cvtColor(self.resized11, cv2.COLOR_BGR2RGB)
cv2.namedWindow("image")
cv2.setMouseCallback("image", self.draw_circle)
while True:
cv2.imshow("image", self.resized11)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
def set_default_points(self) -> None:
default_points = [[448, 609], [580,609], [580,741], [448,741]]
for pts in default_points:
cv2.circle(self.resized11, pts, 5, (0, 0, 255), -2)
self.canvas1.itemconfig(self.org_image_container, image=self.resized11)
def transform(self) -> None:
if len(self.input_points) == 4 and len(self.output_points) == 4:
ordered_pts = transform.order_points(np.array(self.input_points, dtype=np.float32))
ordered_out_pts = transform.order_points(np.array(self.output_points, dtype=np.float32))
self.ipm_matrix = cv2.getPerspectiveTransform(ordered_pts, ordered_out_pts)
self.transf_img = cv2.warpPerspective(self.resized11, self.ipm_matrix, self.resized11.shape[:2][::-1])
self.transf_img = cv2.cvtColor(self.transf_img, cv2.COLOR_BGR2RGB)
self.transf_img = Image.fromarray(self.transf_img)
warpedtk = ImageTk.PhotoImage(self.transf_img)
self.canvas2.warpedtk = warpedtk
self.canvas2.itemconfig(self.trf_image_container, image=warpedtk)
else:
tkinter.messagebox.showwarning("Warning", "Choose right points to transformation!")
def clear(self) -> None:
self.output_points = []
self.input_points = []
self.show_frame()
def save(self) -> None:
# add to self.ipm_matrixes tuple (camera number, ipm_matrix)
self.ipm_matrixes.append((int(self.selected_camera.get()), self.ipm_matrix))
answer = tkinter.messagebox.askyesno("Save", "Do you want to save your all ipm matrixes to file?")
if answer:
self.safe_to_file()
pass
def up(self) -> None:
pass
def down(self) -> None:
pass
def left(self) -> None:
pass
def right(self) -> None:
pass
def draw_circle(self, event, x, y, flags, param) -> None:
if event == cv2.EVENT_LBUTTONDBLCLK:
cv2.circle(self.resized11, (x, y), 5, (255, 0, 0), -2)
self.input_points.append([x, y])
if event == cv2.EVENT_RBUTTONDBLCLK:
cv2.circle(self.resized11, (x, y), 5, (0, 0, 255), -2)
self.output_points.append([x, y])
def show_frame(self) -> None:
"""
https://www.tutorialspoint.com/how-to-show-webcam-in-tkinter-window
"""
img11 = self.cap.read()[1]
cv2image = cv2.cvtColor(img11, cv2.COLOR_BGR2RGB)
self.resized11 = cv2.resize(cv2image, (canvas_width, canvas_height))
im11 = Image.fromarray(self.resized11)
self.imgtk = ImageTk.PhotoImage(im11)
self.canvas1.imgtk = self.imgtk
self.canvas1.itemconfig(self.org_image_container, image=self.imgtk)
self.after_id = self.canvas1.after(10, self.show_frame)
def safe_to_file(self) -> None:
with open("saved_conf.txt", "a") as file:
res = ""
for elem in self.ipm_matrixes:
res += f"Camera {elem[0]}: {elem[1]}\n"
res += "\n\n"
file.write(res)
if __name__ == "__main__":
app = App()
app.show_frame()
app.mainloop()
但是如果有人有不同的想法,我怎么能在一个单一的 tkinter 中做到这一点 window 我将不胜感激。
如有任何帮助,我将不胜感激。
迈克尔
在 show_frame() 中,您使用 opencv 读取帧作为 Mat,然后使用 self.imgtk = ImageTk.PhotoImage(im11) 将其转换为 PIL 图像。 然后当您调用 set_default_points() 以使用 cv2.circle(self.imgtk, ... ) 时,您再次需要 Mat。
编辑: 您需要使用opencv格式(Mat)进行opencv操作,然后当您要显示结果时,Tkinter需要PIL格式。
opencv_image --> opencv_operation --> ImageTk.PhotoImage() --> Tkinter 显示