Quelle représentation Haskell est recommandée pour les tableaux de pixels 2D non boxed avec des millions de pixels?

117

Je veux aborder certains problèmes de traitement d'image dans Haskell. Je travaille avec des images bitonales (bitmap) et couleur avec des millions de pixels. J'ai un certain nombre de questions:

  1. Sur quelle base dois-je choisir entre Vector.Unboxedet UArray? Ce sont tous les deux des tableaux sans boîte, mais l' Vectorabstraction semble fortement annoncée, en particulier autour de la fusion de boucles. Est-ce Vectortoujours mieux? Sinon, quand dois-je utiliser quelle représentation?

  2. Pour les images couleur, je souhaite stocker des triplets d'entiers 16 bits ou des triplets de nombres à virgule flottante simple précision. À cette fin, est-ce l'un Vectorou l' autre UArrayplus facile à utiliser? Plus performant?

  3. Pour les images bitonales, je n'aurai besoin de stocker que 1 bit par pixel. Existe-t-il un type de données prédéfini qui peut m'aider ici en regroupant plusieurs pixels dans un mot, ou suis-je seul?

  4. Enfin, mes tableaux sont bidimensionnels. Je suppose que je pourrais gérer l'indirection supplémentaire imposée par une représentation en tant que "tableau de tableaux" (ou vecteur de vecteurs), mais je préférerais une abstraction qui prend en charge le mappage d'index. Quelqu'un peut-il recommander quelque chose d'une bibliothèque standard ou de Hackage?

Je suis programmeur fonctionnel et n'ai pas besoin de mutation :-)

Norman Ramsey
la source
2
Je pense qu'il n'y a que Repa qui rencontre le numéro 4, voir cse.unsw.edu.au/~chak/papers/repa.pdf .
stephen tetley
5
@stephen: l' Arrayinterface standard prend en charge les tableaux multidimensionnels. Vous pouvez simplement utiliser un tuple pour l'index.
John L
13
Le fait que cette question soit fortement votée et favorisée (y compris par moi) semble indiquer que la gestion des tableaux par Haskell n'est pas très bien documentée.
Alexandre C.
2
@Alexandre C.: La gestion des tableaux de base de tous les jours est bien documentée; la gestion de gros blocs de mémoire contenant des données mutables est aussi simple qu'elle le serait en C; la gestion aussi efficace que possible de grands tableaux multidimensionnels immuables est un peu moins évidente. Il s'agit d'optimiser les performances d'un scénario dans lequel des détails subtils et moins bien documentés seraient un problème dans n'importe quelle langue.
CA McCann
1
@Alexandre C.: Pour la plupart des applications, c'est transparent. Et ce n'est pas vraiment Haskell lui-même en cause, c'est la bibliothèque et le compilateur. Un simple UArrayindexé par un tuple de Ints est simple à utiliser et souvent suffisant, mais même la magie profonde de GHC ne va pas optimiser le code en utilisant son API minimale en quelque chose de compétitif avec une bibliothèque modifiée pour un traitement rapide de données en masse parallélisé.
CA McCann

Réponses:

89

Pour les tableaux multidimensionnels, la meilleure option actuelle dans Haskell, à mon avis, est repa .

Repa fournit des matrices parallèles polymorphes de forme régulière, multidimensionnelles et de haute performance. Toutes les données numériques sont stockées sans boîte. Les fonctions écrites avec les combinateurs Repa sont automatiquement parallèles à condition que vous fournissiez + RTS -Nwthing sur la ligne de commande lors de l'exécution du programme.

Récemment, il a été utilisé pour certains problèmes de traitement d'image:

J'ai commencé à écrire un tutoriel sur l'utilisation de repa , qui est un bon point de départ si vous connaissez déjà les tableaux Haskell ou la bibliothèque vectorielle. Le tremplin clé est l'utilisation de types de forme au lieu de types d'index simples, pour traiter les indices multidimensionnels (et même les stencils).

Le package repa-io inclut la prise en charge de la lecture et de l'écriture de fichiers image .bmp, bien que la prise en charge de plusieurs formats soit nécessaire.

Répondant à vos questions spécifiques, voici un graphique, avec discussion:


Tous les trois UArray, Vector et Repa prennent en charge le déballage.  Vector et Repa ont une API riche et flexible, mais pas UArray.  UArray et Repa ont une indexation multidimensionnelle, mais pas Vector.  Ils prennent tous en charge le bit-packing, bien que Vector et Repa aient quelques mises en garde à cet égard.  Vector et Repa interagissent avec les données et le code C, mais pas UArray.  Seul Repa prend en charge les pochoirs.


Sur quelle base dois-je choisir entre Vector.Unboxed et UArray?

Ils ont à peu près la même représentation sous-jacente, cependant, la principale différence est l'étendue de l'API pour travailler avec des vecteurs: ils ont presque toutes les opérations que vous associeriez normalement aux listes (avec un cadre d'optimisation basé sur la fusion), alors qu'ils UArrayont presque pas d'API.

Pour les images couleur, je souhaite stocker des triplets d'entiers 16 bits ou des triplets de nombres à virgule flottante simple précision.

UArrayprend mieux en charge les données multidimensionnelles, car il peut utiliser des types de données arbitraires pour l'indexation. Bien que cela soit possible dans Vector(en écrivant une instance de UApour votre type d'élément), ce n'est pas l'objectif principal de Vector- au lieu de cela, c'est là Repaqu'intervient, ce qui facilite l'utilisation des types de données personnalisés stockés de manière efficace, grâce à l' indexation de forme .

Dans Repa, votre triple de shorts aurait le type:

Array DIM3 Word16

Autrement dit, un tableau 3D de Word16.

Pour les images bitonales, je n'aurai besoin de stocker que 1 bit par pixel.

UArrays pack Bools sous forme de bits, Vector utilise l'instance de Bool qui fait le pack de bits, en utilisant à la place une représentation basée sur Word8. Cependant, il est facile d'écrire une implémentation de bits pour les vecteurs - en voici une , de la bibliothèque uvector (obsolète). Sous le capot, Repautilise Vectors, donc je pense qu'il hérite des choix de représentation des bibliothèques.

Existe-t-il un type de données prédéfini qui peut m'aider ici en regroupant plusieurs pixels dans un mot

Vous pouvez utiliser les instances existantes pour n'importe laquelle des bibliothèques, pour différents types de mots, mais vous devrez peut-être écrire quelques helpers en utilisant Data.Bits pour rouler et dérouler des données compressées.

Enfin, mes tableaux sont bidimensionnels

UArray et Repa prennent en charge des tableaux multidimensionnels efficaces. Repa dispose également d'une interface riche pour ce faire. Le vecteur seul ne le fait pas.


Mentions notables:

  • hmatrix , un type de tableau personnalisé avec des liaisons étendues aux packages d'algèbre linéaire. Doit être lié à l'utilisation des types vectorou repa.
  • ix-shapeable , obtenant une indexation plus flexible à partir de tableaux réguliers
  • tableau noir , la bibliothèque d'Andy Gill pour la manipulation d'images 2D
  • codec-image-devil , lit et écrit divers formats d'image dans UArray
Don Stewart
la source
5
De plus, vous pouvez désormais effectuer des E / S d'image de tableaux de repa 3D dans de nombreux formats, grâce à repa-devil .
Don Stewart
2
Pouvez-vous expliquer comment Repa peut interagir avec le code C? Je n'ai pas trouvé d'instances stockables pour Data.Array.Repa ...
sastanin
2
La copie vers des pointeurs est probablement le chemin le plus simple vers des données stockables, mais ce n'est clairement pas une solution à long terme. Pour cela, nous aurons besoin de vecteurs stockables sous le capot.
Don Stewart
1
Un exemple de désaturation d'image avec repa et repa-devil
Don Stewart
17

Une fois, j'ai passé en revue les fonctionnalités des bibliothèques de tableaux Haskell qui comptent pour moi, et j'ai compilé un tableau de comparaison (feuille de calcul uniquement: lien direct ). Je vais donc essayer de répondre.

Sur quelle base dois-je choisir entre Vector.Unboxed et UArray? Ce sont tous les deux des tableaux sans boîte, mais l'abstraction vectorielle semble fortement annoncée, en particulier autour de la fusion de boucles. Vector est-il toujours meilleur? Sinon, quand dois-je utiliser quelle représentation?

UArray peut être préféré à Vector si l'on a besoin de tableaux bidimensionnels ou multidimensionnels. Mais Vector a une API plus agréable pour manipuler des vecteurs. En général, Vector n'est pas bien adapté pour simuler des tableaux multidimensionnels.

Vector.Unboxed ne peut pas être utilisé avec des stratégies parallèles. Je soupçonne que UArray ne peut pas être utilisé non plus, mais au moins il est très facile de passer de UArray à Boxed Array et de voir si les avantages de la parallélisation dépassent les coûts de boxe.

Pour les images couleur, je souhaite stocker des triplets d'entiers 16 bits ou des triplets de nombres à virgule flottante simple précision. Dans ce but, est-ce que Vector ou UArray est plus facile à utiliser? Plus performant?

J'ai essayé d'utiliser des tableaux pour représenter des images (même si je n'avais besoin que d'images en niveaux de gris). Pour les images couleur, j'ai utilisé la bibliothèque Codec-Image-DevIL pour lire / écrire des images (liaisons à la bibliothèque DevIL), pour les images en niveaux de gris, j'ai utilisé la bibliothèque pgm (pure Haskell).

Mon problème majeur avec Array était qu'il ne fournit que du stockage à accès aléatoire, mais il ne fournit pas beaucoup de moyens de créer des algorithmes Array et ne vient pas avec des bibliothèques prêtes à l'emploi de routines de tableau (ne s'interface pas avec les bibliothèques d'algèbre linéaire, n'est pas 't permettent d'exprimer des convolutions, fft et autres transformations).

Presque chaque fois qu'un nouveau tableau doit être construit à partir de l'existant, une liste intermédiaire de valeurs doit être construite (comme dans la multiplication matricielle de l'introduction douce). Le coût de la construction de tableaux surpasse souvent les avantages d'un accès aléatoire plus rapide, au point qu'une représentation basée sur une liste est plus rapide dans certains de mes cas d'utilisation.

STUArray aurait pu m'aider, mais je n'aimais pas lutter contre les erreurs de type cryptique et les efforts nécessaires pour écrire du code polymorphe avec STUArray .

Le problème avec les tableaux est qu'ils ne sont pas bien adaptés aux calculs numériques. Data.Packed.Vector et Data.Packed.Matrix de Hmatrix sont meilleurs à cet égard, car ils sont accompagnés d'une solide bibliothèque de matrices (attention: licence GPL). En termes de performances, sur la multiplication de la matrice, hmatrix était suffisamment rapide ( seulement légèrement plus lente qu'Octave ), mais très gourmande en mémoire (consommée plusieurs fois plus que Python / SciPy).

Il existe également une bibliothèque blas pour les matrices, mais elle ne s'appuie pas sur GHC7.

Je n'ai pas encore beaucoup d'expérience avec Repa, et je ne comprends pas bien le code repa. D'après ce que je vois, il a une gamme très limitée d'algorithmes de matrice et de tableau prêts à l'emploi écrits dessus, mais au moins il est possible d'exprimer des algorithmes importants au moyen de la bibliothèque. Par exemple, il existe déjà des routines pour la multiplication matricielle et pour la convolution dans les algorithmes de repa. Malheureusement, il semble que la convolution soit désormais limitée aux noyaux 7 × 7 (ce n'est pas assez pour moi, mais devrait suffire pour de nombreuses utilisations).

Je n'ai pas essayé les liaisons Haskell OpenCV. Ils devraient être rapides, car OpenCV est vraiment rapide, mais je ne suis pas sûr que les liaisons soient complètes et suffisamment bonnes pour être utilisables. De plus, OpenCV de par sa nature est très impératif, plein de mises à jour destructrices. Je suppose qu'il est difficile de concevoir une interface fonctionnelle agréable et efficace en plus. Si l'on choisit OpenCV, il est susceptible d'utiliser la représentation d'image OpenCV partout, et d'utiliser les routines OpenCV pour les manipuler.

Pour les images bitonales, je n'aurai besoin de stocker que 1 bit par pixel. Existe-t-il un type de données prédéfini qui peut m'aider ici en regroupant plusieurs pixels dans un mot, ou suis-je seul?

Autant que je sache, les tableaux Unboxed de Bools prennent soin de compresser et de décompresser les vecteurs de bits. Je me souviens avoir regardé l'implémentation de tableaux de Bools dans d'autres bibliothèques, et je n'ai pas vu cela ailleurs.

Enfin, mes tableaux sont bidimensionnels. Je suppose que je pourrais gérer l'indirection supplémentaire imposée par une représentation en tant que "tableau de tableaux" (ou vecteur de vecteurs), mais je préférerais une abstraction qui prend en charge le mappage d'index. Quelqu'un peut-il recommander quelque chose d'une bibliothèque standard ou de Hackage?

En dehors de Vector (et des listes simples), toutes les autres bibliothèques de tableaux sont capables de représenter des tableaux ou des matrices à deux dimensions. Je suppose qu'ils évitent les indirections inutiles.

sastanin
la source
Les liaisons opencv mentionnées ci-dessous sont incomplètes. Il n'est vraiment pas possible pour une seule personne de créer et de maintenir un ensemble complet pour une bibliothèque aussi vaste. Cependant, il est toujours rentable d'utiliser opencv même si vous devez créer un wrapper pour la fonction dont vous avez besoin vous-même, car il implémente des choses vraiment complexes.
événement
@aleator Oui, je comprends que c'est vraiment un travail énorme pour une personne. BTW, si vous êtes un responsable de la maintenance, pourriez-vous s'il vous plaît publier des documents haddock quelque part, afin qu'il soit possible d'évaluer la couverture de la bibliothèque et des liaisons sans installer localement? (les documents ne sont pas disponibles sur Hackage en raison d'une erreur de construction; et il ne se construit pas pour moi avec ni GHC 6.12.1 ni GHC 7.0.2 en raison de M_PInon-déclarés).
sastanin
@jextee Hé, merci pour le tuyau! J'ai téléchargé une nouvelle version qui pourrait résoudre les deux problèmes.
événement
@aleator Merci, maintenant il se construit proprement.
sastanin
5

Bien que cela ne réponde pas exactement à votre question et ne soit même pas vraiment haskell en tant que tel, je recommanderais de jeter un œil à CV ou CV-combinators bibliothèques de à hackage. Ils lient les nombreux opérateurs de traitement d'image et de vision plutôt utiles de la bibliothèque opencv et permettent de travailler beaucoup plus rapidement avec les problèmes de vision industrielle.

Ce serait plutôt génial si quelqu'un comprenait comment repa ou une telle bibliothèque de tableaux pourrait être directement utilisée avec opencv.

aleator
la source
0

Voici une nouvelle bibliothèque de traitement d'image Haskell qui peut gérer toutes les tâches en question et bien plus encore. Actuellement, il utilise les packages Repa et Vector pour les représentations sous-jacentes, qui héritent par conséquent de la fusion, du calcul parallèle, de la mutation et de la plupart des autres avantages fournis avec ces bibliothèques. Il fournit une interface facile à utiliser et naturelle pour la manipulation d'images:

  • Indexation 2D et des pixels sans emballage avec une précision arbitraire ( Double, Float, Word16, etc ..)
  • toutes les fonctions essentielles comme map, fold, zipWith, traverse...
  • prise en charge de différents espaces colorimétriques: RVB, HSI, échelle de gris, bi-ton, complexe, etc.
  • fonctionnalité de traitement d'image commune:
    • Morphologie binaire
    • Convolution
    • Interpolation
    • Transformée de Fourier
    • Tracé d'histogramme
    • etc.
  • Possibilité de traiter les pixels et les images comme des nombres réguliers.
  • Lecture et écriture de formats d'image courants via la bibliothèque JuicyPixels

Plus important encore, c'est une pure bibliothèque Haskell, donc elle ne dépend d'aucun programme externe. Il est également hautement extensible, de nouveaux espaces colorimétriques et représentations d'images peuvent être introduits.

Une chose qu'il ne fait pas est de regrouper plusieurs pixels binaires dans un Word , au lieu de cela, il utilise un Wordpar pixel binaire, peut-être dans un futur ...

lehins
la source