Ceci est une ancienne révision du document !
Table des matières
Présentation du projet
vintage est un programme qui a pour but d'appliquer un effet vintage; vieilli à une image donnée ou prise.
Ce programme n'a pas d'autre but que de divertir.
Exemple d'exécution de ce programme:
Outils requis et Librairies Python
Le programme
Le programme se trouve en intégralité ici.
Outils requis
Aucun outil spécifique n'est requis pour faire fonctionner ce programme.
Librairies
Ce programme utilise les librairies suivantes:
import cv2 import numpy as np import copy import random as rng import sys
lignes 1 à 5.
cv2
référence la librairie OpenCV permettant de nombreuses opérations sur les images en Python et C++
numpy
référence la librairie NumPy prodiguant une meilleure création/utilisation/gestion des objets à plusieurs dimensions.
sys
référence la librairie sys pour la gestion des paramètres en Python.
Explication du programme
Le contrôle d'erreur
Il s'agit d'un pan entier de code contrôlant l'arbre d'argument suivant:
Ce n'est pas nécessairement excitant mais ça permet au programme de détecter différentes erreurs, de s'arrêter dans de meilleures conditions et en indiquant l'erreur en question.
Si cela vous intéresse, le contrôle s'étend lignes 163 à 196.
Les fonctions
Ce programme contient 7 fonctions:
- generateCircleMask(img,center,radius).
- cappedValue(value,least=0,most=255).
- applyVariationToBGRPixel(pixel,var,withRNG=False,low_rng=0,high_rng=0).
- maskDifference(mask1,mask2).
- vintage(img).
- options().
- optionTime(time).
generateCircleMask
def generateCircleMask(img,center,radius): h,w,v = img.shape sq_rad = radius**2 toThreshold = cv2.cvtColor(img.astype('uint8',copy=False),cv2.COLOR_BGR2GRAY) for i in range (0,h): for j in range (0,w): if(((i-center[0])**2 + (j-center[1])**2) <= sq_rad): toThreshold[i,j] = 0 else: toThreshold[i,j] = 255 ret, circleMask = cv2.threshold(toThreshold, 10, 255, cv2.THRESH_BINARY) return circleMask
lignes 9 à 21
La fonction prend une image img et renvoie un masque circulaire de centre center et de rayon radius de la même taille que celle de l'image donnée.
cappedValue
def cappedValue(value,least=0,most=255): if(value < least): value = least elif(value > most): value = most return value
lignes 25 à 31
La fonction prend une valeur value et s'assure qu'elle appartienne à l'intervalle [least,most].
applyVariationToBGRPixel
def applyVariationToBGRPixel(pixel,var,withRNG=False,low_rng=0,high_rng=0): b,g,r = pixel if(withRNG): b = cappedValue(b - (var + rng.randint(low_rng,high_rng))) g = cappedValue(g - (var + rng.randint(low_rng,high_rng))) r = cappedValue(r - (var + rng.randint(low_rng,high_rng))) else: b = cappedValue(b - var) g = cappedValue(g - var) r = cappedValue(r - var) pixel = [b,g,r] return pixel
lignes 35 à 47
La fonction prend un pixel \\pixel
(i.e. un objet à trois dimensions), une valeur \\var
et soustrait cette valeur uniformément aux valeurs que contient pixel.
Si le booléen withRNG est vrai, à chaque instance de var lui sera ajouté une valeur aléatoire comprise dans l'intervalle [low_rng,high_rng].
maskDifference
def maskDifference(mask1,mask2): h,w = mask1.shape mask_diff = copy.copy(mask1) for i in range (0,h): for j in range (0,w): if(mask1[i,j] == 0 and mask2[i,j] == 255): mask_diff[i,j] = 255 else: mask_diff[i,j] = 0 return mask_diff
lignes 51 à 61
La fonction prend deux masques mask1 et mask2 de même taille et renvoie le masque résultant de leur différence.
vintage
def vintage(img): # Useful datas height, width, value = img.shape # Get Canny edges grayimg = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) edgyimg = cv2.Canny(grayimg,50,200,apertureSize=3) # Dilate edges rect = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3)) thicc_img = cv2.dilate(edgyimg,rect,iterations=1) # Thickening edges invertimg = cv2.bitwise_not(thicc_img) colorgray = cv2.cvtColor(invertimg,cv2.COLOR_GRAY2BGR) ret, mask = cv2.threshold(grayimg, 10, 255, cv2.THRESH_BINARY) mask_inv = mask roi = grayimg[0:height, 0:width] img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv) img2_fg = cv2.bitwise_and(img,img,mask = mask) img1_bg = cv2.cvtColor(img1_bg,cv2.COLOR_GRAY2BGR) addimg = cv2.add(img1_bg,img2_fg) img[0:height, 0:width] = addimg # Speckle Noise gauss = np.random.randn(height,width,value) gauss = gauss.reshape(height,width,value) img = img + (img * gauss)/16 # Sepia filter I sepia = np.matrix([[0.272,0.534,0.131],[0.349,0.686,0.168],[0.393,0.769,0.189]]) img = cv2.transform(img,sepia) # Toneing center_h = int(height/2) center_w = int(width/2) mask = generateCircleMask(img,[center_h,center_w],center_h-5) outer_mask = generateCircleMask(img,[center_h,center_w],center_h+5) outer_mask = maskDifference(outer_mask,mask) var = 5 unb = 1 low = -2 high = 2 for i in range (0,height): for j in range (0,width): if(mask[i,j] == 255): img[i,j] = applyVariationToBGRPixel(img[i,j],var,True,low,high) elif(outer_mask[i,j] == 255): img[i,j] = applyVariationToBGRPixel(img[i,j],int(var/2),True,low,high) # Sepia filter II sepia = np.matrix([[0.272,0.534,0.131],[0.349,0.686,0.168],[0.393,0.769,0.189]]) img = cv2.transform(img,sepia) # Gaussian blur img = cv2.GaussianBlur(img,(5,5),0) # Border bs = 10 img = cv2.copyMakeBorder(img,bs,bs,bs,bs,cv2.BORDER_CONSTANT,value=(230,245,245)) # Return result return img
lignes 65 à 128
La fonction prend une image img.
La partie #Useful Datas
récupère la taille, la hauteur et la valeur de img et les place dans des variables.
La partie Get Canny edges
trace les contours de l'image en niveaux de gris.
La partie Dilate edges
dilate ses contours.
La partie Thickening edge
les désagrège de l'image originale.
La partie Speckle noise
rajoute du bruit sur l'image.
La partie Sepia filter I
applique un filtre sépia sur l'image.
La partie Toneing
éclaircit une zone centrale de l'image.
La partie Sepia filter II
applique à nouveau un filtre sépia.
La partie Gaussian Blur
applique un flou Gaussien à l'image.
La partie Border
génère des bordures à l'image.
La partie Return result
retourne l'image ainsi changée.
options
def options(): print("Nom ou chemin de l'image à traiter (avec l'extension)") name_i = input("-> ") print("") image = cv2.imread(name_i) try: dtype_debug = image.dtype except AttributeError as ae: print("Erreur,",name_i,"ne peut pas être lu (fichier invalide ou absent)\n") raise ae print("Quel nom donner à l'image en sortie (avec l'extension)?") name_o = input("-> ") print("") return image,name_o
lignes 132 à 147
Communique avec l'utilisateur pour récupérer certaines informations (notamment l'image à traiter).
Remarque: uniquement si le programme a été lancé avec l'argument -o
.
optionTime
def optionTime(time): # See Execution time of vintage function print("Voulez-vous connaître le temps d'execution de la fonction vintage? [y/n]") know = input("-> ") if(know == 'y'): print("Temps d'execution du programme:",time,"secondes")
lignes 151 à 156
Demande à afficher le temps time d'exécution du programme.
Remarque: uniquement si le programme a été lancé avec l'argument -o
.
Le main
Le main ne s'occupe que des options (si argument -o
) et de la fonction principale.
Problèmes et améliorations
Problèmes
Même si la scène ou l'image comporte un visage, le programme peut:
- Ne pas détecter pas de visage ou en détecter trop.
- Ne pas substituer le visage même s'il l'a détecté.
Solutions possibles et améliorations
En ce qui concerne les problèmes de détection (1), il suffirait d'utiliser un classifier mieux entraîné, ou bien de faire le traitement (detectMultiScale
) avec plusieurs classifiers et compiler les résultats dans une liste sans doublon.
Faire tourner plusieurs classifiers par image porterait un certain coup sur le temps réel, mais est envisageable pour l'image fixe.
Concernant l'absence d'affichage malgré détection (2), il s'agit d'un compromis.
Vous vous souvenez du traitement coincé dans un try … catch
? C'est de ça qu'il s'agit. Cela provient de “l'explosion” du ROI qui se fait sans contrôle de valeur aucun.
Une solution serait d'avoir un traitement plus rigoureux concernant les valeurs maximales de l'image de départ, définir une limite à ne pas franchir et ne superposer qu'une portion du flash lumineux d'une limite vers la fin, du début jusqu'à une limite, ou même d'une limite jusqu'à une limite.
Cette fois-ci, on peut considérer que cela n’entacherait pas le temps réel car il s'agit d'opérations de tests, peu coûteuses. Seulement, par soucis de temps, cette solution n'a pas été réalisée.
Enfin, vis à vis des améliorations possibles, j'ai surtout en tête des améliorations de performance pour le temps réel sur une machine moins puissante (RaspBerry par exemple). Une amélioration évidente serait de paralléliser le programme, puisque la fonction s'applique indépendamment à chaque visage.
Merci pour votre attention.
Philibert Thomas