Fusionner une image transparente et une photo


La page Créer des images transparentes avec CodeBlocks et OpenCV montre comment créer automatiquement une image transparente et l'enregistrer dans un fichier png. Une image transparente peut être superposée à une photo, pour lui servir de cadre par exemple. Cette opération peut être réalisée manuellement, à l'aide d'un logiciel graphique comme GIMP ou bien automatiquement à l'aide d'un programme.

Le programme exemple décrit dans cette page réalise cette tâche. Il superpose une image transparente ("C:/ masque.png"), à une photo ("C:/ photo.jpg") et enregistre le résultat dans un fichier jpg ( "photo-sauv.jpg"). Cette technique peut être utilisée pour encadrer une photo ou pour ajouter un texte, une signature, un copyright ou un dessin à cette photo.

Remarque : La page Programmer avec CodeBlocks et OpenCV montre comment installer et mettre en oeuvre cet l’environnement.

1) Rappels

1.1) Pixels et transparence

Lorsqu’on ajoute une image1 transparente à une image2 non transparente, les pixels de l’image3 résultante prennent comme valeurs de couleur une combinaison linéaire des valeurs de couleur des pixels des images 1 et 2. Pour préciser les choses, on superpose, à l’aide de GIMP, une image verte transparente et une image rouge non transparente. On obtient comme résultat une image de couleur marron non transparente.

2

Si on appelle r3, g3, b3 et a3 les valeurs de couleur rgb (red, green, blue) et de transparence (alpha) de l'image3 on constate, en utilisant l'outil pipette de GIMP, que (r3, g3, b3, a3) = (153, 102, 0, 255). On remarque qu'on trouve les valeurs obtenues en apppliquant les formules suivantes :
r3 = 153 = 0,4 x 0 + 0,6 x 255
g3 = 102 = 0,4 x 255 + 0,6 x 0
b3 = 0 = 0,4 x0 + 0,6 x 0

Conclusion

D’une façon générale, soient :

  • une image 1 transparente, ayant un taux de transparence a compris entre 0 et 1, et possédant la couleur C1= (r1,g1,b1,a1) avec a1 = a x 255;
  • une image 2 non transparente possédant la couleur C2= (r2,g2,b2);
  • une image 3 non transparente, résultat de la superposition de l’image1 et de l’image 2, possédant la couleur C3= (r3,g3,b3)

Alors on a : C3 = aC1 + (1-a)C2

  • r3 = a r1 + (a-1) r2
  • g3 = a g1 + (a-1)g2
  • b3 = a b1 + (a-1) b2

1.2) Pixels, fichiers images et classe Mat de OpenCV

1.2.1) Méthode de rangement des pixels dans un objet Mat de OpenCV

Comme on peut le vérifier à l'aide du petit programme exemple ci-après, OpenCV range les images, dans les objets de la classe Mat, dans l’ordre des pixels BGRA. Pour chaque pixel, il utilise un octet (unsigned char) pour représenter chaque composante de couleur (soient 3 octets pour un pixel bgr) et un octet supplémentaire pour représenter la transparence éventuelle (soient 4 octets pour représenter un pixel bgra). Chaque pixel est donc représenté par 3 ou 4 octets selon que l’image chargée possède des pixels déclarés transparents (fichiers png) ou non (fichier jpg) .

3

1.2.2) Positions des pixels et de leurs composants bgra

Pour une image de dimension W x H chargée dans un objet Mat :

  • Du point de vue "logique" les valeurs de couleur des pixels (type unsigned char) sont rangées dans l'objet Mat dans l'ordre des lignes i et des colonnes j. Ainsi le pixel ij se trouve à l'intersection de la ligne i et de la colonne j.
  • Du point de vue "physique" les valeurs de couleur de ces pixels sont en réalité rangées en mémoire de façon séquentielle.
Image transparente

Pour une image transparente de largeur W, l'octet b00 du pixel 00 (b00, g00, r00, a00) 00 se trouve à l'adresse relative 0 et l'octet bij du pixel ij (bij, gij, rij, aij)j se trouve à l'adresse relative i*W*4 + j*4.
5

Les adresses physiques relatives, dans l'objet Mat contenant l'image 1, des composantes de couleur du pixel ij sont :

  • i*W*4 + j*4 pour bij
  • i*W*4 + j*4 + 1 pour gij
  • i*W*4 + j*4 + 2 pour rij
  • i*W*4 + j*4 + 3 pour aij
Image non transparente

Pour une image non transparente de largeur W, l'octet b00 du pixel 00 (b00, g00, r00) se trouve à l'adresse relative 0 et l'octet bij du pixel ij (bij, gij, rij) se trouve à l'adresse relative i*W*3 + j*3 car il n'y a pas de valeur a.
6

  • i*W*3 + j*3 pour bij
  • i*W*3 + j*3 + 1 pour gij
  • i*W*3 + j*3 + 2 pour rij

Programme exemple

Code source

/* -----------
Ajout d'un masque (png) sur une image (jpg)
Auteur C.Turrier - Juin 2016
----------- */
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
#include "stdio.h"
using namespace cv;
using namespace std;
int main(int argc, char *argv[])
{
//----- valeurs modifiables
int W=1200;//px : largeur de la photo.jpg et du masque.png
int H=800; //px : hauteur de la photo.jpg et du masque.png
//------------------------
//lecture image1 et image2
Mat image1 = imread("C:/masque.png", -1);//-1 car canal alpha
Mat image2 = imread("C:/photo.jpg", 1);//1 car pas d'alpha
Mat image3 = imread("C:/photo.jpg", 1);
if (image1.cols!=image2.cols){
cout << "erreur ! largeurs des images 1 et 2 non égales" << endl;
cout << image1.cols << endl; cout << image2.cols << endl; exit(0);}
if (image1.rows!=image2.rows){
cout << "erreur ! hauteurs des images 1 et 2 non égales" << endl;
cout << image1.rows << endl; cout << image2.rows << endl; exit(0);}
//création image3 = image1 + image2
for(int i = 0; i < H; i++){
for(int j = 0; j < W; j++){
//a pixel ij
unsigned char image1_aij =image1.data[i*W*4 + j*4 + 3];
double alpha_ij = (double)image1_aij/255;
//b pixel ij
unsigned char image1_bij = image1.data[i*W*4 + j*4];
unsigned char image2_bij = image2.data[i*W*3 + j*3];
image3.data [i*W*3 + 3*j] = image1_bij*alpha_ij + image2_bij*(1-alpha_ij);
//g pixel ij
unsigned char image1_gij = image1.data[i*W*4 + j*4 +1];
unsigned char image2_gij = image2.data[i*W*3 + j*3 +1];
image3.data [i*W*3 + 3*j + 1] = image1_gij*alpha_ij + image2_gij*(1-alpha_ij);
//r pixel ij
unsigned char image1_rij = image1.data[i*W*4 + j*4 + 2];
unsigned char image2_rij = image2.data[i*W*3 + j*3 + 2];
image3.data [i*W*3 + 3*j + 2] = image1_rij*alpha_ij + image2_rij*(1-alpha_ij);
}}
//affichage et enregistrement image3
imshow("image3", image3);
vector compression;
compression.push_back (CV_IMWRITE_JPEG_QUALITY );
compression.push_back(100); // 100 (qualité max) -> 0 (qualité min)
imwrite("C:/photo-sauv.jpg", image3, compression);
waitKey( 0 );
return(0);
}

Explications

Les images 1 et 2 associées aux fichiers "C:/masque.png" et "C:/photo.jpg" sont, à l'aide de la méthode imread(), chargées en mémoire dans les objets image1 et image2 de la classe Mat.

Pour que le programme puisse fonctionner:

  • les deux images doivent avoir la même taille. Le programme s'arrête automatiquement, à l'aide de l'instruction exit(0), si cette condition n'est pas vérifiée.
  • L'image 1 doit présenter 4 canaux (rgba) mais l'image 2 doit présenter seulement 3 canaux (rgb). Cela est normalement le cas si ces images sont respectivement de type png et jpg.

On balaye ensuite les pixels de 0 à H-1 et de 0 à W-1 et on place dans image3, une par une, toutes les valeurs des composantes de couleurs des pixels résultant de la fusion des images 1 et 2.

Une fois cette opération terminée, on affiche l'image3 obtenue dans la fenêtre du programme puis on l'enregistre, en qualité maximale, dans le fichier "photo-sauv.jpg"

Utilisation

Avant de lancer le programme, il faut vérifier qu'on a bien placé sur "C:\" les images "photo.jpg" et "masque.png" dotées des mêmes dimensions. Le résultat est immédiat. Il ne reste plus qu'à ouvrir le fichier "photo-sauv.jpg enregistré sur "C:\" afin de vérifier que tout s'est passé comme prévu.

7 photo-sauv.jpg

Télécharger le code source main.cpp