关于我为什么要使用update()和update_idletask()而不只是使用mainloop()的原因:我需要显示一个cv2.imshow全屏FullHD 50 FPS视频流,该视频流是从Basler Dart USB相机的另一个线程中抓取的,并在最上方显示一个(Tkinter)GUI窗口。
从cv 2 Mat到tk PhotoImage的转换以及更新显示图像的画布或标签在最佳条件下需要超过30毫秒,而捕获帧并执行一些逐位操作以覆盖透明图像只需要几毫秒。
为了不需要马上学习另一个gui框架,并且重用大部分现有代码,我找到了一个解决方案,可以显示一个cv2.imshow()全屏窗口,同时在最上面显示一个tk.tk窗口,但是我不确定这是否是一个好主意,以及如何正确地实现它,因为cpython_tkinter. c中有关于线程锁定情况的警告。
我读了一些建议的解决方案,通过使用线程同时显示tk和cv 2窗口,但这些对我不起作用,可能是因为我的CV 2 Window中的图像捕获已经在线程中了。
在cv 2循环中调用update()很容易,但我不知道这是否是个好主意:如果我用线程安全队列实现两个窗口之间的通信,并且tkinter事件块中没有太长的东西,那么不考虑tcl锁而只使用update()是否安全?
我现在的简化代码是:
# Standard library imports
from cv2 import waitKey
from sys import exit
# Local application imports
from CV2Window import CV2Window
from tkWindows import MenuWindow, MenuFrame
# child class of tk.Tk
# -topmost and overrideredirect True, geometry "+0+0"
class MenuApp(MenuWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# frame withdraws and deiconifies buttons when the menu toggle button is clicked,
# to create a dropdown-menu. Row 0 is used by the toggle button
self.menu_frame = MenuFrame(self)
self.menu_frame.add_button("Example Button", command=None, icon="file-plus", row=1)
self.menu_frame.add_button("Close", command=self.close, icon="power", row=2)
self.menu_frame.grid()
def close(self):
self.destroy()
def main():
cv2window = CV2Window()
gui = MenuApp()
while True:
## Update cv2 window
cv2window.update()
## Update GUI manually
gui.update()
## Check if GUI is still open, otherwise close cv2 window
try:
gui.winfo_exists()
except:
print("GUI closed, closing cv2 window..")
cv2window.close()
break
但CV 2 Window包含使用MyCameraHandler进行相机抓取的功能,MyCameraHandler是Basler pypylon示例“grabusinggrabloopthread.py“的类版本,在不同的线程中获取帧。
CV 2 Window的简化代码:
# Standard library imports
from cv2 import namedWindow, setWindowProperty, imshow, WINDOW_FULLSCREEN, WND_PROP_FULLSCREEN, waitKey, destroyAllWindows
from pypylon.genicam import GenericException
# Local application imports
from CameraClass import MyCameraHandler
class CV2Window():
def __init__(self):
try:
self.cam_handler = MyCameraHandler()
self.cam_handler.start_grabbing()
except GenericException as e:
print("Exception in CV2Window: ", e)
try:
self.cam_handler.stop()
except:
pass
exit(1)
self.img = self.cam_handler.get_image()
namedWindow("cam", WND_PROP_FULLSCREEN)
setWindowProperty("cam", WND_PROP_FULLSCREEN, WINDOW_FULLSCREEN)
imshow("cam", self.img)
def update(self):
self.img = self.cam_handler.get_image()
waitKey(0)
imshow("cam", self.img)
def close(self):
destroyAllWindows()
self.cam_handler.stop()
通过阅读cpython _tkinter.c 代码,这可能是一个问题:
线程化的情况很复杂。Tcl不是线程安全的,除非配置了--enable-threads。所以我们需要在所有Tcl的使用周围使用一个锁。以前,Python解释器锁被用于此。然而,当其他Python线程需要运行而Tcl被阻塞等待事件时,这会导致问题。
如果使用mainloop(),Tkinter会在正确的时间获得并释放正确的锁,但这样就不能显示和更新我的CV 2 Window了,据我理解,update()只是一个单一的tcl调用,没有任何锁管理。
截至目前,它正在工作,但两个窗口之间没有通信。我需要能够从Tkinter GUI调用CV 2 Window中的方法,可能在未来还可以将CV 2 Window中的小数据/信息共享到Tkinter。
接下来我将尝试与队列进行通信,因为我不需要共享图像,只需要共享一些信息或要执行的操作,以及队列。队列是线程安全的......我认为这应该可以工作吗?
只要由于更新调用而执行的事件花费的时间小于~ 15 ms,我就应该没事,并获得我所需的〈20 ms的帧时间以及帧抓取和imshow,对吗?
我对Python和Tkinter还很陌生,而且对Tkinter调用的tcl内容进行深入研究对我来说并不容易,因此任何帮助都将非常感谢。
1条答案
按热度按时间t3irkdon1#
如果您使用Unix,任何Tkinter小部件的
winfo_id()
方法都将返回基础绘图图面的XID。您可以使用该句柄获取一些其他代码,以便在该图面上进行绘制。建议您使用背景设置为None
或空字符串的Frame
(不太确定哪个有效!)这样Tkinter就不会同时使用它;(我知道我会在Tk中使用一个空字符串来达到这个效果,它在底层结构中会被转换为NULL,并触发帧绘制代码在重绘/重绘请求时不做任何事情。)我不知道如何将XID导入其他框架;它将负责打开自己连接并设置侦听器,但这在另一个线程(甚至另一个进程;该解决方案在其他平台上不起作用,因为我相信他们使用的ID在工具包实现之外没有任何意义。(从技术上讲,Tkinter没有公开Tk中的一些选项,* 可能会有帮助,* 但我不确定。-container
/-use
协议没有得到很好的记录。)更一般地说,自定义图像类型(几乎需要编写C或C++代码)可以让您做一些接近的事情,尽管整个图像系统并不是真正为动画流媒体设计的。(它当然不是为硬件加速设计的!)它不是快速修复的东西,我自己也从未尝试过这样做。