I am trying to turn images into infinite looping GIFs, basically you have an image and a number, you then create an array of that number elements, each element is the original image with hue rotated by index divided by number times 360°, and you save the array as a GIF.
Working solution:
import numpy as np
from PIL import Image
def rgb_to_hsv(rgb):
rgb = rgb.astype('float')
hsv = np.zeros_like(rgb)
r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]
maxc = np.max(rgb[..., :3], axis=-1)
minc = np.min(rgb[..., :3], axis=-1)
hsv[..., 2] = maxc
mask = maxc != minc
hsv[mask, 1] = (maxc - minc)[mask] / maxc[mask]
rc = np.zeros_like(r)
gc = np.zeros_like(g)
bc = np.zeros_like(b)
rc[mask] = (maxc - r)[mask] / (maxc - minc)[mask]
gc[mask] = (maxc - g)[mask] / (maxc - minc)[mask]
bc[mask] = (maxc - b)[mask] / (maxc - minc)[mask]
hsv[..., 0] = np.select(
[r == maxc, g == maxc], [bc - gc, 2.0 rc - bc], default=4.0 gc - rc)
hsv[..., 0] = (hsv[..., 0] / 6.0) % 1.0
return hsv
def huegify(img, filepath, n=360):
assert 0 < n <= 360
height, width = img.shape[:2]
hsv = rgb_to_hsv(img)
h, s, v = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2]
p = v * (1.0 - s)
def adjust_hue(d):
rgb = np.zeros([height, width, 3])
h = ((h d/n)%1)*6
i = h.astype('uint8')
f = h - i
q = v * (1.0 - s * f)
t = v * (1.0 - s * (1.0 - f))
i = i % 6
conditions = [s == 0.0, i == 1, i == 2, i == 3, i == 4, i == 5]
rgb[..., 0] = np.select(conditions, [v, q, p, p, t, v], default=v)
rgb[..., 1] = np.select(conditions, [v, v, v, q, p, p], default=t)
rgb[..., 2] = np.select(conditions, [v, p, t, v, v, q], default=p)
return rgb.astype('uint8')
images = [Image.fromarray(adjust_hue(i)) for i in range(n)]
images[0].save(filepath, format='GIF', save_all=True, append_images=images[1:], quality=100, loop=0, duration=42)
Example input:
Method taken from here
The above is the fastest method I have found, but it is still not ideal, it takes around 528 milliseconds to complete one shift (or 170 milliseconds to create an RGB image from adjusted HSV values):
In [38]: %%timeit
...: hsv = rgb_to_hsv(arr)
...: h, s, v = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2]
...: Image.fromarray(hsv_to_rgb((h 1/360)%1, s, v))
528 ms ± 29.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [39]: hsv = rgb_to_hsv(arr)
...: h, s, v = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2]
In [40]: %timeit Image.fromarray(hsv_to_rgb((h 1/360)%1, s, v))
170 ms ± 2.81 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Is there any way this can be faster? All other methods I have found are using for loops.
Edit
I am able to give it a little speed up, but it is not very much.
CodePudding user response:
I switched to cv2
and now it's much faster. I also replaced GIF with MP4, because GIFs are of low quality.
But I didn't use cv2.VideoWriter
, because: 1, I can't control the bitrate, and 2, it doesn't use an FFMPEG version that supports CUDA, instead I found pre-compiled FFMPEG binaries with CUDA support for Windows here.
The code:
import cv2
import os
FFMPEG = 'D:/ffmpeg/ffmpeg.exe'
def hueloopvid(imagefile, outfile, n=256, loops=1, fps=24):
assert 0 < n <= 256
rgb = cv2.imread(imagefile, cv2.IMREAD_COLOR)
height, width = rgb.shape[:2]
hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV_FULL)
h = hsv[:, :, 0]
chsv = hsv.copy()
file_name = imagefile.split('/')[-1][::-1].split('.', 1)[1][::-1]
tmp_folder = os.environ['tmp']
for i in range(n):
chsv[..., 0] = (h round(i/n*256)) % 256
cv2.imwrite('{}/{}_{}.png'.format(tmp_folder, file_name, i), cv2.cvtColor(chsv, cv2.COLOR_HSV2BGR_FULL))
command = '{} -y -stream_loop {} -framerate {} -hwaccel cuda -hwaccel_output_format cuda -i {}/{}_%d.png -c:v h264_nvenc -b:v 5M -vf scale={}:{} {}'
os.system(command.format(FFMPEG, loops-1, fps, tmp_folder, file_name, width, height, outfile))
for i in range(n):
os.remove('{}/{}_{}.png'.format(tmp_folder, file_name, i))
if __name__ == '__main__':
hueloopvid("D:/images/Matplotlib/Misc/new_art_32.png", 'D:/hueloopvid.mp4', loops=6, fps=24)
It is tremendously faster than before:
In [12]: imagefile = "D:/images/Matplotlib/Misc/new_art_32.png"
In [13]: %%timeit
...: rgb = cv2.imread(imagefile, cv2.IMREAD_COLOR)
...: height, width = rgb.shape[:2]
...: hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV_FULL)
...: h = hsv[:, :, 0]
...: chsv = hsv.copy()
...: images = []
libpng warning: iCCP: known incorrect sRGB profile
...
23.4 ms ± 2.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [14]: rgb = cv2.imread(imagefile, cv2.IMREAD_COLOR)
...: height, width = rgb.shape[:2]
...: hsv = cv2.cvtColor(rgb, cv2.COLOR_BGR2HSV_FULL)
...: h = hsv[:, :, 0]
...: chsv = hsv.copy()
...: images = []
libpng warning: iCCP: known incorrect sRGB profile
In [15]: %%timeit
...: images = []
...: for i in range(256):
...: chsv[..., 0] = (h i) % 256
...: images.append(cv2.cvtColor(chsv, cv2.COLOR_HSV2BGR_FULL))
3.81 s ± 94.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [16]: %%timeit
...: chsv[..., 0] = (h 1) % 256
...: cv2.cvtColor(chsv, cv2.COLOR_HSV2BGR_FULL)
15.5 ms ± 689 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)