I am trying to save 10 seconds of buffered video using Python, in particular '.h264' format.
In order to do so, I have been using a PiCamera connected to a Raspberry Pi and the script shown below. The main road block I am facing right now is that instead of saving the file directly to a location [stream.copy_to(str(time)+'.h264')] I would like to save it to a variable in order to perform certain operations (e.g. change video resolution) before finally saving it. Any idea how this can be achieve?
Thanks in advance!
import time
import io
import os
import picamera
import datetime as dt
from PIL import Image
import cv2
#obtain current time
def return_currentTime():
return dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
#trigger event declaration
def motion_detected():
while True:
print ("Trigger event(y)?")
trigger = input ()
if trigger =="y":
time = return_currentTime()
print ("Buffering...")
stream.copy_to(str(time)+'.h264')
else:
camera.stop_recording()
break
#countdown timer
def countdown (t):
while t:
mins, secs = divmod (t,60)
timer = '{:02d}:{:02d}'.format(mins, secs)
print(timer, end="\r")
time.sleep(1)
t-=1
print('Buffer available!')
camera = picamera.PiCamera()
camera.resolution = (640, 480)
stream = picamera.PiCameraCircularIO(camera, seconds = 5)
#code will work using h264 as format
camera.start_recording (stream, format = 'h264')
countdown(5)
motion_detected()
I don't have a Raspberry Pi, but I have an idea of how you can do it.
I used VideoStream which also supports PiCamera so you can use the below code.
stream = VideoStream(usePiCamera=False,
resolution=(640, 480),
framerate=32).start()
# Wait for a two-second for warming the webcam.
time.sleep(2.0)
Start getting frames
while True:
frame = stream.read()
countdown(5)
motion_detected()
Modify motion_detected() for saving frames.
while True:
frame = stream.read()
countdown(5)
motion_detected(frame)
Now we need to store frames either using array or dictionary.
Dictionary is faster than the array. (source)
We need to initialize a global dictionary on top of the project file.
import time
import datetime as dt
from imutils.video import VideoStream
dictionary = {}
count = 0
We need to modify the motion_detected method, we start by initializing the input parameter
# trigger event declaration
def motion_detected(input_frame):
Second, we define the global variables inside motion_detected
# trigger event declaration
def motion_detected(input_frame):
global dictionary
global count
Unfortunately, VideoStream object has no copy_to attribute, therefore I have to directly assign frame to the dictionary:
def motion_detected(input_frame):
global dictionary
global count
while True:
print("Trigger event(y)?")
trigger = input()
if trigger == "y":
current_time = return_current_time()
print("Buffering...")
# stream.copy_to(str(current_time) + '.h264')
dictionary[count] = input_frame
count += 1
if count == 10:
print("\n10 frames are stored\n")
else:
stream.stop()
break
Now we can perform certain operations like detecting edges.
while True:
frame = stream.read()
countdown(5)
motion_detected(frame)
for stored_frame in dictionary.values():
result = cv2.Canny(image=stored_frame,
threshold1=50,
threshold2=100)
Output:
Saving the frames
To save the frames, you need to enumerate over the stored frames.
for count, stored_frame in enumerate(dictionary.values()):
Then, apply your operation:
for count, stored_frame in enumerate(dictionary.values()):
result = cv2.Canny(image=stored_frame,
threshold1=50,
threshold2=100)
Save it to a folder.
for count, stored_frame in enumerate(dictionary.values()):
result = cv2.Canny(image=stored_frame,
threshold1=50,
threshold2=100)
cv2.imwrite("output/frame_{}.png".format(count), result)
If you want to loop through multiple times, the above code won't work. In this case, you need to iniitialize loop above the while loop.
counter = 0
while True:
frame = stream.read()
countdown(5)
motion_detected(frame)
for stored_frame in dictionary.values():
result = cv2.Canny(image=stored_frame,
threshold1=50,
threshold2=100)
cv2.imwrite("output/frame_{}.png".format(counter), result)
counter += 1
Code:
import cv2
import time
import datetime as dt
from imutils.video import VideoStream
dictionary = {}
count = 0
# obtain current time
def return_current_time():
return dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# trigger event declaration
def motion_detected(input_frame):
global dictionary
global count
while True:
print("Trigger event(y)?")
trigger = input()
if trigger == "y":
current_time = return_current_time()
print("Buffering...")
# stream.copy_to(str(current_time) + '.h264')
dictionary[count] = input_frame
count += 1
if count == 10:
print("\n10 frames are stored\n")
else:
stream.stop()
break
# countdown timer
def countdown(t):
while t:
mins, secs = divmod(t, 60)
timer = '{:02d}:{:02d}'.format(mins, secs)
print(timer, end="\r")
time.sleep(1)
t -= 1
print('Buffer available!')
stream = VideoStream(usePiCamera=False,
resolution=(640, 480),
framerate=32).start()
time.sleep(2.0)
counter = 0
while True:
frame = stream.read()
countdown(5)
motion_detected(frame)
for stored_frame in dictionary.values():
result = cv2.Canny(image=stored_frame,
threshold1=50,
threshold2=100)
cv2.imwrite("output/frame_{}.png".format(counter), result)
counter += 1
Related
I want to capture image and store it on my local system after every n seconds, I can not set frame(5) as I want video and detection to run completely. Currently my code is capturing image whenever a condition is failed.
def create_alert(self):
count = 0
cap = cv2.VideoCapture(0)
while cap.isOpened():
r,f = cap.read()
try:
info = ppe.detection(f)
x,y,w,h,label,conf = info[0]
if label == "lineman_fail":
# engine.say("Warning")
# engine.runAndWait()
ppe.take_screenshot(f,count)
count+=1
print(count)
print("Something wrong")
# cv2.imwrite("img_"+str(count)+".jpg",f)
except Exception as e:
print("_______-",e)
cv2.imshow("image",f)
if cv2.waitKey(1) & 0xFF == ord("q") :
break
cap.release()
cv2.destroyAllWindows()
def take_screenshot(self,frame,count):
prev = time.time()
cv2.imwrite("screen_shot/img_"+str(count)+".jpg",frame)
In order to capture image every n seconds try using datetime library, and find the difference between current time and elapsed time. Then use cv2's imwrite
while True:
current = time()
yolo_v4.delta += current - previous
previous = current
frame = camera.get_frame()
if yolo_v4.delta > 10:
ct=datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
cv2.imwrite("screen_shot/img_"+str(ct)+".jpg",frame)
at the moment I am reading an ip cameras live image by using the following code:
def livestream(self):
print("start")
stream = urlopen('http://192.168.4.1:81/stream')
bytes = b''
while True:
try:
bytes += stream.read(1024)
a = bytes.find(b'\xff\xd8')
b = bytes.find(b'\xff\xd9')
if a != -1 and b != -1:
jpg = bytes[a:b+2]
bytes = bytes[b+2:]
getliveimage = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
livestreamrotated1 = cv2.rotate(getliveimage, cv2.ROTATE_90_CLOCKWISE) #here I am rotating the image
print(type(livestreamrotated1)) #type at this point is <class 'numpy.ndarray'>
cv2.imshow('video',livestreamrotated1)
if cv2.waitKey(1) ==27: # if user hit esc
exit(0) # exit program
except Exception as e:
print(e)
print("failed at this point")
Now I want to integrate the result-image into Kivy-GUI and want to get rid of the while-loop since it freezes my GUI. Unfortunately the loop is necessary to recreate the image byte-by-byte. I would like to use cv2.VideoCapture instead and schedule this multiple times per second. This is not working at all, I am not able to capture the image from the live stream this way...where am I wrong?
cap = cv2.VideoCapture('http://192.168.4.1:81/stream?dummy.jpg')
ret, frame = cap.read()
cv2.imshow('stream',frame)
I read in some other post that a file-ending like "dummy.jpg" would be necessary at this point, but it is still not working, the program freezes.
Please help. Thank you in advance!
If you want to decouple your reading loop from your GUI loop you can use multithreading to separate the code. You can have a thread running your livestream function and dumping the image out to a global image variable where your GUI loop can pick it up and do whatever to it.
I can't really test out the livestream part of the code, but something like this should work. The read function is an example of how to write a generic looping function that will work with this code.
import cv2
import time
import threading
import numpy as np
# generic threading class
class Reader(threading.Thread):
def __init__(self, func, *args):
threading.Thread.__init__(self, target = func, args = args);
self.start();
# globals for managing shared data
g_stop_threads = False;
g_lock = threading.Lock();
g_frame = None;
# reads frames from vidcap and stores them in g_frame
def read():
# grab globals
global g_stop_threads;
global g_lock;
global g_frame;
# open vidcap
cap = cv2.VideoCapture(0);
# loop
while not g_stop_threads:
# get a frame from camera
ret, frame = cap.read();
# replace the global frame
if ret:
with g_lock:
# copy so that we can quickly drop the lock
g_frame = np.copy(frame);
# sleep so that someone else can use the lock
time.sleep(0.03); # in seconds
# your livestream func
def livestream():
# grab globals
global g_stop_threads;
global g_lock;
global g_frame;
# open stream
stream = urlopen('http://192.168.4.1:81/stream')
bytes = b''
# process stream into opencv image
while not g_stop_threads:
try:
bytes += stream.read(1024)
a = bytes.find(b'\xff\xd8')
b = bytes.find(b'\xff\xd9')
if a != -1 and b != -1:
jpg = bytes[a:b+2]
bytes = bytes[b+2:]
getliveimage = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
livestreamrotated1 = cv2.rotate(getliveimage, cv2.ROTATE_90_CLOCKWISE) #here I am rotating the image
# acquire lock and replace image
with g_lock:
g_frame = livestreamrotated1;
# sleep to allow other threads to get the lock
time.sleep(0.03); # in seconds
except Exception as e:
print(e)
print("failed at this point")
def main():
# grab globals
global g_stop_threads;
global g_lock;
global g_frame;
# start a thread
# reader = Reader(read);
reader = Reader(livestream);
# show frames from g_frame
my_frame = None;
while True:
# grab lock
with g_lock:
# show
if not g_frame is None:
# copy # we copy here to dump the lock as fast as possible
my_frame = np.copy(g_frame);
# now we can do all the slow manipulation / gui stuff here without the lock
if my_frame is not None:
cv2.imshow("Frame", my_frame);
# break out if 'q' is pressed
if cv2.waitKey(1) == ord('q'):
break;
# stop the threads
g_stop_threads = True;
if __name__ == "__main__":
main();
I have a function that replay some steps from a .json file and another fuction which record those steps. I need the recordingScreen function to stop once the playActions function finishes simultaneously (by using the flag) and create a video for each iteration, but it only creates the video for the last file (iteration)
I have tried with a flag that changes from false when the playActions function finishes I have also tried with queue from this example link and using using a threadsafe threading.Event() from this example link. But as I am a beginner I have not been able to implement any of them correctly within my code, which is as follow:
files= ["actions_test_10-07-2020_15-56-43.json", "actions_test_10-08-2020_14-59-00.json"]
date = datetime.today().strftime("%m-%d-%Y_%H-%M-%S")
Stop_recording = False
def main():
initializePyAutoGUI()
countdownTimer()
for i in range(len(files)):
global Stop_recording
Stop_recording = False
t1 = threading.Thread(target=playActions, args=[files[i]])
t2 = threading.Thread(target=recordScreen)
t1.start()
t2.start()
t1.join()
t2.join()
print("Done")
def recordScreen():
output = '{}.avi'.format(date)
img = pyautogui.screenshot()
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
# get info from img
height, width, channels = img.shape
# Define the codec and create VideoWriter object
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output, fourcc, 20.0, (width, height))
while not Stop_recording:
img = pyautogui.screenshot()
image = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
out.write(image)
StopIteration(0.5)
out.release()
cv2.destroyAllWindows()
def playActions(filename):
# Read the file
script_dir = os.path.dirname(__file__)
filepath = os.path.join(script_dir, 'recordings', filename)
with open(filepath, 'r') as jsonfile:
# parse the json
data = json.load(jsonfile)
# loop over each action
# Because we are not waiting any time before executing the first action, any delay before the initial
# action is recorded will not be reflected in the playback.
for index, action in enumerate(data):
action_start_time = time()
# look for escape input to exit
if action['button'] == 'Key.esc':
break
# perform the action
if action['type'] == 'keyDown':
key = convertKey(action['button'])
pyautogui.keyDown(key)
print("keyDown on {}".format(key))
elif action['type'] == 'keyUp':
key = convertKey(action['button'])
pyautogui.keyUp(key)
print("keyUp on {}".format(key))
elif action['type'] == 'click' and action['button'] == "Button.right":
pyautogui.rightClick(action['pos'][0], action['pos'][1], duration=0.25)
print("right click on {}".format(action['pos']))
elif action['type'] == 'click' and action['button'] == "Button.left":
# Check if the period between clicks is short and perform a double click then, otherwise
# it performs a single click
if index > 0:
if (data[index]['time']) - (data[index - 1]['time']) < 0.5:
pyautogui.doubleClick(action['pos'][0], action['pos'][1])
print("Double click on {}".format(action['pos']))
pyautogui.leftClick(action['pos'][0], action['pos'][1], duration=0.25)
print("left click on {}".format(action['pos']))
# then sleep until next action should occur
try:
next_action = data[index + 1]
except IndexError:
# this was the last action in the list
break
elapsed_time = next_action['time'] - action['time']
# if elapsed_time is negative, that means our actions are not ordered correctly. throw an error
if elapsed_time < 0:
raise Exception('Unexpected action ordering.')
# adjust elapsed_time to account for our code taking time to run
elapsed_time -= (time() - action_start_time)
if elapsed_time < 0:
elapsed_time = 0
print('sleeping for {}'.format(elapsed_time))
sleep(elapsed_time)
global Stop_recording
Stop_recording = True
The data was received by socket ,with no more shell , they are pure I P B frames begin with NAL Header(something like 00 00 00 01). I am now using pyav to decode the frames ,but i can only decode the data after the second pps info(in key frame) was received(so the chunk of data I send to my decode thread can begin with pps and sps ), otherwise the decode() or demux() will return error "non-existing PPS 0 referenced decode_slice_header error" .
I want to feed data to a sustaining decoder which can remember the previous P frame , so after feeding one B frame, the decoder return a decoded video frame. Or someform of IO that can be opened as container and keep writing data into it by another thread.
Here is my key code:
#read thread... read until get a key frame, then make a new io.BytesIO() to store the new data.
rawFrames = io.BytesIO()
while flag_get_keyFrame:()
....
content= socket.recv(2048)
rawFrames.write(content)
....
#decode thread... decode content between two key frames
....
rawFrames.seek(0)
container = av.open(rawFrames)
for packet in container.demux():
for frame in packet.decode():
self.frames.append(frame)
....
My code will play the video but with a 3~4 seconds delay. So I am not putting all of it here, because I know it's not actually working for what I want to achieve.
I want to play the video after receiving the first key frame and decode the following frames right after receiving them . Pyav opencv ffmpeg or something else ,how can I achieve my goal?
After hours of finding an answer for this as well. I figure this out myself.
For single thread, you can do the following:
rawData = io.BytesIO()
container = av.open(rawData, format="h264", mode='r')
cur_pos = 0
while True:
data = await websocket.recv()
rawData.write(data)
rawData.seek(cur_pos)
for packet in container.demux():
if packet.size == 0:
continue
cur_pos += packet.size
for frame in packet.decode():
self.frames.append(frame)
That is the basic idea. I have worked out a generic version that has receiving thread and decoding thread separated. The code will also skip frames if the CPU does not keep up with the decoding speed and will start decoding from the next key frame (so you will not have the teared green screen effect). Here is the full version of the code:
import asyncio
import av
import cv2
import io
from multiprocessing import Process, Queue, Event
import time
import websockets
def display_frame(frame, start_time, pts_offset, frame_rate):
if frame.pts is not None:
play_time = (frame.pts - pts_offset) * frame.time_base.numerator / frame.time_base.denominator
if start_time is not None:
current_time = time.time() - start_time
time_diff = play_time - current_time
if time_diff > 1 / frame_rate:
return False
if time_diff > 0:
time.sleep(time_diff)
img = frame.to_ndarray(format='bgr24')
cv2.imshow('Video', img)
return True
def get_pts(frame):
return frame.pts
def render(terminated, data_queue):
rawData = io.BytesIO()
cur_pos = 0
frames_buffer = []
start_time = None
pts_offset = None
got_key_frame = False
while not terminated.is_set():
try:
data = data_queue.get_nowait()
except:
time.sleep(0.01)
continue
rawData.write(data)
rawData.seek(cur_pos)
if cur_pos == 0:
container = av.open(rawData, mode='r')
original_codec_ctx = container.streams.video[0].codec_context
codec = av.codec.CodecContext.create(original_codec_ctx.name, 'r')
cur_pos += len(data)
dts = None
for packet in container.demux():
if packet.size == 0:
continue
dts = packet.dts
if pts_offset is None:
pts_offset = packet.pts
if not got_key_frame and packet.is_keyframe:
got_key_frame = True
if data_queue.qsize() > 8 and not packet.is_keyframe:
got_key_frame = False
continue
if not got_key_frame:
continue
frames = codec.decode(packet)
if start_time is None:
start_time = time.time()
frames_buffer += frames
frames_buffer.sort(key=get_pts)
for frame in frames_buffer:
if display_frame(frame, start_time, pts_offset, codec.framerate):
frames_buffer.remove(frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
if dts is not None:
container.seek(25000)
rawData.seek(cur_pos)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
terminated.set()
cv2.destroyAllWindows()
async def receive_encoded_video(websocket, path):
data_queue = Queue()
terminated = Event()
p = Process(
target=render,
args=(terminated, data_queue)
)
p.start()
while not terminated.is_set():
try:
data = await websocket.recv()
except:
break
data_queue.put(data)
terminated.set()
Its normal getting 3~4 seconds delay because you are reading encoded data and decoding it takes time via on CPU.
If you have GPU hardware, you can use FFMPEG to decode H264 by GPU. Here is an example.
If you don't have a GPU, decoding H264 on CPU always will cause delays. You can use FFMPEG for effective decoding but this will also decrease total delay almost 10%
I made this script:
import cv2
import pyaudio
import wave
import threading
import time
import subprocess
import os
import keyboard
class VideoRecorder():
# Video class based on openCV
def __init__(self):
self.open = True
self.fps = 6 # fps should be the minimum constant rate at which the camera can
self.fourcc = "MJPG" # capture images (with no decrease in speed over time; testing is required)
self.frameSize = (640,480) # video formats and sizes also depend and vary according to the camera used
self.video_filename = "temp_video.avi"
self.video_cap = cv2.VideoCapture(0)
self.video_writer = cv2.VideoWriter_fourcc(*self.fourcc)
self.video_out = cv2.VideoWriter(self.video_filename, self.video_writer, self.fps, self.frameSize)
self.frame_counts = 1
self.start_time = time.time()
# Video starts being recorded
def record(self):
timer_start = time.time()
timer_current = 0
while(self.open==True):
ret, video_frame = self.video_cap.read()
if self.frame_counts > 10:
break
if (ret==True):
self.video_out.write(video_frame)
self.frame_counts += 1
print self.frame_counts
time.sleep(0.16)
else:
#threading.Thread(target=self.stop).start()
break
# 0.16 delay -> 6 fps
time.sleep(1)
self.video_out.release()
cv2.VideoCapture(0).release()
cv2.destroyAllWindows()
dwhuiadhuiahdwia = raw_input("Testtidhwuia?")
# Finishes the video recording therefore the thread too
def stop(self):
print "You made it"
if self.open==True:
self.open=False
self.video_out.release()
self.video_cap.release()
cv2.destroyAllWindows()
hduwahduiwahdiu = raw_input("Press enter to continue...")
else:
pass
# Launches the video recording function using a thread
def start(self):
video_thread = threading.Thread(target=self.record)
video_thread.start()
def start_video_recording():
global video_thread
video_thread = VideoRecorder()
video_thread.start()
def stop_AVrecording():
frame_counts = video_thread.frame_counts
elapsed_time = time.time() - video_thread.start_time
recorded_fps = frame_counts / elapsed_time
print "total frames " + str(frame_counts)
print "elapsed time " + str(elapsed_time)
print "recorded fps " + str(recorded_fps)
video_thread.stop()
# Makes sure the threads have finished
time.sleep(1)
video_thread.stop()
print ".."
start_video_recording()
duiwhaiudhwauidhwa = raw_input("hello")
It should record a video with the camera for about 10 seconds, then save it and then turn off the camera and close the script when the user presses enter.
But it doesn't really work.
It will record a video, and it does save the video but the camera only turns off when I close the script (the camera is on, but isn't recording.)
I know this because the led next to my camera doesn't turn off when I'm prompted to press enter to continue, but does when I press enter and the script closes.
I found this, and I haven't tested it yet. But if I were to use that solution and it worked, I'd have to do it on every computer I run the script on manually.