{"title":"","byline":null,"dir":null,"lang":null,"content":"<div id=\"readability-page-1\" class=\"page\"><div data-hpc=\"true\"><article><p dir=\"auto\"><a href=\"https://camo.githubusercontent.com/878b3edc3ff46136fc895d545a54b1363714844b1ba8e34accb15f4d444516b4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e342e646576302d79656c6c6f772e737667\" rel=\"noopener noreferrer nofollow\"><img data-canonical-src=\"https://img.shields.io/badge/version-0.4.dev0-yellow.svg\" alt=\"Version\" src=\"https://camo.githubusercontent.com/878b3edc3ff46136fc895d545a54b1363714844b1ba8e34accb15f4d444516b4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e342e646576302d79656c6c6f772e737667\"></a>\n<a href=\"https://camo.githubusercontent.com/7145a148f71eead879cd3249e3df5f768e4ba94ca723cb6fb33c197929c8ba70/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e3130253230253743253230332e3131253230253743253230332e3132253230253743253230332e31332d6f72616e67652e737667\" rel=\"noopener noreferrer nofollow\"><img data-canonical-src=\"https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-orange.svg\" alt=\"Python\" src=\"https://camo.githubusercontent.com/7145a148f71eead879cd3249e3df5f768e4ba94ca723cb6fb33c197929c8ba70/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332e3130253230253743253230332e3131253230253743253230332e3132253230253743253230332e31332d6f72616e67652e737667\"></a>\n<a href=\"https://camo.githubusercontent.com/a889565ed2a695f53d88a87c99d150bed2d18fe680d2d6efb7c426a6285174f6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d474e5525324647504c76332d626c75652e737667\" rel=\"noopener noreferrer nofollow\"><img data-canonical-src=\"https://img.shields.io/badge/license-GNU%2FGPLv3-blue.svg\" alt=\"License\" src=\"https://camo.githubusercontent.com/a889565ed2a695f53d88a87c99d150bed2d18fe680d2d6efb7c426a6285174f6/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d474e5525324647504c76332d626c75652e737667\"></a>\n<a href=\"https://camo.githubusercontent.com/15bb70f72103e9f33760189b865ac9bb61707d0568ee6aa72e378294b47b5704/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6f732d4c696e757825323025374325323057696e646f77732d6d6167656e74612e737667\" rel=\"noopener noreferrer nofollow\"><img data-canonical-src=\"https://img.shields.io/badge/os-Linux%20%7C%20Windows-magenta.svg\" alt=\"OS\" src=\"https://camo.githubusercontent.com/15bb70f72103e9f33760189b865ac9bb61707d0568ee6aa72e378294b47b5704/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6f732d4c696e757825323025374325323057696e646f77732d6d6167656e74612e737667\"></a></p>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Puppet Warp</h2><a href=\"#puppet-warp\" aria-label=\"Permalink: Puppet Warp\" id=\"user-content-puppet-warp\"></a></p>\n<p dir=\"auto\">The goal of the package <strong>puppet-warp</strong> is to provide a plug-and-play solution for image\ntransformation similar to Adobe Photoshop’s <em>Puppet Warp</em> tool. Since the Photoshop\nsolution is proprietary (and scripting can be painful, especially on unsupported platforms),\nthis project implements Puppet Warp in Python so it can be used programmatically in\nautomation pipelines where advanced deformation is required.</p>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Features</h2><a href=\"#features\" aria-label=\"Permalink: Features\" id=\"user-content-features\"></a></p>\n<ul dir=\"auto\">\n<li>As-Rigid-as-Possible (ARAP) shape manipulation of a triangular mesh</li>\n<li>Image transfer from a triangular mesh at rest to a mesh defined by ARAP deformation</li>\n</ul>\n<blockquote>\n<p dir=\"auto\">Note: Please report issues and feel free to open pull requests.</p>\n</blockquote>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Requirements</h2><a href=\"#requirements\" aria-label=\"Permalink: Requirements\" id=\"user-content-requirements\"></a></p>\n<div><pre><code>numpy&gt;=1.21.5\nopencv-contrib-python&gt;=4.5.4.60,&lt;=4.12.0.88\nopencv-python&gt;=4.5.4.60,&lt;=4.12.0.88\nscikit-image&gt;=0.19.2,&lt;=0.26.0\nscikit-learn&gt;=1.0.2,&lt;=1.8.0\n</code></pre></div>\n<p dir=\"auto\">Optional:</p>\n\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Installation</h2><a href=\"#installation\" aria-label=\"Permalink: Installation\" id=\"user-content-installation\"></a></p>\n\n<p dir=\"auto\">For the latest version from git:</p>\n<div dir=\"auto\"><pre>pip install git+https://github.com/mikecokina/puppet-warp.git@dev</pre></div>\n<p dir=\"auto\">Install with Jonathan Richard Shewchuk’s Triangle bindings:</p>\n<div dir=\"auto\"><pre>pip install puppet-warp[jrs]</pre></div>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Usage</h2><a href=\"#usage\" aria-label=\"Permalink: Usage\" id=\"user-content-usage\"></a></p>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Demo</h2><a href=\"#demo\" aria-label=\"Permalink: Demo\" id=\"user-content-demo\"></a></p>\n<p dir=\"auto\">The package comes with a live interactive demo:</p>\n<div dir=\"auto\"><pre><span>from</span> <span>pwarp</span> <span>import</span> <span>Demo</span>\n\n<span>Demo</span>().<span>run</span>()</pre></div>\n<p dir=\"auto\">To manipulate the image:</p>\n<ul dir=\"auto\">\n<li>Select control points by clicking on vertices in the mesh.</li>\n<li>Drag a selected control point to deform the mesh.</li>\n</ul>\n<p dir=\"auto\">Demo also supports saving the transformed mesh:</p>\n<ul dir=\"auto\">\n<li><strong>Space</strong>: save current mesh (Wavefront OBJ)</li>\n<li><strong>Esc</strong>: quit</li>\n</ul>\n<p dir=\"auto\">By default, outputs are stored in <code>~/pwarp</code>.</p>\n<p dir=\"auto\"><h3 dir=\"auto\" tabindex=\"-1\">Custom demo</h3><a href=\"#custom-demo\" aria-label=\"Permalink: Custom demo\" id=\"user-content-custom-demo\"></a></p>\n<div dir=\"auto\"><pre><span>import</span> <span>cv2</span>\n\n<span>from</span> <span>pwarp</span> <span>import</span> <span>Demo</span>, <span>triangular_mesh</span>\n<span>from</span> <span>pwarp</span>.<span>_io</span> <span>import</span> <span>save_wavefront</span>\n\n<span># Define WIDTH and HEIGHT of your image and DELTA step to create a triangular mesh.</span>\n<span>width</span> <span>=</span> <span>800</span>\n<span>height</span> <span>=</span> <span>492</span>\n<span>delta</span> <span>=</span> <span>100</span>\n<span>method</span> <span>=</span> <span>\"scipy\"</span>  <span># or \"jrs\"</span>\n\n<span># Define paths to your image and the OBJ file.</span>\n<span>wavefront_path</span> <span>=</span> <span>\"image.obj\"</span>\n<span>image_path</span> <span>=</span> <span>\"image.jpg\"</span>\n\n<span>image</span> <span>=</span> <span>cv2</span>.<span>cvtColor</span>(<span>cv2</span>.<span>imread</span>(<span>image_path</span>), <span>cv2</span>.<span>COLOR_BGR2RGB</span>)\n\n<span># Generate triangular mesh over the image.</span>\n<span>r</span>, <span>f</span> <span>=</span> <span>triangular_mesh</span>(<span>width</span><span>=</span><span>width</span>, <span>height</span><span>=</span><span>height</span>, <span>delta</span><span>=</span><span>delta</span>, <span>method</span><span>=</span><span>method</span>)\n\n<span># Save wavefront object.</span>\n<span>save_wavefront</span>(<span>wavefront_path</span>, <span>no_vertices</span><span>=</span><span>len</span>(<span>r</span>), <span>no_faces</span><span>=</span><span>len</span>(<span>f</span>), <span>vertices</span><span>=</span><span>r</span>, <span>faces</span><span>=</span><span>f</span>)\n\n<span>Demo</span>(\n    <span>image</span><span>=</span><span>image_path</span>,\n    <span>obj_path</span><span>=</span><span>wavefront_path</span>,\n    <span>screen_height</span><span>=</span><span>height</span>,\n    <span>screen_width</span><span>=</span><span>width</span>,\n    <span>scale</span><span>=</span><span>1</span>,\n    <span>dx</span><span>=</span><span>0</span>,\n    <span>dy</span><span>=</span><span>0</span>,\n    <span>verbose</span><span>=</span><span>True</span>,\n).<span>run</span>()</pre></div>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Graph warp</h2><a href=\"#graph-warp\" aria-label=\"Permalink: Graph warp\" id=\"user-content-graph-warp\"></a></p>\n<p dir=\"auto\">Graph warp requires vertices and faces (triangulation), control points, and new positions of\ncontrol points. Based on that information, graph warp computes new positions of the supplied\nvertices.</p>\n<p dir=\"auto\"><strong>Example:</strong></p>\n<div dir=\"auto\"><pre><span>import</span> <span>numpy</span> <span>as</span> <span>np</span>\n\n<span>from</span> <span>pwarp</span> <span>import</span> <span>get_default_puppet</span>, <span>graph_warp</span>\n<span>from</span> <span>pwarp</span>.<span>core</span>.<span>precompute</span> <span>import</span> <span>arap_precompute</span>\n\n<span># Control points represent indices of points in original vertex array.</span>\n<span>control_pts</span> <span>=</span> <span>np</span>.<span>array</span>([<span>22</span>, <span>50</span>, <span>94</span>, <span>106</span>], <span>dtype</span><span>=</span><span>int</span>)\n\n<span># Shift represents new positions of control points respectively to `control_pts` list.</span>\n<span>shift</span> <span>=</span> <span>np</span>.<span>array</span>(\n    [\n        [<span>0.555</span>, <span>-</span><span>0.905</span>],\n        [<span>-</span><span>0.965</span>, <span>-</span><span>0.875</span>],\n        [<span>-</span><span>0.950</span>, <span>0.460</span>],\n        [<span>0.705</span>, <span>0.285</span>],\n    ],\n    <span>dtype</span><span>=</span><span>float</span>,\n)\n\n<span>puppet</span> <span>=</span> <span>get_default_puppet</span>()\n\n<span># Precompute once per mesh (recommended).</span>\n<span>pre</span> <span>=</span> <span>arap_precompute</span>(<span>vertices</span><span>=</span><span>puppet</span>.<span>r</span>, <span>faces</span><span>=</span><span>puppet</span>.<span>f</span>)\n\n<span>new_vertices</span> <span>=</span> <span>graph_warp</span>(\n    <span>vertices</span><span>=</span><span>puppet</span>.<span>r</span>,\n    <span>faces</span><span>=</span><span>puppet</span>.<span>f</span>,\n    <span>control_indices</span><span>=</span><span>control_pts</span>,\n    <span>shifted_locations</span><span>=</span><span>shift</span>,\n    <span>precomputed</span><span>=</span><span>pre</span>,\n)</pre></div>\n<p dir=\"auto\"><a href=\"https://github.com/mikecokina/puppet-warp/blob/master/docs/source/_static/readme/graph_t.png\" rel=\"noopener noreferrer\"><img alt=\"mesh\" src=\"https://github.com/mikecokina/puppet-warp/raw/master/docs/source/_static/readme/graph_t.png\"></a></p>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Graph defined warp</h2><a href=\"#graph-defined-warp\" aria-label=\"Permalink: Graph defined warp\" id=\"user-content-graph-defined-warp\"></a></p>\n<p dir=\"auto\">Graph defined warp transforms image regions covered by source vertices to given destination vertices.\nIt requires:</p>\n<ul dir=\"auto\">\n<li>input image</li>\n<li>source vertices + faces</li>\n<li>destination vertices + faces</li>\n</ul>\n<p dir=\"auto\">Faces (triangles) must correspond pairwise between source and destination.</p>\n<p dir=\"auto\"><strong>Example:</strong></p>\n<div dir=\"auto\"><pre><span>import</span> <span>cv2</span>\n<span>import</span> <span>numpy</span> <span>as</span> <span>np</span>\n<span>from</span> <span>matplotlib</span> <span>import</span> <span>pyplot</span> <span>as</span> <span>plt</span>\n\n<span>from</span> <span>pwarp</span> <span>import</span> <span>get_default_puppet</span>, <span>graph_defined_warp</span>, <span>graph_warp</span>\n<span>from</span> <span>pwarp</span>.<span>core</span>.<span>precompute</span> <span>import</span> <span>arap_precompute</span>\n\n<span>control_pts</span> <span>=</span> <span>np</span>.<span>array</span>([<span>22</span>, <span>50</span>, <span>94</span>, <span>106</span>], <span>dtype</span><span>=</span><span>int</span>)\n<span>shift</span> <span>=</span> <span>np</span>.<span>array</span>(\n    [\n        [<span>0.555</span>, <span>-</span><span>0.905</span>],\n        [<span>-</span><span>0.965</span>, <span>-</span><span>0.875</span>],\n        [<span>-</span><span>0.950</span>, <span>0.460</span>],\n        [<span>0.705</span>, <span>0.285</span>],\n    ],\n    <span>dtype</span><span>=</span><span>float</span>,\n)\n\n<span>puppet</span> <span>=</span> <span>get_default_puppet</span>()\n<span>pre</span> <span>=</span> <span>arap_precompute</span>(<span>vertices</span><span>=</span><span>puppet</span>.<span>r</span>, <span>faces</span><span>=</span><span>puppet</span>.<span>f</span>)\n\n<span>new_r</span> <span>=</span> <span>graph_warp</span>(\n    <span>vertices</span><span>=</span><span>puppet</span>.<span>r</span>,\n    <span>faces</span><span>=</span><span>puppet</span>.<span>f</span>,\n    <span>control_indices</span><span>=</span><span>control_pts</span>,\n    <span>shifted_locations</span><span>=</span><span>shift</span>,\n    <span>precomputed</span><span>=</span><span>pre</span>,\n)\n\n<span>image</span> <span>=</span> <span>cv2</span>.<span>cvtColor</span>(<span>cv2</span>.<span>imread</span>(<span>\"../data/puppet.png\"</span>), <span>cv2</span>.<span>COLOR_BGR2RGB</span>)\n<span>width</span>, <span>height</span> <span>=</span> <span>1280</span>, <span>800</span>\n<span>dx</span>, <span>dy</span> <span>=</span> <span>int</span>(<span>width</span> <span>//</span> <span>2</span>), <span>int</span>(<span>height</span> <span>//</span> <span>2</span>)\n<span>scale_x</span>, <span>scale_y</span> <span>=</span> <span>200</span>, <span>-</span><span>200</span>\n\n<span>r</span> <span>=</span> <span>puppet</span>.<span>r</span>.<span>copy</span>()\n<span>r</span>[:, <span>0</span>] <span>=</span> <span>r</span>[:, <span>0</span>] <span>*</span> <span>scale_x</span> <span>+</span> <span>dx</span>\n<span>r</span>[:, <span>1</span>] <span>=</span> <span>r</span>[:, <span>1</span>] <span>*</span> <span>scale_y</span> <span>+</span> <span>dy</span>\n\n<span>new_r</span> <span>=</span> <span>new_r</span>.<span>copy</span>()\n<span>new_r</span>[:, <span>0</span>] <span>=</span> <span>new_r</span>[:, <span>0</span>] <span>*</span> <span>scale_x</span> <span>+</span> <span>dx</span>\n<span>new_r</span>[:, <span>1</span>] <span>=</span> <span>new_r</span>[:, <span>1</span>] <span>*</span> <span>scale_y</span> <span>+</span> <span>dy</span>\n\n<span>image_t</span> <span>=</span> <span>graph_defined_warp</span>(\n    <span>image</span>,\n    <span>vertices_src</span><span>=</span><span>r</span>,\n    <span>faces_src</span><span>=</span><span>puppet</span>.<span>f</span>,\n    <span>vertices_dst</span><span>=</span><span>new_r</span>,\n    <span>faces_dst</span><span>=</span><span>puppet</span>.<span>f</span>,\n)\n\n<span>fig</span>, <span>axs</span> <span>=</span> <span>plt</span>.<span>subplots</span>(<span>1</span>, <span>2</span>, <span>frameon</span><span>=</span><span>False</span>)\n<span>plt</span>.<span>tight_layout</span>(<span>pad</span><span>=</span><span>0</span>)\n\n<span>axs</span>[<span>0</span>].<span>imshow</span>(<span>image</span>)\n<span>axs</span>[<span>1</span>].<span>imshow</span>(<span>image_t</span>)\n<span>axs</span>[<span>0</span>].<span>triplot</span>(<span>r</span>.<span>T</span>[<span>0</span>], <span>r</span>.<span>T</span>[<span>1</span>], <span>puppet</span>.<span>f</span>, <span>lw</span><span>=</span><span>0.5</span>)\n<span>axs</span>[<span>1</span>].<span>triplot</span>(<span>new_r</span>.<span>T</span>[<span>0</span>], <span>new_r</span>.<span>T</span>[<span>1</span>], <span>puppet</span>.<span>f</span>, <span>lw</span><span>=</span><span>0.5</span>)\n\n<span>for</span> <span>ax</span> <span>in</span> <span>axs</span>:\n    <span>ax</span>.<span>set_xlim</span>([<span>380</span>, <span>900</span>])\n    <span>ax</span>.<span>set_ylim</span>([<span>150</span>, <span>750</span>])\n    <span>ax</span>.<span>invert_yaxis</span>()\n    <span>ax</span>.<span>axis</span>(<span>\"off\"</span>)\n\n<span>plt</span>.<span>show</span>()</pre></div>\n<p dir=\"auto\"><a href=\"https://github.com/mikecokina/puppet-warp/blob/master/docs/source/_static/readme/graph_def_t.png\" rel=\"noopener noreferrer\"><img alt=\"mesh\" src=\"https://github.com/mikecokina/puppet-warp/raw/master/docs/source/_static/readme/graph_def_t.png\"></a></p>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Triangular mesh</h2><a href=\"#triangular-mesh\" aria-label=\"Permalink: Triangular mesh\" id=\"user-content-triangular-mesh\"></a></p>\n<p dir=\"auto\">The algorithm generates a triangular mesh within a rectangle defined by its width and height.\nMesh density is adjustable via the <code>delta</code> parameter.</p>\n<p dir=\"auto\"><strong>Example:</strong></p>\n<div dir=\"auto\"><pre><span>from</span> <span>pwarp</span> <span>import</span> <span>triangular_mesh</span>\n\n<span>r</span>, <span>f</span> <span>=</span> <span>triangular_mesh</span>(<span>width</span><span>=</span><span>1280</span>, <span>height</span><span>=</span><span>800</span>, <span>delta</span><span>=</span><span>100</span>)</pre></div>\n<p dir=\"auto\"><a href=\"https://github.com/mikecokina/puppet-warp/blob/master/docs/source/_static/readme/mesh.png\" rel=\"noopener noreferrer\"><img alt=\"mesh\" src=\"https://github.com/mikecokina/puppet-warp/raw/master/docs/source/_static/readme/mesh.png\"></a></p>\n<p dir=\"auto\"><strong>Example on full screen triangular mesh warp:</strong></p>\n<p dir=\"auto\"><a href=\"https://github.com/mikecokina/puppet-warp/blob/master/docs/source/_static/readme/full_graph_def_t.png\" rel=\"noopener noreferrer\"><img alt=\"mesh\" src=\"https://github.com/mikecokina/puppet-warp/raw/master/docs/source/_static/readme/full_graph_def_t.png\"></a></p>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">References</h2><a href=\"#references\" aria-label=\"Permalink: References\" id=\"user-content-references\"></a></p>\n<div><pre><code>[1] https://www-ui.is.s.u-tokyo.ac.jp/~takeo/papers/takeo_jgt09_arapFlattening.pdf\n[2] https://github.com/deliagander/ARAPShapeManipulation.git\n[3] https://learnopencv.com/warp-one-triangle-to-another-using-opencv-c-python/\n[4] https://rufat.be/triangle/\n[5] http://www.cs.cmu.edu/~quake/triangle.html\n</code></pre></div>\n<p dir=\"auto\"><h2 dir=\"auto\" tabindex=\"-1\">Cite</h2><a href=\"#cite\" aria-label=\"Permalink: Cite\" id=\"user-content-cite\"></a></p>\n<div dir=\"auto\"><pre><span>@article</span>{<span>journals/jgtools/IgarashiI09</span>,\n    <span>author</span> = <span><span>{</span>Igarashi, Takeo and Igarashi, Yuki<span>}</span></span>,\n    <span>ee</span> = <span><span>{</span>http://dx.doi.org/10.1080/2151237X.2009.10129273<span>}</span></span>,\n    <span>journal</span> = <span><span>{</span>J. Graphics, GPU, &amp; Game Tools<span>}</span></span>,\n    <span>number</span> = <span>1</span>,\n    <span>pages</span> = <span><span>{</span>17-30<span>}</span></span>,\n    <span>title</span> = <span><span>{</span>Implementing As-Rigid-As-Possible Shape Manipulation and Surface Flattening.<span>}</span></span>,\n    <span>url</span> = <span><span>{</span>http://dblp.uni-trier.de/db/journals/jgtools/jgtools14.html#IgarashiI09<span>}</span></span>,\n    <span>volume</span> = <span>14</span>,\n    <span>year</span> = <span>2009</span>\n}</pre></div>\n<p dir=\"auto\">or</p>\n<div dir=\"auto\"><pre><span>@article</span>{<span>10.1145/1073204.1073323</span>,\n    <span>author</span> = <span><span>{</span>Igarashi, Takeo and Moscovich, Tomer and Hughes, John F.<span>}</span></span>,\n    <span>title</span> = <span><span>{</span>As-Rigid-as-Possible Shape Manipulation<span>}</span></span>,\n    <span>year</span> = <span><span>{</span>2005<span>}</span></span>,\n    <span>publisher</span> = <span><span>{</span>Association for Computing Machinery<span>}</span></span>,\n    <span>address</span> = <span><span>{</span>New York, NY, USA<span>}</span></span>,\n    <span>volume</span> = <span><span>{</span>24<span>}</span></span>,\n    <span>number</span> = <span><span>{</span>3<span>}</span></span>,\n    <span>doi</span> = <span><span>{</span>10.1145/1073204.1073323<span>}</span></span>,\n    <span>journal</span> = <span><span>{</span>ACM Trans. Graph.<span>}</span></span>,\n    <span>month</span> = <span><span>{</span>jul<span>}</span></span>,\n    <span>pages</span> = <span><span>{</span>1134–1141<span>}</span></span>,\n    <span>numpages</span> = <span><span>{</span>8<span>}</span></span>\n}</pre></div>\n</article></div></div>","textContent":"\n\n\n\nPuppet Warp\nThe goal of the package puppet-warp is to provide a plug-and-play solution for image\ntransformation similar to Adobe Photoshop’s Puppet Warp tool. Since the Photoshop\nsolution is proprietary (and scripting can be painful, especially on unsupported platforms),\nthis project implements Puppet Warp in Python so it can be used programmatically in\nautomation pipelines where advanced deformation is required.\nFeatures\n\nAs-Rigid-as-Possible (ARAP) shape manipulation of a triangular mesh\nImage transfer from a triangular mesh at rest to a mesh defined by ARAP deformation\n\n\nNote: Please report issues and feel free to open pull requests.\n\nRequirements\nnumpy>=1.21.5\nopencv-contrib-python>=4.5.4.60,<=4.12.0.88\nopencv-python>=4.5.4.60,<=4.12.0.88\nscikit-image>=0.19.2,<=0.26.0\nscikit-learn>=1.0.2,<=1.8.0\n\nOptional:\n\nInstallation\n\nFor the latest version from git:\npip install git+https://github.com/mikecokina/puppet-warp.git@dev\nInstall with Jonathan Richard Shewchuk’s Triangle bindings:\npip install puppet-warp[jrs]\nUsage\nDemo\nThe package comes with a live interactive demo:\nfrom pwarp import Demo\n\nDemo().run()\nTo manipulate the image:\n\nSelect control points by clicking on vertices in the mesh.\nDrag a selected control point to deform the mesh.\n\nDemo also supports saving the transformed mesh:\n\nSpace: save current mesh (Wavefront OBJ)\nEsc: quit\n\nBy default, outputs are stored in ~/pwarp.\nCustom demo\nimport cv2\n\nfrom pwarp import Demo, triangular_mesh\nfrom pwarp._io import save_wavefront\n\n# Define WIDTH and HEIGHT of your image and DELTA step to create a triangular mesh.\nwidth = 800\nheight = 492\ndelta = 100\nmethod = \"scipy\"  # or \"jrs\"\n\n# Define paths to your image and the OBJ file.\nwavefront_path = \"image.obj\"\nimage_path = \"image.jpg\"\n\nimage = cv2.cvtColor(cv2.imread(image_path), cv2.COLOR_BGR2RGB)\n\n# Generate triangular mesh over the image.\nr, f = triangular_mesh(width=width, height=height, delta=delta, method=method)\n\n# Save wavefront object.\nsave_wavefront(wavefront_path, no_vertices=len(r), no_faces=len(f), vertices=r, faces=f)\n\nDemo(\n    image=image_path,\n    obj_path=wavefront_path,\n    screen_height=height,\n    screen_width=width,\n    scale=1,\n    dx=0,\n    dy=0,\n    verbose=True,\n).run()\nGraph warp\nGraph warp requires vertices and faces (triangulation), control points, and new positions of\ncontrol points. Based on that information, graph warp computes new positions of the supplied\nvertices.\nExample:\nimport numpy as np\n\nfrom pwarp import get_default_puppet, graph_warp\nfrom pwarp.core.precompute import arap_precompute\n\n# Control points represent indices of points in original vertex array.\ncontrol_pts = np.array([22, 50, 94, 106], dtype=int)\n\n# Shift represents new positions of control points respectively to `control_pts` list.\nshift = np.array(\n    [\n        [0.555, -0.905],\n        [-0.965, -0.875],\n        [-0.950, 0.460],\n        [0.705, 0.285],\n    ],\n    dtype=float,\n)\n\npuppet = get_default_puppet()\n\n# Precompute once per mesh (recommended).\npre = arap_precompute(vertices=puppet.r, faces=puppet.f)\n\nnew_vertices = graph_warp(\n    vertices=puppet.r,\n    faces=puppet.f,\n    control_indices=control_pts,\n    shifted_locations=shift,\n    precomputed=pre,\n)\n\nGraph defined warp\nGraph defined warp transforms image regions covered by source vertices to given destination vertices.\nIt requires:\n\ninput image\nsource vertices + faces\ndestination vertices + faces\n\nFaces (triangles) must correspond pairwise between source and destination.\nExample:\nimport cv2\nimport numpy as np\nfrom matplotlib import pyplot as plt\n\nfrom pwarp import get_default_puppet, graph_defined_warp, graph_warp\nfrom pwarp.core.precompute import arap_precompute\n\ncontrol_pts = np.array([22, 50, 94, 106], dtype=int)\nshift = np.array(\n    [\n        [0.555, -0.905],\n        [-0.965, -0.875],\n        [-0.950, 0.460],\n        [0.705, 0.285],\n    ],\n    dtype=float,\n)\n\npuppet = get_default_puppet()\npre = arap_precompute(vertices=puppet.r, faces=puppet.f)\n\nnew_r = graph_warp(\n    vertices=puppet.r,\n    faces=puppet.f,\n    control_indices=control_pts,\n    shifted_locations=shift,\n    precomputed=pre,\n)\n\nimage = cv2.cvtColor(cv2.imread(\"../data/puppet.png\"), cv2.COLOR_BGR2RGB)\nwidth, height = 1280, 800\ndx, dy = int(width // 2), int(height // 2)\nscale_x, scale_y = 200, -200\n\nr = puppet.r.copy()\nr[:, 0] = r[:, 0] * scale_x + dx\nr[:, 1] = r[:, 1] * scale_y + dy\n\nnew_r = new_r.copy()\nnew_r[:, 0] = new_r[:, 0] * scale_x + dx\nnew_r[:, 1] = new_r[:, 1] * scale_y + dy\n\nimage_t = graph_defined_warp(\n    image,\n    vertices_src=r,\n    faces_src=puppet.f,\n    vertices_dst=new_r,\n    faces_dst=puppet.f,\n)\n\nfig, axs = plt.subplots(1, 2, frameon=False)\nplt.tight_layout(pad=0)\n\naxs[0].imshow(image)\naxs[1].imshow(image_t)\naxs[0].triplot(r.T[0], r.T[1], puppet.f, lw=0.5)\naxs[1].triplot(new_r.T[0], new_r.T[1], puppet.f, lw=0.5)\n\nfor ax in axs:\n    ax.set_xlim([380, 900])\n    ax.set_ylim([150, 750])\n    ax.invert_yaxis()\n    ax.axis(\"off\")\n\nplt.show()\n\nTriangular mesh\nThe algorithm generates a triangular mesh within a rectangle defined by its width and height.\nMesh density is adjustable via the delta parameter.\nExample:\nfrom pwarp import triangular_mesh\n\nr, f = triangular_mesh(width=1280, height=800, delta=100)\n\nExample on full screen triangular mesh warp:\n\nReferences\n[1] https://www-ui.is.s.u-tokyo.ac.jp/~takeo/papers/takeo_jgt09_arapFlattening.pdf\n[2] https://github.com/deliagander/ARAPShapeManipulation.git\n[3] https://learnopencv.com/warp-one-triangle-to-another-using-opencv-c-python/\n[4] https://rufat.be/triangle/\n[5] http://www.cs.cmu.edu/~quake/triangle.html\n\nCite\n@article{journals/jgtools/IgarashiI09,\n    author = {Igarashi, Takeo and Igarashi, Yuki},\n    ee = {http://dx.doi.org/10.1080/2151237X.2009.10129273},\n    journal = {J. Graphics, GPU, & Game Tools},\n    number = 1,\n    pages = {17-30},\n    title = {Implementing As-Rigid-As-Possible Shape Manipulation and Surface Flattening.},\n    url = {http://dblp.uni-trier.de/db/journals/jgtools/jgtools14.html#IgarashiI09},\n    volume = 14,\n    year = 2009\n}\nor\n@article{10.1145/1073204.1073323,\n    author = {Igarashi, Takeo and Moscovich, Tomer and Hughes, John F.},\n    title = {As-Rigid-as-Possible Shape Manipulation},\n    year = {2005},\n    publisher = {Association for Computing Machinery},\n    address = {New York, NY, USA},\n    volume = {24},\n    number = {3},\n    doi = {10.1145/1073204.1073323},\n    journal = {ACM Trans. Graph.},\n    month = {jul},\n    pages = {1134–1141},\n    numpages = {8}\n}\n","length":6501,"excerpt":"","siteName":null}