My IP camera seems to be a little unstable and disconnects randomly. I'd like my script to be able to determine when its disconnected and attempt to reconnect a few times, probably waiting 5-10 seconds between attempts. I've tried a few things, but nothing is working.
This is my basic script, when ret is false the script ends:
#!/usr/local/bin/python3
import cv2
import time
import datetime
print("start time: " + datetime.datetime.now().strftime("%A %d %B %Y %I:%M:%S%p"))
cap = cv2.VideoCapture('rtsp://<ip><port>/live0.264')
while(True):
# Capture frame-by-frame
ret, frame = cap.read()
# Confirm we have a valid image returned
if not ret:
print("disconnected!")
break
# Our operations on the frame come here
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2BGRA)
# Display the resulting frame
cv2.imshow('frame', gray)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
print("end time: " + time.strftime("%X"))
# When everything is done, release the capture
cap.release()
cv2.destroyAllWindows()
Edit: I would also like the script to try to reconnect to the camera in the event that my network goes down temporarily or anything like that as well.
I was finally able to solve this myself. Hopefully this is useful for anyone else looking to do the same thing.
This is actually a shell of a more complex script that has logic for motion detection and video recording when motion is detected. Everything is working very well with this basic logic (and my crappy IP camera) although I am still doing testing.
#!/usr/local/bin/python3
import cv2
import datetime
import time
def reset_attempts():
return 50
def process_video(attempts):
while(True):
(grabbed, frame) = camera.read()
if not grabbed:
print("disconnected!")
camera.release()
if attempts > 0:
time.sleep(5)
return True
else:
return False
recall = True
attempts = reset_attempts()
while(recall):
camera = cv2.VideoCapture("rtsp://<ip><port>/live0.264")
if camera.isOpened():
print("[INFO] Camera connected at " +
datetime.datetime.now().strftime("%m-%d-%Y %I:%M:%S%p"))
attempts = reset_attempts()
recall = process_video(attempts)
else:
print("Camera not opened " +
datetime.datetime.now().strftime("%m-%d-%Y %I:%M:%S%p"))
camera.release()
attempts -= 1
print("attempts: " + str(attempts))
# give the camera some time to recover
time.sleep(5)
continue
More detailed description:
https://github.com/Combinacijus/various-code-samples/tree/master/Python/OpenCV/ip_cam_reconnecting
Wrote a class to deal with IP camera disconnecting randomly. Main idea is to check if cap.read() returns a frame and if it doesn't it tries to reconnect to the camera.
import cv2
import requests # NOTE: Only used for forceful reconnection
import time # NOTE: Only used for throttling down printing when connection is lost
class IPVideoCapture:
def __init__(self, cam_address, cam_force_address=None, blocking=False):
"""
:param cam_address: ip address of the camera feed
:param cam_force_address: ip address to disconnect other clients (forcefully take over)
:param blocking: if true read() and reconnect_camera() methods blocks until ip camera is reconnected
"""
self.cam_address = cam_address
self.cam_force_address = cam_force_address
self.blocking = blocking
self.capture = None
self.RECONNECTION_PERIOD = 0.5 # NOTE: Can be changed. Used to throttle down printing
self.reconnect_camera()
def reconnect_camera(self):
while True:
try:
if self.cam_force_address is not None:
requests.get(self.cam_force_address)
self.capture = cv2.VideoCapture(self.cam_address)
if not self.capture.isOpened():
raise Exception("Could not connect to a camera: {0}".format(self.cam_address))
print("Connected to a camera: {}".format(self.cam_address))
break
except Exception as e:
print(e)
if self.blocking is False:
break
time.sleep(self.RECONNECTION_PERIOD)
def read(self):
"""
Reads frame and if frame is not received tries to reconnect the camera
:return: ret - bool witch specifies if frame was read successfully
frame - opencv image from the camera
"""
ret, frame = self.capture.read()
if ret is False:
self.reconnect_camera()
return ret, frame
if __name__ == "__main__":
CAM_ADDRESS = "http://192.168.8.102:4747/video" # NOTE: Change
CAM_FORCE_ADDRESS = "http://192.168.8.102:4747/override" # NOTE: Change or omit
cap = IPVideoCapture(CAM_ADDRESS, CAM_FORCE_ADDRESS, blocking=True)
# cap = IPVideoCapture(CAM_ADDRESS) # Minimal init example
while True:
ret, frame = cap.read()
if ret is True:
cv2.imshow(CAM_ADDRESS, frame)
if cv2.waitKey(1) == ord("q"):
break
Related
Trying to set a max timeout for VideoCapture to catch the exception and print a message if the stream is down. For example, I need it to try for 10 seconds and then assume the stream is offline. I have the following code so far.
import cv2
try:
cap = cv2.VideoCapture("rtsp://Username:Password#IP_ADDRESS:PORT")
print("Stream found.")
cap.release()
cv2.destroyAllWindows()
except:
# It actually never reaches the exception
print("Stream not found.")
Edit: Tried to simplify the answer below and it kind of works as expected but still with its own timeout decision.
import cv2
import time
try:
cap = cv2.VideoCapture("rtsp://DNA:DNA2020!#98.173.8.28:5514")
try:
success, video_frame = cap.read()
if success:
print('Frame read')
else:
print('No frame read')
except cv2.error as e:
print(f'CV2 Exception: {str(e)}')
cap.release()
except:
print("Stream not found.")
When opening the stream you only get a video object. Then you have to start reading the frames from the video object. This will tell you if there is a video stream.
TIMEOUT: The code stops if during 10 seconds no frames have been received.
RESET TIMEOUT: If within the 10 seconds new frames are being received then receiving resumes and the timeout will be again 10 seconds.
I have tested this code on a video file. I think it should work on your camera also.
import cv2
import time
try:
cap = cv2.VideoCapture("rtsp://Username:Password#IP_ADDRESS:PORT")
print("Stream found.")
stream_ok = True
while True:
try:
success, video_frame = cap.read()
if success:
print('Frame read')
frame_ok = True
# show frame
cv2.imshow("Image", video_frame)
cv2.waitKey(1)
else:
print('No frame read')
frame_ok = False
except cv2.error as e:
print(f'CV2 Exception: {str(e)}')
frame_ok = False
if stream_ok:
if not frame_ok:
# start of missing video
stream_ok = False
no_frame_start_time = time.time()
else:
if not frame_ok:
# still no video
if time.time() - no_frame_start_time > 10:
print('NO VIDEO - CAPTURE ABORTED')
break
else:
# video restarted
stream_ok = True
cap.release()
cv2.destroyAllWindows()
except:
# It actually never reaches the exception
print("Stream not found.")
I have a tello ryze drone, which has got a camera onboard. I am connecting to the drone in order to receive the video over wifi. My system is windows 10 and I am using python 2.7.
I am receiving a h264 bytecode and i use the libh264 decoder from tello in order to get the frames of the video, which i also display in my UI.
What i need to do is to save this video as a file on my computer and i have problems with this. I am able to make a snapshot with opencv and save it as an image, this isn't a problem. But making a video isn't working for some reason.
I read many posts here like this one but they dont work out for me. I am getting either an error or i get a very small video file, which doesnt open.
My Frames are List of List with RGB values like:
[[255,200,100][55,200,100][25,20,100]]
Here is my code for a better understanding in order to help me
This is the UI Part(i just copy here the needed code only):
def videoLoop(self):
try:
# start the thread that get GUI image and draw skeleton
time.sleep(0.5)
self.sending_command_thread.start()
while not self.stopEvent.is_set():
system = platform.system()
# read the frame for GUI show
self.frame = self.drone.read()
if self.frame is None or self.frame.size == 0:
continue
# transfer the format from frame to image
image = Image.fromarray(self.frame)
# we found compatibility problem between Tkinter,PIL and Macos,and it will
# sometimes result the very long preriod of the "ImageTk.PhotoImage" function,
# so for Macos,we start a new thread to execute the _updateGUIImage function.
if system == "Windows" or system == "Linux":
self.refreshUI(image)
else:
thread_tmp = threading.Thread(target=self.refreshUI, args=(image,))
thread_tmp.start()
time.sleep(0.03)
except RuntimeError as e:
print("[INFO] caught a RuntimeError")
def refreshUI(self, image):
image = ImageTk.PhotoImage(image)
# if the imagePanel none ,we need to initial it
if self.imagePanel is None:
self.imagePanel = tki.Label(image=image)
self.imagePanel.image = image
self.imagePanel.pack(side="left", fill="both",
expand="yes", padx=10, pady=10)
# otherwise, simply update the imagePanel
else:
self.imagePanel.configure(image=image)
self.imagePanel.image = image
def takeSnapshot(self):
# grab the current timestamp and use it to construct the filename
ts = datetime.datetime.now()
filename = "{}.jpg".format(ts.strftime("%d-%m-%Y_%H-%M-%S"))
p = os.path.sep.join((self.screenShotPath, filename))
# save the file
cv2.imwrite(p, cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR))
print("[INFO] saved {}".format(filename))
Below you can find the Tello code (only the needed part too):
def read(self):
"""Return the last frame from camera."""
if self.is_freeze:
return self.last_frame
else:
return self.frame
def video_freeze(self, is_freeze=True):
"""Pause video output -- set is_freeze to True"""
self.is_freeze = is_freeze
if is_freeze:
self.last_frame = self.frame
def _receive_video_thread(self):
"""
Listens for video streaming (raw h264) from the Tello.
Runs as a thread, sets self.frame to the most recent frame Tello captured.
"""
packet_data = b''
while True:
try:
res_string, ip = self.socket_video.recvfrom(2048)
packet_data += res_string
# end of frame
if len(res_string) != 1460:
for frame in self._h264_decod(packet_data):
self.frame = frame
packet_data = b''
except socket.error as exc:
print(("Caught exception sock.error : %s" % exc))
def _h264_decod(self, packet_data):
"""
decode raw h264 format data from Tello
:param packet_data: raw h264 data array
:return: a list of decoded frame
"""
res_frame_list = []
frames = self.decoder.decode(packet_data)
for framedata in frames:
(frame, w, h, ls) = framedata
if frame is not None:
# print ('frame size %i bytes, w %i, h %i, linesize %i' % (len(frame), w, h, ls))
frame = np.frombuffer(frame, dtype=np.ubyte, count=len(frame))
frame = (frame.reshape((h, ls // 3, 3)))
frame = frame[:, :w, :]
res_frame_list.append(frame)
return res_frame_list
It would be very kind of you if someone could help me write a method like this pseudocode:
def saveVideo(self, frame_or_whatever_i_need_here):
out = cv2.VideoWriter('output.avi_or_other_format', -1, 20.0, (640,480))
out.write(frame_or_whatever_i_need_here)
out.release()
Edit 1:
i found an option making a video out of my snapshots, this means that i coud make snapshot over a thread and later save them to a video. That would be an option. The problem is, that it would consume too much space in the disk. The workaround link is here
I found the solution, so i will post it here if someone needs the same thing. I used the following blog and modified the code to do my work, you can find the post here
self.frame = None
self.frame_array = []
def videoLoop(self):
try:
# start the thread that get GUI image and draw skeleton
time.sleep(0.5)
self.sending_command_thread.start()
while not self.stopEvent.is_set():
system = platform.system()
# read the frame for GUI show
self.frame = self.drone.read()
self.frame_array.append(self.frame)
if self.frame is None or self.frame.size == 0:
continue
# transfer the format from frame to image
image = Image.fromarray(self.frame)
if system == "Windows" or system == "Linux":
self.refreshUI(image)
else:
thread_tmp = threading.Thread(target=self.refreshUI, args=(image,))
thread_tmp.start()
time.sleep(0.03)
except RuntimeError as e:
print("[INFO] caught a RuntimeError")
def convert_frames_to_video(self, pathOut, fps):
size = (self.videoWidth, self.videoHeight)
out = cv2.VideoWriter(pathOut, cv2.VideoWriter_fourcc(*'DIVX'), fps, size)
for i in range(len(self.frame_array)):
# writing to a image array
out.write(self.frame_array[i])
out.release()
def onClose(self):
print("[INFO] closing...")
self.convert_frames_to_video('video.avi', 25.0)
self.stopEvent.set()
del self.drone
self.root.quit()
I have the code to get images from the video stream of a laptop camera. I want to reduce the photo saving interval to one photo per minute. The original code looks like this
# Importing all necessary libraries
import cv2
import os
# Read the video from specified path
cam = cv2.VideoCapture(0)
try:
# creating a folder named data
if not os.path.exists('data'):
os.makedirs('data')
# if not created then raise error
except OSError:
print ('Error: Creating directory of data')
# frame
currentframe = 0
while(True):
# reading from frame
ret,frame = cam.read()
if ret:
# if video is still left continue creating images
name = './data/frame' + str(currentframe) + '.jpg'
print ('Creating...' + name)
# writing the extracted images
cv2.imwrite(name, frame)
# increasing counter so that it will
# show how many frames are created
currentframe += 1
else:
break
# Release all space and windows once done
cam.release()
cv2.destroyAllWindows()
For this task I try to use the parameter CAP_PROP_POS_MSEC
[...]
# Read the video from specified path
cam = cv2.VideoCapture(0)
cam.set(cv2.CAP_PROP_POS_MSEC,20000)
[...]
while(True):
[...]
# writing the extracted images
cv2.imwrite(name, frame)
cv2.waitKey()
[...]
But, the saving speed remains the same and I see the following error
videoio error v4l2 property pos_msec is not supported
I use Ubuntu 18.04, Python 3.7, and OpenCV 4.1.
Where do I have a mistake, and whether I chose the right way to minimize the load on my computer's resources?
UPD
Using the recommendation of J.D. this code is working
import cv2
import os
import time
prev_time = time.time()
delay = 1 # in seconds
# Read the video from specified path
cam = cv2.VideoCapture(0)
currentframe = 0
while (True):
# reading from frame
ret, frame = cam.read()
if ret:
if time.time() - prev_time > delay:
# if video is still left continue creating images
name = './data/frame' + str(currentframe) + '.jpg'
print('Creating...' + name)
# writing the extracted images
cv2.imwrite(name, frame)
currentframe += 1
prev_time = time.time()
else:
break
EDIT: this answer is not a good solution - due to the frame buffer, as described in the comments. Because of the relevant information in the comments I will leave the answer.
If you don't plan to expand the code to do other things, you can just use the waitkey:
cv2.waitKey(60000) will freeze code execution 60 secs.
If you want to expand the code, you have to create a time based loop:
import time
prev_time = time.time()
count = 0
delay = 1 # in seconds
while True:
if time.time()-prev_time > delay:
count += 1
print(count)
prev_time = time.time()
you should read separately and set the values again for each image, like this:
while(True):
cam = cv2.VideoCapture(0)
cam.set(cv2.CAP_PROP_POS_MSEC,20000)
# reading from frame
ret,frame = cam.read()
if ret:
# if video is still left continue creating images
name = './data/frame' + str(currentframe) + '.jpg'
print ('Creating...' + name)
# writing the extracted images
cv2.imwrite(name, frame)
cv2.waitKey()
# increasing counter so that it will
# show how many frames are created
currentframe += 1
else:
break
cam.release()
and check your openCV version for python about the error.
I am trying to pull individual frames at specified times from an RTSP feed.
This works fine for video streaming:
vcap = cv2.VideoCapture(RTSP_URL)
while(1):
ret, frame = vcap.read()
cv2.imshow('VIDEO', frame)
cv2.waitKey(1)
But if I want to take an image every second and save it by doing something like this:
vcap = cv2.VideoCapture(RTSP_URL)
for t in range(60):
ret, frame = vcap.read()
if ret:
cv2.imwrite("{}.jpg".format(t), frame)
time.sleep(1);
Every image will look exactly the same as the first image. In every instance ret == True.
(Also this was working fine for me a week ago and then ipython did something that required me to do a re-install)
cv2.waitKey(1000) wouldn't do anything if you didn't show image with cv2.imshow(). Try:
vcap = cv2.VideoCapture(RTSP_URL)
for t in range(60):
ret, frame = vcap.read()
cv2.imwrite('{}.jpg'.format(t), frame)
# this will activate the waitKey funciton
cv2.imshow('preview', frame)
cv2.waitKey(1000)
On another note, iPython/jupyter doesn't play well with the cv2's imshow and the whole GUI functionality. If, for example, you can't break the loop by keypress
if (cv2.waitKey(1000) == 27 & 0xff): break;
Alright, after endless messing with it over the last few days, 1 second is not fast enough for the feed for whatever reason.
This will work:
vcap = cv2.VideoCapture(RTSP_URL)
for t in range(60):
ret, frame = vcap.read()
if ret and t % 1000 == 0:
cv2.imwrite("{}.jpg".format(t), frame)
time.sleep(0.001)
I tried to capture (stereo) images with Python's opencv and two cameras so therefore every 5 seconds an image should be saved. But the problem here is that an old frame is saved.
The minified code is as follows:
cap = cv2.VideoCapture(0)
for i in range(20):
time.sleep(5)
print "Taking image %d:" % i
ret, frame = cap.read()
cv2.imwrite("image %d" % i, frame)
print " image done." if ret else " Error while taking image..."
cap.release()
cv2.destroyAllWindows()
To check this, I changed the position of the camera after each taken image. But nevertheless an image from an old position is saved (actually not the same, but I assume some frames after the last saved image). After 5 (or more) images finally the captured location in the image does also change.
So, is there any problem with time.sleep? I guess that I'm not getting the actual frame, but a buffered one. If this is the case, how could I fix it and capture the actual frame?
VideoCapture is buffering.
If you always want the actual frame, do this:
while True:
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cap.release()
cv2.imshow(" ", frame)
if cv2.waitKey(2000) != -1:
break
you need to count the elapsed time, but not stop read frames. like this:
import cv2
import time
cap = cv2.VideoCapture(0)
preframe_tm = time.time()
i = 0
while True:
ret, frame = cap.read()
elapsed_time = time.time() - preframe_tm
if elapsed_time < 5:
continue
preframe_tm = time.time()
i += 1
print("Taking image %d:" % i)
cv2.imwrite("image_%d.jpg" % i, frame)
if i >= 20:
break
cap.release()
cv2.destroyAllWindows()