/dev/log

Blog technique de Benjamin Billet

Extraire l'essentiel de pHash 0.9.6

Rédigé par Benjamin Billet -
Classé dans : Projet similarity-finder - Mots clés : phash

L'implémentation de pHash 0.9.6 fournit différentes fonctionnalités :
  • image hash (requiert CImg 1.3+)
  • video hash (requiert CImg 1.3+ et FFmpeg)
  • audio hash (requiert libsndfile, libsamplerate, libmpg123)
Mon problème de recherche d'images similaires ne requiert que la partie image de la librairie pHash, c'est-à-dire une centaine de ligne sur les 2000+ du projet. Concrètement, seules trois fonctions sont nécessaires et peuvent directement être extraites de pHash.cpp :
// 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.
Une méthode simple pour ne plus polluer la console avec ces warnings consiste à rediriger stderr vers un fichier :
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
}

Sources

J'ai intégré ce code avec une interface ligne de commande basique, voir la page du projet similarity-finder.

Les commentaires sont fermés.