【问题标题】:How can I get the stream I processed in cv2 into the tkinter window?如何将我在 cv2 中处理的流放入 tkinter 窗口?
【发布时间】:2021-10-01 11:24:30
【问题描述】:

我有一个从树莓派中弹出的帧,它通过网络广播其信号。摄像机源在第 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()

【问题讨论】:

  • 查看.after 脚本。你也不需要使用cv2。在使用tkinter 时,大多数时候也不应该使用while True 循环。如果你想要一个例子,请看here
  • @TheLizzard,感谢您的快速回复。我添加了我从你所说的内容中理解的内容,这就是结果。如果这是您的意思,请告诉我,我会将其添加到主线程中
  • 已添加。感谢您到目前为止的帮助以及您可以提供的任何其他内容@TheLizzard
  • 你仍然运行while True(在tk.Tk()之前),它会阻止所有代码。这是主要问题。您必须将套接字代码放入函数中并使用after 而不是while True 运行它。或者您必须在分隔符thread 中运行套接字
  • 我制作了 server,它应该可以工作,但我没有 client 来测试它。

标签: python opencv tkinter raspberry-pi


【解决方案1】:

这是在thread 中以socket server 开头然后显示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()

【讨论】:

  • 非常感谢!!我下班后会测试一下。拥有一个我可以从中学习的社区意义重大。
猜你喜欢
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 2015-04-03
  • 2021-07-15
  • 2021-07-25
  • 1970-01-01
  • 2023-04-02
相关资源
最近更新 更多