<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lucas de Ávila Martins</title>
    <description>The latest articles on DEV Community by Lucas de Ávila Martins (@lucasavila00_39).</description>
    <link>https://dev.to/lucasavila00_39</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F304467%2F5655bfeb-fcee-425f-be98-187909789b20.jpeg</url>
      <title>DEV Community: Lucas de Ávila Martins</title>
      <link>https://dev.to/lucasavila00_39</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lucasavila00_39"/>
    <language>en</language>
    <item>
      <title>An open source clone of Instragram/Snapchat filters on the web with Javascript</title>
      <dc:creator>Lucas de Ávila Martins</dc:creator>
      <pubDate>Mon, 30 Dec 2019 20:38:46 +0000</pubDate>
      <link>https://dev.to/lucasavila00_39/an-open-source-clone-of-instragram-snapchat-filters-on-the-web-with-javascript-277o</link>
      <guid>https://dev.to/lucasavila00_39/an-open-source-clone-of-instragram-snapchat-filters-on-the-web-with-javascript-277o</guid>
      <description>&lt;p&gt;When I first saw Instagram's and Snapchat's filter I thought they were all &lt;em&gt;magic&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Later I came to know that it is powered by &lt;em&gt;AI&lt;/em&gt; and &lt;em&gt;3D CGI&lt;/em&gt;. But that still doesn't explain much, right?&lt;/p&gt;

&lt;p&gt;In order to build a filter you need to do 3 things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the face&lt;/li&gt;
&lt;li&gt;Put stuff on the face&lt;/li&gt;
&lt;li&gt;Add color to the effect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So lets dig into it!&lt;/p&gt;

&lt;h1&gt;
  
  
  Find the face
&lt;/h1&gt;

&lt;p&gt;What I mean by find the face: Locate it's position and rotation in three dimensions. If you look around you will probably see this refered as defining the &lt;a href="https://www.researchgate.net/publication/321682530_Real-Time_Monocular_6-DoF_Head_Pose_Estimation_from_Salient_2D_Points"&gt;head pose with 6 degrees of fredom&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The approach I used is the one based on this &lt;a href="https://www.learnopencv.com/head-pose-estimation-using-opencv-and-dlib/"&gt;blog post&lt;/a&gt; and it goes like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Locate certain keypoints (nose tip position, left eye position, etc...) in the image.&lt;/li&gt;
&lt;li&gt;Given an approximated 3D representation of the face, solve the &lt;a href="https://en.wikipedia.org/wiki/Perspective-n-Point"&gt;Perspective-n-Point
&lt;/a&gt; and get the face's rotation and translation in 3D.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Locate keypoitns
&lt;/h2&gt;

&lt;p&gt;For this task I'm using an &lt;strong&gt;AWESOME&lt;/strong&gt; library called &lt;a href="https://github.com/justadudewhohacks/face-api.js/"&gt;face-api.js&lt;/a&gt;. You give it an image or a video and it will return a list of where are 68 keypoints on a human face.&lt;/p&gt;

&lt;p&gt;The way it works is best explained at the &lt;a href="https://github.com/justadudewhohacks/face-api.js/"&gt;project's page&lt;/a&gt; but in short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Find where in the image the face is (the blue square on the right side of the gif), this is done using Tensorflow to run the image through a Neural Network.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now that you have only the cropped face apply it to another Neural Network, this one will output positions for the keypoints.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solve Perspective-n-Point
&lt;/h2&gt;

&lt;p&gt;Given where the keypoints are we can use an estimated 3D model of the human face and try to rotate and move it around so that it's projection would be the same as the one observed.&lt;/p&gt;

&lt;p&gt;We need a list of the 3D points that correspond to the 2D ones observed in the image, we don't actually need a 3D model at all.&lt;/p&gt;

&lt;p&gt;But, of course, having this 3D model makes our life easier because it's now a matter of measuring it and getting these 3D points.&lt;/p&gt;

&lt;p&gt;I moved a cube to the desired points and the copied and pasted the location Blender (or any other 3D modelling software) would tell me the object is.&lt;/p&gt;

&lt;p&gt;We would also need to know some parameters about the camera (focal length, center of projection, etc) but we can just approximate them and it works great.&lt;/p&gt;

&lt;p&gt;Now feed your 3D points and 2D points to something like &lt;a href="https://docs.opencv.org/3.4/d9/d0c/group__calib3d.html#ga549c2075fac14829ff4a58bc931c033d"&gt;OpenCV's solvePnP&lt;/a&gt; and you're done. It will give you a rotation value and translation values that when applied to the object in 3D would produce the same projection.&lt;/p&gt;

&lt;p&gt;The only problem I got using this approach was that currently compiling OpenCV to WASM would produce a binary blob of ~1MB and 300k of JS after spending a whole day trying to decrease this size (it started at around 4MB).&lt;/p&gt;

&lt;p&gt;I didn't want to download and parse all of this just to run one function on my client's mobile phone.&lt;/p&gt;

&lt;p&gt;That's why &lt;a href="https://filtrou.me"&gt;Filtrou.me&lt;/a&gt; uses another AI to solve the PnP. &lt;a href="https://filtrou.me/solve-pnp"&gt;If you're interested in the details of this AI read the next blog post.&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Put stuff on the face
&lt;/h1&gt;

&lt;p&gt;Great! We now know the rotation and translation to apply to whatever we want to draw over the face.&lt;/p&gt;

&lt;p&gt;So let's do it! This couldn't be easier.&lt;/p&gt;

&lt;p&gt;We use &lt;a href="https://threejs.org/"&gt;three.js&lt;/a&gt; to create a scene, camera and an object.&lt;/p&gt;

&lt;p&gt;Then we apply the rotation and translation given in the previous step to this object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Quaternion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;threeObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setFromQuaternion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// if you're reading Filtrou.me's source code you'll see that&lt;/span&gt;
  &lt;span class="c1"&gt;// y coordinate is corrected given the video aspect ratio.&lt;/span&gt;
  &lt;span class="c1"&gt;// thats because the solvePnP AI sees the video as a square&lt;/span&gt;
  &lt;span class="c1"&gt;// and we're displaying it with diferent aspect ratios there.&lt;/span&gt;
  &lt;span class="c1"&gt;// If you use OpenCV's solvePnP or a square video with solvePnP AI&lt;/span&gt;
  &lt;span class="c1"&gt;// then the correction won't be needed.&lt;/span&gt;
  &lt;span class="nx"&gt;threeObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We should match three.js' configuration of FOV to the same as the camera where the picture was taken.&lt;/p&gt;

&lt;p&gt;But since we don't know it exactly using an approximation is fine.&lt;/p&gt;

&lt;p&gt;Using 45 degrees works fine if the video is squared. Otherwise it will need to be corrected given the image aspect ratio.&lt;/p&gt;

&lt;h1&gt;
  
  
  Add colors to the effect
&lt;/h1&gt;

&lt;p&gt;Once again, three.js comes to the rescue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/postprocessing"&gt;There is an awesome library called postprocessing that basically has everything done for you.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here at &lt;a href="https://filtrou.me"&gt;Filtrou.me&lt;/a&gt; we use it to apply &lt;a href="(https://threejsfundamentals.org/threejs/lessons/threejs-post-processing-3dlut.html)"&gt;some color changes&lt;/a&gt; based on some &lt;a href="https://www.computerhope.com/jargon/c/clut.htm"&gt;Color Look Up Tables&lt;/a&gt; done in Adobe Photoshop.&lt;/p&gt;

&lt;h1&gt;
  
  
  See it in action
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://filtrou.me/hpbb"&gt;A published filter on Filtrou.me&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Doubts?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/lucasavila00/filtroume"&gt;Take a look at Filtrou.me source code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/lucasavila00"&gt;Talk to me on Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
