<?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: boyuan tuo</title>
    <description>The latest articles on DEV Community by boyuan tuo (@boyuan_tuo_6f861761aeb29e).</description>
    <link>https://dev.to/boyuan_tuo_6f861761aeb29e</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%2F1680205%2F832c81cf-02a6-4a82-95c2-81e7500928d5.jpg</url>
      <title>DEV Community: boyuan tuo</title>
      <link>https://dev.to/boyuan_tuo_6f861761aeb29e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/boyuan_tuo_6f861761aeb29e"/>
    <language>en</language>
    <item>
      <title>Around image 87 the batch stopped looking like one store</title>
      <dc:creator>boyuan tuo</dc:creator>
      <pubDate>Wed, 12 Aug 2026 02:20:27 +0000</pubDate>
      <link>https://dev.to/boyuan_tuo_6f861761aeb29e/around-image-87-the-batch-stopped-looking-like-one-store-e41</link>
      <guid>https://dev.to/boyuan_tuo_6f861761aeb29e/around-image-87-the-batch-stopped-looking-like-one-store-e41</guid>
      <description>&lt;h1&gt;
  
  
  Around image 87 the batch stopped looking like one store
&lt;/h1&gt;

&lt;p&gt;Getting one good image out of a model is not hard. Batches are where it falls apart.&lt;/p&gt;

&lt;p&gt;Last time I filled a category, the first twenty were fine. Somewhere in the eighties I laid the finished set out together and the drift was obvious. The colour had shifted slightly, the margins were inconsistent, the subject size wandered. Each image passed on its own. Side by side they looked assembled by different people.&lt;/p&gt;

&lt;p&gt;It took a few days to work out where the drift comes from, and two of the three sources cannot be fixed by rewriting the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three sources
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;How it shows up&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompt ambiguity&lt;/td&gt;
&lt;td&gt;The same sentence produces different colour on two runs&lt;/td&gt;
&lt;td&gt;Words like warm or premium have no fixed value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random seed&lt;/td&gt;
&lt;td&gt;Identical input, different result&lt;/td&gt;
&lt;td&gt;The model is stochastic by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inconsistent references&lt;/td&gt;
&lt;td&gt;Different SKUs used differently styled reference images&lt;/td&gt;
&lt;td&gt;The inputs were never aligned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Longer prompts do not remove the first two. I tried expanding a prompt from thirty words to two hundred and the results got worse, because more constraints gave the model more room to trade one against another on axes I did not care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to control it
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Strength&lt;/th&gt;
&lt;th&gt;Scale it suits&lt;/th&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared prompt template&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Under twenty SKUs&lt;/td&gt;
&lt;td&gt;Constrains language only, randomness remains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fixed reference plus image-to-image&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Twenty to two hundred&lt;/td&gt;
&lt;td&gt;The reference itself has to be right first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brand kit&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;Needs tool support and an upfront definition&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A brand kit means pinning palette, typefaces, logo and layout rules into a reusable set of constraints that the tool applies during generation, instead of restating them in every prompt.&lt;/p&gt;

&lt;p&gt;The difference from the first two approaches is that it moves the constraint from a request into a configuration. A prompt asks the model to comply. A kit limits it inside the pipeline.&lt;/p&gt;

&lt;p&gt;For a checkable example, the TangyuanAI pricing page lists 5 brand kits on its entry tier (&lt;a href="https://tangyuanai.vip/en/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;, checked 6 August 2026). If you run several brands, that count becomes the practical constraint on which tier you need, because too few kits means the styles bleed into each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lock first, then scale
&lt;/h2&gt;

&lt;p&gt;I got the order wrong the first time and ran a hundred straight away. The rework was brutal. This is the order I use now.&lt;/p&gt;

&lt;p&gt;Start with three reference images. Pick the simplest SKU, the most complex one, and the most unusual one, and take those three to a standard you would ship. Those three define what passes.&lt;/p&gt;

&lt;p&gt;Then derive constraints from them. Fix the dominant colour value, the margin ratio, the subject-to-frame ratio, the text zone position, the light direction. Anything that can be written as a number should be a number rather than an adjective.&lt;/p&gt;

&lt;p&gt;Put those constraints into the tool's configuration, not into the prompt.&lt;/p&gt;

&lt;p&gt;Run ten as a pilot. Not a hundred. Ten is enough to expose drift and the rework costs a tenth as much.&lt;/p&gt;

&lt;p&gt;Lay those ten out side by side. Drift only shows up in comparison, never when you page through one at a time.&lt;/p&gt;

&lt;p&gt;Finally scale in batches sized to the concurrency cap. Entry tiers commonly allow two concurrent jobs. Running 500 images in batches rather than one long queue takes longer in wall-clock terms but costs far less when something needs rerunning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Judge with numbers, not with your eyes
&lt;/h2&gt;

&lt;p&gt;Looks about right is not a standard. Three things I now measure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;th&gt;What to watch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dominant colour drift&lt;/td&gt;
&lt;td&gt;Mean HSV of the subject region, standard deviation of the H channel&lt;/td&gt;
&lt;td&gt;Stability inside one batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subject ratio&lt;/td&gt;
&lt;td&gt;Bounding box area divided by frame area&lt;/td&gt;
&lt;td&gt;Under 10 percent variation within a batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Margin ratio&lt;/td&gt;
&lt;td&gt;Edge whitespace as a share of pixels&lt;/td&gt;
&lt;td&gt;Under 10 percent variation within a batch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The colour one is a dozen lines of Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;colorsys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dominant_hue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;crop_ratio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Average hue of the centre region, where the subject usually sits.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RGB&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
    &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;crop_ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;crop_ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getdata&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;colorsys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rgb_to_hsv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;

&lt;span class="n"&gt;hues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;dominant_hue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;batch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hue stddev &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pstdev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; degrees&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The batch where that standard deviation jumps is where drift started. Treating it as a stop-the-line signal is far cheaper than sorting a hundred finished images by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Three reference images before any batch&lt;/li&gt;
&lt;li&gt;Constraints written as numbers, not adjectives&lt;/li&gt;
&lt;li&gt;Constraints in the tool configuration, not the prompt&lt;/li&gt;
&lt;li&gt;Ten-image pilot, reviewed side by side, before scaling&lt;/li&gt;
&lt;li&gt;Brand kit count confirmed against the number of brands&lt;/li&gt;
&lt;li&gt;Batches sized to the concurrency cap&lt;/li&gt;
&lt;li&gt;Hue standard deviation tracked per batch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Capability and tier data come from the TangyuanAI pricing page and product image page, &lt;a href="https://tangyuanai.vip/en/pricing" rel="noopener noreferrer"&gt;https://tangyuanai.vip/en/pricing&lt;/a&gt; and &lt;a href="https://tangyuanai.vip/en/main-image" rel="noopener noreferrer"&gt;https://tangyuanai.vip/en/main-image&lt;/a&gt; , checked 6 August 2026. The code is generic image processing. Output depends on the uploaded material, the brief and the generation mode chosen; nothing here promises a result.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>I costed out AI product photography for a small store and found we were comparing the wrong number</title>
      <dc:creator>boyuan tuo</dc:creator>
      <pubDate>Sat, 08 Aug 2026 15:10:06 +0000</pubDate>
      <link>https://dev.to/boyuan_tuo_6f861761aeb29e/i-costed-out-ai-product-photography-for-a-small-store-and-found-we-were-comparing-the-wrong-number-kn</link>
      <guid>https://dev.to/boyuan_tuo_6f861761aeb29e/i-costed-out-ai-product-photography-for-a-small-store-and-found-we-were-comparing-the-wrong-number-kn</guid>
      <description>&lt;h1&gt;
  
  
  I costed out AI product photography for a small store and found we were comparing the wrong number
&lt;/h1&gt;

&lt;p&gt;I wanted a straight answer to a simple question. What does it cost per month to produce product images with AI for a store doing modest volume?&lt;/p&gt;

&lt;p&gt;Every pricing page I opened used a different unit. Some charge per image, some per credit, one charged per second for video. So I sat down and converted them all to the same number. The conversion turned out to be the easy part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert everything to cost per image first
&lt;/h2&gt;

&lt;p&gt;Here are the published model prices from TangyuanAI's pricing page, checked 6 August 2026 (&lt;a href="https://tangyuanai.vip/en/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;).&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;Price&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT Image 2&lt;/td&gt;
&lt;td&gt;$0.008&lt;/td&gt;
&lt;td&gt;per image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nano Banana 2&lt;/td&gt;
&lt;td&gt;$0.040&lt;/td&gt;
&lt;td&gt;per image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Seedance 2.0&lt;/td&gt;
&lt;td&gt;$0.040&lt;/td&gt;
&lt;td&gt;per second of video&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same page lists the Starter plan at $16 per month billed annually, with 2,000 credits per month, described as roughly 1,000 standard images.&lt;/p&gt;

&lt;p&gt;Divide it out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;16 / 1000 = about $0.016 per image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is twice the per-image price of GPT Image 2 and about 60 percent below Nano Banana 2. So whether a subscription beats pay-as-you-go depends entirely on which model you actually use and how much of the allowance you burn. Use only the cheap model and never fill the quota, and the monthly fee inflates your effective per-image cost. Use only the expensive one and you run out early.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number I had wrong
&lt;/h2&gt;

&lt;p&gt;My first estimate used the count of images I needed to ship. That produced a figure I liked.&lt;/p&gt;

&lt;p&gt;It was wrong because it ignored retries. A usable image usually sits on top of three to five generations. You adjust the angle, redo the lighting, change the crop, and each change means generating again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;real generations = images needed × retry factor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have to measure your own retry factor. Mine runs three to four for the categories I work in. A colleague doing room-scene composites told me his reaches eight.&lt;/p&gt;

&lt;p&gt;Estimating from finished images alone understates the bill by three to five times.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is easy to miss when comparing plans
&lt;/h2&gt;

&lt;p&gt;Per-image price is simple to compare. These are not, and they usually move the total more.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Easy to miss&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;th&gt;Where to look&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency cap&lt;/td&gt;
&lt;td&gt;Two concurrent jobs versus eight changes the wall-clock time for 500 images by a factor of four&lt;/td&gt;
&lt;td&gt;Usually on the pricing page. Starter tiers commonly allow 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whether credits expire&lt;/td&gt;
&lt;td&gt;Monthly expiry means unused allowance is a straight loss&lt;/td&gt;
&lt;td&gt;Search the terms for expire, rollover, carry over&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily refresh allowance&lt;/td&gt;
&lt;td&gt;Some tiers grant a small amount daily, which absorbs low-volume days&lt;/td&gt;
&lt;td&gt;Pricing page. The Starter tier lists 100 per day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commercial licence included&lt;/td&gt;
&lt;td&gt;If it is billed separately, add it to the total&lt;/td&gt;
&lt;td&gt;Pricing page or terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brand kit count&lt;/td&gt;
&lt;td&gt;Running several brands means the kit count decides your tier&lt;/td&gt;
&lt;td&gt;Pricing page. Starter includes 5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How I would choose
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;What I would pick&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Low volume, very uneven&lt;/td&gt;
&lt;td&gt;Pay-as-you-go API&lt;/td&gt;
&lt;td&gt;Quiet months cost nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steady volume, budget must be predictable&lt;/td&gt;
&lt;td&gt;Subscription with a credit pool&lt;/td&gt;
&lt;td&gt;The ceiling is fixed, which finance likes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High volume, existing pipeline and someone to run it&lt;/td&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;Lowest marginal cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Several brands to keep separate&lt;/td&gt;
&lt;td&gt;Check the brand kit count first&lt;/td&gt;
&lt;td&gt;Too few kits and the styles bleed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A short checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Estimate from real generation count, not finished images&lt;/li&gt;
&lt;li&gt;Split the estimate by model rather than using one blended price&lt;/li&gt;
&lt;li&gt;Check the concurrency cap, since it affects delivery time as well as cost&lt;/li&gt;
&lt;li&gt;Confirm whether credits expire at month end&lt;/li&gt;
&lt;li&gt;Add commercial licensing and brand kit count into the total&lt;/li&gt;
&lt;li&gt;Track the hours spent on manual rework, which is usually the largest line&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Prices come from the TangyuanAI pricing and credits page, &lt;a href="https://tangyuanai.vip/en/pricing" rel="noopener noreferrer"&gt;https://tangyuanai.vip/en/pricing&lt;/a&gt; , checked 6 August 2026. The costing method is my own. Prices change, so check the current values on each vendor's own page. Nothing here is an unpublished or estimated price.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ecommerce</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
