Videos mit beweglichen Objekten

Spiral like a Sun

Die Idee hinter diesem Kapitel unserer Python-Kurse ist es, einen Film aus einem oder mehreren Bildern zu erstellen, in dem ein oder mehrere Objekte auf statischen Bildern herumbewegt werden. Wir werden feste Objekte oder durchscheinende Wasserzeichenobjekte über Bilder bewegen. Wir haben ein Bild mit einer Wasserzeichenstruktur für unser Kapitel über Dekoratoren und Dekoration erstellt. Sie können es ganz oben auf der Seite sehen: Ein "kaufmännisches Und" (in der Python-Gemeinschaft besser bekannt als das Dekorator-Zeichen. Sie können auch einen Kurs finden, wie man Bilder mit Wasserzeichen wie dieses erstellt mit Python, Numpy, Scipy und Matplotlib in dem Kapitel Bildverarbeitungstechniken. Darin wird der gesamte Prozess der Erstellung unseres Dekorators und des Bildes mit dem Zeichen, d.h. der mit Wasserzeichen versehenen Bilder, erklärt. Es ist also eine gute Idee, diese Kapitel zu lesen, bevor man weitermacht.

Technische Anmerkung: opencv muss für die folgenden Python-Code-Beispiele installiert sein.

Interessante Objekte mit Matplotlib erstellen

Wir beginnen mit der Erstellung einiger interessanter Objekte, die wir in den Videos, die wir erstellen wollen, als bewegliche Objekte verwenden können. Wir beginnen mit der Erstellung eines Verzeichnisses, in dem die Objekte gespeichert werden:

In [1]:
import os
import shutil
target_dir = 'images4video'
watermarks_dir = f"{target_dir}/watermarks_dir"
if os.path.exists(watermarks_dir):
    # delete the existing directory
    shutil.rmtree(watermarks_dir)
# Create target_dir, because it doesn't exist so far
os.makedirs(watermarks_dir)

Wir verwenden im folgenden Code den Parameter bbox_inches="tight" in savefig. Wenn dieser Parameter auf "tight" gesetzt ist, versucht matplotlib, alle zusätzlichen Leerzeichen oder Padding an den Rändern der Abbildung zu entfernen, so dass nur der eigentliche Inhalt der Abbildung gespeichert wird. Dies kann nützlich sein, wenn man eine Abbildung mit minimalen Rändern oder Auffüllungen speichern will.

In [2]:
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-2, 2, 1000)
y1 = np.sqrt(1-(abs(x)-1)**2)
y2 = -3 * np.sqrt(1-(abs(x)/2)**0.5)

fig, ax = plt.subplots()

ax.fill_between(x, y1, color='red')
ax.fill_between(x, y2, color='red')
ax.axis(xmin=-2.3, xmax=2.3)
ax.axis('off')
#ax.xlim([-2.5, 2.5])
txt2include = "python-kurs.eu"
ax.text(0, -0.4,
         txt2include, 
         fontsize=24, 
         fontweight='bold',
         color='orange', 
         horizontalalignment='center')

plt.savefig(f"{watermarks_dir}/heart.png", bbox_inches="tight")

Wir erzeugen nun ein Bild mit einer schwarz-weißen Spirale:

In [3]:
import numpy as np
import matplotlib.pyplot as plt

theta = np.radians(np.linspace(0, 360*5, 1000))
r = theta**2
x_2 = r*np.cos(theta)
y_2 = r*np.sin(theta)
plt.figure(figsize=[5, 5])
plt.gcf().set_size_inches(10, 10)
plt.plot(x_2, y_2, linewidth=50.5, color='black')
plt.axis('off')
plt.savefig(f"{watermarks_dir}/spiral.png", bbox_inches="tight")
plt.show()

Wie sieht es mit Farben aus? Eine andere Spirale, aber jetzt in Farben:

In [4]:
import matplotlib.pyplot as plt
import numpy as np

theta = np.arange(0, 8*np.pi, 0.1)
a, b = 1, 0.5

for dt in np.arange(0, 2*np.pi, np.pi/2.0):
    x = a*np.cos(theta + dt)*np.exp(b*theta)
    y = a*np.sin(theta + dt)*np.exp(b*theta)
    dt = dt + np.pi/4.0
    x2 = a*np.cos(theta + dt)*np.exp(b*theta)
    y2 = a*np.sin(theta + dt)*np.exp(b*theta)
    xf = np.concatenate((x, x2[::-1]))
    yf = np.concatenate((y, y2[::-1]))
    p1 = plt.fill(xf, yf)

plt.axis('equal')
plt.axis('off')
plt.tight_layout()
plt.savefig(f'{watermarks_dir}/coloured_spiral.png', bbox_inches="tight")

Größe eines Bildes

Die folgende Funktion gibt die Größe eines Bildes als Tupel (Breite, Höhe) zurück. opencv muss installiert sein, damit Sie das cv2-Modul verwenden können:

In [5]:
import cv2

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

get_frame_size(f"{watermarks_dir}/spiral.png")
Out[5]:
(794, 790)

Zufällige Kachel (tile) im Bild

Jetzt definieren wir eine Funktion random_tile, die die Koordinaten (obere linke Ecke und untere rechte Ecke) einer Kachel im Bild img zurückgibt. Die Größe dieser Kachel ist gleich der Größe des Objektes bzw. Wasserzeichenbildes, das der Funktion übergeben wird:

In [6]:
import random 

def random_tile(img, watermark_image):
    """ returns the top left and bottom right corner
    of a random tile in an image. The size correspondents to
    the size of the watermake image """
    height, width, colours = img.shape
    w_height, w_width, colours = watermark_image.shape
    tile_upper_left_row = int(round(random.randint(0, height - w_height), 0))
    tile_upper_left_column = int(round(random.randint(0, width - w_width), 0))
    top_left = tile_upper_left_row, tile_upper_left_column
    bottom_right_row = int(round(tile_upper_left_row + w_height, 0))
    bottom_right_column = int(round(tile_upper_left_column + w_width, 0))
    bottom_right = bottom_right_row, bottom_right_column
    return top_left, bottom_right

Wir werden cv2.imread verwenden, um ein Bild einzulesen, das wir in unseren Beispielen verwenden wollen. Wir könnten auch cv2.imshow verwenden, aber das öffnet ein externes Fenster im Jupyter-Notebook, das wir zum Erstellen dieser Website verwenden. Das ist der Grund, warum wir plt.imshow verwenden, um das Bild zu betrachten. Wir müssen die Farben spiegeln, bevor wir plt.imshow verwenden können, da wir mit cv2.imread ein BGR-Bild (blau, grün, rot) erhalten, während plt.imread RGB-Bilder liefert.

In [7]:
import matplotlib.pyplot as plt
import random
import numpy as np
import cv2

image = cv2.imread("images4video/paths/pic_004.jpg")
rgb_img = image[:, :, ::-1]  # BGR ---> RGB
#plt.axis('off')
plt.imshow(rgb_img)
Out[7]:
<matplotlib.image.AxesImage at 0x7fcaa45ed750>
In [8]:
spiral = cv2.imread(f"{watermarks_dir}/spiral.png")
rgb_spiral = spiral[:, :, ::-1]  # BGR ---> RGB
plt.imshow(rgb_spiral)
Out[8]:
<matplotlib.image.AxesImage at 0x7fcaa4455210>

Wir testen unsere Funktion random_tile mit diesen beiden Bildern:

In [9]:
top_left, bottom_right = random_tile(image, spiral)
print(f"{top_left=}, {bottom_right=}")
print(f"{spiral.shape=}, {image.shape=}")
top_left=(577, 2569), bottom_right=(1367, 3363)
spiral.shape=(790, 794, 3), image.shape=(1800, 4000, 3)

Es ist zu erkennen, dass die Abmessungen der zufälligen Kachel dem Wasserzeichenbild entsprechen:

In [10]:
height_of_tile = bottom_right[0] - top_left[0]
width_of_tile = bottom_right[1] - top_left[1]
print( (height_of_tile, width_of_tile) == spiral.shape[:2])
True

Schauen wir uns ein weiteres Bild an. Diesmal geht es in den Hafen von Konstanz am Bodensee:

In [11]:
import matplotlib.pyplot as plt
import random
import numpy as np
import cv2

image2 = cv2.imread(f"{target_dir}/bodensee_area/konstanz.jpg")
rgb_img2 = image2[:, :, ::-1]  # BGR ---> RGB
plt.imshow(rgb_img2)
Out[11]:
<matplotlib.image.AxesImage at 0x7fcaa44af150>

Wasserzeichen mit zusätzlichem Bild

Mit Hilfe der Funktion watermark_tile können wir ein Wasserzeichen auf den Bereich der Kachel setzen, der durch die Parameter oben_links, unten_rechts. Das Wasserzeichen soll schwarz und weiß sein. Wenn das Wasserzeichen schwarz ist, werden die entsprechenden Pixel von imag2 genommen, wenn es weiß ist, werden die Pixel von imag1 genommen.

In [12]:
def watermark_tile(imag1, imag2, watermark_imag, top_left, bottom_right):
    """ The pixels of imag2 are used when the watermark_imag is black """
    r1, r2 = top_left[0], bottom_right[0]
    c1, c2 = top_left[1], bottom_right[1]
    tile = imag1[r1:r2, c1:c2] 
    tile[:] = np.where(watermark_imag>(1, 1, 1), imag1[r1:r2, c1:c2], imag2[r1:r2, c1:c2])

Wir testen diese Funktion nun mit den Bildern 'image', 'image2' und 'spiral' als Wasserzeichenbild:

In [13]:
watermark_tile(image, image2, spiral,  top_left, bottom_right)
In [14]:
rgb_img = image[:, :, ::-1]  # BGR ---> RGB
plt.imshow(rgb_img)
Out[14]:
<matplotlib.image.AxesImage at 0x7fcaa4340050>

Wasserzeichen auf Kachel

Nun wollen wir ein Wasserzeichen direkt auf das Bild "imag1" setzen. Wir verwenden ein farbiges Bild als Wasserzeichen.

In [15]:
def object_on_tile(imag1, object_image, top_left, bottom_right):
    """ The object is put over the image """
    r1, r2 = top_left[0], bottom_right[0]
    c1, c2 = top_left[1], bottom_right[1]
    #print(f'{object_image=}')
    #tile = imag1[r1:r2, c1:c2] 
    imag1[r1:r2, c1:c2] = np.where(object_image>(250, 250, 250), 
                                   imag1[r1:r2, c1:c2], 
                                   object_image)
In [16]:
watermark_image = cv2.imread(f"{watermarks_dir}/coloured_spiral.png")
rgb_watermark_image = watermark_image[:, :, ::-1]  # BGR ---> RGB
plt.imshow(rgb_watermark_image)
Out[16]:
<matplotlib.image.AxesImage at 0x7fcaa4354ad0>
In [17]:
watermark_image.shape
Out[17]:
(470, 630, 3)
In [18]:
image = cv2.imread(f"{target_dir}/bodensee_area/boat.jpg")
plt.imshow(image[:, :, ::-1])
Out[18]:
<matplotlib.image.AxesImage at 0x7fcaa43b4ad0>