imageio: How to increase quality of output gifs? - python

I have a list of .png (PIL) images I want to string together into an animated gif
To do this, I am currently using the imageio library
However, I cannot find a way to do this to produce good quality gifs
As far as I know, there are two types of settings I can change. The imageio.imread() settings for reading .pngs, and the imageio.mimwrite() settings for writing gifs. According to imageio.help(),
imageio.imread() has one read paramter only for PNG-PIL, ignoregamma which takes a boolean. This does not change anything for my output gifs.
imageio.mimwrite() can refer to two formats. The first is GIF-PIL. The output from this format only shows one frame, and is thus undesirable. Output here.
imageio.mimwrite() has the second format GIF-FI. This produces more promising outputs with the following options:
'quantizer':'wu' generates a full gif with specified frames and frame rates, but produces a 'corrupted' kind-of quality. Output here.
'quantizer':'nq' generates a full gif with better 'less corrupted' quality than 'wu', but does not handle colour well. Notice how the legend in the bottom right tends to change its colour. Output here.
Here's the relevant code for the best quality I could get so far (GIF-FI with nq)
def gen_gif(self, datetime_list):
kwargs_write = {'fps':5.0, 'quantizer':'nq'}
frames = []
for datetime in datetime_list:
frames.append(imageio.imread(datetime+'.png'))
exportname = '{} to {}.gif'.format(datetime_list[0], datetime_list[-1])
imageio.mimsave(exportname, frames, 'GIF-FI', **kwargs_write)
The function is called with one list parameter of strings containing the full path to the .png images to be compiled to the gif.

The GIF format can only handle 256 colors per frame. The quantizer parameter determines the method that will be used to find these colors.
The best way to control which colors will be used is probably to reduce the number of colors (to 256) on your own.

Related

Python: SVG to PNG converting issue

UPDATE: I tried increasing size in the chess.svg.board and it somehow cleared all the rendering issues at size = 900 1800
I tried using the svglib and reportlab to make .png files from .svg, and here is how the code looks:
import sys
import chess.svg
import chess
from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
board = chess.Board("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR")
drawing = chess.svg.board(board, size=350)
f = open('file.svg', 'w')
f.write(drawing)
drawing = svg2rlg("file.svg")
renderPM.drawToFile(drawing, "file.png", fmt="png")
If you try to open file.png there is a lot of missing parts of the image, which i guess are rendering issues. How can you fix this?
Sidenote: also getting a lot of 'x_order_2: colinear!' messages when running this on a discord bot, but I am not sure if this affects anything yet.
THIS!! I am having the same error with the same libraries... I didn't find a solution but just a workaround which probably won't help too much in your case, where the shapes generating the bands are not very sparse vertically.
I'll try playing with the file dimensions too, but so far this is what I got. Note that my svg consists of black shapes on a white background (hence the 255 - x in the following code)
Since the appearance of the bands is extremely random, and processing the same file several times in a row produces different results, I decided to take advantage of randomness: what I do is I export the same svg a few times into different pngs, import them all into a list and then only take those pixels that are white in all the exported images, something like:
images_files = [my_convert_function(svgfile=file, index=i) for i in range(3)]
images = [255 - imageio.imread(x) for x in images_files]
result = reduce(lambda a,b: a & b, images)
imageio.imwrite(<your filename here>, result)
[os.remove(x) for x in images_files]
where my_convert_function contains your same svg2rlg and renderPM.drawToFile, and returns the name of the png file being written. The index 'i' is to save several copies of the same png with different names.
It's some very crude code but I hope it can help other people with the same issue
The format parameter has to be in uppercase
renderPM.drawToFile(drawing, "file.png", fmt="PNG")

GIMP Python Plugin to load 2 images as layers

I'm trying to make a plugin for gimp that opens two images as separate layers and transforms one of them (more on that below). I'm using GIMP 2.10.12.
I've been struggling to find a proper complete documentation for GIMP's Python interface and am mostly just working from what code snippets I've been able to find. This is what I have so far:
#!/usr/bin/env python2
import os
from gimpfu import *
def load_pair(img_f):
mask_f = img_f.replace(IMG_DIR, PRED_DIR)
result_f = os.path.splitext(img_f.replace(IMG_DIR, SAVE_DIR))[0]
result_dir = os.path.dirname(result_f)
if not os.path.isdir(result_dir):
os.makedirs(result_dir)
img = gimp.Image(100, 100)
pdb.gimp_display_new(img)
for f, name, pos in ((img_f, "Image", 0), (mask_f, "Mask", 1)):
layer = pdb.gimp_file_load_layer(img, f)
pdb.gimp_layer_set_name(layer, name)
pdb.gimp_image_insert_layer(img, layer, None, pos)
register(
"python_fu_open_image_pair",
...,
"<Toolbox>/Image/Open Image Pair",
"",
[(PF_FILE, "img_f", "Image:", None)],
[],
load_pair
)
main()
This kind of does what I want but with a couple of problems.
Question 1
Currently I'm using gimp.Image(100, 100) to open a new image. This means I have to then Fit Canvas to Layers and adjust the zoom and position every time I load a new image pair.
Is there a way to find an image's size from pdb before opening it or do I have to use another library (like PIL) for this? I'm trying to keep my plugin's dependencies to a minimum.
The two images are guaranteed to have the same size.
Since File->Open automatically adjusts the canvas to the image size, I would hope there'd be a nice way to achieve this.
Question 2
I would like to automatically create and set the current working file to result_f + '.xcf' (see above code) - such that File -> Save would automatically save to this file. Is this possible in pdb?
Question 3
Most importantly, I currently have the Mask images saved as black-and-white images. Upon loading a mask as a new layer, I'd like to transform the black colour to transparent and white colour to green (0,255,0). Additionally, since they are saved as .jpg images, the white and black aren't necessarily exactly 255 and 0 intensities but can be off by a bit.
How do I do this automatically in my plugin?
The good way would be to load the first image normally, and the rest as additional layers. Otherwise you can reset the canvas size (pdb.gimp_image_resize(...)) once you have loaded all the layers, and then create the Display.
You can give a name and a default file to the image by setting image.name and image.filename.
To convert the white to green use pdb.plug_in_colors_channel_mixer(...) and set all the gains to 0., except green in green. Make the black transparent use pdb.plug_in_colortoalpha(...).
PS: For color2alpha:
import gimpcolor
color=gimpcolor.RGB(0,255,0) # green, integer args: 0->255)
# or
color=gimpcolor.RGB(0.,1.,0) # green, floating point args (0.->1.)
pdb.plug_in_colortoalpha(image, layer, color)
The Python doc is a direct copy of the Scheme one. In Python, the RUN-INTERACTIVE parameter is not positional, so it doesn't appear in most calls, if you need it, it is a keyword parameter.

How do I create an animated gif in Python using Wand?

The instructions are simple enough in the Wand docs for reading a sequenced image (e.g. animated gif, icon file, etc.):
>>> from wand.image import Image
>>> with Image(filename='sequence-animation.gif') as image:
... len(image.sequence)
...but I'm not sure how to create one.
In Ruby this is easy using RMagick, since you have ImageLists. (see my gist for an example.)
I tried creating an Image (as the "container") and instantiating each SingleImage with an image path, but I'm pretty sure that's wrong, especially since the constructor documentation for SingleImage doesn't look for use by the end-user.
I also tried creating a wand.sequence.Sequence and going from that angle, but hit a dead-end as well. I feel very lost.
The best examples are located in the unit-tests shipped with the code. wand/tests/sequence_test.py for example.
For creating an animated gif with wand, remember to load the image into the sequence, and then set the additional delay/optimize handling after all frames are loaded.
from wand.image import Image
with Image() as wand:
# Add new frames into sequance
with Image(filename='1.png') as one:
wand.sequence.append(one)
with Image(filename='2.png') as two:
wand.sequence.append(two)
with Image(filename='3.png') as three:
wand.sequence.append(three)
# Create progressive delay for each frame
for cursor in range(3):
with wand.sequence[cursor] as frame:
frame.delay = 10 * (cursor + 1)
# Set layer type
wand.type = 'optimize'
wand.save(filename='animated.gif')

Weird behaviour using writeFrame in Opencv

I have a small problem using the video creation capability of OpenCV.
For the same images, I get a weird output depending on the output size I want.
Here is an example of the results I can get.
http://www.youtube.com/watch?v=1wm8VjyfdyA&feature=youtu.be
I tried with several different sets of images, and on different computers.
It seems to run fine on Windows, and I have problems with the Opencv that ships in Ubuntu packages (current 2.3.1-7).
As the problem is not reproductible on my windows, I guess its was either fixed in the 2.4 or specific to Linux.
Here is a (python) test code that highlight the problem :
import os
import cv
in_dir = "../data/inputs/sample-test"
out = "output.avi"
# loading images, create Guys and store it into guys
frameSize = (652, 498)
#frameSize = (453, 325)
fourcc = cv.CV_FOURCC('F', 'M', 'P', '4')
my_video = cv.CreateVideoWriter(out,
fourcc,
15,
frameSize,
1)
for root, _, files in os.walk(in_dir):
for a_file in files:
guy_source = os.path.join(in_dir, a_file)
print guy_source
image = cv.LoadImage(guy_source)
small_im = cv.CreateImage(frameSize,
image.depth ,
image.nChannels)
cv.Resize(image, small_im, cv.CV_INTER_LINEAR)
cv.WriteFrame(my_video, small_im)
print "Finished !"
My concern is that depending on the output size, the video is fine (652, 498 is ok for example).
The behaviour is the same whatever codec I use.
If not a fix, I´d like some more information about the reason for this bug.
As I want to ship for Ubuntu, I´d better use their packaging system and keep the 2.3 for some time.
So I would like to know how I can wisely solve the problem, by choosing educated sizes.
Any information is welcome
Thx !
This is a common problem in video coding. As you can see, the image is shifted with a small amount to left each row.
As you may know, the image is saved as a long row of chars: BGRBGRBGR....
It is also defined by its width and height, and by step - the distance, in bytes, between two consecutive rows. A naive supposition is that the step is 3(channels)*width. But in addition, for memory alignment reasons, the image rows are padded with some extra bits, in order to make the step value a multiple of 4 (usually) or 16. The reason is that hardware codec acceleration works with aligned data - 32bit architectures read 32bits at once, and for SIMD processing, aligned data is loaded faster.
So the image will be represented as
BGRBGR00
BGRBGR00
Now, if a codec does not know of this padding, it will read the width of the image as 2, and will interpret the data as follows:
BGRBGR
00BGRB
0000BG // note the extra padding
To make sure you do not experience this issue, you should select image width in such a way that the step value (channels*width) is a multiple of four. All of the standard resolutions have this property, and this is one of the reasons they were selected so:
640x480
1024x768
etc

opencv zoom function strange results

i am trying to write a zoom function which looks something like this:
centre = ((im.width-1)/2, (im.height-1)/2)
width = int(im.width/(2.0*level))
height = int(im.height/(2.0*level))
rect = (centre[0]-width, centre[1]-height, width*2, height*2)
dst = cv.GetSubRect(im, rect)
cv.Resize(dst, im)
when I use exactly what is written above, I get an odd result where the bottom half of the resultant image is distorted and blurry. However when I replace the line cv.Resize(dst, im) with
size = cv.CloneImage(im)
cv.Resize(dst, size)
im = size
it works fine. Why is this? is there something fundamentally wrong with the way i am performing the zoom?
cv.Resize requires source and destination to be separate memory locations.
Now in the first snippet of your code, you are using cv.GetSubRect to generate an object pointing to area of image which you wish to zoom in. Here the new object is NOT pointing to a new memory location. It is pointing to a memory location which is a subset of original object.
Since cv.Resize requires both the memory locations to be different, what you are getting is a result of undefined behavior.
In the second part of your code you are fulfilling this criteria by using cv.CloneImage.
you are first creating a copy of im (i.e. size. however you could have used a blank image aswell) and then you are using cv.Resize to resize dst and write the resulting image in size.
My advice is to go through the function documentation before using them.

Categories