Python function to read video and convert to frames - python

I want to convert my input video to a set of frames. I have read the post on
Python - Extracting and Saving Video Frames.
But I would like to have a function where I can insert the video as the parameter, not the location of the video file.
In the VideoCapture function below, it takes in the location of the video file.
import cv2
def vidtoframes(videoFile):
vidcap = cv2.VideoCapture(videoFile)
success,image = vidcap.read()
count = 0
while success:
cv2.imwrite("frame%d.jpg" % count, image) # save frame as JPEG file
success,image = vidcap.read()
print('Read a new frame: ', success)
count += 1
But is there a function or way to pass a video to the method and convert it to array of frames without saving anything onto the disk.

The video that is taken must be saved to a directory and then we can perform the functions upon it. That is how an application would work as well.

OO approch: write a sort of "VideoWrapper" class and derived classes:
from abc import abstractmethod
class VideoWrapper:
def __init__(self, path: str, chunk_size: int = 1):
self.path = path
self.chunk_size = chunk_size
#abstractmethod
def __iter__(self): ...
class VideoFileWrapper(VideoWrapper):
def __iter__(self):
chunk = []
cap = cv2.VideoCapture(self.path)
while cap.isOpened():
ret, frame = cap.read()
chunk.append(frame)
if len(chunk) == self.chunk_size:
yield chunk
chunk = []
class VideoFolderWrapper(VideoWrapper):
def __iter__(self):
chunk = []
for frame_path in glob(os.path.join(self.path, '*')):
frame = cv2.imread(frame_path)
chunk.append(frame)
if len(chunk) == self.chunk_size:
yield chunk
chunk = []
in that case you could pass a single class type across your code.
an even nicer class would implement __enter__ and __exit__ methods in order to use the with statement, exception handling and so on. that might be too much, a simpler "Pythonic" version will be:
def video_wrapper(path):
if os.path.isdir(path):
frames = list(glob(os.path.join(path, '*.png')))
frames.sort(key=file_name_order)
for frame_path in frames:
frame = cv2.cvtColor(cv2.imread(frame_path),cv2.COLOR_BGR2RGB)
yield frame
elif os.path.exists(path):
cap = cv2.VideoCapture(path)
while cap.isOpened():
ret, frame = cap.read()
yield frame

Yes! There is! But it will require a wee bit of setup. Everything is detailed here:
Streaming video in memory with OpenCV VideoWriter and Python BytesIO
The basics are that you need a tmpfs partition in linux, and utilize the tempfile functionality of Python (which just wraps mkstemp in linux again).
If you have a video file in memory already, something like:
video_bytes = s3.download('file.avi')
And just want to deal with it in memory (and continue to use OpenCV), then check out the other post I listed above.

Related

Writing and Reading video without saving in opencv

I want to read a video after I write it with cv2.VideoWriter without saving the video.
For example:
video = cv2.VideoWriter('using.mp4', cv2.VideoWriter_fourcc(*'MJPG'), 10, size)
Now, after writing this cv2.VideoWriter object, is it possible to read it likevideo.read(), but since read() is a function of cv2.VideoCapture and it will throw an error
Exception has occurred: AttributeError
'cv2.VideoWriter' object has no attribute 'read'
So, is there possible way of reading the cv2.VideoWriter?
An alternative to reading frames from the video writer, is to save the frames in a list instead of saving each frame in the the loop. when you finished, you can write them outside the loop and have the save affect as video.read()
video = cv2.VideoWriter('using.mp4', cv2.VideoWriter_fourcc(*'MJPG'), 10, size)
for frame in frames:
writer.write(frame)
for frame in frames:
# do other stuff here
detailed example (Notice i changed the fourcc - your example didnt work for me)
import cv2
def cam_test(port: int = 0) -> None:
frames = []
cap = cv2.VideoCapture(port)
if not cap.isOpened(): # Check if the web cam is opened correctly
print("failed to open cam")
else:
print('cam opened on port {}'.format(port))
for i in range(10 ** 10):
success, cv_frame = cap.read()
if not success:
print('failed to capture frame on iter {}'.format(i))
break
frames.append(cv_frame)
cv2.imshow('Input', cv_frame)
k = cv2.waitKey(1)
if k == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
# Now you have the frames at hand
if len(frames) > 0:
# if you want to write them
size = (frames[0].shape[1], frames[0].shape[0])
video = cv2.VideoWriter(
filename='using.mp4',
fourcc=cv2.VideoWriter_fourcc(c1='m', c2='p', c3='4', c4='v'),
fps=10,
frameSize=size
)
for frame in frames:
video.write(frame)
# and to answer your question, you wanted to do video.read() which would have gave you frame by frame
for frame in frames:
pass # each iteration is like video.read() if video.read() was possible
return
if __name__ == '__main__':
cam_test()

Can't write frames to a video with multiprocessing + cv2

I have a code which breaks down a video into frames and edits the image and puts it back into a video, but I am realizing that it's really slow... So I looked into multiprocessing for speeding up the code, and it works! As I can see it processes the images much faster, but the problem is, when I add those frames to a new video, it doesn't work, the video remains empty!
Here is my code:
# Imports
import cv2, sys, time
import numpy as np
from scipy.ndimage import rotate
from PIL import Image, ImageDraw, ImageFont, ImageOps
import concurrent.futures
def function(fullimg):
img = np.array(Image.fromarray(fullimg).crop((1700, 930, 1920-60, 1080-80)))
inpaintRadius = 10
inpaintMethod = cv2.INPAINT_TELEA
textMask = cv2.imread('permanentmask.jpg', 0)
final_result = cv2.inpaint(img.copy(), textMask, inpaintRadius, inpaintMethod)
text = Image.fromarray(np.array([np.array(i) for i in final_result]).astype(np.uint8)).convert('RGBA')
im = np.array([[tuple(x) for x in i] for i in np.zeros((70, 160, 4))])
im[1:-1, 1:-1] = (170, 13, 5, 40)
im[0, :] = (0,0,0,128)
im[1:-1, [0, -1]] = (0,0,0,128)
im[-1, :] = (0,0,0,128)
im = Image.fromarray(im.astype(np.uint8))
draw = ImageDraw.Draw(im)
font = ImageFont.truetype('arialbd.ttf', 57)
draw.text((5, 5),"TEXT",(255,255, 255, 128),font=font)
text.paste(im, mask=im)
text = np.array(text)
fullimg = Image.fromarray(fullimg)
fullimg.paste(Image.fromarray(text), (1700, 930, 1920-60, 1080-80))
fullimg = cv2.cvtColor(np.array(fullimg), cv2.COLOR_BGR2RGB)
return fullimg
cap = cv2.VideoCapture('before2.mp4')
_fourcc = cv2.VideoWriter_fourcc(*'MPEG')
out = cv2.VideoWriter('after.mp4', _fourcc, 29.97, (1280,720))
frames = []
lst = []
while cap.isOpened():
ret, fullimg = cap.read()
if not ret:
break
frames.append(fullimg)
if len(frames) >= 8:
if __name__ == '__main__':
with concurrent.futures.ProcessPoolExecutor() as executor:
results = executor.map(function, frames)
for i in results:
print(type(i))
out.write(i)
frames.clear()
cap.release()
out.release()
cv2.destroyAllWindows() # destroy all opened windows
My code inpaints a watermark and adds another watermark using PIL.
If I don't use multiprocessing the code works. But if I do use multiprocessing, it gives an empty video.
I am not that familiar with OpenCV, but there seems to be a few things that should be corrected in your code. First, if you are running under Windows, as you appear to be because you have if __name__ == '__main__': guarding the code that creates new processes (by the way, when you tag a question with multiprocessing, you should also tag the question with the platform being used), then any code at global scope will be executed by every process created to implement your pool. That means you should move if __name__ == '__main__': as follows:
if __name__ == '__main__':
cap = cv2.VideoCapture('before2.mp4')
_fourcc = cv2.VideoWriter_fourcc(*'MPEG')
out = cv2.VideoWriter('after.mp4', _fourcc, 29.97, (1280,720))
frames = []
lst = []
while cap.isOpened():
ret, fullimg = cap.read()
if not ret:
break
frames.append(fullimg)
if len(frames) >= 8:
with concurrent.futures.ProcessPoolExecutor() as executor:
results = executor.map(function, frames)
for i in results:
print(type(i))
out.write(i)
frames.clear()
cap.release()
out.release()
cv2.destroyAllWindows() # destroy all opened windows
If you do not do this it seems to me that every sub-process in the pool will first attempt in parallel to create an empty video (the function worker function and out.write will never be called by these processes) and only then will the main process be able to invoke the function worker function using map. This doesn't quite explain why the main process doesn't succeed after all of these wasteful attempts. But...
You also have:
while cap.isOpened():
The documentations states that isOpened() returns True if the previous VideoCapture constructor succeeded. Then if this returns True once, why wouldn't it return True the next time it is tested and you end up looping indefinitely? Shouldn't the while be changed to an if? And doesn't this suggest that isOpened() is perhaps returning False or else you would be looping indefinitely? Or what if len(frames) < 8? It seems then you would also end up with an empty output file.
My suggestion would be to make the above changes and try again.
Update
I took a closer look at the code more closely and it appears that it is looping reading the input (before2.mp4) one frame at a time and when it has accumulated 8 frames or more it creates a pool and processes the frames it has accumulated and writing them out to the output (after.mp4). But that means that if there are, for example, 8 more frames, it will create a brand new processing pool (very wasteful and expensive) and then write out the 8 additional processed frames. But if there were only 7 additional frames, they would never get processed and written out. I would suggest the following code (untested, of course):
def main():
import os
cap = cv2.VideoCapture('before2.mp4')
if not cap.isOpened():
return
_fourcc = cv2.VideoWriter_fourcc(*'MPEG')
out = cv2.VideoWriter('after.mp4', _fourcc, 29.97, (1280,720))
FRAMES_AT_A_TIME = 8
pool_size = min(FRAMES_AT_A_TIME, os.cpu_count())
with concurrent.futures.ProcessPoolExecutor(max_workers=pool_size) as executor:
more_frames = True
while more_frames:
frames = []
for _ in range(FRAMES_AT_A_TIME):
ret, fullimg = cap.read()
if not ret:
more_frames = False
break
frames.append(fullimg)
if not frames:
break # no frames
results = executor.map(function, frames)
for i in results:
print(type(i))
out.write(i)
cap.release()
out.release()
cv2.destroyAllWindows() # destroy all opened windows
if __name__ == '__main__':
main()

Python + OpenCV + Base64: Issue with converting frame to base64

I am trying to convert a video to frames and those frames to base64 strings. I am unable to do so and getting some exceptions. Below is my code:
import cv2
import base64
def footage_to_frame(video):
vidcap = cv2.VideoCapture(video)
success, frames = vidcap.read()
if success:
return frames
def frame_to_base64(frames):
with frames as frame:
frame_b64 = base64.b64encode(frame.read())
return frame_b64
The function calls to this method are:
frames = converter.footage_to_frame("/Users/myname/Desktop/video.mp4")
converter.frame_to_base64(frames)
Below is the error I get in console:
File "/Users/myname/Desktop/Test/src/service/converter.py", line 13, in frame_to_base64
with frames as frame:
AttributeError: __enter__
In function frame_to_base64(frames), frames is already a single image because VideoCapture.read returns a single image. Also it is a opencv image (numpy array) which is not something you can use "with" on.
def frame_to_base64(frame):
return base64.b64encode(frame)
If you want to read all frames of video, you should do something like:
import cv2
import base64
def footage_to_frame(video):
vidcap = cv2.VideoCapture(video)
frames = []
# read until no more frames exist in the video
while True:
success, frame = vidcap.read()
if (success):
frames.append(frame)
else:
# unable to read a frame
break
return frames
def frames_to_base64(frames):
frames_b64 = []
# iterate frames and convert each of them to base64
for frame in frames:
frames_b64.append(base64.b64encode(frame))
return frames_b64
Although depending on video length, you may experience memory problems.

OpenCV overwrites the previous video I have saved. Python 3

import cv2, time
import numpy as np
I want to make this code not overwrite the previously saved video
video = cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*"XVID")
This line of code makes the video save as output.avi all the time while I want to make it so that it saves the date of that day
out = cv2.VideoWriter('output.avi', fourcc, 20.0, (640,480))
a = 0
while True:
a = a + 1
check, frame = video.read()
print(check)
print(frame)
out.write(frame)
cv2.imshow("Capturing", frame)
I feel as if I used an if statement somewhere around here then it could make the video no overwrite the previous video however I have tried but it did not work
key = cv2.waitKey(1)
if key == ord('q') :
break
print(a)
video.release()
out.release()
cv2.destroyAllWindows()
I also want to make this code save the video as the current date of day if that is possible
You simply format the filename and put the date in it:
import datetime
filename = 'output_{0}.avi'.format(datetime.datetime.now().strftime("%Y-%m-%d"))
print (filename)
Output:
output_2018-02-25.avi
Then you supply it to your video-create method:
out = cv2.VideoWriter(filename, fourcc, 20.0, (640,480))
Look here for formatting tips for datetime.strftime
Probably best to put it inside a function that gives you the correct name, also checking if that file already exists and eventually adding a running number as well:
import datetime
import os
def getAviNameWithDate(nameIn="output.avi"):
"""Needs a file ending on .avi, inserts _<date> before .avi.
If file exists, it appends a additional _number after the <date>
ensuring filename uniqueness at this time."""
if not nameIn.endswith(".avi"):
raise ValueError("filename must end on .avi")
filename = nameIn.replace(".avi","_{0}.avi")
.format(datetime.datetime.now().strftime("%Y-%m-%d"))
if os.path.isfile(filename): # if already exists
fn2 = filename[0:-4]+'_{0}.avi' # modify pattern to include a number
count = 1
while os.path.isfile(fn2.format(count)): # increase number until file not exists
count += 1
return fn2.format(count) # return file with number in it
else: # filename ok, return it
return filename
# test it
for _ in range(5):
with open(getAviNameWithDate("a.avi"),"w") as w:
w.write("1")
Execute the snippit to see how it creates:
a_2018-02-25.avi
a_2018-02-25_1.avi
a_2018-02-25_2.avi
a_2018-02-25_3.avi
a_2018-02-25_4.avi

Python two functions threading

I want to thread two functions, first function streaming a video and passing frames to second function, second function reading frames with Optical Character Recognition and converting frames to text. The question how to pass frames from first threaded function to second threaded function?
What I have done already, with first function saving video frames to local file 'frame.jpg' and at the same time reading with second function from 'frame.jpg'. Is it possible to define video frames as global variable and pass to reading function?
import cv2
import pytesseract
from multiprocessing import Process
def video_straming(): #Video streaming function, First Function
vc = cv2.VideoCapture(0)
cv2.namedWindow("preview")
if vc.isOpened():
rval, frame = vc.read()
else:
rval = False
while rval:
rval, frame = vc.read()
cv2.imwrite('frame.jpg',frame)
key = cv2.waitKey(20)
if key == 27: # exit on ESC
break
cv2.destroyWindow("preview")
def reading(): #Reading from frame.jpg function, Second Function
while:
frame = cv2.imread('frame.jpg')
read = Image.fromarray(frame)
read = pytesseract.image_to_string(read)
if len(read) > 80:
break
if __name__ == '__main__':
video_stream = Process(target=video_streaming)
video_stream.start()
frame_read = Process(target=reading)
frame_read.start()
video_stream.join()
frame_read.join()
Hope this answer can still be of some use.
I use multiprocessing.Pipe() to pass video frames from one processes to another with cv2.VideoCapture() to capture frames and write each image to the Pipe.
import multiprocessing
multiprocessing.set_start_method('spawn')
video_outfrompipe, video_intopipe = multiprocessing.Pipe()
vs = multiprocessing.Process(target=VideoSource, args=(video_intopipe))
vs.start()
vc = multiprocessing.Process(target=VideoConsumer, args=(video_outfrompipe))
vc.start()
vs.join()
vc.join()

Categories

Resources