<?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: 景阳</title>
    <description>The latest articles on DEV Community by 景阳 (@johnim2w).</description>
    <link>https://dev.to/johnim2w</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4236575%2Fcbdcd885-0ef1-4ae6-87d4-3bc5e9d5824b.png</url>
      <title>DEV Community: 景阳</title>
      <link>https://dev.to/johnim2w</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnim2w"/>
    <language>en</language>
    <item>
      <title>What I learned building an AI tool to remove people from photos</title>
      <dc:creator>景阳</dc:creator>
      <pubDate>Tue, 10 Nov 2026 08:08:28 +0000</pubDate>
      <link>https://dev.to/johnim2w/what-i-learned-building-an-ai-tool-to-remove-people-from-photos-3p77</link>
      <guid>https://dev.to/johnim2w/what-i-learned-building-an-ai-tool-to-remove-people-from-photos-3p77</guid>
      <description>&lt;p&gt;I'm a solo developer, and my second side project is a small web tool that lets you &lt;a href="https://swiftremover.com/remove-person-from-photo/" rel="noopener noreferrer"&gt;remove a person from a photo&lt;/a&gt;: you paint over a photobomber, tap Remove, and the AI fills the area with background. It sounds like a solved problem. It mostly is, until someone uploads a photo from a restaurant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 1: LaMa, and why it is so good on beaches
&lt;/h2&gt;

&lt;p&gt;The first version uses &lt;a href="https://github.com/advimman/lama" rel="noopener noreferrer"&gt;LaMa&lt;/a&gt;, the classic inpainting model. You send an image and a black-and-white mask (white = remove), and it fills the hole using what it sees around it. On Replicate it runs in about 1.4 seconds and costs about $0.0003 per image, which is cheap enough to offer three free removals a day without thinking twice.&lt;/p&gt;

&lt;p&gt;On simple backgrounds such as sand, sea, sky, grass or a plain wall, the result is genuinely hard to tell from the original.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it breaks
&lt;/h2&gt;

&lt;p&gt;My first real test was a dinner photo: six people around a round table, a carved wooden chair behind each of them, an ink-painting mural on the wall. I painted over the woman on the right and got… a smear. The chair turned into a brown cloud, the mural into grey mush, and the arm of the woman next to her, which was reaching out to toast, bent into something unnatural.&lt;/p&gt;

&lt;p&gt;That is not a bug. LaMa does not know what a chair is. It continues textures. When the hole is large and the background has structure, there is nothing to continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying four models on the same photo
&lt;/h2&gt;

&lt;p&gt;I ran the same photo and the same mask through four models:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LaMa&lt;/td&gt;
&lt;td&gt;Smeared chair and wall&lt;/td&gt;
&lt;td&gt;~$0.0003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bria Eraser&lt;/td&gt;
&lt;td&gt;Clean chair and wall, but the neighbour still toasts an empty seat&lt;/td&gt;
&lt;td&gt;~$0.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FLUX Fill Pro (with a prompt)&lt;/td&gt;
&lt;td&gt;Good, but it added a glass of whisky&lt;/td&gt;
&lt;td&gt;$0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FLUX Kontext Pro (text instruction)&lt;/td&gt;
&lt;td&gt;The most natural: chair rebuilt, the neighbour's arm moved back&lt;/td&gt;
&lt;td&gt;$0.04&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Kontext won because it understands the scene, not just the pixels. It even fixed the toast. But there was a catch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch: instruction editors don't read masks
&lt;/h2&gt;

&lt;p&gt;Kontext takes an image and a sentence like "remove the woman in the dark navy top on the far right". It ignores masks. My users paint; they do not type descriptions. When I sent Kontext the photo with the red paint on it and asked it to "remove whatever is under the red paint", it removed the red paint and kept the woman.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: let a vision model write the sentence
&lt;/h2&gt;

&lt;p&gt;So the Pro mode is a two-step pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send a cheap vision model (Gemini 2.5 Flash) two images: the clean photo and the same photo with the user's red paint. Ask it to name what is under the paint in under 15 words. Giving it the clean photo matters: with only the red version it described a green trash bin as "dark brown".&lt;/li&gt;
&lt;li&gt;Send the clean photo and "Remove {description}. Fill that area naturally… Keep everything else exactly the same." to Kontext.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On four test scenes (a beach photobomber, a street bin, a power pole by a lake, the dinner photo) the descriptions were all right, for example "the large dark green wheeled trash bin on the right", and the removals looked natural. Total cost is about $0.04 per image and 6 to 8 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it is offered
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard (LaMa):&lt;/strong&gt; free, 3 a day, no sign-up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro (describe + Kontext):&lt;/strong&gt; one free try a day, then one-time credit packs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a Standard result there is a "Try Pro on this photo" button that reruns the same painted area with Pro. Seeing the two side by side explains the difference better than any pricing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small things that mattered
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grow the mask by 2 to 3 pixels.&lt;/strong&gt; Users never paint the edge exactly, and the leftover outline is the most visible artifact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replicate throttles small accounts.&lt;/strong&gt; Mine was limited to "6 predictions per minute with a burst of 1", so the second call in the Pro pipeline failed. The fix: read &lt;code&gt;Retry-After&lt;/code&gt;, wait and retry, and if it still fails, give the user's credit back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Daily limits need to be atomic.&lt;/strong&gt; "Read the count, check it, write it back" in a key-value store lets parallel requests slip through. A Cloudflare Durable Object per day handles one request at a time, so 20 parallel requests from one IP get exactly 3 through.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I open-sourced the brush that paints the mask (zero dependencies, touch-friendly, exports the black-and-white PNG): &lt;a href="https://github.com/Ethan03015828/mask-brush" rel="noopener noreferrer"&gt;mask-brush on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to try it, it is at &lt;a href="https://swiftremover.com" rel="noopener noreferrer"&gt;swiftremover.com&lt;/a&gt;. I would love to hear about photos where it fails.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
