HOWTO · Python

Bildmaskierung in OpenCV

In diesem Artikel werden wir sehen, wie man eine Bildmaskierung mit Bitwise AND in OpenCV Python durchführt.

Auf dieser Seite

Mit der Bildmaskierung behalten Sie einen Teil eines Bildes bei und kombinieren ihn mit einem anderen Bild. In OpenCV basiert die Maskierung auf der bitweisen AND-Operation: Die Funktion cv.bitwise_and() berechnet das elementweise bitweise UND zweier Arrays, und eine optionale Maske entscheidet, welche Pixel dieses Ergebnisses in der Ausgabe landen.

Dieser Artikel führt durch den üblichen Maskierungs-Workflow in Python. Anhand eines Autofotos und einer Logo-Datei bereiten wir eine Region of Interest (ROI) vor, erstellen aus dem Logo eine Binärmaske und ihre Umkehrung, wenden beide Masken an und fügen das Logo in die obere linke Ecke des Autobildes ein.

So wendet cv.bitwise_and() eine Maske an

cv.bitwise_and() ist eine Funktion auf Modulebene im Paket cv2, keine Methode eines Bildobjekts. Ihre Signatur in Python sieht so aus:

cv.bitwise_and(src1, src2[, dst[, mask]])

  • src1 und src2 sind die beiden Eingabearrays. Sie müssen dieselbe Größe und denselben Datentyp haben. Um ein einzelnes Bild zu maskieren, übergeben Sie für beide dasselbe Array.
  • dst ist ein optionales Ausgabearray derselben Größe und desselben Typs; wenn Sie es weglassen, reserviert die Funktion den Speicher und gibt das Ergebnis zurück.
  • mask ist ein optionales 8-Bit-Einkanal-Array in der Größe der Quellen. Wo die Maske einen Wert ungleich null enthält, ist das Ausgabepixel src1 & src2; wo sie 0 enthält, ist das Ausgabepixel 0. Die Maske wählt also aus, welche Pixel der Operation im Ergebnis erscheinen – genau diese Auswahl ist die Grundidee der Bildmaskierung.

Führen Sie dieses Beispiel aus: Es ruft cv.bitwise_and() auf, wenn OpenCV installiert ist, und führt andernfalls dasselbe elementweise UND mit reinen Python-Listen aus. Der unveränderte Beispielcode gibt die berechneten Pixelwerte als Liste aus:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import importlib.util

if importlib.util.find_spec("cv2") is not None:
    import cv2 as cv
    import numpy as np

    pixels = np.array([[200, 120, 60], [30, 90, 250]], dtype=np.uint8)
    mask = np.array([[255, 255, 0], [0, 255, 255]], dtype=np.uint8)
    result = cv.bitwise_and(pixels, pixels, mask=mask).tolist()
else:
    pixels = [[200, 120, 60], [30, 90, 250]]
    mask = [[255, 255, 0], [0, 255, 255]]
    result = [[v & m for v, m in zip(pr, mr)] for pr, mr in zip(pixels, mask)]

print(result)
[[200, 120, 0], [0, 90, 250]]

Jedes Pixel unter einem Maskenwert von 255 bleibt erhalten; jedes Pixel unter einem Wert von 0 wird gelöscht. In einer echten Maske entstehen die Werte durch Schwellwertbildung statt von Hand – das ist der nächste Schritt.

Die Region of Interest des Bildes vorbereiten

Ziel ist es, das Logo in der oberen linken Ecke des Autobildes zu platzieren. Der Code liest daher beide Dateien und schneidet eine passende Region aus dem Autobild heraus:

  • car = cv.imread("car.jpg") und logo = cv.imread("logo.png") laden die Bilder als BGR-Arrays. cv.imread gibt None zurück, wenn eine Datei nicht geladen werden kann; prüfen Sie daher beide Ergebnisse, bevor Sie fortfahren.
  • logo.shape liefert (rows, cols, channels); die ersten beiden Einträge geben die Abmessungen des Logos an.
  • roi = car[0:rows, 0:cols] erzeugt eine NumPy-View der oberen linken Ecke des Autobildes, die genau der Logogröße entspricht. Da es sich um eine View handelt, aktualisiert das Zurückschreiben des zusammengesetzten Bildes in car[0:rows, 0:cols] das Originalbild.

Das Logo muss in das Autobild passen. Ist das Logo breiter oder höher als das Foto, verkleinert sich der Slice stillschweigend, und cv.bitwise_and(roi, roi, mask=mask_inv) schlägt später mit einem Größenfehler fehl, weil mask_inv zur Größe des Logos passt, nicht zur geschrumpften ROI. Skalieren Sie das Logo daher zuerst oder wählen Sie eine passende Platzierung. Beachten Sie außerdem: cv.imread lädt ein PNG standardmäßig als dreikanaliges BGR-Bild – der Alphakanal geht verloren –, sodass Logo und ROI am Ende dieselbe Kanalzahl haben.

Eine Binärmaske für das Logo erstellen

Aus dem Logo entstehen zwei Masken: mask, die die Pixel des Logos selbst auswählt, und mask_inv, die alles andere auswählt.

  • gray_logo = cv.cvtColor(logo, cv.COLOR_BGR2GRAY) wandelt das Logo in Graustufen um; mehr zur Farbkonvertierung finden Sie unter der Funktion cv.cvtColor().
  • _, mask = cv.threshold(gray_logo, 10, 255, cv.THRESH_BINARY) erzeugt die Binärmaske: Pixel heller als 10 werden zu 255, dunklere Pixel zu 0. Bei einem Logo auf dunklem oder schwarzem Hintergrund isoliert dies die Form des Logos; passen Sie die 10 an Ihren eigenen Hintergrund an.
  • mask_inv = cv.bitwise_not(mask) kippt jedes Bit und liefert die komplementäre Maske, die stattdessen den Hintergrundbereich auswählt.

Logo und Region of Interest kombinieren

Mit beiden Masken teilen zwei maskierte UND-Operationen die Arbeit auf: cv.bitwise_and(roi, roi, mask=mask_inv) behält den Autohintergrund und schwärzt die Logoform, während cv.bitwise_and(logo, logo, mask=mask) nur die Logopixel behält. cv.add() summiert dann beide Ergebnisse zum Composite, das zurück in das Autobild geschrieben wird.

Das vollständige Skript unten führt jeden Schritt aus. Wenn OpenCV oder die Bilddateien nicht vorhanden sind, gibt es eine kurze Meldung aus und endet ohne Traceback. Die im unveränderten Code verbleibenden englischen Meldungen weisen auf ein fehlendes Paket, fehlende Bilddateien oder ein zu großes Logo hin und bestätigen zuletzt das Schreiben von car-with-logo.png:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import importlib.util

if importlib.util.find_spec("cv2") is None:
    print("This example needs OpenCV installed: pip install opencv-python.")
    raise SystemExit

import cv2 as cv

car = cv.imread("car.jpg")
logo = cv.imread("logo.png")
if car is None or logo is None:
    print("car.jpg and logo.png must sit next to this script.")
    raise SystemExit

rows, cols = logo.shape[:2]
if rows > car.shape[0] or cols > car.shape[1]:
    print("The logo is larger than the car image; resize it first.")
    raise SystemExit

roi = car[0:rows, 0:cols]

gray_logo = cv.cvtColor(logo, cv.COLOR_BGR2GRAY)
_, mask = cv.threshold(gray_logo, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)

background = cv.bitwise_and(roi, roi, mask=mask_inv)
foreground = cv.bitwise_and(logo, logo, mask=mask)

composite = cv.add(background, foreground)
car[0:rows, 0:cols] = composite

cv.imwrite("car-with-logo.png", car)
print("Wrote car-with-logo.png")

Das Skript speichert das fertige Bild mit cv.imwrite(), sodass es auch auf Rechnern ohne Bildschirm läuft; die Anzeigeaufrufe folgen im nächsten Abschnitt.

Das Maskierungsergebnis prüfen

Das zusammengesetzte Bild platziert das Logo in der oberen linken Ecke des Autobildes, während der Rest des Fotos unverändert bleibt:

Autobild mit dem in die obere linke Ecke eingefügten Logo

Um zu sehen, was jede Stufe erzeugt hat, zeigen oder speichern Sie die Zwischenergebnisse – cv.imshow("mask", mask), cv.imshow("mask_inv", mask_inv), cv.imshow("background", background) und cv.imshow("foreground", foreground) gefolgt von cv.waitKey(0) zeigen sie auf dem Bildschirm, während cv.imwrite() sie auf Rechnern ohne Anzeige auf die Festplatte schreibt. cv.imshow() benötigt eine GUI-Anzeige; deshalb speichert das obige Skript stattdessen Dateien.

In mask sollten Sie die weiße Silhouette des Logos auf Schwarz sehen, und mask_inv ist ihr Spiegelbild. background zeigt die Autoecke mit ausgeschnittener Logoform, foreground nur das Logo. Das ist das gesamte Maskierungsmuster: einmal den Schwellwert bilden, cv.bitwise_and() zweimal anwenden, die Ergebnisse addieren – und die ROI trägt das zusammengefügte Bild.