Outils pour utilisateurs

Outils du site


diy:projets:vintage

Ceci est une ancienne révision du document !


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:
Avant Après


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.
  • copy référence la librairie copy en sa qualité de duplicateur de données.
  • random référence la librairie random pour la génération de valeurs aléatoires.
  • 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:

  1. Ne pas détecter pas de visage ou en détecter trop.
  2. 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

diy/projets/vintage.1527850231.txt.gz · Dernière modification : 2018/06/01 10:50 de tphilibert