python - 如何将我在 cv2 中处理的流放入 tkinter 窗口?
问题描述
我有一个从树莓派中弹出的框架,通过网络广播其信号。摄像机源在第 37 行用 cv2 打开的帧中打开并运行良好。但是,我试图在 tkinter 中显示摄像机源,如第 42 行及以后所见。 简而言之:我需要在 tkinter 中显示相机供稿。 任何帮助是极大的赞赏。
import io
import socket
import struct
import cv2
import numpy as np
from PIL import Image
import tkinter as tk
global image
# Start a socket listening for connections on 0.0.0.0:8000 (0.0.0.0 means
# all interfaces)
server_socket = socket.socket()
server_socket.bind(('0.0.0.0', 5001))
server_socket.listen(0)
# Accept a single connection and make a file-like object out of it
connection = server_socket.accept()[0].makefile('rb')
while True:
# Read the length of the image as a 32-bit unsigned int. If the
# length is zero, quit the loop
image_len = struct.unpack('<L', connection.read(struct.calcsize('<L')))[0]
if not image_len:
break
# Construct a stream to hold the image data and read the image
# data from the connection
global image
image_stream = io.BytesIO()
image_stream.write(connection.read(image_len))
# Rewind the stream, open it as an image with opencv and do some
# processing on it
image_stream.seek(0)
image = Image.open(image_stream)
data = np.frombuffer(image_stream.getvalue(), dtype=np.uint8)
imagedisp = cv2.imdecode(data, 1)
cv2.imshow("Frame",imagedisp)
cv2.waitKey(1) #imshow will not output an image if you do not use waitKey
#cleanup windows
root = tk.Tk()
image = Image.fromarray(image)
photo = ImageTk.PhotoImage(image2) # it has to be after `tk.Tk()`
canvas = tk.Canvas(root, width=photo.width(), height=photo.height())
canvas.pack(side='left', fill='both', expand=True)
canvas.create_image((0,0), image2=photo, anchor='nw')
description = tk.Label(root, text="Place for description")
description.pack(side='right')
# - start -
update_frame() # update it first time
root.mainloop() # start program - this loop runs all time
根据我对@TheLizzard 的理解,已经进行了一些更改。它仍然没有拉起页面。这些是变化:
from tkinter import *
from PIL import ImageTk, Image
import cv2
import io
import socket
import struct
import numpy as np
# Start a socket listening for connections on 0.0.0.0:8000 (0.0.0.0 means
# all interfaces)
server_socket = socket.socket()
server_socket.bind(('0.0.0.0', 5001))
server_socket.listen(0)
# Accept a single connection and make a file-like object out of it
connection = server_socket.accept()[0].makefile('rb')
try:
while True:
# Read the length of the image as a 32-bit unsigned int. If the
# length is zero, quit the loop
image_len = struct.unpack('<L', connection.read(struct.calcsize('<L')))[0]
if not image_len:
break
# Construct a stream to hold the image data and read the image
# data from the connection
image_stream = io.BytesIO()
image_stream.write(connection.read(image_len))
# Rewind the stream, open it as an image with opencv and do some
# processing on it
image_stream.seek(0)
image = Image.open(image_stream)
data = np.fromstring(image_stream.getvalue(), dtype=np.uint8)
imagedisp = cv2.imdecode(data, 1)
#cleanup windows
finally:
connection.close()
server_socket.close()
root = Tk()
# Create a frame
app = Frame(root, bg="white")
app.grid()
# Create a label in the frame
lmain = Label(app)
lmain.grid()
# Capture from camera
cap = cv2.VideoCapture(0)
# function for video streaming
def video_stream():
_, frame = cap.read()
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
lmain.imgtk = imgtk
lmain.configure(image=imgtk)
lmain.after(1, video_stream)
video_stream()
root.mainloop()
客户端:
import io
import socket
import struct
import time
import picamera
# Connect a client socket to my_server:8000 (change my_server to the
# hostname of your server)
client_socket = socket.socket()
client_socket.connect(('192.168.1.000', 5001))
# Make a file-like object out of the connection
connection = client_socket.makefile('wb')
try:
with picamera.PiCamera() as camera:
camera.resolution = (320, 240)
# Start a preview and let the camera warm up for 2 seconds
camera.start_preview()
time.sleep(1)
# Note the start time and construct a stream to hold image data
# temporarily (we could write it directly to connection but in this
# case we want to find out the size of each capture first to keep
# our protocol simple)
start = time.time()
stream = io.BytesIO()
for foo in camera.capture_continuous(stream, 'jpeg', use_video_port=True):
# Write the length of the capture to the stream and flush to
# ensure it actually gets sent
connection.write(struct.pack('<L', stream.tell()))
connection.flush()
# Rewind the stream and send the image data over the wire
stream.seek(0)
connection.write(stream.read())
# Reset the stream for the next capture
stream.seek(0)
stream.truncate()
# Write a length of zero to the stream to signal we're done
connection.write(struct.pack('<L', 0))
finally:
connection.close()
client_socket.close()
解决方案
这是开始和下一个显示socket server
的示例。thread
tkinter
开始时tkinter
显示为空image
,每 100 毫秒将其替换为当前image
(仍为空)。
当客户端连接然后服务器替换image
因此自动tkinter
显示在窗口中。
当客户端断开连接时,服务器再次放空image
。
窗口有`按钮将当前屏幕保存在文件中-它表明窗口没有冻结。
当您关闭窗口时,它会停止服务器。问题是accept()
阻塞,当程序停止运行时thread
我daemon=True
会杀死它。thread
import io
import socket
import struct
import tkinter as tk
from PIL import Image, ImageTk
#import cv2
#import numpy as np
import threading
import datetime # screenshot filename
def server():
global image
global server_socket
print('Start Server')
# Start a socket listening for connections on 0.0.0.0:8000 (0.0.0.0 means
# all interfaces)
server_socket = socket.socket()
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # solution for '[Error 89] Address already in use'. Use before bind()
server_socket.bind(('0.0.0.0', 5001))
server_socket.listen(0)
while server_running:
print('Waiting for client')
# Accept a single connection and make a file-like object out of it
conn, addr = server_socket.accept()
connection = conn.makefile('rb')
print('Connected:', addr)
while client_running: # to stop it from TK
# Read the length of the image as a 32-bit unsigned int. If the
# length is zero, quit the loop
image_len = struct.unpack('<L', connection.read(struct.calcsize('<L')))[0]
if not image_len:
break
# Construct a stream to hold the image data and read the image
# data from the connection
image_stream = io.BytesIO()
image_stream.write(connection.read(image_len))
# Rewind the stream, open it as an image with opencv and do some
# processing on it
image_stream.seek(0)
image = Image.open(image_stream)
#data = np.frombuffer(image_stream.getvalue(), dtype=np.uint8)
#imagedisp = cv2.imdecode(data, 1)
#cv2.imshow("Frame",imagedisp)
#cv2.waitKey(1) #imshow will not output an image if you do not use waitKey
#cleanup windows
image = empty_image.copy()
connection.close()
conn.close()
print('Disconnected')
# ---
def update_frame():
# replace image on Canvas
global photo
photo = ImageTk.PhotoImage(image)
canvas.itemconfig(photo_id, image=photo)
root.after(100, update_frame) # repeate after 100ms
def screenshot():
filename = datetime.datetime.now().strftime('%Y.%m.%d-%H.%M.%S.jpg')
image.save(filename)
print('Saved:', filename)
# ---
client_running = True
server_running = True
# empty image to set at start and when client disconnets
empty_image = Image.new('RGB', (320, 240))
# empty image at start
image = empty_image.copy()
t = threading.Thread(target=server)
t.daemon = True # use daemon to stop thread when accept() wait for client
t.start()
print('Start GUI')
root = tk.Tk()
# create empty object on canvas
photo = ImageTk.PhotoImage(image)
canvas = tk.Canvas(root, width=photo.width(), height=photo.height())
canvas.pack(fill='both', expand=True)
# set object and get ID to update it later
photo_id = canvas.create_image((0,0), image=photo, anchor='nw')
button = tk.Button(root, text='Exit', command=root.destroy)
button.pack()
button = tk.Button(root, text='Screenshot', command=screenshot)
button.pack()
# - start -
update_frame() # update it first time
root.mainloop() # start program - this loop runs all time
print('Stop GUI')
# - end -
# stop loops in thread (and stop server)
client_running = False
server_running = False
print('Stop Server')
server_socket.close()
# don't wait - accept() is blocking thread - use daemon
#t.join()
推荐阅读
- javascript - 我目前正在尝试创建一个 chrome 扩展(带有右键搜索的 ContextMenu)
- python - 并排绘制多个混淆矩阵
- sql-server - 如果有使用该用户运行的 SSIS 包,我从 Active Directory 重置 SQL Server 密码会发生什么
- javascript - JS - 满足条件时将对象从数组复制到新数组[编辑]
- firebase - Firestore,检查 valueChanges() 期间更改了哪些字段
- css - CSS:如何将一个 div 移到另一个上方?
- ios - 更新后无法打开 Realm Studio - Realm 文件当前在另一个进程中打开 - Mac 和 Realm Swift
- performance - 有效地循环子阵列
- c++ - libwebsockets:待处理的消息
- javascript - 如何使用 axios.delete() 请求发送 ID