I have written a python code which creates a gif from a list of images. In order to do this, I used the python library: imageio. Here is my code :
def create_gif(files, gif_path):
"""Creates an animated gif from a list of figures
Args:
files (list of str) : list of the files that are to be used for the gif creation.
All files should have the same extension which should be either png or jpg
gif_path (str) : path where the created gif is to be saved
Raise:
ValueError: if the files given in argument don't have the proper
file extenion (".png" or ".jpeg" for the images in 'files',
and ".gif" for 'gif_path')
"""
images = []
for image in files:
# Make sure that the file is a ".png" or a ".jpeg" one
if splitext(image)[-1] == ".png" or splitext(image)[-1] == ".jpeg":
pass
elif splitext(image)[-1] == "":
image += ".png"
else:
raise ValueError("Wrong file extension ({})".format(image))
# Reads the image with imageio and puts it into the images list
images.append(imageio.imread(image))
# Mak sure that the file is a ".gif" one
if splitext(gif_path)[-1] == ".gif":
pass
elif splitext(gif_path)[-1] == "":
gif_path += ".gif"
else:
raise ValueError("Wrong file extension ({})".format(gif_path))
# imageio writes all the images in a .gif file at the gif_path
imageio.mimsave(gif_path, images)
When I try this code with a list of images the Gif is correctly created but I have no idea how to change its parameters :
What I mean by that is that I would like to be able to control the delay between the gif's images, and also to control how much time the gif's is running.
I have tried to my gif with the Image module from PIL, and change its info, but when I save it my gif turns into my first image.
Could you please help me understand what I am doing wrong?
here is the code that I ran to try to change the gif prameter :
# Try to change gif parameters
my_gif = Image.open(my_gif.name)
my_gif_info = my_gif.info
print(my_gif_info)
my_gif_info['loop'] = 65535
my_gif_info['duration'] = 100
print(my_gif.info)
my_gif.save('./generated_gif/my_third_gif.gif')
You can just pass both parameters, loop and duration, to the mimsave/mimwrite method.
imageio.mimsave(gif_name, fileList, loop=4, duration = 0.3)
Next time you want to check which parameters can be used for a format compatible with imageio you can just use imageio.help(format name).
imageio.help("gif")
GIF-PIL - Static and animated gif (Pillow)
A format for reading and writing static and animated GIF, based
on Pillow.
Images read with this format are always RGBA. Currently,
the alpha channel is ignored when saving RGB images with this
format.
Parameters for reading
----------------------
None
Parameters for saving
---------------------
loop : int
The number of iterations. Default 0 (meaning loop indefinitely).
duration : {float, list}
The duration (in seconds) of each frame. Either specify one value
that is used for all frames, or one value for each frame.
Note that in the GIF format the duration/delay is expressed in
hundredths of a second, which limits the precision of the duration.
fps : float
The number of frames per second. If duration is not given, the
duration for each frame is set to 1/fps. Default 10.
palettesize : int
The number of colors to quantize the image to. Is rounded to
the nearest power of two. Default 256.
subrectangles : bool
If True, will try and optimize the GIF by storing only the
rectangular parts of each frame that change with respect to the
previous. Default False.
Related
I am using ffmpeg-python 0.2.0 with Python 3.10.0. Displaying videos in VLC 3.0.17.4.
I am making an animation from a set of images. Each image is displayed for different amount of time.
I have the basics in place with inputting images and concatenating streams, but I can't figure out how to correctly set frame duration.
Consider the following example:
stream1 = ffmpeg.input(image1_file)
stream2 = ffmpeg.input(image2_file)
combined_streams = ffmpeg.concat(stream1, stream2)
output_stream = ffmpeg.output(combined_streams, output_file)
ffmpeg.run(output_stream)
With this I get a video with duration of a split second that barely shows an image before ending. Which is to be expected with two individual frames.
For this example, my goal is to have a video of 5 seconds total duration, showing the image in stream1 for 2 seconds and the image in stream2 for 3 seconds.
Attempt 1: Setting t for inputs
stream1 = ffmpeg.input(image1_file, t=2)
stream2 = ffmpeg.input(image2_file, t=3)
combined_streams = ffmpeg.concat(stream1, stream2)
output_stream = ffmpeg.output(combined_streams, output_file)
ffmpeg.run(output_stream)
With this, I get a video with the duration of a split second and no image displayed.
Attempt 2: Setting frames for inputs
stream1 = ffmpeg.input(image1_file, frames=48)
stream2 = ffmpeg.input(image2_file, frames=72)
combined_streams = ffmpeg.concat(stream1, stream2)
output_stream = ffmpeg.output(combined_streams, output_file, r=24)
ffmpeg.run(output_stream)
In this case, I get the following error from ffmpeg:
Option frames (set the number of frames to output) cannot be applied to input url ########## -- you are trying to apply an input option to an output file or vice versa. Move this option before the file it belongs to.
I can't tell if this is a bug in ffmpeg-python or if I did it wrong.
Attempt 3: Setting framerate for inputs
stream1 = ffmpeg.input(image1_file, framerate=1/2)
stream2 = ffmpeg.input(image2_file, framerate=1/3)
combined_streams = ffmpeg.concat(stream1, stream2)
output_stream = ffmpeg.output(combined_streams, output_file)
ffmpeg.run(output_stream)
With this, I get a video with the duration of a split second and no image displayed. However, when I set both framerate values to 1/2, I get an animation of 4 seconds duration that displays the first image for two seconds and the second image for two seconds. This is the closest I got to a functional solution, but it is not quite there.
I am aware that multiple images can be globbed by input, but that would apply the same duration setting to all images, and my images each have different durations, so I am looking for a different solution.
Any ideas for how to get ffmpeg-python to do the thing is much appreciated.
A simple solution is adding loop=1 and framerate=24 to the "first example":
import ffmpeg
image1_file = 'image1_file.png'
image2_file = 'image2_file.png'
output_file = 'output_file.mp4'
stream1 = ffmpeg.input(image1_file, framerate=24, t=2, loop=1)
stream2 = ffmpeg.input(image2_file, framerate=24, t=3, loop=1)
combined_streams = ffmpeg.concat(stream1, stream2)
output_stream = ffmpeg.output(combined_streams, output_file)
ffmpeg.run(output_stream)
loop=1 - Makes the input image repeating in a loop (the repeated duration is set by t=2 and t=3).
framerate=24 - Images don't have framerate (opposed to video), so they are getting the default framerate (25fps) if framerate is not specified.
Assuming the desired output framerate is 24fps, we may set the input framerate to 24fps.
Selecting framerate=24 sets the input framerate to 24fps (and prevents framerate conversion).
You need to manipulate the timestamp of the source images and use the ts_from_file option of the image2 demuxer:
ts_from_file
If set to 1, will set frame timestamp to modification time of image file. Note that monotonity of timestamps is not provided: images go in the same order as without this option. Default value is 0. If set to 2, will set frame timestamp to the modification time of the image file in nanosecond precision.
You should be able to use os.utime if ok to modify the original file or shutil.copy2 to copy and modify.
I am using python libraries PyPDF2 and reportlab to add text fields into an existing PDF.
I currently use the function
def makeTextFields():
packet = io.BytesIO()
can = canvas.Canvas(packet, pagesize=landscape(letter))
can.acroForm.textfield(name='fname', tooltip='First Name',
x=500, y=20, borderStyle='inset',
borderColor=blue, fillColor=blue,
width=79, height=24,
textColor=black, forceBorder=False, annotationFlags ="")
can.showPage()
can.save()
packet.seek(0)
text_fields = PdfFileReader(packet)
return text_fields
to create a PDF with the text fields then the following to load the base pdf, merge and save
main = PdfFileReader(open("master.pdf", 'rb'))
text_fields = makeTextFields()
output = PdfFileWriter()
text_field_page = text_fields.getPage(0)
page = main.getPage(0)
page.mergePage(text_field_page)
output.addPage(text_field_page)
stream = open("dest.pdf", "wb")
output.write(stream)
stream.close()
this solution would work fine however master.pdf has a rotation of 90 this means that when page.mergePage is called the text field pdf is automatically roated 90 degrees to match the base pdf and leaves the text fields 90 degrees with sideways text
WHAT I'VE TRIED
I have tried replacing page.mergePage with page.mergeRotatedTranslatedPage to no luck, I have also tried setting annotationFlags ="norotate" which according to reportlab docs should allow the text field to ignore canvas rotation but that did not work. lastly i tried
can.saveState()
can.rotate(90)
can.acroForm.textfield(name='fname', tooltip='First Name',
x=500, y=20, borderStyle='inset',
borderColor=blue, fillColor=blue,
width=79, height=24,
textColor=black, forceBorder=False, annotationFlags ="")
can.restoreState()
in the hopes of rotating the text field 90 degrees offset of the page so it will be 0 when the page is rotated to 90 but that seemed to have no affect
I believe the solution will lie in either finding a way to nullify the rotation on the text field,
applying an initial rotation to the text field, or merging the two pdfs without matching the rotation. However, any other solutions / libraries are apreciated.
I am also open to creating the pdf in another program and just merging them using python. Or using a different language if python isnt the best language for the job
try this
text_field_page.mergeRotatedTranslatedPage(page , -90, page .mediaBox.getWidth() / 2, page .mediaBox.getWidth() / 2)
I have a script that uses the MTCNN face detection library that iterates through a fair amount of directories, totaling thousands of images. An issue that I've been running into with this script is the excessive memory usage when processing all of these images, which will eventually cause my MacBook (16gb of RAM) to run out of memory. What I'm looking to do is to implement batching on a folder by folder basis, instead of a specific batch limit because none of the folders contain enough images individually that would make the system run out of memory.
# open up the csv file
with open(csv_path, 'w', newline='') as file:
writer = csv.writer(file)
writer.writerow(['Index', 'Threshhold', 'Path'])
for path, subdirs, files in os.walk(path):
for name in files:
if name == '.DS_Store':
print("Skipping .DS_Store")
continue
else:
try:
image = os.path.join(path, name)
pixels = pyplot.imread(image)
print("Processing " + image)
print("Count: " + str(inc))
# calculate the area of the image
total_height = pixels.shape[0]
total_width = pixels.shape[1]
total_area = total_height * total_width
# create the detector, using default weights
detector = MTCNN()
faces = detector.detect_faces(pixels)
ax = pyplot.gca()
face_total_area = 0
if faces == []:
print("No faces detected.")
# pass in 0 for the threshold becuase there's no faces
#write_to_csv(inc, 0, image)
print()
else:
for face in faces:
# get dimensions from the face
x, y, width, height = face['box']
# calculate the area of the face
face_area = width * height
face_total_area += face_area
threshold = face_total_area / total_area
# write to csv only if the threshold is less than the limit
# change back to this eventually ^^^^^^^^^
if threshold > threshhold_limit:
print("Facial area is over the threshold - writing file path to csv.")
write_to_csv(inc, threshold, image)
else:
print("Image threshold is under the limit - good")
print(threshold)
print()
inc += 1
except:
print("Processing error - skipping image")
Is something like this possible to do? Or should it be done a different way? The idea is that batching like this will allow mtcnn to release the memory it's holding onto when it's done processing that folder.
Memory usage should not increase with this program, because it does not accumulate data from one image to the next one. So, what you are asking for will have no effect. Have you tried runnng tis same code outside of a Python notebook? As a standalone program? It may be that the notebook is keeping references to all read images.
Either that, or find a call that would really reset pyplot's internal state inside the innermost loop. (maybe pyplot.clf()).
"Batching" as you say is what takes place inside the first for loop, which will run once for each folder in your tree. The only bennefit you could possibly have would be to reset the internal state inside the first loop, but outside the second for (for name in ...), you'd have to find the exactly same call to reset the internal state.
(also, on a side note, you create a csv writer in your with block that is invalidated at the end of the block - you should refactor this code not to keep reopening the CSV file for each new line - (which happens in the not-shown write_to_csv function) )
I'm trying to read an image in unchanged format, do some operations and convert it back to the colored format
im = cv2.imread(fname,cv2.IMREAD_UNCHANGED) # shape(240,240,4)
....
im2 = cv2.imread(im,cv2.IMREAD_COLOR) # required shape(240,240,3)
But, looks like I can't input the result of first numpy array into the second imread.
So currently I've created a temporary image after the operations and reading that value to get the required im2 value.
im = cv2.imread(fname,cv2.IMREAD_UNCHANGED) # shape(240,240,4)
....
cv2.imwrite('img.png',im)
im2 = cv2.imread('img.png',cv2.IMREAD_COLOR) # required shape(240,240,3)
However I would like to avoid the step of creating temporary image. How would I achieve the same with a better approach
OpenCV has a function for color conversion cvtColor
https://docs.opencv.org/3.1.0/de/d25/imgproc_color_conversions.html
im2 = cv2.cvtColor(im, <conversion code>)
You should figure out conversion code yourself, based on image format you have. Probably, it would be cv2.COLOR_BGRA2BGR
I use the following code to extract a specific frame from a video file. In this example, I'm simply getting the middle frame:
import cv2
video_path = '/tmp/wonderwall.mp4'
vidcap = cv2.VideoCapture(video_path)
middle_frame = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT) / 2)
success, image = vidcap.read()
count = 0
success = True
while success:
success, image = vidcap.read()
if count == middle_frame:
temp_file = tempfile.NamedTemporaryFile(suffix='.jpg', delete=False)
cv2.imwrite(temp_file.name, image)
count += 1
However, with this method, capturing the middle frame in a very large file can take a while.
Apparently, in the older cv module, one could do:
import cv
img = cv.QueryFrame(capture)
Is there a similar way in cv2 to grab a specific frame in a video file, without having to iterate through all frames?
You can do it in the same way, in C++ (python conversion should be more than easy).
cv::VideoCapture cap("file.avi");
double number_of_frame = cap.get(CV_CAP_PROP_FRAME_COUNT);
cap.set(CV_CAP_PROP_POS_FRAMES, IndexOfTheFrameYouWant);
cv::Mat frameIwant = cap.read();
For reference :
VideoCapture::get(int propId)
Can take various flag returning nearly all you can wish for (http://docs.opencv.org/2.4/modules/highgui/doc/reading_and_writing_images_and_video.html and look for get() ).
VideoCapture::set(int propId, double value)
Set will do what you want (same doc look for set) if you use the propID 1, and the index of the frame you desire.
You should note that if the index you use as parameter is superior to the max frame that the code will grab the last frame of the video if you are lucky, or crash at run time.