How do I shutdown a socket properly that is streaming webcam footage? - python-3.x

I am trying to stream my laptops webcam stream to my pc for cv2 manipulation. The stream works but I am not really sure how to go about closing the socket.
I don't have a webcam on my desktop so I wanted to try and stream my laptops webcam over to learn cv2 on the footage. I am new to sockets, I went through the documentation and did a bunch of googling to get this far, even though most of what I now have was copy pasted. I am not really sure where to look to learn how to shut down sockets propery when streaming video data with cv2.
I've tried using with socket.socket( ... ) as s: to close the connection
breaks after the while and try loops, that go into shutdown(socket.SHUT_WR and close() and I've tried terminating the program on both the client and server.
#server.py
import socket
import cv2
import pickle
import struct
def main():
HOST=''
PORT=12397
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
print('Socket created')
s.bind((HOST,PORT))
print('Socket bind complete')
s.listen(10)
print('Socket now listening')
conn,addr=s.accept()
data = b""
payload_size = struct.calcsize(">L")
while True:
try:
key = cv2.waitKey(20) & 0xFF
while len(data) < payload_size:
data += conn.recv(4096)
if key == ord("q"):
print("Socket closed.")
break
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack(">L", packed_msg_size)[0]
while len(data) < msg_size:
data += conn.recv(4096)
if key == ord("q"):
print("Socket closed.")
break
frame_data = data[:msg_size]
data = data[msg_size:]
frame=pickle.loads(frame_data, fix_imports=True, encoding="bytes")
frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
cv2.imshow('ImageWindow',frame)
cv2.waitKey(1)
if key == ord("q"):
print("Socket closed.")
break
except:
cv2.destroyAllWindows()
break
s.shutdown(socket.SHUT_WR)
s.close()
if __name__ == "__main__":
main()
#client.py
import socket
import cv2
import pickle
import struct
def main():
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as client_socket:
print('Connecting to socket.')
client_socket.connect((ENTER IP HERE, 12397))
connection = client_socket.makefile('wb')
print('Starting stream.')
cam = cv2.VideoCapture(0)
cam.set(3, 320*2);
cam.set(4, 240*2);
frame_time = int((1.0 / 30.0) * 1000.0)
img_counter = 0
while True:
try:
ret, frame = cam.read()
if ret:
result, frame = cv2.imencode('.jpg', frame, encode_param)
data = pickle.dumps(frame, 0)
size = len(data)
client_socket.sendall(struct.pack(">L", size) + data)
img_counter += 1
if cv2.waitKey(frame_time) & 0xFF == ord('q'):
break
else:
break
except KeyboardInterrupt:
break
cam.release()
client_socket.shutdown(socket.SHUT_WR)
client_socket.close()
if __name__ == "__main__":
main()
I want to close the connection by pressing q.
I currently get WinErrors on the server
OSError: [WinError 10038] An operation was attempted on something that is not a socket
and on the client
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
when I (spam) "q" on the server side, it doesn't seem to catch if I just press it once.

That error typically happen if you use the socket after you have closed it.
Now if we take your server, you do something like
with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s:
... code here ...
s.shutdown(socket.SHUT_WR)
s.close()
That means the shutdown and close calls are done after the socket have already been closed.
You have the same problem in the client program.

Related

Receiving a full image from a TCP socket communication

I have a TCP communication where my client continuously sends images as a byte array to a server and receives a response back, my problem is that when the server receives the images they are not done being received even though I've added a flag to indicate the end of the image.
I'd like to know a better way to ensure that the image file is received completely before receiving a new one
EDIT: My new attempt:
Client.py
import numpy as np
import cv2
from PIL import Image
import base64
import socket
def main(data):
s = socket.socket()
s.connect(("127.0.0.1", 999))
decoded_data = base64.b64decode(data)
print("Sending...")
s.sendall(decoded_data)
s.shutdown(s.SHUTWR)
b_data = b''
while True:
txt_data = s.recv(2048)
if not txt_data: break
b_data += txt_data
print('response received from the server: ' + b_data.decode())
return b_data.decode()
Server.py
import socket
from PIL import Image
import io
import numpy as np
import cv2
import uuid
IP = '127.0.0.1'
PORT = 999
with socket.socket() as s:
s.bind((IP,PORT))
s.listen(1)
count = 0
print ('The server is ready')
while True:
con, addr = s.accept()
filename = str(uuid.uuid4())
count = count + 1
img_dir = 'C:/Users/my_user/stream_images/'
img_format = '.png'
with con:
img = b''
while True:
data = con.recv(2048)
if not data:
break
img += data
image_name = img_dir+'frame'+str(count)+img_format
pil_image = io.BytesIO(img)
img = np.array(Image.open(pil_image))
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
cCode = str('Thank you for connecting')
con.sendall(cCode.encode())
print("called con.sendall")
cv2.imshow('frame', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
Currently, I am now able to fully send the images and receive them properly at the server, the only problem is that I am no longer sending a response back after the image is received, so there is something wrong with how I am receiving the reply message at the client side.
As user207421 suggested you can shutdown the socket for writing after sending the image on the client-side while still being able to receive an confirmatory answer from the server. Another problem you're facing here is the blocking nature of cv2.waitKey, which essentially halts the server until the user presses q in the cv2 window (the server will not be able to handle any other requests). I'd generally recommend to separate your network/IO logic from user interface logic. To circumvent the blocking behaviour of I've implemented a very basic image_viewer, which waits for incoming images in a thread that runs separately from the server loop by passing images through a Queue.
The client code looks as follows:
import socket
from PIL import Image
def send_image(img: Image, host: str = '127.0.0.1', port: int = 999):
s = socket.socket()
s.connect((host, port))
img_data = img._repr_png_()
s.sendall(img_data)
s.shutdown(socket.SHUT_WR) # close socket for writing, receiving is still possible
print(f'Sent {len(img_data) / 1024:,.1f} kB of image data.')
b_data = b''
while recv_data := s.recv(2048):
b_data += recv_data
print(f'Server response: {b_data.decode()}')
# maybe check server response for server side errors etc. and add return value for this function?
# use like: send_image(Image.open('test.png'))
The server code is:
import io
import queue
import socket
import threading
import cv2
import numpy as np
from PIL import Image
def image_viewer(q: queue.Queue):
while True:
try:
img_name, img = q.get(block=True, timeout=.1) # poll every 0.1 seconds
print(f'Image viewer: displaying `{img_name}`!')
cv2.imshow('Image preview', img)
except queue.Empty:
... # no new image to display
key = cv2.pollKey() # non-blocking
if key & 0xff == ord('q'):
cv2.destroyAllWindows()
print('Image viewer was closed')
return
def serve_forever(host: str, port: int, img_dir: str = 'C:/Users/my_user/stream_images/', img_format: str = '.png'):
q = queue.Queue()
img_viewer = threading.Thread(target=image_viewer, args=(q,))
img_viewer.start()
with socket.socket() as s:
s.bind((host, port))
s.listen(1)
count = 0
print('The server is ready')
while True:
conn, addr = s.accept()
count = count + 1
img_name = img_dir + 'frame' + str(count) + img_format
print (f'Client connected: {addr}')
img = b''
while data := conn.recv(2048):
img += data
conn.sendall('Thank you for connecting'.encode()) # maybe use return codes for success, error etc.?
conn.close()
pil_img = Image.open(io.BytesIO(img)) # might want to save to disk?
np_img = np.asarray(pil_img)
np_img = cv2.rotate(np_img, cv2.ROTATE_90_CLOCKWISE)
q.put((img_name, np_img))
print (f'Client at {addr} disconnected after receiving {len(img) / 1024:,.1f} kB of data.')
if __name__ == '__main__':
serve_forever('127.0.0.1', 999)

Running opencv in a thread, unable to start a second time

When I try to start openvc in a thread, its working once, but after I call it a second time, it won’t start again. The code stuck’s when it reaches a cv2 function in this casecv2.imshow() (but after cv2.VideoCapture()).
If I call the start_videofeed() function right after it receives the signal of the socket, its working properly, but I would like to have the sock.recvfrom() to continue receiving for other commands.
This code is only a small example of my script, but I tried a lot and also searched on web but I couldn’t find any answer, only others with similar problems.
I’m running the latest opencv-python package, with python3.8 on manjaro.
I hope someone knows an answer how to solve this issue.
Thanks in advance.
import socket
from threading import Thread
import cv2
class VideoFeed():
def __init__(self):
self.url = 0
self.window_name = "Video"
def run(self):
sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
sock.bind( ('',54666) )
while True:
d, addr = sock.recvfrom( 1024 )
payload = d.decode()
if payload == "show_video":
self.start_video()
if payload == "exit":
break
def start_video(self):
vs = Thread(target=self.start_videofeed)
vs.start()
def start_videofeed(self):
cap = cv2.VideoCapture(self.url)
if (cap.isOpened()== False):
print("Error opening video file")
print("Starting videofeed")
while True:
ret, self.frame = cap.read()
cv2.imshow(self.window_name, self.frame)
key = cv2.waitKey(10)
if key == 27 or key == ord('q'):
break
if cv2.getWindowProperty(self.window_name, cv2.WND_PROP_VISIBLE) < 1:
break
cv2.destroyAllWindows()
cap.release()
print("Video release and Destroy")
return
vf = VideoFeed()
vf.run()
Edit:
I finally found an answer. It seems it's not possible to start the video from or in a thread a second time, so I set a boolean in the socket thread to start the videofeed from the main program.
class VideoFeed():
def __init__(self):
self.url = "http://192.168.178.103:8081"
self.window_name = "Video"
self.start_video = False
def run(self):
Thread(target=self.start_socket).start()
while True:
if self.start_video:
self.start_videofeed()
self.start_video = False
def start_socket(self):
sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
sock.bind( ('',54666) )
while True:
d, addr = sock.recvfrom( 1024 )
payload = d.decode()
if payload == "show_video":
self.start_video = True
if payload == "exit":
break
def start_videofeed(self):
cap = cv2.VideoCapture(self.url)
if (cap.isOpened()== False):
print("Error opening video file")
print("Starting videofeed")
while True:
ret, self.frame = cap.read()
cv2.imshow(self.window_name, self.frame)
key = cv2.waitKey(10)
if key == 27 or key == ord('q'):
break
if cv2.getWindowProperty(self.window_name, cv2.WND_PROP_VISIBLE) < 1:
break
cv2.destroyAllWindows()
cap.release()
print("Video release and Destroy")
return

Minimize lag in video streaming over TCP in python

I am doing a research using raspberry pi. I want to stream video from my raspberry pi (Client) to my laptop (Server) for my project. After two days research i have found following code.
CLIENT
import socket
import cv2
import pickle
import struct
device = cv2.VideoCapture(-1)
ok, frame = device.read()
host = '192.168.1.4'
port = 12345
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((host, port))
while True:
ok, frame = device.read()
data = pickle.dumps(frame)
client_socket.sendall(struct.pack("L", len(data)) + data)
cv2.imshow("",frame)
if cv2.waitKey(1) == 27:
break
device.release()
cv2.destroyAllWindows()
SERVER
import socket
import cv2
import numpy as np
import time
import pickle
import struct
host = '192.168.1.4'
port = 12345
client_socket = None
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen(5)
conn, addr = server_socket.accept()
data = b''
payload_size = struct.calcsize("L")
while True:
while len(data) < payload_size:
#data += conn.recv(90456)
data += conn.recv(4096)
packed_msg_size = data[:payload_size]
data = data[payload_size:]
msg_size = struct.unpack("L", packed_msg_size)[0]
while len(data) < msg_size:
data += conn.recv(4096)
frame_data = data[:msg_size]
data = data[msg_size:]
frame = pickle.loads(frame_data)
#print (frame.size)
cv2.imshow('frame', frame)
cv2.waitKey(1)
But there is a big lag. What i want is live streaming (if possible) or as much as close to live streaming (with minimum lag). Someone could suggest UDP but since it not guarantee that getting all frames.
Can some one suggest me to improve my code. Any help??
Thank you very much in advance!!

How to read serial data with multiprocessing in python?

I have a device that outputs data at irregular intervals. I want to write data onto a csv in 2 second intervals. So I figured multiprocessing with a queue might work.
Here I'm trying to just pass data from one process to another but I get Serial Exception. Also, I'm unable to run it on IDLE. So I'm stuck with using the terminal. As a result, the error message closes as soon as it opens.
Here's the code:
import multiprocessing
import time
import datetime
import serial
try:
fio2_ser = serial.Serial("COM3",
baudrate=2400,
bytesize=serial.EIGHTBITS,
parity =serial.PARITY_ODD)
except serial.SerialException:
print("FiO2 Analyser Device not detected")
def Read_Data(q):
global fio2_ser
while True:
try:
fio2_data = fio2_ser.readline().decode('utf-8')
fio2_data = str(fio2_data).replace("\r\n","")
fio2_data = fio2_data.replace("\x000","")
except:
fio2_data = "FiO2 Data Unavailable"
q.put(fio2_data)
def Disp_Data(q):
while q.empty() is False:
fio2_data = q.get()
print(fio2_data)
time.sleep(2)
if __name__ == "__main__":
q = multiprocessing.Queue()
p1 = multiprocessing.Process(target=Read_Data, args=(q,))
p2 = multiprocessing.Process(target=Disp_Data, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
When I run a separate module to collect data, it runs fine and collects data.
import serial
try:
fio2_ser = serial.Serial("COM3",
baudrate=2400,
bytesize=serial.EIGHTBITS,
parity =serial.PARITY_ODD)
except serial.SerialException:
print("FiO2 Analyser Device not detected")
def Reader():
global fio2_ser
try:
fio2_data = fio2_ser.readline().decode('utf-8')
fio2_data = str(fio2_data).replace("\r\n","")
fio2_data = fio2_data.replace("\x000","")
return fio2_data
except:
return "FiO2 Data Unavailable"
if __name__ =='__main__':
value = Reader()
print(value)
The Disp_Data() function will stop running when q.empty() is True. In my case the loop exits immediately.
Might be useful to display the error message thrown by SerialException to see the cause:
except serial.SerialException as msg:
print( "Error opening serial port %s" % msg)
Also, it would be better to gracefully shut down the child-processes. In my case, they kept running after killing the main process, so the Read_Data() process keeps the port open.
The multiprocessing module doesn't like pickling pyserial.
This following code snip works on my Windows10 box
Used threading instead.
Added some print statements here ad there to understand what is
happening.
Used multiprocessing.Event() to improve shutdown.
Print exception error messages to see what causes serial exceptions.
One second timeout on serial port to allow read loop to continue.
Maybe not necessary for release code.
import threading, multiprocessing
import time
import serial
import sys
def OpenSerialPort(port=""):
print ("Open port %s" % port)
fio2_ser = None
try:
fio2_ser = serial.Serial(port,
baudrate=2400,
bytesize=serial.EIGHTBITS,
parity =serial.PARITY_ODD)
except serial.SerialException as msg:
print( "Error opening serial port %s" % msg)
except:
exctype, errorMsg = sys.exc_info()[:2]
print ("%s %s" % (errorMsg, exctype))
return fio2_ser
def Read_Data(queue, serialPort, stopped):
print ("Start reading data.")
serialPort.timeout = 1.0
while not stopped.is_set():
fio2_data = ''
try:
#print "Reading port..."
fio2_data = serialPort.readline()
except:
exctype, errorMsg = sys.exc_info()[:2]
print ("Error reading port - %s" % errorMsg)
stopped.set()
break
if len(fio2_data) > 0:
fio2_data = fio2_data.decode('utf-8')
fio2_data = str(fio2_data).replace("\r\n","")
fio2_data = fio2_data.replace("\x000","")
queue.put(fio2_data)
else:
queue.put("Read_Data() no Data")
serialPort.close()
print ("Read_Data finished.")
def Disp_Data(queue, stopped):
print ("Disp_Data started")
while not stopped.is_set():
#print "Check message queue."
if queue.empty() == False:
fio2_data = queue.get()
print(fio2_data)
print ("Disp_Data finished")
if __name__ == "__main__":
#serialPort = OpenSerialPort('/dev/ttyUSB0')
serialPort = OpenSerialPort('COM3')
if serialPort == None: sys.exit(1)
queue = multiprocessing.Queue()
stopped = threading.Event()
p1 = threading.Thread(target=Read_Data, args=(queue, serialPort, stopped,))
p2 = threading.Thread(target=Disp_Data, args=(queue, stopped,))
p1.start()
p2.start()
loopcnt = 20
while (loopcnt > 0) and (not stopped.is_set()):
loopcnt -= 1
print ("main() %d" % loopcnt)
try:
time.sleep(1)
except KeyboardInterrupt: #Capture Ctrl-C
print ("Captured Ctrl-C")
loopcnt=0
stopped.set()
stopped.set()
loopcnt=0
print ("Stopped")
p1.join()
p2.join()
serialPort.close()
print ("Done")

Stop server from client's thread / Modify server's variable from client's thread

I would like to write an application that could stop the server based on client's input. The server is multi-threaded and I do not understand how can I do this.
Basically, I described my problem here: Modify server's variable from client's thread (threading, python).
However, this is the Python solution, not the general solution I could implement in Java, C, C++, etc.
I need to close other clients, when one of them guesses the number, but the server should be still alive, ready for the new game.
Can I ask for some advices, explanations?
I tried this (still do not know how to port it to C or Java), but it lets the clients send the numbers even if one of them just guesses it. It seems to me that kill_em_all does not do it's job, it does not close all the connections and does not disconnect the other clients as it should. How to improve this?
#!/usr/bin/env python
from random import randint
import socket, select
from time import gmtime, strftime
import threading
import sys
class Handler(threading.Thread):
def __init__(self, connection, randomnumber, server):
threading.Thread.__init__(self)
self.connection = connection
self.randomnumber = randomnumber
self.server = server
def run(self):
while True:
try:
data = self.connection.recv(1024)
if data:
print(data)
try:
num = int(data)
if self.server.guess(num) :
print 'someone guessed!'
self.server.kill_em_all()
break
else :
msg = "Try again!"
self.connection.sendall(msg.encode())
except ValueError as e:
msg = "%s" % e
self.connection.sendall(msg.encode())
else:
msg = "error"
self.connection.send(msg.encode())
except socket.error:
break
self.connection.close()
def send(self, msg):
self.connection.sendall(msg)
def close(self):
self.connection.close()
class Server:
randnum = randint(1,100)
def __init__(self, ip, port):
self.ip = ip
self.port = port
self.address = (self.ip, self.port)
self.server_socket = None
def guess(self, no):
if self.randnum == no:
self.randnum = randint(1, 100)
print("New number is ", self.randnum )
result = True
else:
result = False
return result
def kill_em_all(self):
for c in self.clients:
c.send("BYE!")
c.close()
def run(self):
try:
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.bind((self.ip, self.port))
self.server_socket.listen(10)
self.clients = []
print('Num is %s' % self.randnum)
while True:
connection, (ip, port) = self.server_socket.accept()
c = Handler(connection, self.randnum, self)
c.start()
self.clients.append(c)
except socket.error as e:
if self.server_socket:
self.server_socket.close()
sys.exit(1)
if __name__ == '__main__':
s = Server('127.0.0.1', 7777)
s.run()
Client code:
import socket
import sys
port = 7777
s = None
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = socket.gethostname()
s.connect(('127.0.0.1', port))
except socket.error, (value, message):
if s:
s.close()
print "Could not open socket: " + message
sys.exit(1)
while True:
data = raw_input('> ')
s.sendall(data)
data = s.recv(1024)
if data:
if data == "BYE!":
break
else:
print "Server sent: %s " % data
s.close()
Log in. Using whatever protocol you have, send the server a message telliing it to shut down. In the server, terminate your app when you get the shutdown message. That's it. It's not a problem with any OS I have used - any thread of a process can terminate that process.

Resources