Lisser une carte de chaleur avec Python

a marqué ce sujet comme résolu.

Bonjour à tous,

Je travaille sur un jeu de données météo qui consiste en une grille de valeurs spatiales :

# lng;lat;value
1.22;0.48;10
1.22;0.49;12
...

Je souhaite représenter sur une carte ces données spatiales, ce que j’ai fait avec Leaflet et du GeoJSON :

Seulement, le rendu n’est pas très esthétique. Je suis prêt à sacrifier un peu de précision pour plus d’esthétisme et aimerais lisser cette carte de chaleur. Pour ce faire, j’ai tenté une moyenne mobile avec scipy :

Le résultat est déjà beaucoup mieux mais j’aimerais dépixeliser les bordures de chaque zone. J’ai tenté de le faire avec du flou mais le résultat ne me satisfait pas :

J’ai également regardé du côté d’algorithmes spécialisés (https://fr.wikipedia.org/wiki/Marching_squares, https://en.wikipedia.org/wiki/Contour_line) mais ils sont lents, le rendu semble beaucoup dépendre de la paramétrisation et ne me satisfait pas visuellement, ni en terme de proximité avec les données brutes.

J’ai pensé à l'upscaling (https://en.wikipedia.org/wiki/Image_scaling) : je double la taille de ma matrice de valeurs en insérant des zéros entre chaque élément (entre chaque ligne et chaque colonne) et remplace les zéros par la moyenne des éléments adjacents :

1 2 3
4 5 6

1 0 2 0 3
0 0 0 0 0
4 0 5 0 6

 1   1.5   2   2.5   3
2.5   3   3.5   4   4.5
 4   4.5   5   5.5   6

Le rendu est pas mal mais toujours pas suffisant : on peut encore observer des pixels. En plus, je fais cela à la main alors que je pressens qu’une fonction existe déjà dans numpy ou scipy :

z2 = np.zeros((2*z.shape[0]-1, 2*z.shape[1]-1), dtype=float)
z2[::2, ::2] = z
z2[::2, 1::2] = (z[:, 1:] + z[:, :-1]) / 2
z2[1::2, :] = (z2[2::2, :] + z2[:-2:2, :]) / 2

Avez-vous connaissance d’une telle fonction ? Pensez-vous qu’elle soit adaptée à mon problème ?

Par avance merci,

Vayel

+0 -0

Salut,

Ça va être dur d’avoir un résultat propre pour deux raisons :

  • Certains bords ont un pixel de large, donc avec le lissage et le flou, le bord peut disparaitre par endroit. À Excideuil (bas, droite), la ligne verte du dessous disaparait.
  • Ton choix de couleur favorise les variations de couleurs brusques (je suis prêt à parier qu’il y a peu de différence de valeur entre le vert et le jaune), donc en moyennant, ça change potentiellement pas mal le résultat visuel.

J’essayerai donc de prendre un meilleur schéma de couleur (matplotlib a pleins de trucs bien par défaut, normalement). Au moins, vire le vert. Ensuite, le flou léger devrait mieux passer. À essayer sur la carte avec ou sans moyenne mobile.

+1 -0

Juste une idée comme ça : au lieu d’appliquer un flou gaussien (qui rend l’image moins nette, par définition), est-ce que tu as moyen d’essayer un filtre médian ?

L’avantage du filtre médian est qu’il ne calcule pas de nouvelles couleurs, il remplace chaque pixel par la médiane des pixels de son voisinage, donc les contours devraient rester sharp. En principe ça sert à débruiter les images mais je pense que ça ne coûte rien de voir ce que ça donne ici. L’autre avantage est que la représentation reste à peu près expliquable et cohérente avec les données initiales.

Sinon dans les traitements classiques il reste aussi les opérations morphologiques (érosion/dilatation) qui peuvent adoucir les contours (les rendre moins carrés) sans pour autant créer de nouvelles valeurs, mais par contre c’est un traitement essentiellement esthétique.

+1 -0

Merci pour vos réponses rapides !

Certains bords ont un pixel de large, donc avec le lissage et le flou, le bord peut disparaitre par endroit. À Excideuil (bas, droite), la ligne verte du dessous disaparait.

En effet !

J’essayerai donc de prendre un meilleur schéma de couleur (matplotlib a pleins de trucs bien par défaut, normalement).

Un essai avec du bleu où la valeur est codée par l’opacité :

Et avec viridis :

Clairement, je préfère le bleu. Utiliser l’opacité pour coder la valeur permet aussi de voir la carte en arrière-plan.

Plutôt que de massacrer tes données, il n’y a pas des options de lissage dans Leaflet ?

J’ai cherché mais rien trouvé de satisfaisant. https://github.com/Leaflet/Leaflet.heat recalcule la carte de chaleur pour chaque niveau de zoom, ce que je ne souhaite pas.

Cela dit, je n’altère les données "que" pour l’affichage graphique. La valeur qui s’affiche au survol est celle d’origine.

L’avantage du filtre médian est qu’il ne calcule pas de nouvelles couleurs, il remplace chaque pixel par la médiane des pixels de son voisinage, donc les contours devraient rester sharp.

Voici ce que ça donne :

Je préfère avec une moving average. Les nuances sont plus conservées et moins de tâches disparaissent (ex : nord-est d’Excideuil).

Sinon dans les traitements classiques il reste aussi les opérations morphologiques (érosion/dilatation) qui peuvent adoucir les contours (les rendre moins carrés) sans pour autant créer de nouvelles valeurs, mais par contre c’est un traitement essentiellement esthétique.

Erosion (3, 3)
Erosion (3, 3)
Dilatation (3, 3)
Dilatation (3, 3)

Le niveau de pixelisation n’est pas impacté, en revanche le taille des tâches l’est de façon assez notable.


Finalement, j’ai réessayé avec juste un flou gaussien :

L’image parait un peu floue (ah bon ?) mais la proximité avec les données d’origine est très bonne :

Je vais voir ce que ça donne sur d’autres datasets puis partirai probablement là-dessus.

Je suis quand même surpris qu’il n’existe pas de méthode standard dans numpy/scipy pour insérer entre deux valeurs d’une matrice un dégradé de n valeurs intermédiaires :

>>> f([1, 2, 3], n_inter=1)
[1, 1.5, 2, 2.5, 3]
>>> f([1, 2, 3], n_inter=2)
[1, 1.33, 1.66, 2, 2.33, 2.66, 3]
>>> f([1, 2, 3], n_inter=3)
[1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3]
>>> f([[1, 2, 3], [4, 5, 6]], n_inter=1)
[[ 1, 1.5, 2, 2.5, 3],
 [2.5, 3, 3.5, 4, 4.5],
 [ 4, 4.5, 5, 5.5, 6]]

Merci encore pour vos retours !

+1 -0

Juste une précision à propos des operateurs morphologiques : le resultat est encore plus carré parce que tu utilises un noyau carré. Après c’est probablement pas ce dont tu as besoin, mais je préfère le préciser.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte