Nächstes Kapitel: Videos mit beweglichen Objekten mit Python
Videos aus Bildern erstellen¶
Dieses Kapitel unseres Python-Kurses beschäftigt sich mit Filmen und Bildern. Sie werden lernen wie Sie aus Bildern Filme erstellen können.
Ein Video aus mehreren Bildern¶
Sie haben also eine Menge Bilder und möchten daraus ein Video erstellen. Und zwar so, dass jedes Bild eine bestimmte Zeit lang gezeigt wird. Wir nehmen an, dass alle Ihre Bilder, - das können Fotos sein, die Sie in Ihrem letzten Urlaub gemacht haben oder Gemälde, die Sie zu einem bestimmten Zeitpunkt gemalt haben - in einem Verzeichnis sind. Wir gehen nun davon aus, dass alle Ihre Bilder die gleiche Größe haben, falls nicht, werden Sie auch lernen, wie Sie die Größe dieser Bilder ändern können. Für unsere Zwecke könnte es gut sein, dass die Bildnamen einem speziellen Benennungsschema folgen, d.h. sie beginnen alle mit dem gleichen Präfix (wie z.B. 'pic_') gefolgt von einer Zahl (z.B. '001', '002', '003', ...) in aufsteigender Reihenfolge. Der letzte Teil des Namens ist natürlich das Suffix, das dem eigentlichen Bildformat entsprechen muss.
Sie können alle benötigten Bilder und die erstellten Videos für den persönlichen Gebrauch herunterladen (das Copyright liegt bei Bernd Klein) unter python-courses: Material
Zunächst stellen wir eine Funktion 'seq_renaming' bereit, die alle Bilder in einem Ordner umbenennt nach dem zuvor beschriebenen Namensschema umbenennt:
import glob
import os
def seq_renaming(folder='.', prefix='pic_', start=0, num_length=3):
count = start
folder += '/'
for fname in glob.glob(folder + '*.jpg'):
suffix = fname[fname.rfind('.'):]
outstr = f"{folder}/{prefix}{count:0{num_length}d}{suffix}"
count += 1
os.rename(fname, outstr)
seq_renaming('flower_images')
Um unsere neu erstellte Funktion zu testen und zu demonstrieren, wie sie funktioniert, erstellen wir ein Testverzeichnis mit leeren Dateien, die Bilder simulieren:
import shutil
target_dir = 'test_dir'
if os.path.exists(target_dir):
# delete the existing directory
shutil.rmtree(target_dir)
# Create target_dir, because it doesn't exist so far
os.makedirs(target_dir)
names = ['apples', 'oranges', 'bananas', 'pears']
for name in names:
open(f'{target_dir}/{name}.jpg', 'w').write(' ')
print('Before renaming: ', os.listdir(target_dir))
# Let's rename the image names:
seq_renaming(target_dir)
print('After renaming: ', os.listdir(target_dir))
Unser erstes Video wird mit Hilfe der Bilder im Ordner flower_images erstellt. Schauen wir uns eines der Bilder an.
import matplotlib.pyplot as plt
import random
import numpy as np
import cv2
image = plt.imread("im2video/images/20210617_201910.jpg")
plt.imshow(image)
#cv2.imshow(image)
Das Bild ist in Radolfzell am Bodensee aufgenommen und im Hintergrund sind die Berge Hohentwiel und Hohenstoffeln zu sehen.
import matplotlib.pyplot as plt
import random
import numpy as np
import os
import cv2
import glob
def get_frame_size(image_path):
""" Reads an image and calculates
the width and length of the images,
which will be returned """
frame = cv2.imread(image_path)
height, width, layers = frame.shape
frame_size = (width, height)
return frame_size
def video_from_images(folder='.',
video_name = 'video.avi',
suffix='png',
prefix='pic_',
reverse=False,
length_of_video_in_seconds=None,
codec = cv2.VideoWriter_fourcc(*'DIVX')):
""" Die Funktion erstellt ein Video aus allen Bildern mit
dem Suffix 'suffix' (Standard ist 'png') und dem Präfix
'prefix'(Standard ist 'pic_') im Ordner 'folder'.
Wenn 'length_of_video_in_seconds' auf None gesetzt ist,
ist dies die Anzahl der Bilder in Sekunden.
Wenn ein positiver Wert angegeben wird, handelt es sich
um die Länge des Videos in Sekunden. Die Funktion geht davon
aus, dass die 'shape' des ersten Bildes gleich für alle
anderen Bilder ist. Ist dies nicht der Fall, so wird eine
Warnung ausgegeben und die Größe wird entsprechend angepasst! """
images = []
for fname in glob.glob(f'{folder}/{prefix}*{suffix}'):
images.append(fname)
images.sort(reverse=reverse)
if length_of_video_in_seconds is None:
# jedes Bild wird für eine Sekunde angezeigt
length_of_video_in_seconds = len(images)
# Es wird die Anzahl der Frames pro Sekunde berechnet:
frames_per_second = len(images) / length_of_video_in_seconds
frame_size = get_frame_size(images[0])
video = cv2.VideoWriter(video_name,
codec,
frames_per_second,
frame_size)
for image in images:
im = cv2.imread(image)
height, width, layers = im.shape
if (width, height) != frame_size:
print(f'Warning: {image} resized from {(width, height)} to {frame_size}')
im = cv2.resize(im, frame_size)
video.write(im)
cv2.destroyAllWindows()
video.release()
video_from_images('im2video/flower_images', 'flowers.avi', 'jpg')
Das Ergebnis ist in flowers.avi zu sehen!
Ein Video aus einem Bild¶
Wir verwenden nun ein Bild als Grundlage, um eine Bildfolge zu erstellen. Wir tun dies, indem wir die Farben eines Teils (einer Tile / Kachel) des Bildes ändern. Wir können uns das Bild als ein Schachbrett mit n Zeilen und m Spalten vorstellen. Die Funktion random_tile kann verwendet werden, um die obere linke und untere rechte Ecke einer zufällig ausgewählten Kachel zu liefern. Wir übergeben das Bild an den Parameter 'img' und die Anzahl der Zeilen und Spalten werden durch height_factor, width_factor definiert:
import random
def random_tile(img, height_factor, width_factor):
""" returns the top left and bottom right corner
of a random tile in an image. The tile sizes are
determined by height_factor, width_factor, the division
factors """
height, width, colours = img.shape
tiles = height_factor, width_factor
tile_height, tile_width = height / height_factor, width / width_factor
tile_upper_left_row = int(round(random.randint(0, height_factor) * tile_height, 0))
tile_upper_left_column = int(round(random.randint(0, width_factor) * tile_width, 0))
top_left = tile_upper_left_row, tile_upper_left_column
bottom_right_row = int(round(tile_upper_left_row + tile_height, 0))
bottom_right_column = int(round(tile_upper_left_column + tile_width, 0))
bottom_right = bottom_right_row, bottom_right_column
return top_left, bottom_right
Im folgenden Code lesen wir ein Bild ein und rufen die Funktion random_tile einige Male auf:
import matplotlib.pyplot as plt
import random
import numpy as np
imag = cv2.imread("im2video/images/20210617_201910.jpg")
print(f'{imag.shape=}')
for i in range(4):
tile = random_tile(imag, 4, 5)
print(tile)
Die Funktion 'colorize_tile' kann verwendet werden, um die Farbe einer Kachel zufällig zu ändern. Dies geschieht durch Hinzufügen eines zufälligen Farbwert zu jedem Pixel der Kachel hinzugefügt wird.
def colorize_tile(imag, top_left, bottom_right, max_col_val):
color = np.random.randint(0, max_col_val, (3,))
r1, r2 = top_left[0], bottom_right[0]
c1, c2 = top_left[1], bottom_right[1]
imag[r1:r2, c1:c2] = imag[r1:r2, c1:c2] + color
Wir demonstrieren die Arbeitsweise von colorize_tile im folgenden Code. Schauen wir uns zunächst die Bilder an. Wir verwenden plt.imread anstelle von cv2.imread, weil cv2.imshow ein externes Fenster im Jupyter-Notebook öffnet, das wir zur Erstellung dieser Website verwenden. Außerdem sind die Bilder von cv2.imread im BGR-Format, während plt.imread RGB-Bilder liefert. Das bedeutet, dass sie nicht kompatibel sind.
import matplotlib.pyplot as plt
import random
import numpy as np
imag = plt.imread("im2video/images/20230215_161843.jpg")
plt.imshow(imag)
Lassen Sie uns jetzt mit colorize_tile experimentieren:
imag = cv2.imread("im2video/images/20230215_161843.jpg")
for i in range(6):
top_left, bottom_right = random_tile(imag, 4, 5)
colorize_tile(imag, top_left, bottom_right, max_col_val=100)
# Wir speichern das Ergebnis in 'experiments.png'
cv2.imwrite('experiments.png', imag)
# Wegen Einschränkungen des Jupyter-Noebook
# müssen wir plt zum Lesen und Schreiben verwenden:
plt.imshow(plt.imread('experiments.png'))
Wir werden nun 100 Bilder in einem Ordner mit dem Namen "video_pics" erstellen. Das Originalbild wird unter dem Namen pic_101.png in diesem Ordner gespeichert. Das Bild mit dem Namen "pic_001.png" ist das Bild mit den meisten geänderten Kacheln:
pics_dir='im2video/video_pics'
n = 100
im = cv2.imread("im2video/images/20210617_201910.jpg")
fname = f"{pics_dir}/pic_{n+1:03d}.png"
cv2.imwrite(fname, im)
for i in range(n, 0, -1):
top_left, bottom_right = random_tile(im, 5, 6)
colorize_tile(im, top_left, bottom_right, 100)
fname = f"{pics_dir}/pic_{i:03d}.png"
cv2.imwrite(fname, im)
Schauen wir uns das endgültige Bild "pic_001.png" der erstellten Bilder an:
plt.imshow(plt.imread(f'{pics_dir}/pic_001.png'))
Wir werden nun wieder unsere Funktion video_from_images verwenden, um aus diesen neu erstellten Bildern ein Video mit dem Namen 'rand_squares.avi' zu erstellen:
video_from_images(pics_dir,
'random_squares.avi',
'png',
length_of_video_in_seconds = 280)
Ein Video mit Kacheln (Tiles) aus mehreren Bildern¶
Wir werden nun Bilder erstellen, wobei wir ein Startbild verwenden und zufällig Kacheln durch die entsprechenden Kacheln anderer Bilder ersetzen. Die Funktion 'tile_from_image' ersetzt den Inhalt einer Kachel eines Bildes1 durch den Inhalt der entsprechenden Kachel eines anderen Bildes 'image2'. Beide Bilder müssen die gleiche Form haben:
def tile_from_image(image1, image2, top_left, bottom_right):
""" Nimmt eine Kachel aus Bild2 und ersetzen den
entsprechenden Bereich in Bild1 """
r1, r2 = top_left[0], bottom_right[0]
c1, c2 = top_left[1], bottom_right[1]
image1[r1:r2, c1:c2] = image2[r1:r2, c1:c2]
Im folgenden Code verwenden wir Bilder, die aus der Bodensee-Region stammen:
images_dir = 'im2video/bodensee_area'
target_dir = 'video_pics_bodensee'
if not os.path.exists(target_dir):
# target_dir erstellen, da es bisher nicht existiert
os.makedirs(target_dir)
n_of_images = 120
import random
images = []
for fname in glob.glob(f'{images_dir}/*.jpg'):
images.append(fname)
imags = [cv2.imread(fname) for fname in images]
start_image = imags[0]
for i in range(n_of_images, 0, -1):
top_left, bottom_right = random_tile(start_image, 5, 6)
image2 = random.choice(imags)
tile_from_image(start_image, image2, top_left, bottom_right)
fname = f"{target_dir}/pic_{i:03d}.png"
cv2.imwrite(fname, start_image)
video_from_images('video_pics_bodensee',
'bodensee.avi',
'png',
length_of_video_in_seconds = 60)
Ich habe diese Technik verwendet, um ein Video für eine Musikkomposition von mir zu erstellen. Das Video ist bei YouTube zu finden:
Die Musik ist in beiden Videos die gleiche. Ein Musikstück für Streichorchester und Solocello. Die Tonspur habe ich mit dem Linux-Programm kdenlive hinzugefügt.
Nächstes Kapitel: Videos mit beweglichen Objekten mit Python