5
votes

Comment détecter et mesurer des objets (fitEllipse) sur l'image SEM en utilisant OpenCV?

J'ai environ 30 images SEM (microscope électronique à balayage) comme celle-ci:

Ce que vous voyez, ce sont des piliers de photorésist sur un substrat en verre . Ce que je voudrais faire, c'est obtenir le diamètre moyen dans les directions x et y ainsi que la période moyenne dans les directions x et y.

Maintenant, au lieu de faire toutes les mesures manuellement, Je me demandais s'il existe un moyen de l'automatiser en utilisant python et opencv ?

EDIT: J'ai essayé le code suivant, il semble travailler pour détecter les cercles , mais ce dont j'ai réellement besoin, c'est une ellipse, car j'ai besoin du diamètre dans les directions x et y.

... et je ne vois pas encore tout à fait comment obtenir l'échelle?

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread("01.jpg",0)
output = img.copy()

edged = cv2.Canny(img, 10, 300)
edged = cv2.dilate(edged, None, iterations=1)
edged = cv2.erode(edged, None, iterations=1)



# detect circles in the image
circles = cv2.HoughCircles(edged, cv2.HOUGH_GRADIENT, 1.2, 100)


# ensure at least some circles were found
if circles is not None:
    # convert the (x, y) coordinates and radius of the circles to integers
    circles = np.round(circles).astype("int")

    # loop over the (x, y) coordinates and radius of the circles
    for (x, y, r) in circles[0]:
        print(x,y,r)
        # draw the circle in the output image, then draw a rectangle
        # corresponding to the center of the circle
        cv2.circle(output, (x, y), r, (0, 255, 0), 4)
        cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)

    # show the output image
    plt.imshow(output, cmap = 'gray', interpolation = 'bicubic')
    plt.xticks([]), plt.yticks([])  # to hide tick values on X and Y axis
    plt.figure()
    plt.show()

 entrez la description de l'image ici a >

Source d'inspiration: https://www.pyimagesearch.com/2014/07/21/detecting-circles-images-using-opencv-hough-circles/


2 commentaires

Un prétraitement aiderait probablement. Tout d'abord, j'avais coupé cette zone de texte en bas. Identifiez toutes les grosses gouttes lumineuses. Partitionnez l'image en ROI, de sorte que chaque ROI ne contienne qu'un seul objet blob. Ignorez les retours sur investissement qui contiennent des blobs partiels (c'est-à-dire là où le blob est près du bord). Faites une analyse plus approfondie sur les retours sur investissement restants. (Oh, et bravo pour ne pas utiliser JPEG pour l'image d'entrée)


Puisque vous mentionnez ellipse, vous pouvez faire cv2.fitEllipse sur les contours des piliers.


3 Réponses :


1
votes

J'irais avec la méthode HoughCircles , de openCV. Cela vous donnera tous les cercles de l'image. Ensuite, il sera facile de calculer le rayon et la position de chaque cercle.

Regardez: https://docs.opencv.org/3.4/ d4 / d70 / tutorial_hough_circle.html


3 commentaires

Merci pour cette note! Je vais vérifier cela. :)


Savez-vous comment détecter les ellipses?


Le package skimage.transform a une fonction hough_ellipse mais je ne l'ai jamais utilisée.



5
votes

Je trouve rarement Hough utile pour les applications du monde réel, donc je préfère suivre le chemin du débruitage, de la segmentation et de l'ajustement elliptique.

Pour le débruitage, on sélectionne les moyens non locaux (NLM). Pour la segmentation - juste en regardant l'image - j'ai trouvé un modèle de mélange gaussien avec trois classes: une pour le fond et deux pour l'objet (composante diffuse et spéculaire). Ici, le modèle de mélange modélise essentiellement la forme de l'histogramme de l'image de niveau de gris par trois fonctions gaussiennes (comme démontré dans Wikipedia mix-histogram gif ). Le lecteur intéressé est redirigé vers article de Wikipedia .

Ellipse fit à la fin est juste un outil OpenCV élémentaire.

En C ++, mais en analogue à OpenCV-Python

img = cv.imread('D:/tmp/8b3Lm.jpg', cv.IMREAD_GRAYSCALE )

class Predictor :
    def train( self, img ):
        self.em = cv.ml.EM_create()
        self.em.setClustersNumber( 3 )
        self.em.setTermCriteria( ( cv.TERM_CRITERIA_COUNT,4,0 ) )
        samples = np.reshape( img, (img.shape[0]*img.shape[1], -1) ).astype('float')
        self.em.trainEM( samples )

    def predict( self, img ):
        samples = np.reshape( img, (img.shape[0]*img.shape[1], -1) ).astype('float')
        labels = np.zeros( samples.shape, 'uint8' )
        for i in range ( samples.shape[0] ):
            retval, probs = self.em.predict2( samples[i] )
            labels[i] = retval[1] * (255/3) # make it [0,255] for imshow
        return np.reshape( labels, img.shape )

predictor = Predictor()

predictor.train( img )
t = time.perf_counter()
predictor.train( img )
t = time.perf_counter() - t
print ( "train %s s" %t )

t = time.perf_counter()
labels = predictor.predict( img )
t = time.perf_counter() - t
print ( "predict %s s" %t )

cv.imshow( "prediction", labels  )
cv.waitKey( 0 )

J'aurais dû nettoyer les très petits contours (en deuxième ligne supérieure) (plus grand que les 5 points minimum) avant de dessiner des ellipses.

* modifier * Ajout du prédicteur Python sans la partie débruitage et recherche de contours. Après avoir appris le modèle, le temps de prédiction est d'environ 1,1 seconde

#include "opencv2/ml.hpp"
#include "opencv2/photo.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
void gaussianMixture(const cv::Mat &src, cv::Mat &dst, int nClasses )
{
    if ( src.type()!=CV_8UC1 )
        CV_Error(CV_StsError,"src is not 8-bit grayscale");

    // reshape
    cv::Mat samples( src.rows * src.cols, 1, CV_32FC1 );
    src.convertTo( cv::Mat( src.size(), CV_32FC1, samples.data ), CV_32F );

    cv::Mat labels;
    cv::Ptr<cv::ml::EM> em = cv::ml::EM::create();
    em->setClustersNumber( nClasses );
    em->setTermCriteria( cv::TermCriteria(CV_TERMCRIT_ITER, 4, 0.0 ) );
    em->trainEM( samples );

    if ( dst.type()!=CV_8UC1 || dst.size()!=src.size() )
        dst = cv::Mat( src.size(),CV_8UC1 );
    for(int y=0;y<src.rows;++y)
    {
        for(int x=0;x<src.cols;++x)
        {
            dst.at<unsigned char>(y,x) = em->predict( src.at<unsigned char>(y,x) );
        }
    }
}
void automate()
{
    cv::Mat input = cv::imread( /* input image in color */,cv::IMREAD_COLOR);
    cv::Mat inputDenoised;
    cv::fastNlMeansDenoising( input, inputDenoised, 8.0, 5, 17 );
    cv::Mat gray;
    cv::cvtColor(inputDenoised,gray,cv::COLOR_BGR2GRAY );
    gaussianMixture(gray,gray,3 );

    typedef std::vector< std::vector< cv::Point > >  VecOfVec;
    VecOfVec contours;
    cv::Mat objectPixels = gray>0;
    cv::findContours( objectPixels, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE );
    cv::Mat inputcopy; // for drawing of ellipses
    input.copyTo( inputcopy );
    for ( size_t i=0;i<contours.size();++i )
    {
        if ( contours[i].size() < 5 )
            continue;
        cv::drawContours( input, VecOfVec{contours[i]}, -1, cv::Scalar(0,0,255), 2 );
        cv::RotatedRect rect = cv::fitEllipse( contours[i] );
        cv::ellipse( inputcopy, rect, cv::Scalar(0,0,255), 2 );
    }
}

Denoised Contours Ellipses Mélange de gaussiens


9 commentaires

Agréable. Pourriez-vous, peut-être, ajouter des informations sur le fonctionnement de gaussianMixture ? (BTW, l'entrée est en niveaux de gris, vous pouvez donc la lire comme telle et ignorer le cvtColor - vous n'obtiendrez pas la couleur d'un microscope électronique).


@ DanMašek, je voulais dessiner en couleur, donc de toute façon, j'ai dû utiliser cvtColor :)


Droite ... doh: D Merci pour les informations supplémentaires. Je suppose que dans ce cas, cela s'apparente à un clustering k-means? (Je devrai le lire plus en détail plus tard)


Très bonne réponse, mais pouvez-vous également extraire la longueur des axes de l'ellipse majeure et mineure dans les directions x et y?


@james les axes de l'ellipse dans le cadre pivoté sont facilement lus dans le membre cv :: RotatedRect s size ( a = size.width / 2.0 etc.). Si vous souhaitez calculer le cadre de sélection dans le cadre de l'image, vous pouvez suivre l'équation dans math.stackexchange.com/questions/91132/...


De plus, si vous disposez d'une configuration d'imagerie cohérente, vous ne pouvez apprendre le modèle de mélange qu'une seule fois et appeler simplement predict pour les images suivantes


@mainactual J'ai une configuration d'imagerie cohérente, donc ce serait une bonne option, mais je ne suis pas un expert en C ++. Savez-vous comment écrire ce code en Python?


@james, a ajouté un prédicteur Python, vous pouvez remplir les majuscules en copiant à partir de l'autre réponse


@mainactual Merci beaucoup !! Désolé pour la réponse tardive, j'ai manqué votre commentaire ...



1
votes

J'utilise cv2.ml.EM pour segmenter d'abord l'image en OpenCV (Python), cela coûte environ 13 s . Si juste fitEllipse sur les contours de l'image battue, cela coûte 5 ms , le résultat n'est peut-être pas aussi précis. Juste un compromis.

 entrez la description de l'image ici


Détails:

  1. Conversion en niveaux de gris et battue

  2. Morph pour débruiter

  3. Trouvez les contours externes

  4. Ajuster les ellipses


Code:

#!/usr/bin/python3
# 2019/02/13 
# https://stackoverflow.com/a/54604608/54661984

import cv2
import numpy as np

fpath = "sem.png"
img = cv2.imread(fpath)

## Convert into grayscale and threshed it
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
th, threshed = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY)

## Morph to denoise
threshed = cv2.dilate(threshed, None)
threshed = cv2.erode(threshed, None)

## Find the external contours
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2]
cv2.drawContours(img, cnts, -1, (255, 0, 0), 2, cv2.LINE_AA)

## Fit ellipses
for cnt in cnts:
    if cnt.size < 10 or cv2.contourArea(cnt) < 100:
        continue

    rbox = cv2.fitEllipse(cnt)
    cv2.ellipse(img, rbox, (255, 100, 255), 2, cv2.LINE_AA)

## This it
cv2.imwrite("dst.jpg", img)


1 commentaires

Merci pour votre réponse. Pouvez-vous extraire l'axe de l'ellipse majeur et mineur?