<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en_US"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://stefanwagner.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://stefanwagner.dev/" rel="alternate" type="text/html" hreflang="en_US" /><updated>2026-02-20T12:43:50+00:00</updated><id>https://stefanwagner.dev/feed.xml</id><title type="html">Stefan Wagner</title><subtitle>Senior Software Engineer with 20 years of experience building innovative solutions in robotics, AI/ML, and full-stack development. From building software for industrial robots to creating mobile games with millions of downloads, I bring technical expertise and proven leadership to every project.</subtitle><author><name>Stefan Wagner</name></author><entry><title type="html">Joining Wandelbots</title><link href="https://stefanwagner.dev/2021/02/15/wandelbots/" rel="alternate" type="text/html" title="Joining Wandelbots" /><published>2021-02-15T00:00:00+00:00</published><updated>2021-02-15T00:00:00+00:00</updated><id>https://stefanwagner.dev/2021/02/15/wandelbots</id><content type="html" xml:base="https://stefanwagner.dev/2021/02/15/wandelbots/"><![CDATA[<figure class="video-container video-landscape">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/robot_clip_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/robot_clip.webm" type="video/webm" />
    <source data-src="/assets/videos/robot_clip.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/robot_clip.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>I’m excited to join <a href="https://wandelbots.com">Wandelbots</a> as a Senior Software Engineer! We’re building no-code solutions that make industrial robotics accessible to everyone.</p>

<!--more-->

<p>Traditional robot programming is complex, especially when environments change frequently, making it extremely hard to account for every variation in code. Wandelbots is changing this by letting robots learn from human demonstrations and adapt to new situations through imitation learning.</p>

<p>I’m working across several areas. On the platform side, I’m building infrastructure that lets customers embed interactive 3D robot visualizations in their own applications using Three.js and React. This includes real-time robot simulation and path planning visualization. I’m also contributing to AI systems where robots learn from human demonstrations to adapt to changing environments that would otherwise be very difficult to program explicitly. Additionally, I’m developing specialized industrial solutions like robot welding applications with path planning and weld parameter optimization.</p>

<p>The tech stack spans TypeScript, React, Three.js, Python, WebGL, and ROS. It’s a great mix of full-stack development, 3D graphics, robotics control systems, and cutting-edge AI/ML work. Looking forward to helping manufacturers adopt robotics automation more easily!</p>

<p><a href="https://wandelbots.com">Learn more about Wandelbots</a></p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. I’m excited to join Wandelbots as a Senior Software Engineer! We’re building no-code solutions that make industrial robotics accessible to everyone.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SQIN - Google Play Best App of 2020</title><link href="https://stefanwagner.dev/2020/12/01/sqin-google/" rel="alternate" type="text/html" title="SQIN - Google Play Best App of 2020" /><published>2020-12-01T00:00:00+00:00</published><updated>2020-12-01T00:00:00+00:00</updated><id>https://stefanwagner.dev/2020/12/01/sqin-google</id><content type="html" xml:base="https://stefanwagner.dev/2020/12/01/sqin-google/"><![CDATA[<figure class="video-container video-landscape">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/sqin_google_best_app_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/sqin_google_best_app.webm" type="video/webm" />
    <source data-src="/assets/videos/sqin_google_best_app.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/sqin_google_best_app.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>Google Play recognized SQIN as one of the <strong>Best Apps of 2020</strong> in the #PersonalGrowth category! 🥇</p>

<!--more-->

<p>Out of millions of apps on Google Play, SQIN was selected for Google’s prestigious annual awards. It’s a beauty and skincare app that helps users make informed decisions about cosmetic products through ingredient analysis, personalized recommendations, and community reviews.</p>

<p>As a lead developer, I worked on performance optimization, technical architecture improvements, and implementing beauty and skincare analysis features. This included mobile app development with modern Android best practices and integration with computer vision and ML systems for product recognition and analysis.</p>

<p>The award was great validation of our focus on user experience and technical excellence in the personal care space.</p>

<p><a href="https://play.google.com/store/apps/editorial_collection/promotion_topic_bestof2020_xfn_hub">View Google Play Best of 2020 Collection</a></p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. Google Play recognized SQIN as one of the Best Apps of 2020 in the #PersonalGrowth category! 🥇]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SQIN Face Scanner</title><link href="https://stefanwagner.dev/2020/08/20/bequ-face-scanner/" rel="alternate" type="text/html" title="SQIN Face Scanner" /><published>2020-08-20T00:00:00+00:00</published><updated>2020-08-20T00:00:00+00:00</updated><id>https://stefanwagner.dev/2020/08/20/bequ-face-scanner</id><content type="html" xml:base="https://stefanwagner.dev/2020/08/20/bequ-face-scanner/"><![CDATA[<figure class="video-container video-portrait">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/sqin_face_shape_scanner_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/sqin_face_shape_scanner.webm" type="video/webm" />
    <source data-src="/assets/videos/sqin_face_shape_scanner.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/sqin_face_shape_scanner.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>Built a custom face analysis system for SQIN using TensorFlow Lite and Unity. The system does accurate facial feature recognition across devices without requiring ARKit or ARCore.</p>

<!--more-->

<p>I developed a Unity plugin that integrates TensorFlow Lite for real-time 3D face mesh extraction. It’s based on Google’s MediaPipe Face Mesh technology but optimized for mobile performance. The plugin works consistently across iOS, Android, and even the Unity Editor, with no dependency on platform-specific AR frameworks. We’re getting 30+ FPS on mid-range devices with 468 3D facial landmark points.</p>

<p>The implementation involved building a custom C++ to C# bridge for TensorFlow Lite integration and careful memory management to stay within mobile constraints. The face scanner analyzes facial features to provide personalized skincare and beauty product recommendations tailored to face shape, skin tone, and other features.</p>

<p>The main challenge was achieving reliable face mesh extraction without platform-specific AR frameworks. This made the feature accessible to a much broader range of devices while maintaining high performance and accuracy.</p>

<figure class="video-container video-landscape">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/sqin_ar_in_unity_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/sqin_ar_in_unity.webm" type="video/webm" />
    <source data-src="/assets/videos/sqin_ar_in_unity.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/sqin_ar_in_unity.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>Built with Unity, C#, C++, and TensorFlow Lite.</p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. Built a custom face analysis system for SQIN using TensorFlow Lite and Unity. The system does accurate facial feature recognition across devices without requiring ARKit or ARCore.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Visualization of the regional COVID-19 Development in Germany</title><link href="https://stefanwagner.dev/2020/04/26/covid/" rel="alternate" type="text/html" title="Visualization of the regional COVID-19 Development in Germany" /><published>2020-04-26T00:00:00+00:00</published><updated>2020-04-26T00:00:00+00:00</updated><id>https://stefanwagner.dev/2020/04/26/covid</id><content type="html" xml:base="https://stefanwagner.dev/2020/04/26/covid/"><![CDATA[<figure class="video-container video-portrait">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/last_7_days_maps_result_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/last_7_days_maps_result.webm" type="video/webm" />
    <source data-src="/assets/videos/last_7_days_maps_result.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/last_7_days_maps_result.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>The situation report from the RKI contains a figure which shows the development of the pandemic in Germany over the last 7 days. I wanted to know how this progresses over time and whether the outbreak clusters remain regional.</p>

<p>I used Pandas and Folium to recreate the figure and created this animation. See the <a href="https://github.com/bompo/covid19_rki_visualization">code on Github</a> to reproduce the result with current data.</p>

<!--more-->

<p>I used Pandas to preprocess the raw data from the RKI.</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">def</span> <span class="nf">new_cases_by_date</span><span class="p">(</span><span class="nx">rki_raw</span><span class="p">,</span> <span class="nx">rki_flag_column</span><span class="o">=</span><span class="dl">'</span><span class="s1">Neuer Fall</span><span class="dl">'</span><span class="p">,</span> <span class="nx">rki_count_columns</span><span class="o">=</span><span class="dl">'</span><span class="s1">AnzahlFall</span><span class="dl">'</span><span class="p">):</span>
    <span class="nx">condition</span> <span class="o">=</span> <span class="nx">rki_raw</span><span class="p">[</span><span class="nx">rki_flag_column</span><span class="p">].</span><span class="nf">isin</span><span class="p">((</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
    <span class="nx">rki_series</span> <span class="o">=</span> <span class="nx">rki_raw</span><span class="p">[</span><span class="nx">condition</span><span class="p">].</span><span class="nf">groupby</span><span class="p">([</span><span class="dl">'</span><span class="s1">IdLandkreis</span><span class="dl">'</span><span class="p">])[</span><span class="nx">rki_count_columns</span><span class="p">].</span><span class="nf">sum</span><span class="p">().</span><span class="nf">to_frame</span><span class="p">(</span><span class="nx">name</span> <span class="o">=</span> <span class="nx">rki_count_columns</span><span class="p">).</span><span class="nf">reset_index</span><span class="p">()</span>
    
    <span class="nx">rki_series</span> <span class="o">=</span> <span class="nx">rki_series</span><span class="p">[[</span><span class="dl">'</span><span class="s1">IdLandkreis</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">AnzahlFall</span><span class="dl">'</span><span class="p">]]</span>

    <span class="err">#</span> <span class="nx">join</span> <span class="nx">geodata</span>
    <span class="nx">rki_series</span><span class="p">[</span><span class="dl">'</span><span class="s1">IdLandkreis</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="nx">rki_series</span><span class="p">[</span><span class="dl">'</span><span class="s1">IdLandkreis</span><span class="dl">'</span><span class="p">].</span><span class="nf">astype</span><span class="p">(</span><span class="nx">str</span><span class="p">).</span><span class="nx">str</span><span class="p">.</span><span class="nf">zfill</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
    <span class="nx">rki_series</span> <span class="o">=</span> <span class="nx">rki_series</span><span class="p">.</span><span class="nf">set_index</span><span class="p">(</span><span class="dl">'</span><span class="s1">IdLandkreis</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">rki_series</span> <span class="o">=</span> <span class="nx">rki_series</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="nx">rki_geo</span><span class="p">)</span>

    <span class="k">return</span> <span class="nx">rki_series</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">start_date</span> <span class="o">=</span> <span class="nx">pd</span><span class="p">.</span><span class="nf">to_datetime</span><span class="p">(</span><span class="dl">'</span><span class="s1">2020-03-01</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">end_date</span> <span class="o">=</span> <span class="nx">rki_raw</span><span class="p">.</span><span class="nx">index</span><span class="p">.</span><span class="nf">max</span><span class="p">().</span><span class="nf">tz_convert</span><span class="p">(</span><span class="nx">None</span><span class="p">)</span>

<span class="nx">frames</span> <span class="o">=</span> <span class="p">[]</span>

<span class="k">for</span> <span class="nx">j</span> <span class="k">in</span> <span class="nx">pd</span><span class="p">.</span><span class="nf">date_range</span><span class="p">(</span><span class="nx">start_date</span><span class="p">,</span> <span class="nx">end_date</span> <span class="o">-</span> <span class="nx">pd</span><span class="p">.</span><span class="nf">to_timedelta</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="dl">'</span><span class="s1">d</span><span class="dl">'</span><span class="p">)):</span>
    <span class="nx">startrange</span> <span class="o">=</span> <span class="nx">j</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="dl">'</span><span class="s1">%Y-%m-%d</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">endrange</span> <span class="o">=</span> <span class="p">(</span><span class="nx">j</span> <span class="o">+</span> <span class="nx">pd</span><span class="p">.</span><span class="nf">to_timedelta</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="dl">'</span><span class="s1">d</span><span class="dl">'</span><span class="p">)).</span><span class="nf">strftime</span><span class="p">(</span><span class="dl">'</span><span class="s1">%Y-%m-%d</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">rki_cases</span> <span class="o">=</span> <span class="nf">new_cases_by_date</span><span class="p">(</span><span class="nx">rki_raw</span><span class="p">[</span><span class="nx">startrange</span><span class="p">:</span><span class="nx">endrange</span><span class="p">],</span> <span class="nx">rki_flag_column</span><span class="o">=</span><span class="dl">'</span><span class="s1">NeuerFall</span><span class="dl">'</span><span class="p">,</span> <span class="nx">rki_count_columns</span><span class="o">=</span><span class="dl">'</span><span class="s1">AnzahlFall</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">rki_cases</span><span class="p">[</span><span class="dl">'</span><span class="s1">date</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">j</span> <span class="o">+</span> <span class="nx">pd</span><span class="p">.</span><span class="nf">to_timedelta</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="dl">'</span><span class="s1">d</span><span class="dl">'</span><span class="p">))</span>
    <span class="nx">rki_cases</span><span class="p">[</span><span class="dl">'</span><span class="s1">RS</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="nx">rki_cases</span><span class="p">.</span><span class="nx">index</span>
    <span class="nx">rki_cases</span><span class="p">[</span><span class="dl">'</span><span class="s1">cases_per_100k</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="nx">rki_cases</span><span class="p">[</span><span class="dl">'</span><span class="s1">AnzahlFall</span><span class="dl">'</span><span class="p">]</span> <span class="o">/</span> <span class="nx">rki_cases</span><span class="p">[</span><span class="dl">'</span><span class="s1">EWZ</span><span class="dl">'</span><span class="p">]</span> <span class="o">*</span> <span class="mi">100000</span>

    <span class="nx">rki_cases</span> <span class="o">=</span> <span class="nx">rki_cases</span><span class="p">.</span><span class="nf">set_index</span><span class="p">(</span><span class="dl">'</span><span class="s1">date</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">frames</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nx">rki_cases</span><span class="p">)</span>

<span class="nx">result</span> <span class="o">=</span> <span class="nx">pd</span><span class="p">.</span><span class="nf">concat</span><span class="p">(</span><span class="nx">frames</span><span class="p">)</span></code></pre></figure>

<p>The map is generated with the choropleth function from Folium. Selenium is used to store the maps as png sequence.</p>

<figure class="highlight"><pre><code class="language-js" data-lang="js"><span class="nx">bins</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">500</span><span class="p">]</span>
<span class="nx">map_osm</span> <span class="o">=</span> <span class="nx">folium</span><span class="p">.</span><span class="nc">Map</span><span class="p">(</span><span class="nx">attr</span><span class="o">=</span><span class="dl">"</span><span class="s2">Robert Koch-Institut (RKI), dl-de/by-2-0</span><span class="dl">"</span><span class="p">,</span> <span class="nx">location</span><span class="o">=</span><span class="p">[</span><span class="mf">51.3</span><span class="p">,</span> <span class="mf">10.5</span><span class="p">],</span> <span class="nx">tiles</span><span class="o">=</span><span class="dl">'</span><span class="s1">cartodbpositron</span><span class="dl">'</span><span class="p">,</span> <span class="nx">zoom_start</span><span class="o">=</span><span class="mi">7</span><span class="p">)</span>

<span class="nx">#result</span><span class="p">.</span><span class="nf">apply</span><span class="p">(</span><span class="nx">lambda</span> <span class="nx">row</span><span class="p">:</span><span class="nx">folium</span><span class="p">.</span><span class="nc">GeoJson</span><span class="p">(</span><span class="nx">row</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="nx">fill_color</span><span class="o">=</span><span class="nf">colorscale</span><span class="p">(</span><span class="nx">row</span><span class="p">[</span><span class="mi">0</span><span class="p">])).</span><span class="nf">add_to</span><span class="p">(</span><span class="nx">map_osm</span><span class="p">),</span> <span class="nx">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="nx">folium</span><span class="p">.</span><span class="nc">Choropleth</span><span class="p">(</span>
    <span class="nx">geo_data</span><span class="o">=</span><span class="nx">rki_geo_raw</span><span class="p">,</span>
    <span class="nx">data</span><span class="o">=</span><span class="nx">result</span><span class="p">[</span><span class="dl">'</span><span class="s1">03-10-2020</span><span class="dl">'</span><span class="p">:</span><span class="dl">'</span><span class="s1">03-10-2020</span><span class="dl">'</span><span class="p">],</span>
    <span class="nx">columns</span><span class="o">=</span><span class="p">[</span><span class="dl">'</span><span class="s1">RS</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">cases_per_100k</span><span class="dl">'</span><span class="p">],</span>
    <span class="nx">key_on</span><span class="o">=</span><span class="dl">'</span><span class="s1">feature.properties.RS</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">fill_color</span><span class="o">=</span><span class="dl">'</span><span class="s1">YlOrRd</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">fill_opacity</span><span class="o">=</span><span class="mf">0.6</span><span class="p">,</span>
    <span class="nx">line_opacity</span><span class="o">=</span><span class="mf">0.0</span><span class="p">,</span>
    <span class="nx">nan_fill_color</span><span class="o">=</span><span class="dl">'</span><span class="s1">#f5f5f3</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">legend_name</span><span class="o">=</span><span class="dl">'</span><span class="s1">cases per 100k</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">bins</span><span class="o">=</span><span class="p">[</span><span class="nf">float</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span> <span class="k">for</span> <span class="nx">x</span> <span class="k">in</span> <span class="nx">bins</span><span class="p">],</span>
    <span class="nx">smooth_factor</span> <span class="o">=</span> <span class="mf">0.1</span>
<span class="p">).</span><span class="nf">add_to</span><span class="p">(</span><span class="nx">map_osm</span><span class="p">)</span></code></pre></figure>

<p>The final animation is created with FFMPEG and this <a href="https://gist.github.com/anguyen8/d0630b6aef6c1cd79b9a1341e88a573e">Gist</a></p>

<p><a href="https://github.com/bompo/covid19_rki_visualization">Github</a></p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. The situation report from the RKI contains a figure which shows the development of the pandemic in Germany over the last 7 days. I wanted to know how this progresses over time and whether the outbreak clusters remain regional. I used Pandas and Folium to recreate the figure and created this animation. See the code on Github to reproduce the result with current data.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BeQu Product Scanner</title><link href="https://stefanwagner.dev/2020/04/10/bequ-scanner/" rel="alternate" type="text/html" title="BeQu Product Scanner" /><published>2020-04-10T00:00:00+00:00</published><updated>2020-04-10T00:00:00+00:00</updated><id>https://stefanwagner.dev/2020/04/10/bequ-scanner</id><content type="html" xml:base="https://stefanwagner.dev/2020/04/10/bequ-scanner/"><![CDATA[<figure class="video-container video-portrait">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/bequ_scanner_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/bequ_scanner.webm" type="video/webm" />
    <source data-src="/assets/videos/bequ_scanner.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/bequ_scanner.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>We developed a product recognition system using machine learning that identifies cosmetic products without requiring barcodes. Many beauty products either don’t have visible barcodes or they’re hidden inside packaging, so this was a real game-changer for the user experience.</p>

<!--more-->

<p>The system uses a custom-trained convolutional neural network that can recognize products in real-time on mobile devices. It works even with partial product views and various lighting conditions, handling thousands of SKUs with high accuracy. The architecture is optimized for on-device inference, so it’s fast and works offline once the model is downloaded.</p>

<p>We integrated it with a product database and inventory system, and added a gamification layer where users earn stars for scanning products. They can exchange these stars for vouchers and products, which significantly improved engagement.</p>

<p>The technical implementation involved mobile-first architecture, integration with backend systems for the product catalog, and careful optimization to make the inference run smoothly on mid-range devices. We built it with TensorFlow/PyTorch for the ML components, standard mobile development frameworks for Android/iOS, and REST APIs for the backend communication.</p>

<div class="youtube-container" data-video-id="wWG9vu5i4ts">
  
  <picture>
    <source srcset="/assets/images/bequ_scanner.webp" type="image/webp" />
    <img class="youtube-thumb" src="/assets/images/bequ_scanner.jpg" alt="Video thumbnail for wWG9vu5i4ts" loading="lazy" width="1280" height="720" decoding="async" />
  </picture>
  
  <button class="youtube-play-btn" aria-label="Play video">
    <svg viewBox="0 0 68 48" width="68" height="48">
      <path d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#f00" />
      <path d="M 45,24 27,14 27,34" fill="#fff" />
    </svg>
  </button>
</div>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. We developed a product recognition system using machine learning that identifies cosmetic products without requiring barcodes. Many beauty products either don’t have visible barcodes or they’re hidden inside packaging, so this was a real game-changer for the user experience.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BeQu at Glow 2019</title><link href="https://stefanwagner.dev/2019/10/10/bequ-at-glow/" rel="alternate" type="text/html" title="BeQu at Glow 2019" /><published>2019-10-10T00:00:00+00:00</published><updated>2019-10-10T00:00:00+00:00</updated><id>https://stefanwagner.dev/2019/10/10/bequ-at-glow</id><content type="html" xml:base="https://stefanwagner.dev/2019/10/10/bequ-at-glow/"><![CDATA[<div class="youtube-container" data-video-id="JMhP_zKbFMo">
  
  <picture>
    <source srcset="/assets/images/bequ_cover.webp" type="image/webp" />
    <img class="youtube-thumb" src="/assets/images/bequ_cover.jpg" alt="Video thumbnail for JMhP_zKbFMo" loading="lazy" width="1280" height="720" decoding="async" />
  </picture>
  
  <button class="youtube-play-btn" aria-label="Play video">
    <svg viewBox="0 0 68 48" width="68" height="48">
      <path d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#f00" />
      <path d="M 45,24 27,14 27,34" fill="#fff" />
    </svg>
  </button>
</div>

<p>We showcased our new app BeQu at Glow 2019. For two days users could try out exclusive content and win products from our partner brands!</p>

<p>In our app, we link the right users with the products that interest them in the form of mobile games, user surveys and state-of-the-art augmented reality.
BeQu turns simple shopping into an emotional, shareable experience that takes the pulse of the hard-to-reach millennials up to the Alpha generation.</p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[We showcased our new app BeQu at Glow 2019. For two days users could try out exclusive content and win products from our partner brands! In our app, we link the right users with the products that interest them in the form of mobile games, user surveys and state-of-the-art augmented reality. BeQu turns simple shopping into an emotional, shareable experience that takes the pulse of the hard-to-reach millennials up to the Alpha generation.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">BeautyGAN</title><link href="https://stefanwagner.dev/2019/08/03/beautygan/" rel="alternate" type="text/html" title="BeautyGAN" /><published>2019-08-03T00:00:00+00:00</published><updated>2019-08-03T00:00:00+00:00</updated><id>https://stefanwagner.dev/2019/08/03/beautygan</id><content type="html" xml:base="https://stefanwagner.dev/2019/08/03/beautygan/"><![CDATA[<p><img src="/assets/images/beautygan.jpg" alt="placeholder" /></p>

<p>Explored Make-Up Style Transfer with <a href="https://github.com/Honlan/BeautyGAN">BeautyGAN</a>.</p>

<p>The network is trained with make-up and non-make-up pictures. It’s possible to apply different make-up styles (eg. from Influencers from Instagram) to your image. See the picture for examples.</p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Explored Make-Up Style Transfer with BeautyGAN. The network is trained with make-up and non-make-up pictures. It’s possible to apply different make-up styles (eg. from Influencers from Instagram) to your image. See the picture for examples.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Coffee Barista</title><link href="https://stefanwagner.dev/2019/07/01/coffee/" rel="alternate" type="text/html" title="Coffee Barista" /><published>2019-07-01T00:00:00+00:00</published><updated>2019-07-01T00:00:00+00:00</updated><id>https://stefanwagner.dev/2019/07/01/coffee</id><content type="html" xml:base="https://stefanwagner.dev/2019/07/01/coffee/"><![CDATA[<figure class="video-container video-landscape">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/coffee_barista_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/coffee_barista.webm" type="video/webm" />
    <source data-src="/assets/videos/coffee_barista.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/coffee_barista.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>An ASMR game prototype this time together with @pavethem. The goal of the game is to draw latte art and satisfy your customers. We used fluid dynamics (base from <a href="https://github.com/keijiro/StableFluids">stable fluids</a>) on the GPU to simulate the foam.</p>

<!--more-->

<figure class="video-container video-landscape">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/coffee_proto_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/coffee_proto.webm" type="video/webm" />
    <source data-src="/assets/videos/coffee_proto.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/coffee_proto.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. An ASMR game prototype this time together with @pavethem. The goal of the game is to draw latte art and satisfy your customers. We used fluid dynamics (base from stable fluids) on the GPU to simulate the foam.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Pix2Pix ML</title><link href="https://stefanwagner.dev/2019/04/14/pix2pix/" rel="alternate" type="text/html" title="Pix2Pix ML" /><published>2019-04-14T00:00:00+00:00</published><updated>2019-04-14T00:00:00+00:00</updated><id>https://stefanwagner.dev/2019/04/14/pix2pix</id><content type="html" xml:base="https://stefanwagner.dev/2019/04/14/pix2pix/"><![CDATA[<figure class="video-container video-landscape">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/pix2pix_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/pix2pix.webm" type="video/webm" />
    <source data-src="/assets/videos/pix2pix.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/pix2pix.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>Different kind of training for CR7 to use him as a virtual avatar with Pix2Pix (using the code of <a href="https://github.com/datitran/face2face-demo">datitran</a>).</p>

<p>I generated the dataset from <a href="https://www.youtube.com/watch?v=V_vjWX8tawQ">this video</a>. The Network is trained with OpenCV landmark images from a source video. The same landmark detection is applied to the webcam output.</p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. Different kind of training for CR7 to use him as a virtual avatar with Pix2Pix (using the code of datitran). I generated the dataset from this video. The Network is trained with OpenCV landmark images from a source video. The same landmark detection is applied to the webcam output.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Visual Importance ML</title><link href="https://stefanwagner.dev/2019/02/26/visual-importance/" rel="alternate" type="text/html" title="Visual Importance ML" /><published>2019-02-26T00:00:00+00:00</published><updated>2019-02-26T00:00:00+00:00</updated><id>https://stefanwagner.dev/2019/02/26/visual-importance</id><content type="html" xml:base="https://stefanwagner.dev/2019/02/26/visual-importance/"><![CDATA[<figure class="video-container video-landscape">
  <video class="lazy-video" controls="" loop="" data-autoplay="" muted="" playsinline="" preload="none" poster="/assets/videos/instagram_visual_importance_poster.jpg" aria-label="Video content">
    <source data-src="/assets/videos/instagram_visual_importance.webm" type="video/webm" />
    <source data-src="/assets/videos/instagram_visual_importance.mp4" type="video/mp4" />
    <p>
      Your browser doesn't support HTML5 video.
      <a href="/assets/videos/instagram_visual_importance.mp4">Download the video</a>
      instead.
    </p>
  </video>
  
</figure>

<p>Experimenting with ML-driven design analysis using <a href="https://github.com/cvzoya/visimportance">visual importance networks</a> trained on eye-tracking data to predict where users focus attention in UI designs.</p>

<!--more-->

<p>Visual importance is a machine learning approach trained on real eye-tracking heatmaps from UI studies. The network predicts which areas of a design naturally draw user attention - red areas show high interest, blue shows low interest.</p>

<p>I applied the model to Instagram’s Oscar feed and analyzed designs from our Quiz Friends app to validate UI flow. The model effectively highlights high-contrast areas and visual hierarchy, but it primarily learned to detect contrast rather than semantic importance or reading flow patterns. It’s useful for quick design iteration and identifying potential attention conflicts, but needs to be combined with user flow analysis, A/B testing, and traditional usability testing for comprehensive UX analysis.</p>

<p>I integrated it with our design pipeline using Python and TensorFlow/PyTorch to generate automated heatmaps for rapid design feedback. It’s a nice example of how ML can augment traditional UX research methods during the design iteration phase, even if it can’t replace them.</p>

<p><img src="/assets/images/heatmap_quizfriends.jpg" alt="placeholder" /></p>]]></content><author><name>Stefan Wagner</name></author><summary type="html"><![CDATA[Your browser doesn't support HTML5 video. Download the video instead. Experimenting with ML-driven design analysis using visual importance networks trained on eye-tracking data to predict where users focus attention in UI designs.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://stefanwagner.dev/assets/images/social_preview.jpg" /><media:content medium="image" url="https://stefanwagner.dev/assets/images/social_preview.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>