-
Notifications
You must be signed in to change notification settings - Fork 0
Documentation Code
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
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 :
4- Conversion en fichier Excel
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 idxextract_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_pathsrotate_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, 0cornerharris 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 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 cubitaldetection_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 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 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 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 imgdetection_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)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")