] >
Paru dans GNU/Linux Magazine n° 193 de mai 2016.
Protégé par la licence CC BY-NC-ND 2.0.
Vous en avez assez de voir des codes QR sans comprendre leur fonctionnement ? Voici comment lire leur contenu, qu’il soit entaché d’erreurs ou non et comment en créer.
Cette série de huit articles présente une étude presque complète des codes QR et pour commencer, une présentation du projet suivie d’une description du format et des premières méthodes de notre classe. Les deux articles suivants déchiffreront l’image en un tableau de bits puis en liront toutes les informations nécessaires à son décodage, c’est-à-dire écrire le message caché dans le magma de carrés noirs et blancs. Toutes les informations de format pourront également être affichées. Les codes QR entachés d’erreurs ou munis d’un petit dessin verront leur contenu corrigé à l’aide de codes de Reed-Solomon (et une brève étude d’un corps fini ainsi que son codage en Python seront nécessaires). Enfin, tout ceci nous servira pour créer un code QR fonctionnel.
On voit de plus en plus de carrés pourvus de petites cibles dans les coins, remplis de carrés noirs et blancs disposés presque aléatoirement. Il ne s’agit pas d’œuvres d’artistes de rue qui veulent remplacer les populaires petits space invaders en céramique mais, et c’est moins joli, de codes QR, destinés à remplacer les codes à barres. Ils contiennent une vraie correction d’erreur contrairement à ces derniers et permettent de stocker des données plus variées et en plus grande quantité. Ils sont protégés par des brevets [1] donc cet article est à usage non professionnel, seulement pour geeker entre amis. Ces codes se limitent à traduire une image carrée faite de carrés, pas à reconnaître un code QR vu en perspective.
Huit articles sont prévus en trois parties, trois articles sur la lecture, trois sur la correction et deux sur la création :
Nous ne nous intéresserons qu’aux codes QR de modèle 2 dits standards, pas au micro QR ni au modèle 1, plus ancien et abandonné.
Ce premier article commence par présenter brièvement le format.
Fig. 1 : Le code QR à décoder.
Le code décortiqué dans cet article est organisé en cinq fichiers non nettoyés en Python 3 : vous bénéficierez de code mort et de tonnes de codes ratés en commentaires, avec les images et exemples sur le github du magazine [2] :
L’organigramme est présenté en figure 2.
Fig. 2 : L’organigramme des bibliothèques utilisées.
Par exemple, qrcorps.py nécessite seulement qrcodeoutils.py et est utilisé par qrdecode.py.
Enfin, qrdecode.py et qrencode.py ont besoin de la bibliothèque d’images
La description en détail du format se fera pendant l’explication du code en Python, il est cependant utile d’avoir un aperçu du vocabulaire employé et de voir globalement où sont les données utiles au décodage.
Un code QR est un carré formé d’un contour blanc de quatre modules d’épaisseur et d’un grand carré central qui contient les données codées sous forme de petits carrés noirs et blancs — les modules — plus quelques zones vierges de données pour calibrer la reconnaissance de l’image, les cibles et deux segments pointillés.
La version d’un code QR est, en gros, sa taille : un code de version 1 a 21 modules de côté (sans le contour) et 4 modules supplémentaires augmentent la version d’une unité.
Le format contient le niveau de correction des erreurs (on peut corriger plus ou moins d’erreurs) et le masque qui sert à éviter des motifs trompeurs dans l’image (par exemple des carrés concentriques où il ne faudrait pas ou des zones monochromes trop grandes). On en détaillera le principe lors de la création d’un code QR.
Enfin, le type désigne le type de données que le code contient parmi les quatre suivants :
Les formats sont compressés sauf le format binaire.
Les différentes zones d’un code QR de version 3 (donc de 29×29 modules) sont illustrées dans la figure 3.
Fig. 3 Les quatre différentes zones d’un code QR.
Un module noir vaut 1 et sera représenté par une couleur pure ; un module blanc vaut 0 et sera représenté par une couleur pastel. Les couleurs d’une même teinte sont de la même zone.
En rouge vif et rose, les cibles pour la reconnaissance et le placement correct de l’image plus un module nécessairement noir au-dessus de la barre verticale verte inférieure. Seul un code QR de version 1 n’a pas la petite cible en bas à droite, en revanche, un code QR de version 7 ou plus a des petites cibles complémentaires, réparties régulièrement dans l’image plus deux zones de vérification de la version de 3×6 modules chacune.
En bleu, les deux zones pointillées qui servent aussi au calibrage, par exemple à la détermination des dimensions des modules.
En vert pomme, les deux zones de format concaténées avec leur correction d’erreur : elle est présente d’une part autour de la cible en haut à gauche en tournant dans le sens trigonométrique, d’autre part en partant du bas et en remontant sur la cible à droite en tournant dans le sens horaire. Regardez bien et observez les deux endroits distincts où est écrite la suite de bits
Enfin, en gris et blanc, la zone de données proprement dite qui contient un court en-tête (dont sa longueur exacte et le type de données), le message et sa correction. Ces données se lisent à partir du coin en bas à droite en suivant un petit serpent qui zigzague que nous repréciserons deux articles plus loin.
Mon premier code était un pur code impératif qui nécessitait un suivi minutieux des données d’une fonction à la suivante. Bref, c’était laid et peu pratique. Allez, je me lance, j’essaie la programmation orientée objet à l’acronyme si amusant en anglais [5].
J’ai finalement utilisé une unique classe
Après l’initialisation de l’instance et son affichage, les méthodes transforment progressivement l’image brute en un tableau de bits : chargement et en passant recadrage si nécessaire, puis recherche des dimensions d’un module. Enfin, l’image est tournée si elle est mal orientée. Ce paragraphe détaille le contenu de l’article suivant.
Le code sera expliqué méthode par méthode en commençant par l’initialisation. Par ailleurs, il sera renuméroté à partir de 01 à chaque section.
01: #!/usr/bin/python3 03: from PIL import Image 04: from sys import exit,stderr 05: from argparse import * 06: from qrcorps import * 07: from qrcodestandard import * 08: from qrcodeoutils import * 10: class qrdecode: 11: def __init__(self): 12: [self.fichier,self.corrige,self.tout,self.image,self.module,self.qr, 13: self.esttourne,self.form,self.formatok,self.nivcor,self.masque,self.dim, 14: self.version,self.gris,self.code,self.clair,self.redondant,self.messageok, 15: self.mode,self.longueur,self.longclair,self.message] = [None]*22
Il s’agit du constructeur d’une instance de la classe
Avant de s’intéresser aux méthodes particulières à la classe (voir article suivant), commençons par la sortie de notre programme et la méthode
01: def __str__(self):
Si on décide d’afficher toutes les informations du code :
02: if self.tout: 03: ch="" 04: if self.fichier is not None: 05: ch=ch+"Fichier : "+self.fichier+"\n" 06: if self.formatok is not None and self.formatok is not True: 07: ch=ch+"Il y a eu besoin de corriger la zone de format.\n" 08: if self.nivcor is not None: 09: ch=ch+"Niveau de correction : "+nomnivcor[self.nivcor]+"\n"
nomnivcor={"L":"Low","M":"Medium","Q":"Quality","H":"High"}
On dessine le masque utilisé :
10: if self.masque is not None: 11: ch=ch+"Masque :\n" 12: ch=ch+dessine([[self.masque(i,j) for j in range(6)] for i in range(6)])
La fonction
Le petit bout de code suivant permet d’afficher les huit masques :
for m in sorted(masques): print(m) print(dessine([[masques[m](j,i) for i in range(6)] for j in range(6)]))
masque | (0,0,0) | (0,0,1) | (0,1,0) | (0,1,1) |
---|---|---|---|---|
image | █·█·█· ·█·█·█ █·█·█· ·█·█·█ █·█·█· ·█·█·█ | ██████ ······ ██████ ······ ██████ ······ | █··█·· █··█·· █··█·· █··█·· █··█·· █··█·· | █··█·· ··█··█ ·█··█· █··█·· ··█··█ ·█··█· |
masque | (1,0,0) | (1,0,1) | (1,1,0) | (1,1,1) |
image | ███··· ███··· ···███ ···███ ███··· ███··· | ██████ █····· █··█·· █·█·█· █··█·· █····· | ██████ ███··· ██·██· █·█·█· █·██·█ █···██ | █·█·█· ···███ █···██ ·█·█·█ ███··· ·███·· |
13: if self.dim is not None: 14: ch=ch+"Dimensions : "+str(self.dim)+"×"+str(self.dim)+"\n" 15: if self.version is not None: 16: ch=ch+"Version : "+str(self.version)+"\n" 17: if self.messageok is not None and self.messageok is not True: 18: ch=ch+"Il y a eu besoin de corriger %d erreur(s) dans la zone de données.\n"%self.messageok 19: if self.mode is not None: 20: ch=ch+"Mode : "+modes[self.mode]+"\n"
modes={0:"Numeric",1:"Alphanumeric",2:"Byte",3:"Kanji"}Et enfin :
21: if self.longclair is not None: 22: ch=ch+"Longueur du message : "+str(self.longclair)+"\n" 23: if self.message is not None: 24: ch=ch+"Message :\n"+self.message+"\n" 26: return ch[:-1]
Si on choisit de tout afficher avec l’option
Si on choisit de n’afficher que le contenu :
28: else: 29: return self.message
Cette méthode doit retourner une chaîne et non l’afficher.
Il peut être utile de taper
Voici le détail de la fonction
def dessine(tableau): dic={0:"·",1:"█",False:"·",True:"█"} c="" for l in tableau: c=c+"".join(dic[i] for i in l)+"\n" return c
Elle sert à afficher le masque de décodage (voir le troisième article de cette série) en noir et blanc dans la console (
31: def __repr__(self): 32: if self.message is not None: 33: return self.message 34: return "Rien n’est défini."
La méthode
Le décor est planté, l’article suivant va transformer l’image de notre code QR en un tableau de bits dans le but de finalement lire effectivement son contenu.