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()
Source d'inspiration: https://www.pyimagesearch.com/2014/07/21/detecting-circles-images-using-opencv-hough-circles/
3 Réponses :
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
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.
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 ); } }
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 code > 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 ...
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.
Détails:
Conversion en niveaux de gris et battue
Morph pour débruiter
Trouvez les contours externes
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)
Merci pour votre réponse. Pouvez-vous extraire l'axe de l'ellipse majeur et mineur?
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.