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.
Related
I am writing a script to encrypt and decrypt video using RSA algo in python. Now I have extracted the frames from the video and encrypt each image individually and then combining the images to create a video. Then I am reading the frames of the encrypted video again and when I am applying the decryption key I am not getting back the original image. But when I am applying the same key on any image with which the video is made I am getting back the original image. let us say we have image1 which is encrypted and will be used to make the encrypted video when I am applying the key on this image I am getting back the original image. now I have image2 which is read from the encrypted video and if the keys are applied then it is giving a more encrypted image. Heres the code :
import cv2
import numpy
import os
import imageio
import time
from tkinter.filedialog import askopenfilename
from tkinter.ttk import *
from tkinter import *
from tkinter import filedialog
from tqdm import tqdm
from tkinter import messagebox
import subprocess
def load_image_decrypt(folder):
videofile = 'envid.avi'
try:
if not os.path.exists('Dedata'):
os.makedirs('Dedata')
except OSError:
messagebox.showinfo('Error Occured', 'Error: Creating directory of decrypted data')
vid_to_image(videofile)
for filename1 in tqdm(os.listdir(folder)):
imgV = imageio.imread(os.path.join(folder, filename1), format='PNG-FI')
if imgV is not None:
RGBdecryption(imgV, filename1)
else:
break
vidname = 'devid.avi'
image_to_vid(dedata2, vidname)
messagebox.showinfo('Finish!', 'Decryption Done succesfully!')
def RGBdecryption(img, filename):
img1 = img
img = img.astype(numpy.uint16)
img1= img1.tolist()
for i1 in tqdm(range(len(img1))):
for j1 in (range(len(img1[i1]))):
for k1 in (range(len(img1[i1][j1]))):
x1 = img1[i1][j1][k1]
x1 = pow(x1,16971,25777)
img1[i1][j1][k1] = x1
img1 = numpy.array(img1).astype(numpy.uint16)
name = './Dedata/'+str(filename)
imageio.imwrite(name, img1, format='PNG-FI')
def vid_to_image(filename):
# Playing video from file:
cap = cv2.VideoCapture(filename)
try:
if not os.path.exists('data'):
os.makedirs('data')
messagebox.showinfo('Info!', 'Data directory is created where the frames are stored')
except OSError:
print ('Error: Creating directory of data')
currentFrame = 0
while(True):
# Capture frame-by-frame
ret, frame = cap.read()
if not ret:
break
# Saves image of the current frame in jpg file
name = './data/frame' + str(currentFrame) + '.png'
print ('Creating...' + name)
imageio.imwrite(name, frame,format='PNG-FI')
# To stop duplicate images
currentFrame += 1
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
def image_to_vid(folder, vidname): #the code which is creating a video out of images stored in the folder
image_folder = folder
video_name = vidname
sort_image = []
images = [img for img in os.listdir(image_folder) if img.endswith(".png")]
print(images)
print('\n\n')
for i in range(0,1000):
for j in range(len(images)):
name = 'frame' + str(i) + '.png'
if ((str(images[j])) == str(name)):
sort_image.append(images[j])
print(sort_image)
frame = cv2.imread(os.path.join(image_folder, sort_image[0]))
height, width, layers = frame.shape
video = cv2.VideoWriter(video_name, 0, 29, (width,height)) #29 is the fs of the original video and I don't know what the 0 is for
for image in sort_image:
video.write(cv2.imread(os.path.join(image_folder, image)))
cv2.destroyAllWindows()
video.release()
data = './data'
load_image_decrypt(data)
I do not know where I am getting it wrong. I am new to opencv and video processing. Any help will be appreciated. Thank you.
Video frames are subject to lossy compression. So you cannot feed a codec some binary data under the guise of images, encode it and expect to get exactly the same binary data back when playing the resulting video.
Your best bet is to encrypt the video file as a whole as per Encryption of video files? or How can I Encrypt Video in Real Time?. It will need to be decrypted to be playable; this is apparently what OSX's "content protection" does, encrypting and decrypting data transparently.
A (paywalled) IEEE article Video Encryption Based on OpenCV - IEEE Conference Publication says they applied an Arnold Transform to image data. It is a transposition cipher and as such, can be broken. Its main strength seems to rather be that it makes content unintelligible in regular playback, and it preserves image characteristics critical for video codecs (lighting, frame differences) and doesn't require the exact ciphertext for decryption, so it's not damaged beyond repair by the lossy compression.
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.
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()
When running the examples from the OpenCV video processing python tutorials, they all pop up in a dedicated window. I know that the IPython notebook can display videos from disk and YouTube, so I wonder if there is a way to direct the OpenCV video playback to the Notebook browser and have it play in the output cell instead of a separate window (preferably without saving it to disk and then playing it from there).
Below is the code from the OpenCV tutorial.
import cv2
cap = cv2.VideoCapture('/path/to/video')
while(True):
# Capture frame-by-frame
ret, frame = cap.read()
# Our operations on the frame come here
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Display the resulting frame
cv2.imshow('frame',gray)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
To make the display faster use only IPython.display.display inside the notebook and JPG format instead of PNG. (Note displaying with cv2.imshow natively outside the notebook is much faster, but this is not what the question asks for):
The code below will test all the supported file formats to find the fastest one (extracted from __doc__ with a regex, not reliable)
from IPython.display import clear_output, Image, display, HTML
import cv2
# Read one frame from the camera for testing
video = cv2.VideoCapture(0)
_, frame = video.read()
video.release()
import re
from timeit import timeit
import math
extensions=re.findall(r"\\\*(\.\w*)", cv2.imread.__doc__)
def test(extension):
try:
totalTime=0
numTry=3
for _ in range(numTry):
totalTime+=timeit(lambda: display(Image(data=cv2.imencode(extension, frame)[1])), number=1)
clear_output(wait=True)
return totalTime/numTry, extension
except cv2.error as e: #usually "unsupported file type"
return (math.inf, extension, e)
for x in sorted(
[test(extension) for extension in extensions], key=lambda x: x[0]
): print(x)
In my case, .jpeg is the fastest. Make sure that the browser display also support that extension:
Image(data=cv2.imencode(".jpeg", frame)[1].tobytes())
Then, to play the video:
import cv2
from IPython.display import display, Image
video = cv2.VideoCapture(0)
display_handle=display(None, display_id=True)
try:
while True:
_, frame = video.read()
frame = cv2.flip(frame, 1) # if your camera reverses your image
_, frame = cv2.imencode('.jpeg', frame)
display_handle.update(Image(data=frame.tobytes()))
except KeyboardInterrupt:
pass
finally:
video.release()
display_handle.update(None)
update is a little faster than clear_output + display every time; however compare to the rendering it isn't a significant improvement.
You can do it with Bokeh and probably it is a bit faster.
from bokeh.plotting import figure
from bokeh.io import output_notebook, show, push_notebook
import cv2
import time
output_notebook()
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
frame=cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # because Bokeh expects a RGBA image
frame=cv2.flip(frame, -1) # because Bokeh flips vertically
width=frame.shape[1]
height=frame.shape[0]
p = figure(x_range=(0,width), y_range=(0,height), output_backend="webgl", width=width, height=height)
myImage = p.image_rgba(image=[frame], x=0, y=0, dw=width, dh=height)
show(p, notebook_handle=True)
while True:
ret, frame = cap.read()
frame=cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
frame=cv2.flip(frame, -1)
myImage.data_source.data['image']=[frame]
push_notebook()
time.sleep(0.3)
The video encoded data (if in a format the browser can decode, eg. h264-encoded in ISO mp4 container) can be displayed using an HTML <video> tag and IPython.core.display.HTML(), this will provide standard playback performance.
The <video> can be a link, or have embedded base64'ed data (the latter is what matplotlib.animation does, for example), and its data can of course be generated in your notebook, using OpenCV (eg. VideoWriter).
Yes. But it will be slooowwww....
Code with Python 3 and OpenCV 3.3 that reads from webcam (from file, just change cv2.VideoCapture("filename.mp4")):
from IPython.display import clear_output, Image, display, HTML
import numpy as np
import cv2
import base64
def arrayShow (imageArray):
ret, png = cv2.imencode('.png', imageArray)
encoded = base64.b64encode(png)
return Image(data=encoded.decode('ascii'))
video = cv2.VideoCapture(0)
while(True):
try:
clear_output(wait=True)
_, frame = video.read()
lines, columns, _ = frame.shape
frame = cv2.resize(frame, (int(columns/4), int(lines/4)))
img = arrayShow(frame)
display(img)
except KeyboardInterrupt:
video.release()
You may need to change IOPub data rate limit.
You can change this in your .jupyter config or just run
jupyter notebook --NotebookApp.iopub_data_rate_limit=1000000000
The keyboard interrupt doesn't work properly, though.
from IPython.display import clear_output, Image, display
import ipywidgets
import cv2
video = cv2.VideoCapture(0)
display_handle=display(None, display_id=True)
image_widget = ipywidgets.Image(format='jpeg')
while True:
try:
clear_output(wait=True)
_, frame = video.read()
lines, columns, _ = frame.shape
frame = cv2.resize(frame, (int(columns/4), int(lines/4)))
image_widget.value =cv2.imencode('.jpeg', frame)[1].tobytes()
display(image_widget)
except KeyboardInterrupt:
video.release()
break
I am trying to send a webcam image to browser using Python. Now, I send it using the following code:
def send_a_frame():
capture = cv2.VideoCapture(0)
frame = capture.read()[1]
cv2.imwrite("im1.png",frame)
cnt = open("im1.png","rb").read()
b64 = base64.encodestring(cnt)
html = "<html><img src='data:image/png;base64,"+base64 +"'></html"
send(html)
How can I save an image and reopen an image and convert to base64 with a single statement?
I had the same problem, in my case I was reading from video file but it should work. Use cv2.imencode() method. See the following code
def send_a_frame():
capture = cv2.VideoCapture(0)
frame = capture.read()[1]
cnt = cv2.imencode('.png',frame)[1]
b64 = base64.encodestring(cnt)
html = "<html><img src='data:image/png;base64,"+b64 +"'></html"
send(html)