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.
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) .
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.
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.
- 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
/* -----------
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:
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"
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.
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.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
Utilisation
Télécharger le code source main.cpp