web-dev-qa-db-fra.com

Supprimez les petits îlots de bruit parasites dans une image - Python OpenCV

J'essaie de me débarrasser du bruit de fond de certaines de mes images. Ceci est l'image non filtrée.

Pour filtrer, j'ai utilisé ce code pour générer un masque de ce qui devrait rester dans l'image:

 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)

Avec ce code et lorsque je masque les pixels indésirables de l'image d'origine, ce que j'obtiens est: 

Comme vous pouvez le voir, tous les minuscules points de la zone centrale ont disparu, mais beaucoup de ceux provenant de la zone plus dense ont également disparu. Pour réduire le filtrage, j'ai essayé de changer le deuxième paramètre de getStructuringElement() pour qu'il soit (1,1) mais cela me donne la première image comme si rien n'avait été filtré.

Est-il possible d'appliquer un filtre entre ces 2 extrêmes?

De plus, quelqu'un peut-il m'expliquer ce que fait exactement getStructuringElement()? Qu'est-ce qu'un "élément structurant"? Que fait-il et comment sa taille (le deuxième paramètre) affecte-t-elle le niveau de filtrage?

29
annena

Beaucoup de vos questions proviennent du fait que vous ne savez pas comment fonctionne le traitement d'image morphologique, mais nous pouvons mettre vos doutes au repos. Vous pouvez interpréter l'élément structurant comme la "forme de base" à comparer. 1 dans l'élément structurant correspond à un pixel que vous souhaitez regarder dans cette forme et 0 est celui que vous souhaitez ignorer. Il existe différentes formes, comme rectangulaire (comme vous l'avez compris avec MORPH_RECT), ellipse, circulaire, etc.

En tant que tel, cv2.getStructuringElement renvoie un élément structurant pour vous. Le premier paramètre spécifie le type souhaité et le second paramètre spécifie la taille souhaitée. Dans votre cas, vous voulez un "rectangle" 2 x 2 ... qui est vraiment un carré, mais c'est très bien.

Dans un sens plus bâtard, vous utilisez l'élément structurant et scannez de gauche à droite et de haut en bas de votre image et vous saisissez les quartiers de pixels. Chaque quartier de pixels a son centre exactement au pixel d'intérêt que vous regardez. La taille de chaque voisinage de pixels est la même taille que l'élément structurant.

Érosion

Pour une érosion, vous examinez tous les pixels dans un voisinage de pixels qui touchent l'élément structurant. Si chaque pixel non nul touche un pixel d'élément structurant qui est 1, alors le pixel de sortie dans la position centrale correspondante par rapport à l'entrée est 1 S'il y a au moins un pixel non nul qui ne touche pas un pixel structurant égal à 1, alors la sortie est 0.

En termes d'élément structurant rectangulaire, vous devez vous assurer que chaque pixel de l'élément structurant touche un pixel non nul dans votre image pour un voisinage de pixels. Si ce n'est pas le cas, la sortie est 0, sinon 1. Cela élimine efficacement les petites zones parasites parasites et diminue également légèrement la zone des objets.

La taille tient compte du fait que plus le rectangle est grand, plus le rétrécissement est important. La taille de l'élément structurant est une ligne de base où tous les objets plus petits que cet élément structurant rectangulaire, vous pouvez les considérer comme étant filtrés et n'apparaissant pas dans la sortie. Fondamentalement, le choix d'un élément structurant 1 x 1 rectangulaire est le même que l'image d'entrée elle-même car cet élément structurant s'adapte à tous les pixels à l'intérieur car le pixel est la plus petite représentation d'informations possible dans une image.

Dilatation

La dilatation est l'opposé de l'érosion. S'il y a au moins un pixel non nul qui touche un pixel dans l'élément structurant qui est 1, alors la sortie est 1, sinon la sortie est 0. Vous pouvez penser à cela comme agrandissant légèrement les zones d'objets et agrandissant les petites îles.

Les implications avec la taille ici sont que plus l'élément structurant est grand, plus les zones des objets seront grandes et plus les îles isolées seront grandes.


Ce que vous faites, c'est d'abord une érosion suivie d'une dilatation. C'est ce qu'on appelle une opération d'ouverture . Le but de cette opération est de supprimer les petits îlots de bruit tout en (essayant de) maintenir les zones des objets plus gros de votre image. L'érosion supprime ces îles tandis que la dilatation ramène les plus gros objets à leur taille d'origine.

Vous suivez cela avec une érosion à nouveau pour une raison que je ne comprends pas très bien, mais ça va.


Ce que je ferais personnellement, c'est d'abord fermer l'opération de fermeture qui est une dilatation suivie d'une érosion. La fermeture permet de regrouper les zones rapprochées en un seul objet. En tant que tel, vous voyez qu'il y a des zones plus grandes qui sont proches les unes des autres qui devraient probablement être jointes avant de faire quoi que ce soit d'autre. En tant que tel, je ferais d'abord une fermeture, puis une ouverture après afin que nous puissions supprimer les zones bruyantes isolées. Notez que je vais agrandir la taille de l'élément structurant de fermeture car je veux m'assurer d'obtenir des pixels proches et la taille de l'élément structurant d'ouverture plus petit afin que je ne veuille pas supprimer par erreur les plus grandes zones.

Une fois que vous avez fait cela, je masquerais toute information supplémentaire avec l'image d'origine afin que vous laissiez les plus grandes zones intactes pendant que les petites îles disparaissent.

Au lieu d'enchaîner une érosion suivie d'une dilatation, ou une dilatation suivie d'une érosion, utilisez cv2.morphologyEx , où vous pouvez spécifier MORPH_OPEN et MORPH_CLOSE comme drapeaux.

En tant que tel, je le ferais personnellement, en supposant que votre image s'appelle 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)

Le code ci-dessus est assez explicite. Tout d'abord, je lis l'image, puis je convertis l'image en niveaux de gris et seuil avec une intensité de 5 pour créer un masque de ce qui est considéré comme des pixels d'objet. C'est une image plutôt propre et donc tout ce qui dépasse 5 semble avoir fonctionné. Pour les routines de morphologie, je dois convertir l'image en uint8 et redimensionnons le masque à 255. Ensuite, nous créons deux éléments structurants - un rectangle 5 x 5 pour l'opération de fermeture et un autre 2 x 2 pour l'opération d'ouverture. Je cours cv2.morphologyEx deux fois pour les opérations d'ouverture et de fermeture respectivement sur l'image seuillée.

Une fois que je fais cela, j'empile le masque pour qu'il devienne une matrice 3D et je le divise par 255 pour qu'il devienne un masque de [0,1] puis nous multiplions ce masque avec l'image d'origine afin de pouvoir récupérer les pixels d'origine de l'image et conserver ce qui est considéré comme un véritable objet à partir de la sortie du masque.

Le reste est juste à titre d'illustration. Je montre l'image dans une fenêtre et j'enregistre également l'image dans un fichier appelé output.png, et son but est de vous montrer à quoi ressemble l'image dans ce post.

J'ai compris:

enter image description here

Gardez à l'esprit que ce n'est pas parfait, mais c'est beaucoup mieux que la façon dont vous l'aviez avant. Vous devrez jouer avec les tailles des éléments structurants pour obtenir quelque chose que vous considérez comme une bonne sortie, mais cela est certainement suffisant pour vous aider à démarrer. Bonne chance!


Version C++

Il y a eu quelques demandes pour traduire le code que j'ai écrit ci-dessus dans la version C++ en utilisant OpenCV. J'ai finalement réussi à écrire une version C++ du code et cela a été testé sur OpenCV 3.1.0. Le code pour cela est ci-dessous. Comme vous pouvez le voir, le code est très similaire à celui vu dans la version Python. Cependant, j'ai utilisé cv::Mat::setTo sur une copie de l'image d'origine et définissez ce qui ne faisait pas partie du masque final sur 0. C'est la même chose que d'effectuer une multiplication par élément en 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);
}

Les résultats doivent être les mêmes que ceux que vous obtenez dans la version Python.

70
rayryeng

On peut également supprimer de petits groupes de pixels en utilisant le remove_small_objects fonction dans skimage:

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)

Cela affiche:

enter image description here

Pour ne conserver que les clusters plus grands, essayez d'augmenter min_size (plus petite taille des clusters retenus) et décroissante connectivity (taille du voisinage en pixels lors de la formation des clusters). En utilisant uniquement ces deux paramètres, on ne peut conserver que des groupes de pixels de taille appropriée.

0
duhaime