Skip to content

Documentation Code

flamendO edited this page May 1, 2024 · 3 revisions

Warning

Si ce n'es pas déjà fait, installez Python et PIP !

Tip

Un fichier src/dist/gui/installateur_module.ps1 vous permet d'installer directement tous les packages python nécessaires pour executer tous les codes dans votre IDE sans à avoir à taper toutes les commandes pip à la main. Pour lancer ce script, ouvrez un powershell, et lancer directement la commande :

.\installateur_module.ps1

image


• Enchaînement du Code

Caution

Tout le code démarre dans l'interface graphique, voir ./src/dist/gui/gui.py

→ Voici ensuite l'enchaînement de l'analyse pour une image d'aile :

1- Extraction des Ailes

Extraction

Extraction des fichiers

2- Rotation des Ailes

Rotation

3- Détection des points

Détection point main

Détection automatique Cubital

Détection pattern Cubital

CornerHarris

Détection automatique Hantel

Détection patern Hantel

CornerHarris

4- Conversion en fichier Excel

ToExcel


• Extraction des Ailes (Wing Extraction)

wing_extraction

wing_extraction est une fonction qui prend en paramètre une image avec les ailes d'abeilles et renvoie le nombre d'image d'aile d'abeilles extraites, elle enregistre également les images extraites sous le nom "idx.png" avec idx un entier allant de 1 au Nombre total d'images détectées.

Important

Prototype : wing_extraction : [String] → [int]

def wing_extraction(filename): # Avec l'extension
    
    nom_fichier = filename 

    image = skio.imread(nom_fichier)
    size = image[:,:,0].shape

    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Pour obtenir les meilleurs résultats sur le seuillage on commence par flouter l'image pour diminuer le bruit autrement,
    # le seuillage considérerait les moindres points foncés issus du bruit sur les zones claires et inversement. C'est 
    # le bruit sel & poivre que l'on veut réduire.


    image = skio.imread(nom_fichier)
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    med = cv2.medianBlur(image,15)
    haus = cv2.bilateralFilter(med, 20, 50, 50) # Blurry the image 
    
    # Le seuillage donnant les meilleurs résultats est le seuillage d'Otsu, on l'effectue alors.

    seu, th = cv2.threshold(haus,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)


    # Ensuite, on veut uniquement les rectangles donc on réalise un opening pour supprimer les artefacts sur l'image.
    # On le fait deux fois, pour supprimer le plus d'artefacts possible.

    th2 = th.copy()
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(17,17))
    opening = cv2.morphologyEx(th2, cv2.MORPH_CLOSE, kernel)

    opening2 = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel) # Il est écrit "close" car cela dépend si l'image est 
    # blanche sur fond noir ou noir sur fond blanche mais l'opération est la même. 

    # L'opération d'opening réduit l'épaisseur des bords des rectangles donc on va les élargir avec une 


    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(10,10))
    final2 = cv2.erode(opening2,kernel,iterations = 4)
    final3 = cv2.dilate(final2,kernel,iterations = 4)
    
    # A partir de la on va commencer la détection des rectangles, on ouvre l'image initiale pour ensuite 
    # découper les rectangles par dessus.
    image = skio.imread(nom_fichier)

    # A l'aide d'openCV on détecte automatiquement les positions des contours. contours est une liste
    contours =cv2.findContours(final3,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)[0]

    # Ensuite on boucle sur les contours puis on stock ceux étant des rectangles dans la liste cntrRect.
    cntrRect = []
    idx = 0
    print(os.getcwd())
    # verification pour le stockage des fichiers
    if os.path.isdir("./save"):
        shutil.rmtree("./save")
        print("Fichier supprimé")

    os.mkdir("./save")
    os.chdir("./save")

    for i in contours:
            epsilon = 0.05*cv2.arcLength(i,True) # Cette fonction calcul le périmètre du contour i, puisque l'on veut 
            # un contour fermé, on ajoute le paramètre True.
            # Then this perimeter is used to calculate the epsilon value for cv2.approxPolyDP() function with a 
            # precision factor for approximating the rectangle.
        
            approx = cv2.approxPolyDP(i,epsilon,True) # Le périmetre précédemment calculé permet alors d'estimer
            # le rectangle le plus proche de ce dernier
            if len(approx) == 4: # on vérifie que le polygone à 4 cotés 
                cv2.drawContours(image,cntrRect,-1,(0,255,0),2)
                cntrRect.append(approx)
                x, y, w, h = cv2.boundingRect(i)
                if w > 400 or h > 600 or w < 100 or h < 100 : # On connait les tailles approximatives de chaque rectangle
                    # donc on supprime ceux trop éloignés. 
                    continue
                roi = image[y:y + h, x:x + w]
                idx += 1
                cv2.imwrite(str(os.getcwd()) + '/' + str(idx) + '.png', roi)
                
    os.chdir("../")            
    return idx

extract_file_paths

extract_file_paths est une fonction qui permet d'extraire les chemins de tous les fichiers dans un répertoire avec leur chemin complet.

Important

Prototype : wing_extraction : [String] → [String]

def extract_file_paths(directory):
    # Liste les fichiers dans le répertoire
    files = os.listdir(directory)
    
    # Extrait le chemin entier
    file_paths = [os.path.join(directory, file) for file in files if file != '.DS_Store' and os.path.isfile(os.path.join(directory, file))]
    
    return file_paths

• Rotation des Ailes (Rotate Wing)

rotate_wing

rotate_wing est un fonction permettant de retourner une image d'aile en fonction de la plus grosse nervure de l'aile.

• Étapes :

1- Seuillage pour ne récupérer que la grosse nervure

2- Binarisation

3- Récupération des lignes avec HoughLines

4- Calcul des angles par rapport à la verticale (on obtient une liste d'angles)

5- Prendre l'angle median

6- Tourner l'image par rapport à cet angle (en degré)

Important

Prototype : rotate_wing : [String] → [image, float]

def rotate_wing(img_path):
    
    ## ÉTAPE 1
    image = sk.imread(img_path)
    shape = image.shape
    
    
    if len(shape) == 2:  # On vérifie si l'image est en niveaux de gris
        image_gray = image
    else:
        image_gray = skc.rgb2gray(image)  
    
    A = np.zeros(shape)
    
    o = skf.threshold_otsu(image_gray) # Seuillage
    o = o/1.5 # Valeur "arbitraire" testé paris plusieurs valeurs et qui donne le meilleur résultat en terme de seuillage
    image_gray[image_gray < o] = 0
    image_gray[image_gray > o] = 1

    
    rectan = morpho.rectangle(10, 3)
    image_ouv = morpho.binary_closing(image_gray, rectan)
    image_f = morpho.binary_opening(image_ouv, rectan)
    image_ero = morpho.binary_dilation(image_f, morpho.square(5))
    image_ero = morpho.binary_dilation(image_f, morpho.rectangle(13, 3))

    ## ÉTAPE 2
    seuil = 0.5  
    image_binaire = (image_ero > seuil).astype(np.uint8)

    ## ÉTAPE 3
    
    lines = cv2.HoughLinesP(image_binaire, 1, np.pi / 180, threshold=100, minLineLength=100, maxLineGap=10)
    
    if lines is not None:
        
        for line in lines:
            x1, y1, x2, y2 = line[0]
            cv2.line(image_binaire, (x1, y1), (x2, y2), (255, 255, 255), 1)

        ## ÉTAPE 4
        angles = []
        for line in lines:
            x1, y1, x2, y2 = line[0]
            angle = np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi
            angles.append(angle)
        
        ## ÉTAPE 5
        ang = np.abs(np.median(angles))
    

        ## ÉTAPE 6
        image_rot = rotate(image, -(90-ang), resize=False, center=None, order=None,
                           mode='constant', cval=1)

        return image_rot, ang
    else:
        return image, 0

• Détection des points (Point Detector)

cornerharris

cornerharris est une fonction qui permet de détecter les intersections à l'aide de la fonction cornerHarris de OpenCV.

L'image en entrée est en binaire (0, 1). Elle est pré-filtré avant son entrée dans cornerHarris par la fonction filtrage (nettoyage et affinage).

• Étapes :

1- Détéction des intersections grâce à cornerHarris (paramètres : image / taille du voisinage / paramètre d'ouverture du filtre de Sobel / paramètre libre de l'équation de détéction de Harris. Cette étape renvoie une image dont chaque pixel a une valeur élevée si forte probabilité que ce soit une intersection, faible valeur si faible probabilité d'intersection.

2- Seuillage de cette nouvelle image pour ne garder que les points qui sont des intersections.

3- Transformation des groupes de points en un point singulier en chaque intersection.

4- Récupération des coordonnées des points d'intersection.

Important

Prototype : cornerharris : [Binary_image, float, float] → [list]

def cornerharris (image, float, float):
    
    image = np.float32(image)
        
    
    # Étape 1
    points = cv2.cornerHarris(image, 5, 3, para1)  # para1 : mis à jour automatiquement par detection_automatique_cubital
    
    # Étape 2
    points_thresh = (points > threshold) * 1       # idem pour threshold
    points_thresh = sku.img_as_ubyte(points_thresh)
    
    # Étape 3
    contours, _ = cv2.findContours(
        points_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    
    # Boucle à travers les tâches blanches autour des intersections pour trouver le centre
    for i, c in enumerate(contours):
    
        # Obtention des frontières des tâches blanches
        x, y, w, h = cv2.boundingRect(c)
        # Calcul des centres des tâches blanches
        cx = int(x + 0.5 * w)
        cy = int(y + 0.5 * h)
    
        # Dessin d'un pixel au milieu détecté
        fillPosition = (cx, cy)
        fillColor = (0, 0, 0)
        cv2.floodFill(points_thresh, None, fillPosition, fillColor,
                      loDiff=(10, 10, 10), upDiff=(10, 10, 10))
        points_thresh[cy, cx] = 255
    
    # Étape 4
    liste_coord = []
    shape = np.shape(points_thresh)
    for i in range(shape[0]):
        for j in range(shape[1]):
            if points_thresh[i, j] == 255:
                liste_coord = liste_coord + [(i, j)]
                
    
    plt.figure()
    plt.imshow(image)
    global indice_images
    
    
    # Marquer les points sur l'image
    for coordonnees in liste_coord:
        plt.scatter(coordonnees[1], coordonnees[0], color='red', marker='o')
    plt.savefig('../tmp/'+str(indice_images)+'.png')
    indice_images = indice_images + 1
    plt.close()
    
        
    return (liste_coord)

detection_automatique_cubital

detection_automatique_cubital est une fonction permettant de traiter l'indice Cubital

• Étapes :

1- Appel de la fonction cornerharris (image, para1, threshold) avec des paramètres initiaux

2- Boucle tant que on a pas 3 points détectés (nécessaires pour le calcul de l'indice cubital)

Warning

Cas d'erreur si moins de points détectés → l'indice Cubital est alors mis à 0

Mise à jour du seuillage puis appel à cornerharris pour réduire le nombre de point et n'avoir que les intersections

3- Calcul de l'indice cubital

Note

On retourne également la liste des distances qui sera utile pour l'indice Hantel

Important

Prototype : detection_automatique_cubital : [image] → [list, float]

def detection_automatique_cubital (img):
   
    img = detection_pattern_cubital(img)   #on travaille sur la zone détectée par detection_automatique_cubital
    img = skm.skeletonize(1-img)
    
    # Étape 1
    a = 0.05
    b = 0.01
    
    list_coord = cornerharris(img, a, b)
    taille = len(list_coord)
    
    # Étape 2 
    while ( taille != 3 ): 
        
        if ( taille == 0 or taille == 1 or taille == 2 ):
            return (0,0) 
  
        b = b + 0.005   
        list_coord = cornerharris(img,a,b)
        taille = len(list_coord)
          
    # Étape 3 
       
    dist = []
    dist.append(np.sqrt(((list_coord[0][0] - list_coord[1][0])**2) + (list_coord[0][1] - list_coord[1][1])**2))
    dist.append(np.sqrt(((list_coord[0][0] - list_coord[2][0])**2) + (list_coord[0][1] - list_coord[2][1])**2))
    dist.append(np.sqrt(((list_coord[1][0] - list_coord[2][0])**2) + (list_coord[1][1] - list_coord[2][1])**2))
    B = min(dist)
    for a in dist :
        if a != max(dist) :
            if a != min(dist) :
                A = a
    
    indice_cubital= A/B
    return (dist, indice_cubital) # renvoie la liste des distances et l'indice cubital

detection_automatique_hantel

detection_automatique_hantel est une fonction permettant de traiter l'indice Hantel

• Étapes :

1- Appel de la fonction cornerharris (image, para1, threshold) avec des paramètres initiaux

2- Boucle tant que on a pas 3 points détectés (nécessaires pour le calcul de l'indice hantel)

Warning

Cas d'erreur si moins de points détectés → l'indice Hantel est alors mis à 0

Mise à jour du seuillage puis appel à cornerharris pour réduire le nombre de point et n'avoir que les intersections

3- Calcul de l'indice hantel

Important

Prototype : detection_automatique_hantel : [image] → [list, float]

def detection_automatique_hantel (img, dist2):

    if (dist2 == 0): #si la distance pour l'indice cubitale est nul, alors on a pas réussi à exploiter l'image donc on renvoit 0 aussi ici
        return 0

    img = detection_pattern_hantel(img)   #on travaille sur la zone détectée par detection_automatique_hantel
    img = skm.skeletonize(1-img)
    
    # Étape 1 
    a = 0.05
    b = 0.01
    
    list_coord = cornerharris(img, a, b)
    taille = len(list_coord)
    
    
    # Étape 2 
    while ( taille != 2 ): 
        
        if ( taille == 0 or taille == 1 ):
            return 0 # On retourne 0 si il y a une erreur
             
              
        b = b + 0.005     
        list_coord = cornerharris(img,a,b)
        taille = len(list_coord)
    
    
    # Étape 3 
    dist = []
    dist.append(np.sqrt(((list_coord[0][0] - list_coord[1][0])**2) + (list_coord[0][1] - list_coord[1][1])**2))
    A = max(dist)
    
    #on récupère la longeur utile qui a été trouvée dans l'indice cubital
    B = max(dist2)

    indice_hantel = A/B    
    
    return indice_hantel 

detection_pattern_cubital

detection_pattern_cubital est une fonction permettant de détecter la zone utile sur laquelle la détection va être réalisée pour l'indice cubital

• Étapes :

1- Test des 6 templates avec la fonction match_template. Cette fonction fait "glisser" le masque sur toute l'image et retourne la zone avec le maximum de ressemblance. On stocke ces résultats dans une liste

2- On souhaite dans la liste récupérer le maximum des maximums. On donne l'image de la zone à la fonction detection_automatique_cubital(img)

Important

Prototype : detection_pattern_cubital : [image] → [image]

def detection_pattern_cubital(image): 
   
    # Étape 1
    
    matchs = []
    for k in range (1, 6) :
        pattern_ref = skio.imread(main_pathname / './images /Masque_cubital_{}.png'.format(k))
        pattern_ref = pattern_ref[:, :, :3]
        pattern_ref = skc.rgb2gray(pattern_ref)
        result = skf.match_template(image, pattern_ref, mode = 'mean')
        matchs.append(result)
    
    # Étape 2
    max_unique_value = float('-inf')
    maxi = None
    
    for i in matchs :
        unique_values = set()
        for row in i:
            unique_values.update(row)
        current_max_unique_value = max(unique_values)
        
        if current_max_unique_value > max_unique_value:
            max_unique_value = current_max_unique_value
            maxi = i
    
    #Affichage de l'image originale et la zone correspondant au motif
    ij = np.unravel_index(np.argmax(maxi), maxi.shape)
    x, y = ij[::-1]

    fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(8, 3))
    hcoin, wcoin = pattern_ref.shape
    matched_area = image[y:y+hcoin, x:x+wcoin]

    
    return (matched_area)

detection_pattern_hantel

detection_pattern_hantel est une fonction permettant de détecter la zone utile sur laquelle la détection va être réalisée pour l'indice hantel

• Étapes :

1- Test des 6 templates avec la fonction match_template. Cette fonction fait "glisser" le masque sur toute l'image et retourne la zone avec le maximum de ressemblance. On stocke ces résultats dans une liste

2- On souhaite dans la liste récupérer le maximum des maximums. On donne l'image de la zone à la fonction detection_automatique_hantel(img)

Important

Prototype : detection_pattern_cubital : [image] → [image]

def detection_pattern_hantel(image):
 
    # Étape 1
    matchs = []
    for k in range (1, 8) :
        pattern_ref = skio.imread(main_pathname / './images /Masque_hantel_{}.png'.format(k))
        pattern_ref = pattern_ref[:, :, :3]
        pattern_ref = skc.rgb2gray(pattern_ref)
        result = skf.match_template(image, pattern_ref, mode = 'mean')
        matchs.append(result)
    
    # Étape 2 
    max_unique_value = float('-inf')
    maxi = None
    
    for i in matchs :
        unique_values = set()
        for row in i:
            unique_values.update(row)
        current_max_unique_value = max(unique_values)
        
        if current_max_unique_value > max_unique_value:
            max_unique_value = current_max_unique_value
            maxi = i
      
    
    ij = np.unravel_index(np.argmax(maxi), maxi.shape)
    x, y = ij[::-1]

    hcoin, wcoin = pattern_ref.shape
    rect = plt.Rectangle((x, y), wcoin, hcoin, edgecolor='r', facecolor='none')
    matched_area = image[y:y+hcoin, x:x+wcoin]


    
    return (matched_area)

filtrage

filtrage est une fonction permettant de filtrer les images d'ailes afin d'enlever les impuretés et ne garder que les nervures

• Étapes :

1- Filtrage de l'image

2- Binarisation de l'image filtrée

Important

Prototype : detection_pattern_cubital : [image] → [binary_image]

def filtrage(img):

    # Étape 1
    img = skc.rgb2gray(img)
    img = ske.equalize_adapthist(img,clip_limit=0.01)
    img = skr.denoise_bilateral(img)
    img = skm.opening(img,footprint = skm.diamond(1))
    img = skm.black_tophat(img, footprint = skm.diamond(9))

    # Étape 2
    img = sku.img_as_uint(img)
    img = sku.img_as_ubyte(img)
    img = img/255
    
    seuil = 0.2
    img = (img < seuil)  #image binaire
    return img

detection_point

detection_point est une fonction qui rassemble toutes les fonctions de la Détection de points. Elle récupère l'image qui a été segmentée, elle appelle la fonction filtrage, puis detection_automatique_cubital(img) et detection_automatique_hantel(img, dist)

Elle retourne les indices qui seront stockés dans le fichier Excel

Important

Prototype : detection_pattern_cubital : [String] → [float, float]

def detection_point (chemin):

    plt.close('all')
    
    img = chemin
    img = img[:, :, :3]
    img = filtrage(img)
    
    dist,indice_cubital = detection_automatique_cubital(img)
    indice_hantel = detection_automatique_hantel(img, dist)

    return (indice_cubital, indice_hantel)

• Conversion en Excel

toExcel

toExcel est une fonction qui permet de générer un fichier excel. Cette fonction qui prends un dataframe en entrée et un chemin de fichier.

Caution

La structre du dataframe doit être comme suit : N lignes correspondants aux N images analysées, 2 colonnes pour les valeurs des indices. Elle ne retourne rien mais sauvegarde un fichier contenant les valeurs des indices, les moyennes et écarts types.

Important

Prototype : toExcel : [NumpyArray(N,2), String] → Void

def toExcel(dataset, file_path):
    path = os.getcwd()
    filename = "Apiculteur.xlsx"
    travail = openpyxl.Workbook()
    sheet = travail.active
    sheet.title = "Données"
    lettre_debut = 65
    
    print("\nNous sommes à la feuille : " + str(sheet.title))
   
    # Écriture des noms de colonnes, pour se placer sur une case du fichier Excel il faut écrire 
    # LETTRENUMERO, par exemple : A1. L'indicage commence à 1 
    for j in range(lettre_debut, lettre_debut + dataset.shape[1]):
        sheet[chr(j) + "1"] = dataset.columns[j - lettre_debut]
   
    # Écriture des données
    for i in range(dataset.shape[0]):
        for j in range(lettre_debut, lettre_debut + dataset.shape[1]):
            sheet[chr(j) + str(i + 2)].value = dataset.iloc[i, j - lettre_debut]
   
    # Écriture des moyennes et des écarts types
    moyenne = ["moy indice cubital", "moyenne hantel"]
    ecart_type = ["E-T indice cubital", "E-T hantel"]
   
    for i in range(dataset.shape[1]):
        # Ecriture des moyennes
        sheet[chr(lettre_debut + 2 + dataset.shape[1]) + str(i + 1)] = moyenne[i]
        sheet[chr(lettre_debut + 3 + dataset.shape[1]) + str(i + 1)].value = dataset.iloc[:, i].mean()
       
        # Ecriture des écarts types
        sheet[chr(lettre_debut + 4 + dataset.shape[1]) + str(i + 1)] = ecart_type[i]
        sheet[chr(lettre_debut + 5 + dataset.shape[1]) + str(i + 1)].value = dataset.iloc[:, i].std()
   
    # Sauvegarde
    travail.save(file_path)
    print("\nSauvegarde effectuée")