如何解决我可以使用相同的python套接字以一种方式发送图像并以另一种方式发送字符串吗?
我需要帮助来弄清楚我的python套接字在做什么。
我制作了一对服务器/客户端程序,其中客户端从运行它的Raspberry Pi的摄像机向服务器发送实时供稿到服务器,该服务器在同一网络上的Linux笔记本电脑上运行。这部分有效。
我的问题是我现在希望能够将字符串发送回Raspberry Pi,以控制图像的缩放级别。与摄影机馈送不同,这种通信不需要连续发生,并且实际上最好仅根据需要发生。
不幸的是,我几乎没有使用python套接字的经验。由于有用的YouTube视频,我才得以使视频源正常工作,然后在Stack Overflow社区的一些帮助下,将其修改为可在Tkinter窗口中工作。 (有问题的视频是:https://www.youtube.com/watch?v=bWwZF_zVf00)
我目前的尝试包括尝试通过与我用来获取图像的套接字相同的套接字连接将字符串数据发送回去,但我怀疑这种方法可能存在缺陷。
所以我的问题是:如何将字符串数据返回给在Raspberry Pi上运行的客户端?
根据我所读的内容,我怀疑答案涉及使用线程在两个设备之间建立第二个套接字连接,但是当我尝试将运行图像套接字的函数放入线程时,图片开始突然闪烁。 公平警告!
我精简了我正在研究的程序,以制作此简化版本来说明问题。如果工作正常,则Z +按钮将放大图像并在客户端控制台上显示“ Received String = ZOOM | 0.5”,而Z-按钮将缩小图像并显示“ Received String = ZOOM | 0.5”。 1.0“。
我将首先考虑服务器和客户端的相关功能,然后提供简化版本的完整副本,以防任何人需要看更多或想要自己使用此功能,因为视频供稿效果很好(如果您注释掉反馈功能)。
我意识到,目前我正尝试在视频的每一帧上发送字符串。这是因为在撰写本文时,我有一些改进的想法。
很抱歉,如果这篇文章发表得这么漫长,但这是我试图简洁的目的。
服务器上的相关功能(已重新安排,以使反馈发送功能位于其顶部):
#=====================================================================================
def Vid_Button(self):
if (self.myvar_vid_on == False):
self.myvar_vid_on = True
# comUnicate = Thread(target = self.Listen_For_Data)
# comUnicate.start() # Threading causes image to strobe.
self.Listen_For_Data()
#=====================================================================================
## Functions Relating To The Camera Live-Feed From The Robot:
# (LFD = Listen For Data)
# (These functions support the main function of this group,Listen_For_Data() )
# Send zoom level:
def LFD_Send_Feedback(self):
global pos_ZOOM
print("pos_ZOOM = " + str(pos_ZOOM))
# Prepare the string to be sent:
send_String = "ZOOM|" + str(pos_ZOOM)
print("send_String = " + send_String)
#vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
# Send the string: # I HAVE NO IDEA HOW TO SEND THE INFORMATION!!
self.connection_Image.send(send_String) # I think I'm trying to send the wrong datatype here.
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Create the server for the robot to send images to:
def LFD_Image_Server_Create(self):
global HOST
global PORT
self.server_socket = socket.socket()
self.server_socket.bind((HOST,PORT))
self.server_socket.listen(0)
# Display the image from the camera live-feed in the window:
def LFD_Display_Image(self):
# Change the image being displayed:
self.label__Image.image = self.img
self.label__Image.configure(image = self.img)
# Refresh the window so that the image will actually be displayed:
self.label__Image.update()
# Image acquisition loop:
def LFD_Loop_Get_Image(self):
while(programme_Open):
global pos_Zoom
# Raise active communication flag:
self.myvar_comms_active = True
# Read the length of the image as a 32-bit unsigned int:
self.image_len = struct.unpack('<L',self.connection_Image.read(struct.calcsize('<L')))[0]
# If the length is zero (i.e. no image),quit the loop:
if not self.image_len:
self.myvar_comms_active = False # Lower active communication flag.
break
# Construct a stream to hold the image data and read the image data from the connection:
self.image_stream = io.BytesIO()
self.image_stream.write(self.connection_Image.read(self.image_len))
# Extract image data:
self.image_stream.seek(0) # Rewind the stream.
self.live_feed = Image.open(self.image_stream) # Open the stream as an image with PIL.
self.img = ImageTk.PhotoImage(self.live_feed) # Convert the image into a format that Tkinter can use.
# Display image in the window:
self.LFD_Display_Image()
# Send zoom level to the robot:
self.LFD_Send_Feedback()
# Lower active communication flag:
self.myvar_comms_active = False
# Used to get a video feed from the Raspberry Pi's camera:
def Listen_For_Data(self):
global programme_Open
try:
# Create server:
self.LFD_Image_Server_Create()
# Accept connection from the Raspberry Pi for sending images: (Accept a single connection and make a file-like object out of it)
self.connection_Image = self.server_socket.accept()[0].makefile('rb')
except:
print("Failed To Establish Connection")
else:
# Indicate that the connection was established:
print("Established Connection")
self.Status_Label_Connected()
# Get images from the Raspberry Pi:
try:
self.LFD_Loop_Get_Image()
except struct.error:
print("Connection Broken")
finally:
try:
# This try statement fixes an error that occurs when quitting programme while the video connection is still running.
# Without the try statement,the program would try to alter a label that no longer exists,which results in an error.
self.Status_Label_Not_Connected()
except:
pass
# Close the connection with the Raspberry Pi:
try:
self.connection_Image.close()
except AttributeError:
print("Can't close a connection that doesn't exist!")
finally:
self.server_socket.close()
# Lower "Video On" flag:
self.myvar_vid_on = False
#=====================================================================================
客户端的相关功能:
# Create the socket connection to the control device:
def MF_Connection_Create():
global HOST
global PORT
global client_socket
global connection
client_socket = socket.socket() # Create a socket.
client_socket.connect((HOST,PORT)) # Tell the socket to talk to the control device.
connection = client_socket.makefile('wb') # Make a file-like object out of the connection.
# Get feedback data:
def MF_Get_Feedback():
global connection
global client_socket
global level_ZOOM
#vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
# Get the string: # THIS IS THE PART I NEED HELP WITH
value_received = connection.recv(2048)
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Display the received string:
print("Received String = " + str(value_received))
# Extract the first 4 characters of the string:
value_split = value_received.split("|")
# Update the desired setting:
if (value_split[0] == "ZOOM"):
level_ZOOM = float(value_split[1])
print("ZOOM = " + str(level_ZOOM))
camera.zoom = (0.0,0.0,level_ZOOM,level_ZOOM)
# Stream images to the control device:
def MF_Stream_Images():
global connection
global camera
global imag_Format
# Construct a stream to hold image data temporarily:
stream = io.BytesIO()
# Continuously capture images in video mode:
for foo in camera.capture_continuous(stream,format = imag_Format,use_video_port = True):
try:
# 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 WiFi link:
stream.seek(0)
connection.write(stream.read())
# Get feedback values: (Under Development)
MF_Get_Feedback()
except (BrokenPipeError,ConnectionResetError):
print("Connection Broken")
break
else:
# Reset the stream for the next capture
stream.seek(0)
stream.truncate()
# If communications are cut,write a length of zero to the stream to signal that we're done:
connection.write(struct.pack('<L',0))
简化服务器的完整副本:
##############################################################################################
## IMPORT LIBRARIES:
# GUI Libraries:
import tkinter as tk
from PIL import Image,ImageTk
# Communication Libraries:
import io
import socket
import struct
# Threading Libraries:
import _thread
from threading import Thread
##############################################################################################
## DEFINE CONSTANTS:
# WiFi connection settings:
ROBO = 'IP Address' # Network IP address of the robot (Raspberry Pi).
HOST = 'IP Address' # Network IP address of the laptop.
PORT = 8000 # Port to connect to.
# Define state colours for the connection label:
conctLab_NO = '#ff8888'
conctLab_YES = '#88ff88'
# Image size settings:
pixels_x = 640 # Width of the images,in pixels
pixels_y = 480 # Height of the images,in pixels
##############################################################################################
## DEFINE VARIABLES:
programme_Open = True # Flag used to help close the programme
comms_Connected = False # Flag used to indicate if the there is a connection between the control device and the robot
pos_ZOOM = 1.0 # Zoom level of the image
##############################################################################################
class Application(tk.Frame):
#=====================================================================================
def __init__(self,master = None):
#-------------------------------------------------------------------------
tk.Frame.__init__(self,master) # super().__init__(master)
#-------------------------------------------------------------------------
self.myvar_vid_on = False
self.myvar_comms_active = False
#-------------------------------------------------------------------------
self.grid( sticky = tk.N + tk.S + tk.E + tk.W ) # Resize window contents when window is resized.
self.Create_Widgets()
#-------------------------------------------------------------------------
#=====================================================================================
def Create_Widgets(self):
#-------------------------------------------------------------------------
## Create Window:
top = self.winfo_toplevel()
top.rowconfigure( 0,weight = 1 )
top.columnconfigure( 0,weight = 1 )
#-------------------------------------------------------------------------
## Create Widgets:
# Image display:
self.label__Image = tk.Label( self ) # Displays video feed.
# Connection status label:
self.label__Status = tk.Label( self,text = "Not Connected",bg = conctLab_NO,fg = "black" ) # Indicates if the video link is working.
# Programme control buttons:
self.button_Start = tk.Button( self,text = "Start Connection",fg = "black",command = self.Vid_Button ) # Connect to camera.
self.button_Quit = tk.Button( self,text = "QUIT",command = self.Applicat_Quit ) # Quit programme.
# Zoom buttons:
self.button_C_Zoom_In = tk.Button( self,text = "Z+",command = self.PanTilt_Zoom_In ) # Zoom in
self.button_C_Zoom_Out = tk.Button( self,text = "Z-",command = self.PanTilt_Zoom_Out ) # Zoom out
#-------------------------------------------------------------------------
## Define Layouts:
# Set relative column widths within the window:
self.columnconfigure( 0,weight = 1 )
self.columnconfigure( 1,weight = 1 )
# Set relative row heights within the window:
self.rowconfigure( 0,minsize = pixels_y)
self.rowconfigure( 1,weight = 1 )
self.rowconfigure( 2,weight = 1 )
self.rowconfigure( 3,weight = 1 )
#-------------------------------------------------------------------------
## Organise Widgets:
self.label__Image.grid( column = 0,row = 0,columnspan = 2,sticky = tk.N + tk.E + tk.S + tk.W )
self.label__Status.grid( column = 0,row = 1,sticky = tk.N + tk.E + tk.S + tk.W )
self.button_Start.grid( column = 0,row = 2,sticky = tk.N + tk.E + tk.S + tk.W )
self.button_Quit.grid( column = 0,row = 3,sticky = tk.N + tk.E + tk.S + tk.W )
self.button_C_Zoom_In.grid( column = 1,sticky = tk.N + tk.E + tk.S + tk.W )
self.button_C_Zoom_Out.grid( column = 1,sticky = tk.N + tk.E + tk.S + tk.W )
#-------------------------------------------------------------------------
#=====================================================================================
## Pan/Tilt Control Functions:
def PanTilt_Zoom_In(self):
print("Zoom In")
global pos_Zoom
pos_Zoom = 0.5
def PanTilt_Zoom_Out(self):
print("Zoom Out")
global pos_Zoom
pos_Zoom = 1.0
#=====================================================================================
## Functions For Updating The Label Which Displays The Video Connection Status:
def Status_Label_Connected(self):
self.label__Status.configure(text = "Connected" )
self.label__Status.configure(bg = conctLab_YES)
def Status_Label_Not_Connected(self):
self.label__Status.configure(text = "Not Connected")
self.label__Status.configure(bg = conctLab_NO )
#=====================================================================================
def Vid_Button(self):
if (self.myvar_vid_on == False):
self.myvar_vid_on = True
# comUnicate = Thread(target = self.Listen_For_Data)
# comUnicate.start() # Threading causes image to strobe.
self.Listen_For_Data()
#=====================================================================================
## Functions Relating To The Camera Live-Feed From The Robot:
# (LFD = Listen For Data)
# (These functions support the main function of this group,Listen_For_Data() )
# Create the server for the robot to send images to:
def LFD_Image_Server_Create(self):
global HOST
global PORT
self.server_socket = socket.socket()
self.server_socket.bind((HOST,quit the loop:
if not self.image_len:
self.myvar_comms_active = False # Lower active communication flag.
break
# Construct a stream to hold the image data and read the image data from the connection:
self.image_stream = io.BytesIO()
self.image_stream.write(self.connection_Image.read(self.image_len))
# Extract image data:
self.image_stream.seek(0) # Rewind the stream.
self.live_feed = Image.open(self.image_stream) # Open the stream as an image with PIL.
self.img = ImageTk.PhotoImage(self.live_feed) # Convert the image into a format that Tkinter can use.
# Display image in the window:
self.LFD_Display_Image()
# Send zoom level to the robot:
self.LFD_Send_Feedback()
# Lower active communication flag:
self.myvar_comms_active = False
# Send zoom level:
def LFD_Send_Feedback(self):
global pos_ZOOM
print("pos_ZOOM = " + str(pos_ZOOM))
# Prepare the string to be sent:
send_String = "ZOOM|" + str(pos_ZOOM)
print("send_String = " + send_String)
#vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
# Send the string: # I HAVE NO IDEA HOW TO DO THIS!!
self.connection_Image.send(send_String) # I think I'm trying to send the wrong datatype here.
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Used to get a video feed from the Raspberry Pi's camera:
def Listen_For_Data(self):
global programme_Open
try:
# Create server:
self.LFD_Image_Server_Create()
# Accept connection from the Raspberry Pi for sending images: (Accept a single connection and make a file-like object out of it)
self.connection_Image = self.server_socket.accept()[0].makefile('rb')
except:
print("Failed To Establish Connection")
else:
# Indicate that the connection was established:
print("Established Connection")
self.Status_Label_Connected()
# Get images from the Raspberry Pi:
try:
self.LFD_Loop_Get_Image()
except struct.error:
print("Connection Broken")
finally:
try:
# This try statement fixes an error that occurs when quitting programme while the video connection is still running.
# Without the try statement,which results in an error.
self.Status_Label_Not_Connected()
except:
pass
# Close the connection with the Raspberry Pi:
try:
self.connection_Image.close()
except AttributeError:
print("Can't close a connection that doesn't exist!")
finally:
self.server_socket.close()
# Lower "Video On" flag:
self.myvar_vid_on = False
#=====================================================================================
## Used To Quit The Programme:
def Applicat_Quit(self):
# Indicate that the programme is beginning to close:
print(MyVar_Divider_String)
print("Starting Closing Procedures")
# Signal to the programme that it should start to close:
global programme_Open
programme_Open = False
# Close the window:
print("About To Quit Tkinter") # Indicate that the Tkinter window is about to be destroyed.
self.master.destroy() # Close Tkinter window.
#=====================================================================================
##############################################################################################
## RUN PROGRAMME:
MyVar_Divider_String = "================================"
# Signify start of programme execution:
print(MyVar_Divider_String)
print("Programme Start")
print(MyVar_Divider_String)
print("Command Log:")
# Run the Tkinter programme:
root = tk.Tk()
root.geometry("1000x800") # Set the default size of the window.
app = Application() # Create Application instance.
app.master.title("Window Title") # Set the title of the window.
app.mainloop() # Start Application.
# Signify end of programme execution:
print(MyVar_Divider_String)
print("Programme Ended")
print(MyVar_Divider_String)
##############################################################################################
简化客户端的完整副本:
##############################################################################################
## IMPORT LIBRARIES:
import io
import socket
import struct
import time
import picamera
##############################################################################################
## CONSTANTS:
## WiFi connection settings:
HOST = 'IP Address' # Network IP address of the laptop controlling the robot.
PORT = 8000 # Port to connect to.
PESTER = 5 # How often the robot will attempt to connect to the controller device (in seconds).
# Camera settings:
cam_res = (640,480) # Camera resolution. # cam_res = (1280,720) # = HD
imag_Format = 'jpeg' # Image format.
##############################################################################################
## VARIABLES:
programme_Open = True # Used to help close the programme.
level_ZOOM = 1.0 # Zoom level of the camera. (1.0 = no zoom)
##############################################################################################
## FUNCTIONS: # (MF = My Function)
# Create the camera object:
def MF_Camera_Create():
global camera
global cam_res
global level_ZOOM
# Create camera object:
camera = picamera.PiCamera() # Make the camera object.
camera.rotation = 180 # Rotate image by 180 degrees. (Camera is mounted upsidedown)
camera.resolution = cam_res # Set the resolution of the image.
camera.zoom = (0.0,level_ZOOM) # Set zoom level.
# Turn on the camera:
def MF_Camera_Start():
global camera
# Start the camera:
camera.start_preview(alpha = 200) # Start the camera & make preview see-through.
time.sleep(2) # Let the camera warm up for 2 seconds.
# Turn off the camera:
def MF_Camera_Stop():
global camera
camera.stop_preview() # Turn off the camera.
# Destroy the camera object:
def MF_Camera_Destroy():
global camera
camera.close() # Destroy the camera object.
# Create the socket connection to the control device:
def MF_Connection_Create():
global HOST
global PORT
global client_socket
global connection
client_socket = socket.socket() # Create a socket.
client_socket.connect((HOST,PORT)) # Tell the socket to talk to the control device.
connection = client_socket.makefile('wb') # Make a file-like object out of the connection.
# Close the socket connection to the control device:
def MF_Connection_Destroy():
global connection
global client_socket
try:
connection.close()
except:
pass # Connection was already closed.
finally:
client_socket.close()
# Get feedback data:
def MF_Get_Feedback():
global connection
global client_socket
global level_ZOOM
#vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
# Get the string: # THIS IS THE PART I NEED HELP WITH
value_received = connection.recv(2048)
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Display the received string:
print("Received String = " + str(value_received))
# Extract the first 4 characters of the string:
value_split = value_received.split("|")
# Update the desired setting:
if (value_split[0] == "ZOOM"):
level_ZOOM = float(value_split[1])
print("ZOOM = " + str(level_ZOOM))
camera.zoom = (0.0,0))
# Quit application: (In future versions,this will also shutdown the robot.)
def applicat_Quit():
MF_Camera_Stop() # Turn off the camera.
MF_Camera_Destroy() # Destroy the camera object.
print("End Programme")
# Add line of code to turn off the robot (Raspberry Pi).
##############################################################################################
## RUN PROGRAMME:
print("Start Programme")
MF_Camera_Create() # Create the camera object.
MF_Camera_Start() # Turn on the camera.
while (programme_Open):
try:
MF_Connection_Create() # Create a socket connection to the control device.
except ConnectionRefusedError:
print("Could Not Connect To Control Device")
pass
else:
print("Connected To Control Device")
try:
MF_Stream_Images() # Stream images to the control device.
finally:
print("Close Connection")
MF_Connection_Destroy() # Close the socket connection.
# Wait PESTER seconds before next attempt to connect to control device:
time.sleep(PESTER)
# Quit application:
applicat_Quit()
##############################################################################################
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。