简体   繁体   English

使用 Tkinter 显示和编辑 opencv 图像

[英]Display and EDIT opencv image using Tkinter

I have a simple GUI app to display image from chosen camera and transformed image.我有一个简单的 GUI 应用程序来显示来自所选相机的图像和转换后的图像。 To do transformation I need to choose 4 points on image, so I would like to pick them by clicking.要进行转换,我需要在图像上选择 4 个点,所以我想通过单击来选择它们。 But in order to do that the picture must be an opencv image.但为了做到这一点,图片必须是 opencv 图像。 So my idea was to "freeze" the image from camera and then edit it (for example by drawing on it 4 circles by function set_default_points()).所以我的想法是“冻结”来自相机的图像,然后对其进行编辑(例如,通过 function set_default_points() 在其上绘制 4 个圆圈)。

Here is my code这是我的代码

(The key function for now are show_frame(), freeze_camera() and set_default_points()) (目前关键的 function 是 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()

The problem is when I try to edit that frozen picture, by using function set_default_points I occure this error:问题是当我尝试使用 function 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'

EDIT: So it seems, like only way to achieve my goal is to do operations like choose transformation points on detached opencv window with opencv image, and then upload it to my tkinter display window.编辑:看来,实现我的目标的唯一方法是在带有 opencv 图像的分离 opencv window 上选择转换点等操作,然后将其上传到我的 tkinter 显示 window。

Here is corrected code:这是更正后的代码:

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()

But if anybody had different idea, how could I do that in one, single tkinter window I would be grateful.但如果有人有不同的想法,我怎么能在一个单一的 tkinter window 中做到这一点,我将不胜感激。

I would appreciate any help.我将不胜感激任何帮助。

Michael迈克尔

In show_frame() you read frame with opencv as Mat then you covert it into PIL image with self.imgtk = ImageTk.PhotoImage(im11).在 show_frame() 中,您使用 opencv 读取帧作为 Mat,然后使用 self.imgtk = ImageTk.PhotoImage(im11) 将其转换为 PIL 图像。 And then when you call set_default_points() to use cv2.circle(self.imgtk, ... ), you need Mat again.然后当你调用 set_default_points() 来使用 cv2.circle(self.imgtk, ...) 时,你又需要 Mat 了。

EDIT: You need to do opencv operation with opencv format (Mat) and then when you want to display result, Tkinter needs PIL format.编辑:您需要使用 opencv 格式(Mat)执行 opencv 操作,然后当您想要显示结果时,Tkinter 需要 PIL 格式。

opencv_image --> opencv_operation --> ImageTk.PhotoImage() --> Tkinter display opencv_image --> opencv_operation --> ImageTk.PhotoImage() --> Tkinter 显示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM