−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
Le programme est particulièrement lent (~4s).
Solutions possibles et améliorations
Doter le programme d'une meilleure algorithmie pour notamment virer des opérations en trop/peu utiles. Le réécrire en C++ serait aussi un bon moyen pour booster ses performances.
Pour les améliorations possibles, on pourrait envisager du temps réel et une meilleure gestion d'arguments.
Merci pour votre attention.
Philibert Thomas