{"id":18,"date":"2017-11-01T22:06:19","date_gmt":"2017-11-01T21:06:19","guid":{"rendered":"http:\/\/pinkieduck.net\/?p=18"},"modified":"2019-02-22T22:54:23","modified_gmt":"2019-02-22T21:54:23","slug":"traitement-dimage-avec-fsharp-et-opencl","status":"publish","type":"post","link":"https:\/\/pinkieduck.net\/?p=18","title":{"rendered":"Traitement d&rsquo;image avec Fsharp et OpenCL"},"content":{"rendered":"<p>En ce moment je d\u00e9couvre les possibilit\u00e9s de F# en mati\u00e8re de scientific computing. F# (prononc\u00e9 \u00ab\u00a0Fsharp\u00a0\u00bb) est un langage calqu\u00e9 sur OCaml dont il partage la syntaxe de base et une grosse partie de la biblioth\u00e8que standard. Quelques fonctionnalit\u00e9s disparaissent (les foncteurs et plus g\u00e9n\u00e9ralement tout ce qui concerne les modules, les types alg\u00e9briques g\u00e9n\u00e9ralis\u00e9s), des fonctionnalit\u00e9s sont l\u00e9g\u00e8rement retouch\u00e9es (le quotation mark devient &lt;@&#8230; @&gt;, indentation non libre\u2026), en contrepartie le langage a acc\u00e8s \u00e0 la totalit\u00e9 de l&rsquo;\u00e9cosyst\u00e8me DotNet ou leur \u00e9quivalent Mono et NetCore. Ces environnements \u00e9tant particuli\u00e8rement populaires il n&rsquo;est pas \u00e9tonnant de voir qu&rsquo;il existe d\u00e9j\u00e0 des biblioth\u00e8ques pour le calcul statistique, la recherche op\u00e9rationnelle, les divers algorithmes d&rsquo;intelligence artificielle, et pour ce qui nous concerne ici l&rsquo;interfa\u00e7age avec des APIs de GPGPU comme OpenCL. Ce qui me donne l&rsquo;occasion de tester les capacit\u00e9s de F# en tant que langage pour le traitement d&rsquo;image face \u00e0 un Python ou \u00e0 un Matlab.<\/p>\n<p>Commen\u00e7ons d\u00e9j\u00e0 par le code permettant de charger une image. De base DotNet fournit la classe Bitmap permettant de charger \u00e0 peu pr\u00e8s n&rsquo;importe quel format courant et nous proposant d&rsquo;acc\u00e9der \u00e0 ses pixels. Cette derni\u00e8re est pr\u00e9sente dans l&rsquo;espace System.Drawing (dont il faudra r\u00e9f\u00e9rencer l&rsquo;assembly)\u00a0:<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nopen System.Drawing\r\n&#x5B;&lt;EntryPoint&gt;]\r\nlet main argv =\r\n    let img = new Bitmap(@&quot;C:\\Users\\moi\\Documents\\fichier.jpg&quot;)\r\n    \/\/ \u2026.\r\n    0\r\n<\/pre>\n<p>Afin de v\u00e9rifier que tout se passe comme attendu il est souhaitable d&rsquo;afficher cette image. Pour cela nous allons cr\u00e9er un System.Windows.Forms et dessiner l&rsquo;image dedans via ce code\u00a0:<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nopen System.Windows.Forms\r\n\/\/ \u2026.\r\nlet form = new Form(Visible=true)\r\nform.Paint.Add(function e-&gt; e.Graphics.DrawImage(img, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel))\r\nSystem.Windows.Forms.Application.Run(form)\r\n<\/pre>\n<p>Si tout se passe bien vous devriez voir apparaitre votre image \u00e0 l&rsquo;\u00e9cran dans une fen\u00eatre relativement simple.<\/p>\n<p>Maintenant que nous avons charg\u00e9 notre image nous pouvons nous pencher sur l&rsquo;utilisation d&rsquo;OpenCL. Pour cela nous allons passer par la biblioth\u00e8que Brahma.OpenCL qui fournit \u00e0 Fsharp une interface de relativement haut niveau autour d&rsquo;OpenCL.Net. Cette derni\u00e8re est disponible via Nuget.<br \/>\nL&rsquo;initialisation de Brahma est relativement simple, il faut deux objets, un provider (qui repr\u00e9sente votre installation OpenCL sur votre machine) et une CommandQueue \u00e0 laquelle nous soumettrons les t\u00e2ches que nous voulons effectuer\u00a0:<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nopen Brahma.OpenCL\r\nopen Brahma.FSharp.OpenCL.Core\r\nopen Brahma.FSharp.OpenCL.Extensions\r\nopen OpenCL.Net\r\nopen FSharp.Core\r\n\/\/ \u2026.\r\nlet provider = ComputeProvider.Create(&quot;*&quot;, DeviceType.Gpu)\r\nlet mutable commandQueue = new Brahma.OpenCL.CommandQueue(provider, provider.Devices |&gt; Seq.head)\r\n<\/pre>\n<p>Ici nous utiliserons le premier GPU disponible, d&rsquo;o\u00f9 le Seq.head.<\/p>\n<p>L&rsquo;avantage de Brahma.OpenCL est qu&rsquo;elle repose sur les quotations marks pour la cr\u00e9ation de kernel OpenCL. Une quotation mark est un code F# sous forme d&rsquo;arbre de syntaxe abstrait, ie une expression qui peut ensuite \u00eatre pass\u00e9e comme argument \u00e0 une fonction F#, par exemple pour la modifier, l&rsquo;interpr\u00e9ter, ou dans le cas de Brahma, la traduire en code OpenCL. Ici nous allons \u00e9crire un filtre de Sobel (norme du gradient) prenant en argument un tableau 1d source et un tableau 1d destination (le gradient a besoin des informations des pixels l&rsquo;entourant, il n&rsquo;est pas possible de modifier \u00ab\u00a0inplace\u00a0\u00bb les donn\u00e9es d&rsquo;un tableau de fa\u00e7on concurrente. A noter que les quotations mark peuvent capturer des variables comme ici stride.<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nopen Microsoft.FSharp.Quotations\r\n\/\/ \u2026.\r\nlet stride = img.Height;\r\nlet command = &lt;@ fun (range:_2D) (buf:array&lt;byte&gt;) (dst:array&lt;byte&gt;) -&gt;\r\n    let i = range.GlobalID0\r\n    let j = range.GlobalID1\r\n    let mutable h = float32 0.\r\n    if (i &gt; 0 &amp;&amp; i &lt; stride) then\r\n        let left = float32 buf.&#x5B;i - 1 + stride * j]\r\n        let right = float32 buf.&#x5B;i + 1 + stride * j]\r\n        h &lt;- abs (left - right)\r\n    let mutable v = float32 0.\r\n    if (j &gt; 0) then\r\n        let top = float32 buf.&#x5B;i + stride * (j - 1)]\r\n        let bottom = float32 buf.&#x5B;i + stride * (j + 1)]\r\n        v &lt;- abs (top - bottom)\r\n    dst.&#x5B;i + stride * j] &lt;- byte (sqrt (h * h + v * v)) @&gt;\r\n<\/pre>\n<p>Bien entendu il n&rsquo;est pas possible d&rsquo;\u00e9crire n&rsquo;importe quel code, les kernels OpenCL ne pouvant pas \u00eatre r\u00e9cursifs par exemple ou appeler des lambdas. La compilation d&rsquo;une quotation mark sur du code qui ne peut \u00eatre traduit g\u00e9n\u00e8rera une exception.<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nlet kernel, kernelprepare, kernelrun = provider.Compile command\r\n<\/pre>\n<p>Les valeurs kernelprepare et kernelrun de la compilation permettent respectivement de mettre \u00e0 disposition les dimensions et arguments (ici les tableaux buf et dst) du kernel au GPU, et d&rsquo;ex\u00e9cuter ce code kernel via la syntaxe suivante\u00a0:<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nkernelprepare d src dst\r\ncommandQueue.Add(kernelrun()) |&gt; ignore\r\ncommandQueue.Add(dst.ToHost provider).Finish() |&gt; ignore\r\n<\/pre>\n<p>d est un objet contenant les dimensions du workgroup (global et local) \u00e0 utilizer pour le kernel, src et dst sont des Arrays F# classiques. On aura au pr\u00e9alable transf\u00e9r\u00e9 le contenu 2d de img dans src 1d\u00a0:<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nlet d = _2D(img.Width, img.Height, 8, 8)\r\nlet src = Array.init (img.Width * img.Height) (function i -&gt; img.GetPixel(i \/ stride, i % stride).R)\r\nlet dst = Array.zeroCreate (img.Width * img.Height)\r\n<\/pre>\n<p>Enfin une fois le kernel execute nous copions le contenu de dst dans img :<\/p>\n<pre class=\"brush: fsharp; title: ; notranslate\" title=\"\">\r\nArray.iteri (fun i (v:byte) -&gt; img.SetPixel(i \/ stride, i % stride, Color.FromArgb(255, int(v), int(v), int(v)))) dst\r\n<\/pre>\n<p>Voil\u00e0 !<\/p>\n","protected":false},"excerpt":{"rendered":"<p>En ce moment je d\u00e9couvre les possibilit\u00e9s de F# en mati\u00e8re de scientific computing. F# (prononc\u00e9 \u00ab\u00a0Fsharp\u00a0\u00bb) est un langage calqu\u00e9 sur OCaml dont il partage la syntaxe de base et une grosse partie de la biblioth\u00e8que standard. Quelques fonctionnalit\u00e9s disparaissent (les foncteurs et plus g\u00e9n\u00e9ralement tout ce qui concerne les modules, les types alg\u00e9briques [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/pinkieduck.net\/index.php?rest_route=\/wp\/v2\/posts\/18"}],"collection":[{"href":"https:\/\/pinkieduck.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pinkieduck.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pinkieduck.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pinkieduck.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=18"}],"version-history":[{"count":7,"href":"https:\/\/pinkieduck.net\/index.php?rest_route=\/wp\/v2\/posts\/18\/revisions"}],"predecessor-version":[{"id":26,"href":"https:\/\/pinkieduck.net\/index.php?rest_route=\/wp\/v2\/posts\/18\/revisions\/26"}],"wp:attachment":[{"href":"https:\/\/pinkieduck.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=18"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pinkieduck.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=18"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pinkieduck.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=18"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}