Supprimer les fausses îlots de bruit dans une image - Python OpenCV

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

Pour le filtre, j'ai utilisé ce code pour générer un masque de ce qui doit 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 quand je masque les pixels indésirables de l'image originale, ce que je reçois est:

Comme vous pouvez le voir, tous les petits points dans la zone centrale ont disparu, mais beaucoup de ceux qui viennent de l' la zone plus dense a également disparu. Pour réduire le filtrage, j'ai essayé de changer le second paramètre de getStructuringElement() (1,1) mais cela me donne la première image, comme si rien n'a été filtré.

y a-t-il un moyen d'appliquer un filtre qui se situe entre ces deux extrêmes?

en outre, Est-ce que quelqu'un peut m'expliquer ce qui fait exactement getStructuringElement() faire? Qu'est ce qu'un "élément structurant"? Que fait-il et comment est sa taille (le deuxième paramètre) qui influe sur le niveau de le filtrage?

25
demandé sur rayryeng 2015-05-21 11:59:11

1 réponses

beaucoup de vos questions proviennent du fait que vous ne savez pas comment fonctionne le traitement d'image morphologique, mais nous pouvons dissiper vos doutes. Vous pouvez interpréter l'élément structurant comme la" forme de base " à comparer. 1 dans l'élément de structuration correspond à un pixel que vous voulez regarder dans cette forme et 0 est une 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 retourne un élément structurant pour vous. Le premier paramètre spécifie le type que vous voulez et le deuxième paramètre spécifie la taille que vous voulez. Dans votre cas, vous voulez un 2 x 2 "rectangle"... ce qui est vraiment un carré, mais c'est très bien.

dans un sens plus Bastardi, vous utilisez l'élément structurant et scannez de gauche à droite et de haut en bas de votre image et vous saisissez des quartiers pixelisés. Chaque pixel quartier a son centre exactement au pixel d'intérêt que vous êtes en train de regarder. La taille de chaque pixel de voisinage est la même que celle de l'élément structurant.

l'Érosion

pour une érosion, vous examinez tous les pixels d'un voisinage de pixels qui touchent l'élément structurant. Si chaque pixel non nul touche un élément structurant pixel 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 pas toucher un pixel structurant qui est 1, puis la sortie est 0.

en ce qui concerne l'é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, alors la sortie est 0, sinon 1. Cela élimine efficacement les petites zones parasites du bruit et diminue aussi légèrement la surface des objets.

les facteurs de taille plus le rectangle, plus la diminution est effectuée. La taille de l'élément structurant est une ligne de base où tous les objets qui sont plus petits que cet élément structurant rectangulaire, vous pouvez les considérer comme étant filtrés et ne pas apparaître dans la sortie. Fondamentalement, le choix d'un élément structurant rectangulaire 1 x 1 est le même que l'image d'entrée elle-même parce que cet élément structurant s'adapte à tous les pixels à l'intérieur de celui-ci comme le pixel est la plus petite représentation d'information possible dans un image.

Dilatation

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 en agrandissant légèrement les zones d'objets et en faisant de petites îles plus grandes.

Les implications avec la taille ici est que plus la taille de l'élément structurant, les plus grands les domaines des objets et le plus isolé îles devenir.


Ce que vous faites est une érosion suivie d'une dilatation. C'est ce qui est connu comme une ouverture opération. Le but de cette opération est d'enlever les petits îlots de bruit tout en (essayant) de maintenir les zones des plus grands objets dans votre image. L'érosion élimine ces îles tandis que la dilatation repousse les plus grands objets à leur taille originale.

nous suivons cela avec une érosion à nouveau pour certains raison, que je ne peux pas tout à fait comprendre, mais ce n'est pas grave.


ce que je ferais personnellement c'est effectuer un clôture opération première, qui est une dilatation suivie d'une érosion. La fermeture aide à grouper les zones qui sont rapprochées en un seul objet. Comme tel, vous voyez qu'il y a des zones plus grandes qui sont proches les unes des autres et qui devraient probablement être jointes avant que nous ne fassions quoi que ce soit d'autre. En tant que tel, je ferais une fermeture d'abord, puis faire un ouverture après pour que nous puissions enlever les zones bruyantes isolées. Notez que je vais faire la taille de l'élément structurant de fermeture plus grand comme je veux m'assurer que j'obtiens des pixels de proximité et la taille de l'élément structurant d'ouverture plus petit de sorte que je ne veux pas enlever par erreur l'une des plus grandes zones.

une fois que vous avez fait ceci, je masquerais toute information supplémentaire avec l'image originale pour que vous laissiez les plus grandes zones intactes tandis que les petites îles en aller.

au Lieu d'enchaîner une érosion suivie d'une dilatation ou d'une dilatation suivie d'une érosion, utilisez cv2.morphologyEx, où vous pouvez spécifier MORPH_OPEN et MORPH_CLOSE comme les 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. D'abord, j'ai lu dans l'image et ensuite j'ai converti l'image en échelle de gris et de seuil avec un 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 nette et donc tout ce qui est plus grand que 5 semble avoir fonctionné. Pour les routines morphologiques, je dois convertir l'image en uint8 et mettez le masque à l'échelle 255. Ensuite, nous créons deux éléments structurants - l'un qui est un rectangle 5 x 5 pour l'opération de fermeture et l'autre qui est 2 x 2 pour l'opération d'ouverture. Je run cv2.morphologyEx deux fois pour les opérations d'ouverture et de fermeture respectivement sur le produit batté image.

une fois que j'ai fait 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] et puis nous multiplions ce masque avec l'image originale de sorte que nous pouvons saisir les pixels originaux de l'image en arrière et en maintenant ce qui est considéré comme un objet vrai de la sortie de masque.

Le reste est juste pour l'illustration. J'ai afficher l'image dans une fenêtre, et j'ai aussi enregistrer l'image dans un fichier appelé output.png, et son but est de montrer vous ce que l'image ressemble à ce poste.

j'obtiens ceci:

enter image description here

gardez à l'esprit qu'il 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 c'est certainement suffisant pour vous lancer. Bonne chance!


C++ version

il y a eu des demandes de traduisez 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 ceci a été testé sur OpenCV 3.1.0. Le code est ci-dessous. Comme vous pouvez le voir, le code est très similaire à celui de la version Python. Cependant, j'ai utilisé cv::Mat::setTo sur une copie de l'image originale et régler à 0 ce qui ne faisait pas partie du masque final. 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 devraient être les mêmes que ceux de la version Python.

58
répondu rayryeng 2017-01-26 21:53:10