8
votes

Somme Intensités d'image dans le GPU

J'ai une application dans laquelle j'ai besoin de prendre l'intensité moyenne d'une image d'environ 1 million d'images. Il "se sent" comme un travail pour un shader de fragment GPU, mais les shaders de fragments sont destinés aux calculs locaux par pixels, tandis que la moyenne de l'image est une opération globale.

Une approche que j'ai envisagée est chargée de charger l'image dans une texture, en appliquant une boîte de boîte 2x2, chargez le résultat dans une texture N / 2 x N / 2 et répétez jusqu'à ce que la sortie soit 1x1. Cependant, cela prendrait la journalisation des applications du shader.

Y a-t-il un moyen de le faire en une seule passe? Ou devrais-je simplement casser et utiliser cuda / opencl?


8 commentaires

Mon application fait la correspondance de chanfrein d'un modèle 3D projeté à une image d'entrée. Je rends une image contenant les bords silhouette de mon modèle et pour chaque pixel Edge, j'utilise une table de recherche pour trouver le pixel de bord le plus proche dans l'image d'entrée. Ensuite, j'ai besoin du résultat moyen, ce qui me dit bien le modèle correspond aux données. J'ai essayé de lire les pixels de bord rendu de OpenGL et de faire du chanfrein correspondant à la CPU, mais l'opération de lecture était un gros goulot d'étranglement. J'espérais que en faisant tout ce qui est sur le GPU et en lisant simplement en une seule valeur, j'aurais une grande vitesse.


(CTD) Depuis que je peux transmettre la table de recherche comme une texture, je peux effectuer les recherches dans un stylvateur de sommet, mais j'ai toujours le goulot d'étranglement de la lecture des données dans la mémoire principale.


Voir la question similaire ici-
Stackoverflow.com/questions/2944290/...


Rien ne vous oblige à faire en flou 2x2, vous pouvez effectuer par exemple en flou avec une boîte 16x16 et après cette charge entraîne une texture N / 16 x N / 16. De cette façon, vous pouvez obtenir des opérations de grande vitesse et moins de copie ...


@ 0x69 J'ai pensé à cela, mais dans mon expérience, en utilisant de gros noyaux de flou comme celui-ci perdent réellement des performances, car les recherches voisines se produisent en série. Considérez le cas extrême, où j'utilise une boîte 512x512 qui couvre toute mon image. Maintenant, j'ai fait toute l'image de la moyenne d'image Seriale dans mon shader, qui vaincit totalement le but de la nature parallèle du shader du fragment du GPU.


Comme toujours la vérité est quelque part au milieu :) La nature de cette tâche est telle que le noyau 2x2 est trop petit pour une performance optimale, mais bien sûr, 512x512 Le noyau est trop grand ... Vous devriez essayer d'expérimenter de quelle taille de noyau est la plus optimale. ..


@ 0x69 Souvent, c'est vrai, mais je pense dans ce cas, 2x2 est optimal, du moins en termes de minimisation des opérations séquentielles. La lecture du quartier se produit séquentiellement, chaque itération nécessite des opérations séquentielles M ^ 2, où M est la taille de quartier. Pour une image N x N sur le nombre total d'opérations séquentielles est O (m ^ 2 log_m n). Par exemple, une image 512 x 512, un quartier 2x2 ne prend que 36 opérations séquentielles, tandis qu'un quartier de 16x16 prend 576.


Nous devons également prendre en compte les opérations de copie de la texture. Par exemple, pour l'image 4096x4096, si le noyau est 2x2 - il faut 12 opérations de copie mémoire, mais pour le noyau 16x16 - il suffit de 3 opérations de copie. C'est pourquoi j'ai dit qu'il a besoin de vérifier expérimental à quelle taille de noyau est optimale, car la minimisation des opérations séquentielles augmente le nombre total d'opérations de copie mémoire, ce qui peut amortir l'accélération.


3 Réponses :


1
votes

My Gut me dit de tenter votre mise en œuvre à Opencl. Vous pouvez optimiser pour votre taille d'image et votre matériel graphique en brisant les images dans des morceaux sur mesure de données qui sont ensuite résumées en parallèle. Pourrait être très rapide en effet.

Les shaders de fragments sont parfaits pour les convolutions, mais ce résultat est généralement écrit sur GL_FRAGCOLOR, de sorte que cela a du sens. En fin de compte, vous devrez faire boucler chaque pixel dans la texture et résumer le résultat qui est ensuite lu dans le programme principal. Générer des statistiques sur des images Peut-être pas ce que le Shader Fragment a été conçu et il n'est pas clair qu'un gain de performance majeur doit être eu depuis sa garantie d'un tampon particulier est situé dans la mémoire GPU.

On dirait que vous pouvez appliquer cet algorithme à un scénario de détection de mouvement en temps réel, ou une autre application de détection de fonctionnalités automatisée. Il peut être plus rapide de calculer certaines statistiques d'un échantillon de pixels plutôt que de l'ensemble de l'image, puis de construire un classificateur d'apprentissage de la machine.

Bonne chance à vous dans tous les cas!


1 commentaires

Merci pour votre réponse. Je vais regarder sur Opencl.



4
votes

L'opération de sommation est un cas spécifique de la "réduction", une opération standard dans les bibliothèques CUDA et OPENCL. Une belle écriture sur elle est disponible sur le Page Démos CUDA . À Cuda, poussée et CUDPP ne sont que deux exemples de bibliothèques qui fournissent une réduction. Je suis moins familier avec opencl, mais CLPP semble être une bonne bibliothèque qui fournit une réduction . Copiez simplement votre tampon de couleur sur un objet tampon de pixel OpenGL et utilisez l'appel d'interopérabilité OpenGL approprié pour rendre la mémoire de la mémoire tampon de pixel accessible dans CUDA / OPENCL.

Si cela doit être fait à l'aide de l'API OpenGL (comme la question initiale requise), la solution consiste à rendu à une texture, à créer une mipmap de texture et à lire dans la texture 1x1. Vous devez définir le droit de filtrage (Bilinéar est approprié, je pense), mais il devrait se rapprocher de la bonne réponse, une erreur de précision modulo.


0 commentaires

1
votes

Il n'a pas besoin de Cuda si vous aimez rester à GLSL. Comme dans la solution CUDA mentionnée ici, cela peut être fait dans un fragment Shader Staight en avant. Cependant, vous avez besoin de connexions (résolution) Draw appels. Il suffit de configurer une shader qui prend des échantillons de 2x2 pixels de l'image d'origine et de produire la somme moyenne de celles-ci. Le résultat est une image avec une demi-résolution chez les deux axes. Répétez jusqu'à ce que l'image soit 1x1 px. Quelques considérations: utilisez gl_float textures de luminance si disponible, pour obtenir une somme plus précise. Utilisez Glviewport à trimestre de la zone de rendu de chaque étape. Le résultat se termine ensuite dans le pixel supérieur gauche de votre tramebuffer.


0 commentaires