Afficher une photo avec la bonne orientation dans MATLAB

Prenons trois photos d’un même objet prises avec trois orientations de l’appareil photo.
robot-1
robot-3
robot-2

La plupart des logiciels détectent automatiquement l’orientation de la photo et affiche l’objet dans sa position naturelle. Ce n’est pas le cas de MATLAB.

Nous allons donc voir comment afficher automatiquement une photo avec la bonne orientation dans MATLAB. Pour ce faire, nous nous intéresserons à des données complémentaires codées dans le fichier JPEG.

Affichage des photos avec MATLAB

Avec MATLAB, pour afficher une image stockée dans un fichier JPEG, on utilise les fonctions imread et image.

Voici le code MATLAB qui lit les fichiers et affiche les images 

filename = {'robot-1.JPG' 'robot-2.JPG' 'robot-3.JPG'};

figure

for n = 1:3
    X = imread(filename{n}, 'jpg');
    subplot(3,1,n)
    image(X)
    axis image off
end

Voici le résultat :
photos-matlab-1
L’orientation des photos n’est pas automatiquement prise en compte par MATLAB.

Les métadonnées EXIF

En plus de la photo en elle-même, l’appareil photo (ou le smartphone) enregistre d’autres informations dans le fichier JPEG. Ces informations sont connues sous le nom de métadonnées EXIF. Plus d’informations sur la page Wikipédia  Exchangeable image file format.

Pour accéder à ces informations avec MATLAB, on utilise la fonction imfinfo (ou la fonction exifread avec les anciennes versions de MATLAB).

Par exemple :

f = imfinfo('robot-1.JPG')

renvoi :

f =

            Filename: 'C:\Users\Jerome\Desktop\robot-1.JPG'
         FileModDate: '04-May-2016 14:14:26'
            FileSize: 92456
              Format: 'jpg'
       FormatVersion: ''
               Width: 640
              Height: 480
            BitDepth: 24
           ColorType: 'truecolor'
     FormatSignature: ''
     NumberOfSamples: 3
        CodingMethod: 'Huffman'
       CodingProcess: 'Sequential'
             Comment: {}
                Make: 'Panasonic'
               Model: 'DMC-FZ18'
         Orientation: 1
         XResolution: 72
         YResolution: 72
      ResolutionUnit: 'Inch'
            Software: 'Ver.1.0  '
            DateTime: '2016:05:04 14:14:26'
    YCbCrPositioning: 'Co-sited'
       DigitalCamera: [1x1 struct]
         UnknownTags: [2x1 struct]
       ExifThumbnail: [1x1 struct]

On remarque la métadonnées Orientation qui, comme son nom l’indique, stocke l’orientation de l’appareil lors de la prise de vue. Ce champs prend une valeur entre 1 et 8 selon la liste suivante :

  • 1 = Horizontal (normal)
  • 2 = Mirror horizontal
  • 3 = Rotate 180
  • 4 = Mirror vertical
  • 5 = Mirror horizontal and rotate 270 CW
  • 6 = Rotate 90 CW
  • 7 = Mirror horizontal and rotate 90 CW
  • 8 = Rotate 270 CW

Les valeurs qui nous intéressent ici sont 1, 6 et 8.

Prise en compte automatique de l’orientation des photos

Vous l’aurez maintenant compris, il faut donc utiliser imfinfo en plus des fonctions imread et image.

Voici le code :

filename = {'robot-1.JPG' 'robot-2.JPG' 'robot-3.JPG'};
figure

for n = 1:3

    X = imread(filename{n}, 'jpg');
    f = imfinfo(filename{n});
    subplot(3,1,n)
    if f.Orientation == 1
        image(X)
    elseif f.Orientation == 6
        X = rot90(X, -1);
        image(X)
    elseif f.Orientation == 8
        X = rot90(X, 1);
        image(X)
    end
    axis image off
end

Ce qui donne :
photos-matlab-2
L’orientation de chaque photo est maintenant bien prise en compte par MATLAB.

Conclusion

Vous savez maintenant comment afficher automatiquement vos photos avec la bonne orientation dans MATLAB en utilisant imfinfo.

Vous trouverez sans doute d’autres informations utiles dans les métadonnées EXIF, n’hésitez pas à partager vos expériences en commentaire de ce billet.

Microchip XC32 1.40 – libc – multiple definition of `_atexitptr’

Problème

Erreur pendant la compilation d’un projet avec le compilateur XC32 de Microchip :

c:/microchip/xc32/v1.40/bin/bin/../../lib/gcc/pic32mx/4.8.3/../../../../pic32mx/lib\libc.a(stdclean_full.o):(.bss+0x0): multiple definition of `_atexitptr'
c:/microchip/xc32/v1.40/bin/bin/../../lib/gcc/pic32mx/4.8.3/../../../../pic32mx/lib\libc.a(stdclean_simple.o):(.bss+0x0): first defined here

Explication

C’est un bug de la version 1.40 du compilateur XC32. Le problème est lié à la fonction exit() et à certaines fonctions de la bibliothèque standard stdio.

Solution

Pour corriger le bug, il va falloir modifier le fichier libc.a fournit avec XC32. Mais, comme ce compilateur est livré avec plusieurs fichiers libc.a, il faut trouver le bon. Pour ce faire, il suffit d’analyser le chemin donné dans le message d’erreur.

Identification le bon fichier libc.a

Dans l’exemple décrit précédemment :

c:/microchip/xc32/v1.40/bin/bin/../../lib/gcc/pic32mx/4.8.3/../../../../pic32mx/lib\libc.a

Simplifions le chemin vers le fichier libc.a en supprimant successivement les chemins relatifs :

c:/microchip/xc32/v1.40/bin/bin/../../lib/gcc/pic32mx/4.8.3/../../../../pic32mx/lib\libc.a
c:/microchip/xc32/v1.40/bin/../lib/gcc/pic32mx/4.8.3/../../../../pic32mx/lib\libc.a
c:/microchip/xc32/v1.40/lib/gcc/pic32mx/4.8.3/../../../../pic32mx/lib\libc.a
c:/microchip/xc32/v1.40/lib/gcc/pic32mx/../../../pic32mx/lib\libc.a
c:/microchip/xc32/v1.40/lib/gcc/../../pic32mx/lib\libc.a
c:/microchip/xc32/v1.40/lib/../pic32mx/lib\libc.a

Une dernière itération et voici le chemin du fichier fautif :

c:/microchip/xc32/v1.40/pic32mx/lib\libc.a

Nous pouvons maintenant appliquer un correctif à ce fichier.

Application le correctif

/!\ Avant toute chose, faite une copie de ce fichier dans son dossier d’origine.

Ensuite, copiez le fichier libc.a dans un dossier temporaire. Par exemple D:\temp

Ouvrir une Invite de commandes avec Windows et se placer dans le dossier temporaire :

cd /d d:\temp

Puis exécutez successivement les commandes suivantes :

xc32-ar x libc.a stdclean_simple.o
xc32-strip -N _atexitptr stdclean_simple.o
xc32-ar r libc.a stdclean_simple.o

Voici la méthode en image :

Modification libc.a de XC32

Replacez le fichier libc.a modifié dans son dossier d’origine.

Conclusion

Vous pouvez maintenant compiler à nouveau votre projet, le message d’erreur ne doit plus apparaitre.

La solution décrite dans ce billet est inspirée par une discussion du forum officiel de Microchip :
Bug in XC32 standard libraries? (stdclean_full.o and stdclean_simple.o both linking)

Ce problème sera sans doute corrigé avec la prochaine version du compilateur XC32.

PIC32, MPLAB Harmony, data logger, RTCC et BCD

Quel titre !

But

Coder un enregistreur de données (data logger) en utilisant l’horloge RTC d’un PIC32 via le framework MPLAB Harmony de Microchip.

L’objectif est donc d’obtenir, par exemple, un fichier texte où chaque ligne commence par la date et l’heure correspondant à un événement :

08/10/15;10:14:57;...
08/10/15;10:17:28;...
08/10/15;10:22:30;...
08/10/15;10:24:33;...
08/10/15;10:30:07;...

Immédiat ? Pas si sûr…

Problématique

Avec le framework Harmony, la date est récupérée avec la fonction DRV_RTCC_DateGet() de la bibliothèque RTCC :

uint32_t date = DRV_RTCC_DateGet();

Or les valeurs renvoyées sont au format BCD.

Pour le jeudi 8 Octobre 2015, la variable date contient (en notation hexadécimale) 0x15100804.

En séparant chaque groupe de 2 digits :

  • 15 : l’année (2015)
  • 10 : le mois (octobre)
  • 08 : le jour (8)
  • 04 : le numéro du jour de la semaine (0 pour dimanche, 1 pour lundi, 2 pour mardi…), ici jeudi

Comme 0x15100804 correspond à la valeur décimale 353372164, on ne peut pas séparer l’année, le mois et le jour par de simples divisions.

Il faut extraire chaque digit hexadécimale un par un.

Solution n°1 : division et modulo

Une première solution consiste à effectuer une série de divisions en séparant à chaque fois la partie entière du reste de la division.

Par exemple en base 10 (décimale), pour obtenir tout les digits de 1587 soir 1, 5, 8 et 7 :

1587/1000 => partie entière = 1 et reste de la division = 587
  587/100  => partie entière = 5 et reste de la division = 87
   87/10    => partie entière = 8 et reste de la division = 7

En langage C :

#include <stdio.h>

void main(void) {
   
    int date = 1587;

    printf("%d ", date / 1000);
    date = date % 1000;
    printf("%d ", date / 100);
    date = date % 100;
    printf("%d ", date / 10);
    date = date % 10;
    printf("%d", date);

    printf("\n");
   
}

L’exécution de ce code affiche 1 5 8 7.

On applique le même principe à la valeur de la date en BCD mais en utilisant la base 16 (hexadécimale) :

#include <stdio.h>
#include <stdint.h>

void main(void) {
   
    uint32_t date = 0x15100804;
   
    printf("%d ", date / 0x10000000);
    date = date % 0x10000000;
    printf("%d ", date / 0x1000000);
    date = date % 0x1000000;
    printf("%d ", date / 0x100000);
    date = date % 0x100000;
    printf("%d ", date / 0x10000);
    date = date % 0x10000;
    printf("%d ", date / 0x1000);
    date = date % 0x1000;
    printf("%d ", date / 0x100);
    date = date % 0x100;
    printf("%d ", date / 0x10);
    date = date % 0x10;
    printf("%d" , date);

    printf("\n");

}

L’exécution de ce code affiche 1 5 1 0 0 8 0 4.

Solution n°2 : masque binaire et décalage de bits

Intéressons-nous au codage la valeur de la date 0x15100804.

Chaque digit est un nombre hexadécimal compris entre 0 et 9. Il faut donc 4 bits pour coder chaque digit de 0000 à 1001. Avec 8 digits, la date prend 8*4 = 32 bits en mémoire (d’où le type uint32_t).

Écrivons chaque digit et son équivalent en notation binaire :

  1    5    1    0    0    8    0    4
0001 0101 0001 0000 0000 1000 0000 0100

Voici la méthode à appliquer à chaque digit :

  • isoler le paquet de 4 bits correspondant avec un masque binaire 1111 et un ET logique
  • décaler le paquet masqué jusqu’à la dernière position à droite, soit n*4 où n est le numéro du digit (origine 0 = digit de droite)

Pour le digit n°7 (le 1 de l’année) :

  0001 0101 0001 0000 0000 1000 0000 0100
+ 1111 0000 0000 0000 0000 0000 0000 0000
-----------------------------------------
  0001 0000 0000 0000 0000 0000 0000 0000

Faire 28 décalages (7*4) vers la droite :

0001 0000 0000 0000 0000 0000 0000 0000
>>>>
0000 0001 0000 0000 0000 0000 0000 0000
>>>>
0000 0000 0001 0000 0000 0000 0000 0000
>>>>
0000 0000 0000 0001 0000 0000 0000 0000
>>>>
0000 0000 0000 0000 0001 0000 0000 0000
>>>>
0000 0000 0000 0000 0000 0001 0000 0000
>>>>
0000 0000 0000 0000 0000 0000 0001 0000
>>>>
0000 0000 0000 0000 0000 0000 0000 0001

La conversion du résultat en décimal donne bien 1

Pour le digit n°6 (le 5 de l’année) :

  0001 0101 0001 0000 0000 1000 0000 0100
+ 0000 1111 0000 0000 0000 0000 0000 0000
-----------------------------------------
  0000 0101 0000 0000 0000 0000 0000 0000

Faire 24 décalages (6*4) vers la droite :

0000 0101 0000 0000 0000 0000 0000 0000
>>>>
0000 0000 0101 0000 0000 0000 0000 0000
>>>>
0000 0000 0000 0101 0000 0000 0000 0000
>>>>
0000 0000 0000 0000 0101 0000 0000 0000
>>>>
0000 0000 0000 0000 0000 0101 0000 0000
>>>>
0000 0000 0000 0000 0000 0000 0101 0000
>>>>
0000 0000 0000 0000 0000 0000 0000 0101

La conversion du résultat en décimal donne bien 5

On répète ensuite la méthode pour chaque digit.

Voici le code C correspondant à cette méthode :

#include <stdio.h>
#include <stdint.h>

void main(void) {
   
    uint32_t date = 0x15100703;
   
    printf("%d ", (date & 0xF0000000) >> (4*7));
    printf("%d ", (date & 0x0F000000) >> (4*6));
    printf("%d ", (date & 0x00F00000) >> (4*5));
    printf("%d ", (date & 0x000F0000) >> (4*4));
    printf("%d ", (date & 0x0000F000) >> (4*3));
    printf("%d ", (date & 0x00000F00) >> (4*2));
    printf("%d ", (date & 0x000000F0) >> (4*1));
    printf("%d",  (date & 0x0000000F));
   
    printf("\n");
   
}

Conclusion

Vous pouvez maintenant convertir les dates du format BCD vers un format plus approprié pour leurs manipulations.

Les codes présentés ici sont volontairement simples. À vous de les optimiser si nécessaire.

N’hésitez pas à poster un commentaire si vous connaissez une solution alternative, ou si vous voulez plus d’informations.