I am writing a script (my first script-fu) to export .jpeg to .avif and .webp, and I want to optimize the files for the web. Somewhere I read the suggestion to change from RGB to an indexed color palette, so I implemented the procedure gimp-image-convert-indexed. I was expecting smaller, web optimized files, but the opposite happened. The output resulted in huge files:
Original jpeg: 7.510 KB
Exported files without gimp-image-convert-indexed:
.avif: 95 KB
.webp: 2.052 KB
Exported files with gimp-image-convert-indexed:
.avif: 6.337 KB
.webp: 19.393 KB
Did I sent a parameter wrong? Or is the whole idea of using 'gimp-image-convert-indexed' nonsense?
(Aside: any other obvious flaws in my beginner's script? Comments and hints welcome.)
convert.scm:
(define (filename-basename orig-name)
(car (strbreakup orig-name "."))
)
(define
(convert in_filename WebP Avif)
(let* (
(image (car (gimp-file-load RUN-NONINTERACTIVE in_filename in_filename)))
(drawable (car (gimp-image-flatten image)))
(outWebP (string-append (filename-basename in_filename) ".webP"))
(outAvif (string-append (filename-basename in_filename) ".avif"))
)
; small output files sizes without this line, huge with:
(gimp-image-convert-indexed image CONVERT-DITHER-FS CONVERT-PALETTE-WEB 0 0 1 "")
(cond ((equal? WebP 1)
(gimp-message "exporting as .webP")
(file-webp-save2 RUN-NONINTERACTIVE image drawable outWebP outWebP 0 0 90 100 0 0 0 0 0 0 0 0 0 0)))
(cond ((equal? Avif 1)
(gimp-message "exporting as .avif")
(file-heif-av1-save RUN-NONINTERACTIVE image drawable outAvif outAvif 50 0)))
(gimp-image-delete image)
)
)
Invoke under windows:
gimp-console-2.10 -idf -b "(convert \"IMG.jpg\" 1 1)" -b "(gimp-quit 0)"
Converting to indexed is nonsense since your output format does not support it. Color-indexing may reduce image size on PNG (which is the only format I know with both color-indexed and full-RGB formats).
I don't see much benefit on WebP/Avif over Jpeg, for the same final visual quality. In other words,there are JPEG options that work just as well: reducing quality or increasing chroma sub-sampling.
Also to batch-convert images, ImageMagick is much easier to code around than Gimp.
Related
I am new to Python and am trying to parallelize a program that I somehow pieced together from the internet. The program reads all image files (usually multiple series of images such as abc001,abc002...abc015 and xyz001,xyz002....xyz015) in a specific folder and then combines images in a specified range. Most times, the number of files exceeds 10000, and my latest case requires me to combine 24000 images. Could someone help me with:
Taking 2 sets of images from different directories. Currently I have to move these images into 1 directory and then work in said directory.
Reading only specified files. Currently my program reads all files, saves names in an array (I think it's an array. Could be a directory also) and then uses only the images required to combine. If I specify a range of files, it still checks against all files in the directory and takes a lot of time.
Parallel Processing - I work with usually 10k files or sometimes more. These are images saved from the fluid simulations that I run at specific times. Currently, I save about 2k files at a time in separate folders and run the program to combine these 2000 files at one time. And then I copy all the output files to a separate folder to keep them together. It would be great if I could use all 16 cores on the processor to combine all files in 1 go.
Image series 1 is like so.
Consider it to be a series of photos of the cat walking towards the camera. Each frame is is suffixed with 001,002,...,n.
Image series 1 is like so.
Consider it to be a series of photos of the cat's expression changing with each frame. Each frame is is suffixed with 001,002,...,n.
The code currently combines each frame from set1 and set2 to provide output.png as shown in the link here.
import sys
import os
from PIL import Image
keywords=input('Enter initial characters of image series 1 [Ex:Scalar_ , VoF_Scene_]:\n')
keywords2=input('Enter initial characters of image series 2 [Ex:Scalar_ , VoF_Scene_]:\n')
directory = input('Enter correct folder name where images are present :\n') # FOLDER WHERE IMAGES ARE LOCATED
result1 = {}
result2={}
name_count1=0
name_count2=0
for filename in os.listdir(directory):
if keywords in filename:
name_count1 +=1
result1[name_count1] = os.path.join(directory, filename)
if keywords2 in filename:
name_count2 +=1
result2[name_count2] = os.path.join(directory, filename)
num1=input('Enter initial number of series:\n')
num2=input('Enter final number of series:\n')
num1=int(num1)
num2=int(num2)
if name_count1==(num2-num1+1):
a1=1
a2=name_count1
elif name_count2==(num2-num1+1):
a1=1
a2=name_count2
else:
a1=num1
a2=num2+1
for x in range(a1,a2):
y=format(x,'05') # '05' signifies number of digits in the series of file name Ex: [Scalar_scene_1_00345.png --> 5 digits], [Temperature_section_2_951.jpg --> 3 digits]. Change accordingly
y=str(y)
for comparison_name1 in result1:
for comparison_name2 in result2:
test1=result1[comparison_name1]
test2=result2[comparison_name2]
if y in test1 and y in test2:
a=test1
b=test2
test=[a,b]
images = [Image.open(x) for x in test]
widths, heights = zip(*(i.size for i in images))
total_width = sum(widths)
max_height = max(heights)
new_im = Image.new('RGB', (total_width, max_height))
x_offset = 0
for im in images:
new_im.paste(im, (x_offset,0))
x_offset += im.size[0]
output_name='output'+y+'.png'
new_im.save(os.path.join(directory, output_name))
I did a Python version as well, it's not quite as fast but it is maybe closer to your heart :-)
#!/usr/bin/env python3
import cv2
import numpy as np
from multiprocessing import Pool
def doOne(params):
"""Append the two input images side-by-side to output the third."""
imA = cv2.imread(params[0], cv2.IMREAD_UNCHANGED)
imB = cv2.imread(params[1], cv2.IMREAD_UNCHANGED)
res = np.hstack((imA, imB))
cv2.imwrite(params[2], res)
if __name__ == '__main__':
# Build the list of jobs - each entry is a tuple with 2 input filenames and an output filename
jobList = []
for i in range(1000):
# Horizontally append a-XXXXX.png to b-XXXXX.png to make c-XXXXX.png
jobList.append( (f'a-{i:05d}.png', f'b-{i:05d}.png', f'c-{i:05d}.png') )
# Make a pool of processes - 1 per CPU core
with Pool() as pool:
# Map the list of jobs to the pool of processes
pool.map(doOne, jobList)
You can do this a little quicker with libvips. To join two images left-right, enter:
vips join left.png out.png result.png horizontal
To test, I made 200 pairs of 1200x800 PNGs like this:
for i in {1..200}; do cp x.png left$i.png; cp x.png right$i.png; done
Then tried a benchmark:
time parallel vips join left{}.png right{}.png result{}.png horizontal ::: {1..200}
real 0m42.662s
user 2m35.983s
sys 0m6.446s
With imagemagick on the same laptop I see:
time parallel convert left{}.png right{}.png +append result{}.png ::: {1..200}
real 0m55.088s
user 3m24.556s
sys 0m6.400s
You can do that much faster without Python, and using multi-processing with ImageMagick or libvips.
The first part is all setup:
Make 20 images, called a-000.png ... a-019.png that go from red to blue:
convert -size 64x64 xc:red xc:blue -morph 18 a-%03d.png
Make 20 images, called b-000.png ... b-019.png that go from yellow to magenta:
convert -size 64x64 xc:yellow xc:magenta -morph 18 b-%03d.png
Now append them side-by-side into c-000.png ... c-019.png
for ((f=0;f<20;f++))
do
z=$(printf "%03d" $f)
convert a-${z}.png b-${z}.png +append c-${z}.png
done
Those images look like this:
If that looks good, you can do them all in parallel with GNU Parallel:
parallel convert a-{}.png b-{}.png +append c-{}.png ::: {1..19}
Benchmark
I did a quick benchmark and made 20,000 images a-00000.png...a-019999.png and another 20,000 images b-00000.png...b-019999.png with each image 1200x800 pixels. Then I ran the following command to append each pair horizontally and write 20,000 output images c-00000.png...c-019999.png:
seq -f "%05g" 0 19999 | parallel --eta convert a-{}.png b-{}.png +append c-{}.png
and that takes 16 minutes on my MacBook Pro with all 12 CPU cores pegged at 100% throughout. Note that you can:
add spacers between the images,
write annotation onto the images,
add borders,
resize
if you wish and do lots of other processing - this is just a simple example.
Note also that you can get even quicker times - in the region of 10-12 minutes if you accept JPEG instead of PNG as the output format.
I am capturing video from a Ricoh Theta V camera. It delivers the video as Motion JPEG (MJPEG). To get the video you have to do an HTTP POST alas which means I cannot use the cv2.VideoCapture(url) feature.
So the way to do this per numerous posts on the web and SO is something like this:
bytes = bytes()
while True:
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:]
i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
cv2.imshow('i', i)
if cv2.waitKey(1) == 27:
exit(0)
That actually works, except it is slow. I'm processing a 1920x1080 jpeg stream. on a Mac Book Pro running OSX 10.12.6. The call to imdecode takes approx 425000 microseconds to process each image
Any idea how to do this without imdecode or make imdecode faster? I'd like it to work at 60FPS with HD video (at least).
I'm using Python3.7 and OpenCV4.
Updated Again
I looked into JPEG decoding from the memory buffer using PyTurboJPEG, the code goes like this to compare with OpenCV's imdecode():
#!/usr/bin/env python3
import cv2
from turbojpeg import TurboJPEG, TJPF_GRAY, TJSAMP_GRAY
# Load image into memory
r = open('image.jpg','rb').read()
inp = np.asarray(bytearray(r), dtype=np.uint8)
# Decode JPEG from memory into Numpy array using OpenCV
i0 = cv2.imdecode(inp, cv2.IMREAD_COLOR)
# Use default library installation
jpeg = TurboJPEG()
# Decode JPEG from memory using turbojpeg
i1 = jpeg.decode(r)
cv2.imshow('Decoded with TurboJPEG', i1)
cv2.waitKey(0)
And the answer is that TurboJPEG is 7x faster! That is 4.6ms versus 32.2ms.
In [18]: %timeit i0 = cv2.imdecode(inp, cv2.IMREAD_COLOR)
32.2 ms ± 346 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [19]: %timeit i1 = jpeg.decode(r)
4.63 ms ± 55.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Kudos to #Nuzhny for spotting it first!
Updated Answer
I have been doing some further benchmarks on this and was unable to verify your claim that it is faster to save an image to disk and read it with imread() than it is to use imdecode() from memory. Here is how I tested in IPython:
import cv2
# First use 'imread()'
%timeit i1 = cv2.imread('image.jpg', cv2.IMREAD_COLOR)
116 ms ± 2.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# Now prepare the exact same image in memory
r = open('image.jpg','rb').read()
inp = np.asarray(bytearray(r), dtype=np.uint8)
# And try again with 'imdecode()'
%timeit i0 = cv2.imdecode(inp, cv2.IMREAD_COLOR)
113 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
So, I find imdecode() around 3% faster than imread() on my machine. Even if I include the np.asarray() into the timing, it is still quicker from memory than disk - and I have seriously fast 3GB/s NVME disks on my machine...
Original Answer
I haven't tested this but it seems to me that you are doing this in a loop:
read 1k bytes
append it to a buffer
look for JPEG SOI marker (0xffdb)
look for JPEG EOI marker (0xffd9)
if you have found both the start and the end of a JPEG frame, decode it
1) Now, most JPEG images with any interesting content I have seen are between 30kB to 300kB so you are going to do 30-300 append operations on a buffer. I don't know much abut Python but I guess that may cause a re-allocation of memory, which I guess may be slow.
2) Next you are going to look for the SOI marker in the first 1kB, then again in the first 2kB, then again in the first 3kB, then again in the first 4kB - even if you have already found it!
3) Likewise, you are going to look for the EOI marker in the first 1kB, the first 2kB...
So, I would suggest you try:
1) allocating a bigger buffer at the start and acquiring directly into it at the appropriate offset
2) not searching for the SOI marker if you have already found it - e.g. set it to -1 at the start of each frame and only try and find it if it is still -1
3) only look for the EOI marker in the new data on each iteration, not in all the data you have already searched on previous iterations
4) furthermore, actually, don't bother looking for the EOI marker unless you have already found the SOI marker, because the end of a frame without the corresponding start is no use to you anyway - it is incomplete.
I may be wrong in my assumptions, (I have been before!) but at least if they are public someone cleverer than me can check them!!!
I recommend to use turbo-jpeg. It has a python API: PyTurboJPEG.
This is my first foray into gimp scripting, and I seem to be having some basic misunderstanding. I've written the following code which is intended to convert white into transparency in the specified file, and to export the result as a png file:
(define (edit-name original-name)
(let* ((pos (- (string-length original-name) 4))
(suffix (substring original-name pos))
(prefix (substring original-name 0 pos))
(new-name (string-append prefix ".png")))
new-name))
(define (my-process filename)
(let* ((color '(0 0 0))
(image (car (gimp-file-load RUN-NONINTERACTIVE filename filename)))
(drawable (car (gimp-image-get-active-layer image)))
(filename-modified (edit-name filename)))
(if (not (gimp-drawable-is-rgb drawable))
(gimp-image-convert-rgb image))
(plug-in-colortoalpha RUN-NONINTERACTIVE image drawable color)
(file-png-save2 RUN-NONINTERACTIVE image drawable filename-modified filename-modified 0 9 1 1 1 1 1 0 1)))
This script is saved in the file ~/.gimp-2.8/scripts/my-process.scm and is invoked with:
gimp -i -b '(my-process "somefile.xcf")' -b '(gimp-quit 0)'
The script appears to run and I get output on the console saying batch command executed successfully. Furthermore, if I put an obvious syntax or parameter error into the script then I get an error. I've also tried re-getting the drawable after the rgb conversion, in case the conversion results in a different object. I've also tried using file-png-save-defaults instead of file-png-save2, to no avail.
However, the output I get is a simple png export in indexed color mode (the original mode of the source image), which does not contain the alpha channel.
I've looked at the documentation for the conversion steps I'm using and cannot figure out what's going on; help would be much appreciated.
This works for me (this is your code in Python, using the same calls):
image=gimp.image_list()[0]
layer=image.active_layer
print "Type before:",pdb.gimp_drawable_type(layer)
pdb.gimp_image_convert_rgb(image)
print "Type after:",pdb.gimp_drawable_type(layer)
print "Has alpha after:",pdb.gimp_drawable_has_alpha(layer)
pdb.plug_in_colortoalpha(image,layer,(0,0,0))
pdb.file_png_save2(image,layer,'/tmp/foo.png','/tmp/foo.png',0,9,1,1,1,1,1,0,1)
And the output is (you can paste the above directly in the Python console):
>> image=gimp.image_list()[0]
>>> layer=image.active_layer
>>> print "Type before:",pdb.gimp_drawable_type(layer)
Type before: 4
>>> pdb.gimp_image_convert_rgb(image)
>>> print "Type after:",pdb.gimp_drawable_type(layer)
Type after: 0
>>> print "Has alpha after:",pdb.gimp_drawable_has_alpha(layer)
Has alpha after: 0
>>> pdb.plug_in_colortoalpha(image,layer,(0,0,0))
>>> pdb.file_png_save2(image,layer,'/tmp/foo.png','/tmp/foo.png',0,9,1,1,1,1,1,0,1)
What I find fishy is that there is no alpha channel after the conversion (type is RGB_IMAGE and not RGBA_IMAGE) (which is expected), but color-to-alpha works without adding one. You can however try to add one explicitly and see if this changes something.
This said, 95% of all batch processing in Gimp is better done with ImageMagick. For color-to-alpha, see this SO question
I'm trying to convert a 16 bit greyscale PNG to a raw file. The image size is 640*480.
First, identify:
$ identify image.png
image.png PNG 640x480 640x480+0+0 16-bit PseudoClass 65536c 299KB 0.000u 0:00.000
I'm expecting the result file to be 640*480*2 bytes in size.
Attempt 1:
$ convert image.png -depth 16 image.raw
This gives a file size of 330805 bytes. Its first 16 bytes look like:
0x00000000: 89504E47 0D0A1A0A 0000000D 49484452 .PNG........IHDR
Attempt 2:
$ convert image.png -depth 16 image.rgb
This gives a file size of 1843200 bytes, which is 640*480*2*3.
I'm running imagemagick version 6.7.7-10 on Ubuntu 14.04.
Any ideas?
Updated Answer
It occurred to me since answering you, that there is a simpler method of doing what you want, that takes advantage of ImageMagick's little-used stream tool, to stream raw pixel data around.
In effect, you can use this command
stream -map r -storage-type short image.png image.raw
which will read the Red channel (-map r), which is the same as the Green and Blue channels if your image is greyscale, and write it out as unsigned 16-bit shorts (-storage-type short) to the output file image.raw.
This is cleaner than my original answer - though should give identical results.
Original Answer
If you write an RGB raw file, you will get 3 channels - R, G and B. Try writing a PGM (Portable Greymap) like this...
convert image.png -depth 16 pgm:-
P5
640 480
65535
<binary data> < binary data>
The PGM format is detailed here, but suffice to say that there is header with a P followed by a digit describing the actual subtype, then a width and height and then a MAX VALUE that describes the range of the pixel intensities. In your case, the MAX VALUE is 65535 rather than 255 because your data are 16-bit.
You can the strip the header like this:
convert image.png -depth 16 pgm:- | tail -c 614400 > file.raw
If you are converting lots of files of different sizes and dislike the hard-coded 614400, and are using bash, you can get ImageMagick to tell you the size (height * width * 2 bytes/pixel) and use that like this:
bytes=$(identify -format "%[fx:h*w*2]" image.png)
convert image.png -depth 16 pgm:- | tail -c $bytes > file.raw
gray might be the format you want:
convert image.png -depth 16 image.gray
This command stores each pixel in 2 bytes and nothing else in the file.
Here I provide a minimal synthetic example: https://superuser.com/questions/294270/how-to-view-raw-binary-data-as-an-image-with-given-width-and-height/978432#978432
.raw is not really a "pixel only" format: it does contain some metadata: https://en.wikipedia.org/wiki/Raw_image_format#File_contents
I have a large Tiff image that I want to chop into 512x512 tiles and write to disk.
In the past I've used ImageMagick like so:
convert -crop 512x512 +repage image_in.tif image_out_%d.tif
But recently this hasn't been working, processes running out of memory, etc.
Is there a similar command in VIPS? I know there's a CLI but I can't find an example or useful explanation in the documentation, and I'm still trying to figure out the nip2 GUI thing. Any help appreciated. :)
libvips has a operator which can do this for you very quickly. Try:
$ vips dzsave wtc.tif outdir --depth one --tile-size 512 --overlap 0 --suffix .tif
That's the DeepZoom writer making a depth 1 pyramid of tif tiles. Look in outdir_files/0 for the output tiles. There's a chapter in the docs talking about how to use dzsave.
It's a lot quicker than IM for me:
$ time convert -crop 512x512 +repage huge.tif x/image_out_%d.tif
real 0m5.623s
user 0m2.060s
sys 0m2.148s
$ time vips dzsave huge.tif x --depth one --tile-size 512 --overlap 0 --suffix .tif
real 0m1.643s
user 0m1.668s
sys 0m1.000s
Where huge.tif is a 10,000 by 10,000 pixel uncompressed RGB image. Plus it'll process any size image in only a small amount of memory.
I am running into the same issue. It seems that VIPS does not have a built-in command like the one from imagemagick above, but you can do this with some scripting (Python-code snippet):
for x in xrange(0, tiles_per_row):
xoffset = x * tile_size
for y in xrange(0, tiles_per_row):
yoffset = y * tile_size
filename = "%d_%d_%d.png" % (zoom, x, y)
command = "vips im_extract_area %s %s %d %d %d %d" % (base_image_name, filename, xoffset, yoffset, tile_size, tile_size)
os.system(command)
However you won't get the same speed as with imagemagick cropping...