<br />
<b>Warning</b>:  Declaration of Jetpack_IXR_Client::query() should be compatible with IXR_Client::query(...$args) in <b>/home/clients/7267bc096562fcdb78c0ab60d3ac51fb/web/blog/wp-content/plugins/jetpack/class.jetpack-ixr-client.php</b> on line <b>91</b><br />
{"id":1032,"date":"2017-01-16T10:07:09","date_gmt":"2017-01-16T10:07:09","guid":{"rendered":"http:\/\/barradeau.com\/blog\/?p=1032"},"modified":"2019-02-15T11:32:10","modified_gmt":"2019-02-15T11:32:10","slug":"active-contour-model","status":"publish","type":"post","link":"http:\/\/barradeau.com\/blog\/?p=1032","title":{"rendered":"Active Contour Model"},"content":{"rendered":"<p>quick post about something I did\u00a0last year for a project that never went anywhere.<\/p>\n<p>the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Active_contour_model\">Active Contour Model<\/a>\u00a0(ACM) is an old fashioned way of vectorizing the contour of an object. it works very well when an object is isolated on a flat color but the principle can also\u00a0be used &#8220;live&#8221; to determine where the &#8220;border&#8221; between different shapes is.\u00a0In Photoshop, this would correspond to the &#8220;magic wand&#8221; and the &#8220;quick selection tool&#8221; respectively.<\/p>\n<p>ACM uses morphological snakes, the principle of snakes is fairly\u00a0simple to understand, the animations, from <a href=\"https:\/\/github.com\/pmneila\/morphsnakes\">this Python implementation&#8217;s repo<\/a>\u00a0clearly explain the idea. the snake is basically a polyline that can expand or shrink as needed. below\u00a0the snake is used to enclose a\u00a0starfish shape.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/github.com\/pmneila\/morphsnakes\/raw\/master\/examples\/anim_starfish.gif\" width=\"278\" height=\"313\" \/><\/p>\n<p>the snake is like an elastic, first it&#8217;s stretched around the starfish and gradually tightens. there&#8217;s\u00a0a major difference between a snake and an elesatic, the snake can go &#8220;inside&#8221; the creases when an elastic would remain &#8220;outside&#8221; (thus\u00a0describing the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Convex_hull\">convex hull<\/a>\u00a0of the starfish ).<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"alignnone size-full\" src=\"https:\/\/github.com\/pmneila\/morphsnakes\/raw\/master\/examples\/anim_nodule.gif\" width=\"270\" height=\"261\" \/><\/p>\n<p>and in this example, the snake behaves more like expanding foam, progressively filling the space around its starting point until it reaches a boundary.<\/p>\n<p>if the two behaviours seem different, they are\u00a0actually the same ; the snake is driven by <a href=\"https:\/\/en.wikipedia.org\/wiki\/Gradient_descent\">gradient\u00a0descent<\/a>, which is a fancy way of saying that the points of the snake are\u00a0always &#8220;rolling downhill&#8221;.<\/p>\n<p><iframe loading=\"lazy\" width=\"720\" height=\"380\" src=\"https:\/\/www.youtube.com\/embed\/vWFjqgb-ylQ\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p>so, this gradient is the first thing we&#8217;ll have to compute, \u00a0we&#8217;ll need the distance of each pixel to the object we want to capture.<\/p>\n<p>to find this object, I threshold the luminance of each pixel\u00a0against an arbitrary value, this gives a binary (black &amp; white) image.<\/p>\n<p>so this glorious rooster:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1041\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster-1.jpg\" alt=\"rooster\" width=\"800\" height=\"800\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster-1.jpg 800w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster-1-150x150.jpg 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster-1-300x300.jpg 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster-1-768x768.jpg 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/p>\n<p>gives this binary image after threshold<br \/>\n<img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1043\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/threshold.png\" alt=\"threshold\" width=\"800\" height=\"800\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/threshold.png 800w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/threshold-150x150.png 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/threshold-300x300.png 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/threshold-768x768.png 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/p>\n<p>then I found &amp; ported a Java example that computes the gradient flows. I don&#8217;t quite understand everything that happens there but it samples the neighbourhood of each pixel with a sparse convolution matrix twice, forward and backwards. during the forward pass, it gives an average score to each pixel and during the backwards pass, it sums them up to get the final result.<\/p>\n<p>our binary image will turn into this Worley-ish voronoi-ish series of gradients:<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-1042\" src=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster_gradient-1.png\" alt=\"rooster_gradient\" width=\"800\" height=\"800\" srcset=\"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster_gradient-1.png 800w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster_gradient-1-150x150.png 150w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster_gradient-1-300x300.png 300w, http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/rooster_gradient-1-768x768.png 768w\" sizes=\"(max-width: 800px) 100vw, 800px\" \/><\/p>\n<p>along which the snakes&#8217; points will be free to roll. also note that the black areas contain the morphological skeleton of the image which can be extremely useful :)<\/p>\n<p>with 2, 2D dimensional Flow martices flowX &amp; flowY, we move the points around by doing\u00a0something like:<\/p>\n<pre class=\"lang:js decode:true \">var scope = this;\r\nthis.snake.forEach(function (p) {\r\n    if (p[0] &lt;= 0 || p[0] &gt;= scope.w - 1 || p[1] &lt;= 0 || p[1] &gt;= scope.h - 1)return;\r\n    var vx = (.5 - scope.flowX[~~( p[0])][~~( p[1] )] ) * 2;\r\n    var vy = (.5 - scope.flowY[~~( p[0])][~~( p[1] )] ) * 2;\r\n    p[0] += vx * 100;\r\n    p[1] += vy * 100;\r\n});<\/pre>\n<p>an interesting property of the snake is it&#8217;s ability to shrink \/ expand. this is one of the properties of differential growth, it&#8217;s a very rich mechanic. the snake is made of edges and stores\u00a0a\u00a0min\/max length for an edge. each time we update the snake, it can either :<\/p>\n<ol>\n<li>destroy an edge if it is too short<\/li>\n<li>do nothing if the edge is not too short and not too long<\/li>\n<li>subdivide an\u00a0edge if it is too long<\/li>\n<\/ol>\n<p>or in code:<\/p>\n<pre class=\"lang:js decode:true\">var tmp = [];\r\nfor( var i = 0; i &lt; this.snake.length; i++ ){\r\n\r\n    var prev = this.snake[(i - 1 &lt; 0 ? this.snake.length - 1 : (i - 1))];\r\n    var cur = this.snake[i];\r\n    var next = this.snake[(i + 1) % this.snake.length];\r\n\r\n    var dist = distance(prev, cur) + distance(cur, next);\r\n\r\n    \/\/1 if the length is too short (destroy = don't store this point)\r\n    if (dist &gt; this.minlen){\r\n\r\n        \/\/2 if it is below the max length\r\n        if (dist &lt; this.maxlen) {\r\n            \/\/store the point\r\n            tmp.push(cur);\r\n\r\n        }else{\r\n            \/\/3 otherwise split the previous and the next edges\r\n            var pp = [lerp(.5, prev[0], cur[0]), lerp(.5, prev[1], cur[1])];\r\n            var np = [lerp(.5, cur[0], next[0]), lerp(.5, cur[1], next[1])];\r\n\r\n            \/\/ and add the 2 midpoints to the snake\r\n            tmp.push(pp, np);\r\n        }\r\n    }\r\n}\r\nthis.snake = tmp;\r\n\r\n\/\/lerp:\r\n    function lerp(t, a, b) {\r\n        return a + t * ( b - a );\r\n    }\r\n<\/pre>\n<p>simple enough right?<br \/>\nand this kind of logic is used in many procedurally generated pieces like this for instance:<br \/>\n<iframe loading=\"lazy\" width=\"300\" height=\"600\" style=\"width: 100%;\" scrolling=\"no\" src=\"\/\/codepen.io\/nicoptere\/embed\/QyVWmB\/?height=600&amp;theme-id=4835&amp;default-tab=result\" frameborder=\"no\" allowtransparency=\"true\" allowfullscreen=\"allowfullscreen\">See the Pen <a href=\"http:\/\/codepen.io\/nicoptere\/pen\/QyVWmB\/\">smooth growth<\/a> by nicolas barradeau (<a href=\"http:\/\/codepen.io\/nicoptere\">@nicoptere<\/a>) on <a href=\"http:\/\/codepen.io\">CodePen<\/a>.<br \/>\n<\/iframe><\/p>\n<p>it&#8217;s important to note that the snake should stop when\u00a0all the points have reached a local minima (the bottom of a valley) but that it\u00a0doesn&#8217;t always happen\u00a0so I&#8217;ve set a max number of iterations instead. Once the process is over, the callback method recieves a series of points\u00a0that describe the enclosing shape, this polygon can be used to mask or clip the image, or just as a vector shape.<\/p>\n<p>I&#8217;ll leave you with a bunch of samples, click on the pictures below to get started.<br \/>\n<iframe loading=\"lazy\" width=\"720\" height=\"720\" src=\"http:\/\/barradeau.com\/2017\/170116\/index.html\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe><\/p>\n<p><a href=\"http:\/\/barradeau.com\/2017\/170116\/acm.zip\">here&#8217;s a ZIP of the code with the sample images<\/a>, they come from\u00a0<a href=\"https:\/\/www.walldevil.com\/\">https:\/\/www.walldevil.com\/<\/a><\/p>\n<p>enjoy :)<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>quick post about something I did\u00a0last year for a project that never went anywhere. the Active Contour Model\u00a0(ACM) is an old fashioned way of vectorizing the contour of an object. it works very well when an object is isolated on a flat color but the principle can also\u00a0be used &#8220;live&#8221; to determine where the &#8220;border&#8221; &#8230; <span class=\"more\"><a class=\"more-link\" href=\"http:\/\/barradeau.com\/blog\/?p=1032\">[Read more&#8230;]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":1056,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"sharing_disabled":false,"spay_email":"","jetpack_publicize_message":""},"categories":[3],"tags":[],"jetpack_featured_media_url":"http:\/\/barradeau.com\/blog\/wp-content\/uploads\/2017\/01\/cover-1.jpg","jetpack_publicize_connections":[],"jetpack_shortlink":"https:\/\/wp.me\/p4oXhx-gE","_links":{"self":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1032"}],"collection":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1032"}],"version-history":[{"count":18,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1032\/revisions"}],"predecessor-version":[{"id":1187,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1032\/revisions\/1187"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/1056"}],"wp:attachment":[{"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1032"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1032"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/barradeau.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1032"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}