it-swarm.com.de

Entfernen Sie störende kleine Rauschinseln in einem Bild - Python OpenCV

Ich versuche, Hintergrundgeräusche von einigen meiner Bilder loszuwerden. Dies ist das ungefilterte Bild.

Zum Filtern habe ich diesen Code verwendet, um eine Maske für das zu generieren, was im Bild verbleiben soll:

 element = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
 mask = cv2.erode(mask, element, iterations = 1)
 mask = cv2.dilate(mask, element, iterations = 1)
 mask = cv2.erode(mask, element)

Mit diesem Code und wenn ich die unerwünschten Pixel aus dem Originalbild ausblende, erhalte ich Folgendes: 

Wie Sie sehen, sind alle winzigen Punkte im mittleren Bereich verschwunden, aber auch viele aus dem dichteren Bereich. Um die Filterung zu reduzieren, habe ich versucht, den zweiten Parameter von getStructuringElement() auf (1,1) zu ändern. Dabei erhalte ich das erste Bild, als wäre nichts gefiltert worden.

Kann ich irgendeinen Filter zwischen diesen beiden Extremen anwenden?

Kann mir außerdem jemand erklären, was getStructuringElement() genau macht? Was ist ein "Strukturierungselement"? Was macht es und wie wirkt sich seine Größe (der zweite Parameter) auf die Filterstufe aus?

29
annena

Viele Ihrer Fragen ergeben sich aus der Tatsache, dass Sie nicht sicher sind, wie die morphologische Bildverarbeitung funktioniert, aber wir können Ihre Zweifel zerstreuen. Sie können das Strukturierungselement als die "Grundform" interpretieren, mit der verglichen werden soll. 1 im Strukturierungselement entspricht einem Pixel, das Sie in dieser Form betrachten möchten, und 0 ist eines, das Sie ignorieren möchten. Es gibt verschiedene Formen, z. B. rechteckig (wie Sie mit MORPH_RECT herausgefunden haben), ellipse, kreisförmig usw.

Als solches gibt cv2.getStructuringElement ein Strukturierungselement für Sie zurück. Der erste Parameter gibt den gewünschten Typ an, und der zweite Parameter gibt die gewünschte Größe an. In Ihrem Fall möchten Sie ein 2 x 2 "Rechteck" ... das ist wirklich ein Quadrat, aber das ist in Ordnung.

In einem eher bastardisierten Sinne verwenden Sie das Strukturierungselement und scannen von links nach rechts und von oben nach unten in Ihrem Bild und greifen auf Pixelnachbarschaften zu. Jedes Pixelviertel hat seinen Mittelpunkt genau bei dem Pixel von Interesse, das Sie betrachten. Die Größe jeder Pixelumgebung entspricht der Größe des Strukturierungselements.

Erosion

Für eine Erosion untersuchen Sie alle Pixel in einer Pixelumgebung, die das Strukturierungselement berühren. Wenn jedes Nicht-Null-Pixel ein Strukturelementpixel berührt, das 1 ist, dann ist das Ausgabepixel in der entsprechenden Mittelposition in Bezug auf die Eingabe 1 Wenn mindestens ein Nicht-Null-Pixel ein Strukturierungspixel mit dem Wert 1 nicht berührt , ist die Ausgabe 0.

In Bezug auf das rechteckige Strukturierungselement müssen Sie sicherstellen, dass jedes Pixel im Strukturierungselement ein Pixel in Ihrem Bild berührt, das nicht Null ist, um eine Pixelumgebung zu erhalten. Ist dies nicht der Fall, ist die Ausgabe 0, andernfalls 1. Dies eliminiert effektiv kleine Störbereiche und verringert auch den Bereich von Objekten geringfügig.

Die Größenfaktoren, bei denen je größer das Rechteck ist, desto stärker wird die Verkleinerung ausgeführt. Die Größe des Strukturierungselements ist eine Grundlinie, an der Objekte, die kleiner als dieses rechteckige Strukturierungselement sind, als gefiltert und nicht in der Ausgabe enthalten betrachtet werden können. Grundsätzlich entspricht die Auswahl eines rechteckigen 1 x 1-Strukturierungselements dem Eingabebild selbst, da dieses Strukturierungselement alle Pixel in sich fasst, da es sich bei dem Pixel um die kleinstmögliche Darstellung von Informationen in einem Bild handelt.

Erweiterung

Dilatation ist das Gegenteil von Erosion. Wenn mindestens ein Nicht-Null-Pixel ein Pixel im Strukturierungselement berührt, das 1 ist, ist die Ausgabe 1, andernfalls ist die Ausgabe 0. Sie können sich dies als leicht vergrößerte Objektbereiche und größere kleine Inseln vorstellen.

Die Größe impliziert, dass je größer das Strukturierungselement ist, desto größer werden die Flächen der Objekte und desto größer werden die isolierten Inseln.


Was Sie tun, ist eine Erosion, gefolgt von einer Erweiterung. Dies ist eine sogenannte Öffnungsoperation . Der Zweck dieses Vorgangs besteht darin, kleine Rauschinseln zu entfernen, während versucht wird, die Bereiche der größeren Objekte in Ihrem Bild beizubehalten. Durch die Erosion werden diese Inseln entfernt, während durch die Ausdehnung die größeren Objekte auf ihre ursprüngliche Größe zurückgewachsen sind.

Du folgst ihm aus irgendeinem Grund wieder mit einer Erosion, die ich nicht ganz verstehe, aber das ist in Ordnung.


Was ich persönlich tun würde, ist, zuerst eine Abschlussoperation durchzuführen, die eine Erweiterung gefolgt von einer Erosion ist. Durch das Schließen können Bereiche, die nahe beieinander liegen, zu einem einzigen Objekt zusammengefasst werden. Als solches sehen Sie, dass es einige größere Bereiche gibt, die nahe beieinander liegen, die wahrscheinlich verbunden werden sollten, bevor wir etwas anderes tun. Als solches würde ich zuerst ein Schließen machen, dann ein Öffnen , damit wir die isolierten lauten Bereiche entfernen können. Beachten Sie, dass ich die Größe des schließenden Strukturierungselements vergrößern werde , um sicherzustellen, dass in der Nähe befindliche Pixel und die Größe des öffnenden Strukturierungselements kleiner , damit ich keine größeren Bereiche versehentlich entfernen möchte.

Sobald Sie dies tun, würde ich zusätzliche Informationen mit dem Originalbild maskieren, so dass Sie die größeren Bereiche intakt lassen, während die kleinen Inseln verschwinden.

Verwenden Sie cv2.morphologyEx , anstatt eine Erosion, gefolgt von einer Dilatation, oder eine Dilatation, gefolgt von einer Erosion, zu verketten, wobei Sie MORPH_OPEN und MORPH_CLOSE als die Fahnen.

Aus diesem Grund würde ich dies persönlich tun, vorausgesetzt, Ihr Bild heißt spots.png:

import cv2
import numpy as np

img = cv2.imread('spots.png')
img_bw = 255*(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) > 5).astype('uint8')

se1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
se2 = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
mask = cv2.morphologyEx(img_bw, cv2.MORPH_CLOSE, se1)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, se2)

mask = np.dstack([mask, mask, mask]) / 255
out = img * mask

cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('output.png', out)

Der obige Code ist ziemlich selbsterklärend. Zuerst lese ich das Bild ein und konvertiere es dann in Graustufen und Schwellenwerte mit einer Intensität von 5, um eine Maske der als Objektpixel betrachteten Objekte zu erstellen. Dies ist ein ziemlich sauberes Bild und daher scheint alles, was größer als 5 ist, funktioniert zu haben. Für die Morphologieroutinen muss ich das Bild in uint8 konvertieren und die Maske auf 255 skalieren. Als Nächstes erstellen wir zwei Strukturierungselemente - eines, das ein 5 x 5-Rechteck für den Abschlussvorgang ist, und eines, das ist 2 x 2 für den Öffnungsvorgang. Ich führe cv2.morphologyEx zweimal für die Öffnungs- bzw. Schließvorgänge für das Bild mit Schwellenwert aus.

Sobald ich das mache, staple ich die Maske, sodass sie zu einer 3D-Matrix wird, und dividiere sie durch 255, sodass sie zu einer Maske von [0,1] wird. Dann multiplizieren wir diese Maske mit dem Originalbild, sodass wir die Maske greifen können Originalpixel des Bildes zurück und Beibehalten, was von der Maskenausgabe als echtes Objekt angesehen wird.

Der Rest ist nur zur Veranschaulichung. Ich zeige das Bild in einem Fenster und speichere es auch in einer Datei mit dem Namen output.png. Sie soll Ihnen zeigen, wie das Bild in diesem Beitrag aussieht.

Ich bekomme das:

enter image description here

Denken Sie daran, dass es nicht perfekt ist, aber es ist viel besser als vorher. Sie müssen mit den Größen der Strukturierungselemente herumspielen, um etwas zu erhalten, das Sie als eine gute Ausgabe betrachten, aber dies ist mit Sicherheit genug, um Ihnen den Einstieg zu erleichtern. Viel Glück!


C++ Version

Es gab einige Anfragen, den Code, den ich oben geschrieben habe, mit OpenCV in die C++ - Version zu übersetzen. Ich habe mich endlich daran gemacht, eine C++ - Version des Codes zu schreiben, und diese wurde unter OpenCV 3.1.0 getestet. Der Code dafür ist unten. Wie Sie sehen können, ist der Code dem in der Python Version) sehr ähnlich. Ich habe jedoch cv::Mat::setTo auf einer Kopie von verwendet Originalbild und setzen Sie alles, was nicht Teil der endgültigen Maske war, auf 0. Dies entspricht einer elementweisen Multiplikation in Python.

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char *argv[])
{
    // Read in the image
    Mat img = imread("spots.png", CV_LOAD_IMAGE_COLOR);

    // Convert to black and white
    Mat img_bw;
    cvtColor(img, img_bw, COLOR_BGR2GRAY);
    img_bw = img_bw > 5;

    // Define the structuring elements
    Mat se1 = getStructuringElement(MORPH_RECT, Size(5, 5));
    Mat se2 = getStructuringElement(MORPH_RECT, Size(2, 2));

    // Perform closing then opening
    Mat mask;
    morphologyEx(img_bw, mask, MORPH_CLOSE, se1);
    morphologyEx(mask, mask, MORPH_OPEN, se2);

    // Filter the output
    Mat out = img.clone();
    out.setTo(Scalar(0), mask == 0);

    // Show image and save
    namedWindow("Output", WINDOW_NORMAL);
    imshow("Output", out);
    waitKey(0);
    destroyWindow("Output");
    imwrite("output.png", out);
}

Die Ergebnisse sollten mit denen der Python Version übereinstimmen.

70
rayryeng

Kleine Pixelcluster können auch mit der Funktion remove_small_objects in skimage entfernt werden:

import matplotlib.pyplot as plt
from skimage import morphology
import numpy as np
import skimage

# read the image, grayscale it, binarize it, then remove small pixel clusters
im = plt.imread('spots.png')
grayscale = skimage.color.rgb2gray(im)
binarized = np.where(grayscale>0.1, 1, 0)
processed = morphology.remove_small_objects(binarized.astype(bool), min_size=2, connectivity=2).astype(int)

# black out pixels
mask_x, mask_y = np.where(processed == 0)
im[mask_x, mask_y, :3] = 0

# plot the result
plt.figure(figsize=(10,10))
plt.imshow(im)

Dies zeigt an:

enter image description here

Wenn Sie nur größere Cluster beibehalten möchten, erhöhen Sie min_size (Kleinste Größe der beibehaltenen Cluster) und verringern Sie connectivity (Größe der Pixelumgebung beim Bilden von Clustern). Mit nur diesen beiden Parametern können nur Pixelcluster einer geeigneten Größe beibehalten werden.

0
duhaime