Extraire l'essentiel de pHash 0.9.6
Rédigé par Benjamin Billet -
Classé dans : Projet similarity-finder -
Mots clés : phash
- image hash (requiert CImg 1.3+)
- video hash (requiert CImg 1.3+ et FFmpeg)
- audio hash (requiert libsndfile, libsamplerate, libmpg123)
// calcul d'un hash à partir d'un fichier
int ph_dct_imagehash(const char* file,ulong64 &hash);
// calcul de la distance entre deux hash
int ph_hamming_distance(const ulong64 hash1,const ulong64 hash2);
// utilisé par ph_dct_imagehash
CImg<float>* ph_dct_matrix(const int N);
Qui plus est, le bloc permettant d'assurer l'existence du type ulong64 (utilisé pour stocker les hash) doit être récupéré dans pHash.h :
#if defined( _MSC_VER) || defined(_BORLANDC_)
typedef unsigned _uint64 ulong64;
typedef signed _int64 long64;
#else
typedef unsigned long long ulong64;
typedef signed long long long64;
#endif
Remarque : pour afficher un ulong64 avec un printf, %llu doit être utilisé.
Environnement
CImg se présente sous la forme d'un unique fichier CImg.h qui doit être intégré statiquement au projet. Il est nécessaire de définir les constantes suivante, au niveau du Makefile, du projet Eclipse ou directement dans le code :- Selon les formats d'image désirés, il sera nécessaire d'installer les librairies correspondantes : dans mon cas, libpng, libjpeg et libtiff seront utilisées (installer les versions *-devel). Pour que CImg puisse exploiter ces librairies, les constantes cimg_use_png, cimg_use_jpeg et cimg_use_tiff doivent être définies.
- Les constantes WIN32, WIN64 ou unix doivent être définies en fonction du système d'exploitation pour assurer que CImg charge les bons entêtes. Ici, nous opterons plutôt pour __CYGWIN__.
- Comme nous allons uniquement utiliser les fonctions de lecture et de traitement d'images de CImg, nous pouvons définir cimg_display = 0 pour désactiver les fonctionnalités de création/manipulation de fenêtres.
#define cimg_display 0
#define cimg_use_png
#define cimg_use_jpeg
#define cimg_use_tiff
#define __CYGWIN__
Crash de pHash avec les PNG transparent
Lors de mes premiers tests, pHash crashait systématiquement sur les fichiers PNG avec transparence. Après lecture du code, il apparaît qu'il s'agit d'un bug dans ph_dct_imagehash. Le crash est simplement du au fait que les variables width, height et depth du test src.spectrum() == 4 sont assignées à partir de l'image vide img au lieu de l'image à hasher src.int ph_dct_imagehash(const char* file,ulong64 &hash) {
...
CImg<float> img; // img est vide
if (src.spectrum() == 3) {
img = src.RGBtoYCbCr().channel(0).get_convolve(meanfilter);
} else if (src.spectrum() == 4) {
int width = img.width(); // width = 0
int height = img.height(); // height = 0
int depth = img.depth(); // depth = 0
// les trois variables étant nulles, width-1, height-1 et depth-1 équivalent à -1 => crash de CImg
img = src.crop(0,0,0,0,width-1,height-1,depth-1,2).RGBtoYCbCr().channel(0).get_convolve(meanfilter);
} else {
img = src.channel(0).get_convolve(meanfilter);
}
...
Concrètement, la modification à effectuer est la suivante :
// remplacer ces lignes :
int width = img.width();
int height = img.height();
int depth = img.depth();
// par ces lignes :
int width = src.width();
int height = src.height();
int depth = src.depth();
Gérer les warnings de libpng et libtiff
Lors de l'exécution de ph_dct_imagehash, des warnings peuvent apparaître dans la console :TIFFReadDirectory: Warning, Unknown field with tag 20624 (0x5090) encountered. TIFFReadDirectory: Warning, Unknown field with tag 20625 (0x5091) encountered. TIFFReadDirectory: Warning, Unknown field with tag 33434 (0x829a) encountered. ... libpng warning: iCCP: known incorrect sRGB profile libpng warning: iCCP: cHRM chunk does not match sRGB
- Les warnings de libtiff sont levés dès lors qu'un fichier .tif utilise des tags spécifiques à un logiciel ou à une extension du standard. La règle est d'ignorer ces tags, ce que fait libtiff en levant toutefois des warnings.
- Les warnings de libpng sont levés, depuis la version 1.6, dès lors qu'un fichier .png utilise un profil sRGB trop ancien.
freopen("stderr.log", "a", stderr); // a = appending, w = overwrite
Si l'on souhaite aller un peu plus en profondeur et filtrer explicitement les messages, libtiff et libpng proposent tous deux de définir des fonctions qui vont capturer les warnings. Cependant, si libtiff permet de définir un handler global, libpng associe un handler à un png_structp, typiquement lors de la création de la structure en lecture ou en écriture. Aussi, dans le cas de libpng, nous devrons modifier le code de CImg.
Cas libtiff (enregistrement global en début de programme)void my_tiff_warning_handler(const char *module, const char *fmt, va_list ap) {
// voir http://libtiff.org/man/TIFFError.3t.html
}
int main(int argc, const char* argv[]) {
...
// définir le nouveau handler
TIFFErrorHandler oldHandler = TIFFSetWarningHandler(&(my_tiff_warning_handler));
...
}
Cas libpng (enregistrement local dans CImg, au moment où un png_structp pour la lecture est crée)
png_voidp user_error_ptr = 0;
// remplacer cette ligne :
png_error_ptr user_error_fn = 0, user_warning_fn = 0;
// par cette ligne :
png_error_ptr user_error_fn = 0, user_warning_fn = &(my_png_warning_handler);
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn);
En créant au préalable la fonction vide my_warning_handler.
void my_png_warning_handler(png_structp png_ptr, png_const_charp warning_message) {
// voir png_error_ptr dans http://www.libpng.org/pub/png/libpng-manual.txt
}