1
votes

Erreur lors de la tentative de segmentation des cercles dans l'image DICOM à l'aide d'openCV et de HoughCircles

J'essaie de segmenter des cercles dans une image DICOM. J'essaie d'implémenter la transformation hough avec opencv pour le faire. J'obtiens cette erreur:

#how self.image is created in another function
self.image = PIL.Image.fromarray(numpy_array)

Code:

#Segment circle code using openCV
def segment_circles(self): 

    image = np.float(self.image)
    output = image.copy()
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1.2, 100)

    if circles is not None:
        circles = np.round(circles[0, :].astype("int"))

        for (x, y, r) in circles:
            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)

            cv2.imshow("output", np.hstack([image, output]))
            cv2.waitKey(0)
cv2.error: OpenCV(4.1.0) /Users/travis/build/skvark/opencv-python/opencv/modules/imgproc/src/hough.cpp:1736: error: (-215:Assertion failed) !_image.empty() && _image.type() == CV_8UC1 && (_image.isMat() || _image.isUMat()) in function 'HoughCircles'

Merci d'avance pour votre aide.


5 commentaires

Avez-vous essayé de tester les affirmations ci-dessus sur votre image? Vous pouvez affiner davantage quelle assertion déclenche l'erreur.


Pourriez-vous expliquer plus en détail comment je procéderais?


Je vois que quelqu'un a déjà suggéré la même chose dans sa réponse. Essayez de vérifier ce que gray.empty () et gray.type () renvoient


Désolé Tim, j'ai posté ma suggestion après que vous ayez fait sans voir votre réponse. Cela semble être la bonne chose à faire. OP a dit que son image est une image MR, que je m'attendrais déjà à être en niveaux de gris. L'étape de conversion des couleurs échoue probablement.


Oui, merci à tous les deux. Je vais essayer de vérifier cela.


3 Réponses :


0
votes

Eh bien, je pense que j'ajouterais quelques lignes de code après la ligne "gray = ..." pour tester chacune de ces conditions individuellement. Savez-vous avec certitude que gray (). IsEmpty () est vraiment faux? Prenez juste une minute ou deux pour découvrir quelles conditions échouent au test de validation.

Quelle est la modalité de l'image?


33 commentaires

Est-il possible que votre image d'origine soit déjà en niveaux de gris et que la conversion des couleurs échoue donc? (De plus, votre image est-elle vraiment BGR?


La modalité d'image est une image DICOM. J'ai affiché l'image "grise" via plt.show () et elle s'affiche, étant donné que les couleurs sont en violet et jaune et non en niveaux de gris.


Non, je sais que c'est une image DICOM. Je demande quelle est la valeur de l’attribut Modality de l’image DICOM. Autrement dit, est-ce une image CR, une image CT, une image US, etc.


Oh, mes excuses. C'est une image IRM. Je crois que l'image que je traverse (self.image) est RVB, car le tableau de chaque tranche d'image reconstruite est fait avec ce tableau: np.zeros ((img.shape [0], img.shape [1], 3 ) .3 étant RVB.


Aucun problème. Il serait très inhabituel d'avoir une vraie image MR qui soit RVB. (Je ne dis pas que votre image n'est pas RVB, juste que ce serait inhabituel. Les images MR sont généralement en niveaux de gris avec des pixels de 16 bits). Je connais un peu DICOM, je vais essayer de vous aider. Avez-vous un outil qui vous permettra d'afficher l'en-tête DICOM de votre image? Quel autre code traite l'image DICOM avant d'appeler segment_circles?


Hé @ redsoxfan4, juste comme un test rapide et sale, avez-vous essayé de simplement passer "image" au lieu de "gray" dans cv2.HoughCircles?


Merci. Le trancheur 3D fonctionne pour collecter les informations d'en-tête. J'ai implémenté ce code: github.com/1danielcoelho/Caduceus avec quelques mises à jour mineures de code obsolète.


D'ACCORD. Eh bien, voici ce à quoi je veux en venir: je suppose que dans votre code, vous (1) ouvrez un fichier DICOM, (2) créez un tableau numpy ou une image PIL ou une autre représentation des données de pixels, et (3) utilisez cela représentation des données de pixels dans votre fonction segment_circles.


Correct. C'est essentiellement l'essentiel. J'ai essayé de faire passer l'image et j'ai obtenu le même résultat.


La norme DICOM permet de nombreux types d'images différents, certaines niveaux de gris, certaines couleurs, certaines avec des pixels de 8 bits, d'autres avec des pixels de 16 bits. Une image MR typique serait en niveaux de gris avec des pixels de 16 bits. Autrement dit, si la "forme" de votre représentation de l'image est comme (lignes, colonnes, canaux) alors je m'attendrais à ce que votre image soit quelque chose comme (256,256,1) - je m'attendrais à ce qu'il n'y ait qu'un seul canal, pas Trois.


D'accord, mais mon code transforme le tableau 3D en RVB afin que Tkinter puisse le gérer.


Lorsque vous dites que vous avez "essayé de passer l'image et obtenu le même résultat", cela signifie que la validation d'entrée de cv2.HoughCircles a échoué.


OK, je vois que vous créez le tableau 3D - quelque chose avec une forme comme (256, 256, 3) à partir des données de pixels DICOM d'entrée qui est comme (256, 256, 1). Et puis plus tard c'est ce tableau (256,256,3) qui est passé à cv2.cvtColor? Je soupçonne que cv2.cvtColor échoue et renvoie une image vide affectée à "gray"


Oui. self.image est une mémoire d'image créée à partir de ce tableau en utilisant PIL.image.fromarray (). Des idées sur la façon de résoudre ce problème?


Est-il possible que self.image ait des entrées 16 bits, mais cv2.cvtColor n'attend que des entrées 8 bits? Mon raisonnement est que vous lisez une image DICOM en niveaux de gris (un canal) avec des pixels de 16 bits dans un tableau numpy, convertissez cela en un tableau numpy avec 3 canaux (mais avec des entrées de 16 bits) et passez cela à cv2.cvtColor. Je ne sais pas si cv2.cvtColor peut convertir des pixels de couleur 16 bits en niveaux de gris. Peut-être, je ne sais pas.


Je pense qu'il s'agit simplement de garder une trace de la forme (dimensions) et du type de données (entiers 8 bits vs entiers 16 bits) des tableaux que vous créez et utilisez.


L'image d'origine a été créée à partir du fichier DICOM quelque part. Je soupçonne qu'il est stocké dans un tableau de dimensions (256, 256, 1) et a 16 bits de pixels. Je pense que vous devriez vous accrocher à cette version de l'image car elle est la source de la vérité. Vous devrez peut-être créer une version à 3 canaux de cette image pour que TKinter l'affiche si c'est votre flux de travail. Et vous devrez peut-être créer une version 8 bits de cette image pour passer à la transformation de Hough. (Il semble que l'implémentation Hough Transform ne souhaite que des images 8 bits à un canal (c'est ce que _image.type () == CV_8UC1 vérifie).


J'apprécie votre aide jusqu'à présent. Dans le tableau que je passe dans PIL.image.fromarray (), le dtype = uint8. Donc je crois que c'est déjà 8 bits?


OK, allons-y étape par étape. Comment lisez-vous le fichier DICOM à partir du disque et le convertissez-vous en matrice? Vous appelez une fonction dans une bibliothèque? Quelque chose comme un = LoadDICOMFile ("myMRIDICOMFile.dcm") et ensuite vous récupérez un tableau numpy qui est assigné à un?


Oui. Je charge le DICOMS via un visualiseur de fichiers: filenames = list (askopenfilenames (filetypes = (("All files", ". "), ("DICOM files", "* .dcm"))))


Ensuite, ajoutez chaque fichier de la liste à un tableau en utilisant pydicom pour les lire: datasets = [] for file in filenames: try: datasets.append (pydicom.read_file (file))


Ce tableau est ensuite passé dans une classe où self.datasets est maintenant défini comme tableau, et self.values ​​est un tableau numpy via ce code: self.values ​​= np.zeros (self.size, dtype = 'int32') for i, d dans enumerate (datasets): np.copyto (self.values ​​[:,:, i], np.flipud (d.pixel_array), 'unsafe')


Ce tableau self.values ​​est ensuite reconstruit pour créer une image axiale en définissant img = self.values ​​[index,:,:]


Cette image est ensuite convertie en RVB pour tkinter, avec le dtype maintenant défini sur uint8 comme je l'ai mentionné ci-dessus.


OK, bien - au cœur de celui-ci, vous appelez pydicom.read_file pour lire le fichier dicom à partir du disque, puis accédez aux données de pixel via le membre pixel_array de l'objet pydicom.


Ce numpy_array est à son tour envoyé à ma fonction show_image, qui prend le tableau RVB comme entrée et définit self.image = PIL.Image.fromarray (numpy_array). Et c'est maintenant ce que j'essaye de nourrir dans les cercles openCV


Cela vient de résoudre le problème: img = self.imager.values ​​[self.imager.index,:,:] image8 = np.uint8 (img)


Eh bien, cela résoudra en quelque sorte le problème dans le sens où cela permettra à la transformation cv2 Hough de s'exécuter parce que les données sont maintenant 8 bits. Mais vous venez de tronquer toutes les données d'origine. Au lieu de cela, vous devez redimensionner les pixels de l'image d'origine pour qu'ils se situent dans la plage [0,255], puis modifier le type de données de l'image redimensionnée. Je viens de publier une autre réponse qui contient un exemple d'implémentation de la façon de procéder.


Cela a du sens, mais au moins, nous avons résolu pourquoi j'obtenais l'erreur d'origine, qui était votre inclination d'origine. Votre réponse me fournira certainement assez pour implémenter ce vrai correctif dans mon propre code. Merci!


Génial! Maintenant que cela ne vous dérange pas, si je vous demande quel est votre véritable objectif? Êtes-vous simplement en train d'étudier le traitement d'images médicales ou avez-vous une tâche spécifique que vous essayez d'accomplir?


Tâche spécifique - segmentation fiduciale automatique pour une application chirurgicale.


Ça a l'air intéressant! Bonne chance!!


Les commentaires ne sont pas destinés à une discussion approfondie; cette conversation a été déplacé pour chatter .



0
votes

Veuillez consulter la réponse de Wilf pour une solution appropriée.

Correction rapide du code d'origine:

def hough_circles(self):

    #load image
    img = self.imager.values[self.imager.index, :, :]
    image8 = np.uint8(img)
    output = image8.copy()

    #apply hough transform
    circles = cv2.HoughCircles(image8, cv2.HOUGH_GRADIENT, 1.2, 100)

    #place circles and cente rectangle on image
    if circles is not None:
        circles = np.round(circles[0, :].astype("int"))

        for (x, y, r) in circles:
            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)

        cv2.imshow("output", np.hstack([image8, output]))
        cv2.waitKey(0)


0 commentaires

2
votes

Voici un flux de travail qui peut être utilisé pour lire un fichier DICOM à partir du disque et effectuer la transformation de Hough

    original data type=int16
    rescaled data type=uint8
    h=[[[172.20001 259.80002 154.92001]
      [319.80002 273.      161.64   ]]]

Quand je l'exécute avec une vraie image MR sur mon ordinateur, voici la sortie ...

    import pydicom
    import cv2
    import numpy as np

    # read the DICOM file
    d16=pydicom.read_file('view0010.dcm')

    print(f"original data type={d16.pixel_array.dtype}")

    # rescale original 16 bit image to 8 bit values [0,255]
    x0=d16.pixel_array.min()
    x1=d16.pixel_array.max()
    y0=0
    y1=255.0
    i8=((d16.pixel_array-x0)*((y1-y0)/(x1-x0)))+y0

    # create new array with rescaled values and unsigned 8 bit data type
    o8=i8.astype(np.uint8)

    print(f"rescaled data type={o8.dtype}")

    # do the Hough transform
    h=cv2.HoughCircles(o8, cv2.HOUGH_GRADIENT, 1.2, 100)

    print(f"h={h}")

Bien sûr, les résultats de la transformation de Hough seront différents pour vous, mais je pense que cela montre ce qu'il faut faire pour exécuter la fonction cv2 HoughCircles sur un image réelle DICOM.


0 commentaires