<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Computer-Vision on Mulham Fetna</title>
    <link>https://mulhamfetna.com/tags/computer-vision/</link>
    <description>Recent content in Computer-Vision on Mulham Fetna</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <managingEditor>contact@mulhamfetna.com (Mulham Fetna)</managingEditor>
    <webMaster>contact@mulhamfetna.com (Mulham Fetna)</webMaster>
    <copyright>© 2026 Mulham Fetna</copyright>
    <lastBuildDate>Wed, 16 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mulhamfetna.com/tags/computer-vision/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Calibrating hand tracking to your own hand — with real data from a live session</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/calibration/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/calibration/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The shipped thresholds were measured on one hand with one webcam. Here is how to measure yours — and&#xA;what a live recording revealed about how far off &amp;ldquo;good enough&amp;rdquo; can be while the twin still looks&#xA;perfect.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Symptoms that call for calibration&#xA;    &lt;div id=&#34;symptoms-that-call-for-calibration&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#symptoms-that-call-for-calibration&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;What you see in the published flexions&lt;/th&gt;&#xA;          &lt;th&gt;Cause&lt;/th&gt;&#xA;          &lt;th&gt;Change&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;A fist, but flexion stays below 1.0&lt;/td&gt;&#xA;          &lt;td&gt;your fist angle is &lt;strong&gt;above&lt;/strong&gt; the curled limit&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;raise&lt;/strong&gt; &lt;code&gt;*_CURLED_ANGLE&lt;/code&gt; to your fist reading&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Flexion hits 1.0 with the hand half closed&lt;/td&gt;&#xA;          &lt;td&gt;curled limit too high&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;lower&lt;/strong&gt; &lt;code&gt;*_CURLED_ANGLE&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Flexion above 0 with a relaxed open hand&lt;/td&gt;&#xA;          &lt;td&gt;your open angle is &lt;strong&gt;below&lt;/strong&gt; the straight limit&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;lower&lt;/strong&gt; &lt;code&gt;*_STRAIGHT_ANGLE&lt;/code&gt; to your open reading&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;The thumb flickers&lt;/td&gt;&#xA;          &lt;td&gt;thumb window too narrow for your jitter&lt;/td&gt;&#xA;          &lt;td&gt;widen it (&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/&#34; &gt;Part 8&lt;/a&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;  &#xA;  &#xA;  &#xA;  &#xA;&#xA;&#xA;&#xA;&lt;div&#xA;  &#xA;    class=&#34;flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900&#34;&#xA;  &#xA;  &gt;&#xA;  &lt;span&#xA;    &#xA;      class=&#34;text-primary-400 pe-3 flex items-center&#34;&#xA;    &#xA;    &gt;&#xA;    &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; viewBox=&#34;0 0 384 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z&#34;/&gt;&lt;/svg&gt;&#xA;&lt;/span&gt;&#xA;  &lt;/span&gt;&#xA;&#xA;  &lt;span&#xA;    &#xA;      class=&#34;dark:text-neutral-300&#34;&#xA;    &#xA;    &gt;&lt;strong&gt;Calibrate against the numbers, not the render.&lt;/strong&gt; With the current force-control tuning, the&#xA;simulated finger snaps shut past flexion ≈ 0.51 (&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/tendon-physics-switch/&#34; &gt;Part 11&lt;/a&gt;),&#xA;so most calibration errors are invisible in the viewer. Watch &lt;code&gt;ros2 topic echo /hand/target_flexions&lt;/code&gt;.&lt;/span&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Real data: what a live session produced&#xA;    &lt;div id=&#34;real-data-what-a-live-session-produced&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#real-data-what-a-live-session-produced&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;MuJoCo&amp;rsquo;s viewer has a &lt;strong&gt;Control&lt;/strong&gt; panel showing the live force of every motor. Since the force is&#xA;\(F = 50 - 100 \cdot \text{flexion}\), every recorded frame gives back the exact flexion the tracker&#xA;published: &lt;strong&gt;flexion = (50 − F) / 100&lt;/strong&gt;.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/calibration/cover.png" />
    </item>
    
    <item>
      <title>Finger joint angles from three hand landmarks: the dot-product geometry</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Every knuckle angle in this project comes from three landmarks and one dot product. No learning,&#xA;no lookup table — just the definition of the angle between two vectors, plus two guards that keep a&#xA;single glitchy frame from sending NaN into a motor command.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Three points make an angle&#xA;    &lt;div id=&#34;three-points-make-an-angle&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#three-points-make-an-angle&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;An angle needs a vertex and two rays. A knuckle is the vertex; the two bones meeting there are the rays:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;a &lt;strong&gt;base point&lt;/strong&gt;, where the previous bone starts,&lt;/li&gt;&#xA;&lt;li&gt;the &lt;strong&gt;vertex&lt;/strong&gt; — the knuckle being measured,&lt;/li&gt;&#xA;&lt;li&gt;an &lt;strong&gt;end point&lt;/strong&gt;, where the next bone ends.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    P1((&#34;p1&lt;br&gt;base&#34;)) -- &#34;v1 = p1 − p2&#34; --- P2((&#34;p2&lt;br&gt;vertex&lt;br&gt;knuckle&#34;))&#xA;    P2 -- &#34;v2 = p3 − p2&#34; --- P3((&#34;p3&lt;br&gt;end&#34;))&#xA;&lt;/pre&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The triplet table&#xA;    &lt;div id=&#34;the-triplet-table&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-triplet-table&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger_triplets&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;thumb&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;index&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;middle&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;9&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;9&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;ring&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;   &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;pinky&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;19&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;19&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;MediaPipe landmark indices used to build the triplets&#34;&#xA;    width=&#34;2146&#34;&#xA;    height=&#34;744&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_788faf393ce06561.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_788faf393ce06561.png 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_8e6e6baf8bb39074.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks.png&#34;&gt;&lt;figcaption&gt;Landmark 0 — the wrist — starts every finger&amp;rsquo;s first triplet.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Finger&lt;/th&gt;&#xA;          &lt;th&gt;Triplet&lt;/th&gt;&#xA;          &lt;th&gt;Vertex&lt;/th&gt;&#xA;          &lt;th&gt;Joint measured&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(0, 5, 6)&lt;/td&gt;&#xA;          &lt;td&gt;5&lt;/td&gt;&#xA;          &lt;td&gt;MCP — joins finger to palm&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(5, 6, 7)&lt;/td&gt;&#xA;          &lt;td&gt;6&lt;/td&gt;&#xA;          &lt;td&gt;PIP — middle knuckle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(6, 7, 8)&lt;/td&gt;&#xA;          &lt;td&gt;7&lt;/td&gt;&#xA;          &lt;td&gt;DIP — fingertip knuckle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(0, 1, 2)&lt;/td&gt;&#xA;          &lt;td&gt;1&lt;/td&gt;&#xA;          &lt;td&gt;CMC — saddle joint at the wrist&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(1, 2, 3)&lt;/td&gt;&#xA;          &lt;td&gt;2&lt;/td&gt;&#xA;          &lt;td&gt;MCP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(2, 3, 4)&lt;/td&gt;&#xA;          &lt;td&gt;3&lt;/td&gt;&#xA;          &lt;td&gt;IP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;strong&gt;Why every first triplet starts at 0.&lt;/strong&gt; The palm has no landmark of its own, so the wrist → knuckle&#xA;line stands in for the metacarpal bone. It isn&amp;rsquo;t exactly collinear with a straight finger — ring and&#xA;pinky metacarpals fan outward — so a relaxed straight finger rarely measures a full \(\pi\). That&amp;rsquo;s&#xA;part of why the calibrated &amp;ldquo;straight&amp;rdquo; threshold is 3.10 rad rather than 3.14.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/cover.png" />
    </item>
    
    <item>
      <title>How a webcam moves a simulated tendon-driven hand</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  One vision container turns webcam frames into five numbers. One simulation container turns those&#xA;numbers into tendon forces. Everything else in this series is detail inside one of those two boxes&#xA;— or the pipe between them.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Peace sign, live: camera with MediaPipe landmarks, the twin with ring and pinky curled, and the motor forces −21 N on ring and pinky&#34;&#xA;    width=&#34;1570&#34;&#xA;    height=&#34;480&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign_hu_10d6cdce7106932.jpg&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign_hu_10d6cdce7106932.jpg 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign_hu_c82c14c5f3244f84.jpg 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign.jpg&#34;&gt;&lt;figcaption&gt;Left to right, three layers in one frame: vision (landmarks), simulation (the twin), actuation (live motor forces from MuJoCo&amp;rsquo;s Control panel).&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;End to end&#xA;    &lt;div id=&#34;end-to-end&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#end-to-end&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    subgraph VISION[&#34;🐳 vision_tracker container&#34;]&#xA;        direction TB&#xA;        A[&#34;Webcam frame&lt;br&gt;640×480 BGR&#34;] --&gt; B[&#34;MediaPipe Hands&lt;br&gt;21 landmarks&#34;]&#xA;        B --&gt; C[&#34;3 knuckle angles / finger&lt;br&gt;dot product&#34;]&#xA;        C --&gt; D[&#34;mean → 1 curl angle&lt;br&gt;(underactuation)&#34;]&#xA;        D --&gt; E[&#34;normalize + clip&lt;br&gt;flexion 0..1&#34;]&#xA;    end&#xA;    subgraph TWIN[&#34;🐳 mujoco_twin container&#34;]&#xA;        direction TB&#xA;        F[&#34;lerp&lt;br&gt;+50 N … −50 N&#34;] --&gt; G[&#34;data.ctrl on&lt;br&gt;pull_{finger} motor&#34;]&#xA;        G --&gt; H[&#34;spatial tendon&lt;br&gt;through 6 sites&#34;]&#xA;        H --&gt; I[&#34;3 passive hinge joints&lt;br&gt;curl&#34;]&#xA;        I --&gt; J[&#34;MuJoCo viewer&#34;]&#xA;    end&#xA;    E -- &#34;ROS 2 · /hand/target_flexions&lt;br&gt;sensor_msgs/JointState&#34; --&gt; F&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Stage&lt;/th&gt;&#xA;          &lt;th&gt;What comes out&lt;/th&gt;&#xA;          &lt;th&gt;Deep dive&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;MediaPipe Hands&lt;/td&gt;&#xA;          &lt;td&gt;21 (x, y, z) landmarks per frame&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/&#34; &gt;Part 4&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Triplet angles&lt;/td&gt;&#xA;          &lt;td&gt;3 interior angles per finger, in radians&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/&#34; &gt;Part 5&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Averaging&lt;/td&gt;&#xA;          &lt;td&gt;1 curl angle per finger&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/&#34; &gt;Part 6&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Normalization&lt;/td&gt;&#xA;          &lt;td&gt;flexion 0..1 (the thumb has its own window)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/&#34; &gt;Parts 7&lt;/a&gt;–&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/&#34; &gt;8&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;ROS 2 topic&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;JointState&lt;/code&gt;: names are fingers, positions are flexions&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/&#34; &gt;Part 15&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Lerp&lt;/td&gt;&#xA;          &lt;td&gt;force in newtons per tendon&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/&#34; &gt;Part 10&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Tendon physics&lt;/td&gt;&#xA;          &lt;td&gt;joint angles&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/tendon-physics-switch/&#34; &gt;Part 11&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Why the pipe carries flexions, not angles or forces&#xA;    &lt;div id=&#34;why-the-pipe-carries-flexions-not-angles-or-forces&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#why-the-pipe-carries-flexions-not-angles-or-forces&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The contract between the containers is five unitless numbers: 0.0 is an open finger, 1.0 is a closed&#xA;one. That choice is the architecture.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/cover.png" />
    </item>
    
    <item>
      <title>MediaPipe Hands for robotics — and the 16° angle trap in normalized landmarks</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Twenty-one points per frame, on a laptop CPU, from a flat RGB image. That is what MediaPipe hands&#xA;to a robot. It is easy to treat as a black box — until you compute angles from it and discover the&#xA;box stretched your coordinate space.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Where MediaPipe sits&#xA;    &lt;div id=&#34;where-mediapipe-sits&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#where-mediapipe-sits&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart TB&#xA;    CAM[&#34;USB webcam&lt;br&gt;640×480 @ 30 fps · YUYV&#34;] --&gt;|&#34;BGR frame&#34;| RGB[&#34;cv2.cvtColor&lt;br&gt;BGR → RGB&#34;]&#xA;    RGB --&gt; MP[&#34;MediaPipe Hands&lt;br&gt;palm detector + landmark model&#34;]&#xA;    MP --&gt;|&#34;21 × (x, y, z)&#34;| ANG[&#34;Triplet angles&lt;br&gt;Part 5&#34;]&#xA;    ANG --&gt; AVG[&#34;Average per finger&lt;br&gt;Part 6&#34;]&#xA;    AVG --&gt; NORM[&#34;Flexion 0..1&lt;br&gt;Parts 7–8&#34;]&#xA;    NORM --&gt;|&#34;/hand/target_flexions&#34;| TWIN[&#34;MuJoCo twin&#34;]&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The vision layer knows nothing about MuJoCo. Its entire output is &lt;strong&gt;five numbers between 0.0 (open)&#xA;and 1.0 (closed)&lt;/strong&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Two networks, not one&#xA;    &lt;div id=&#34;two-networks-not-one&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#two-networks-not-one&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;MediaPipe Hands is a &lt;strong&gt;cascade&lt;/strong&gt;:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;BlazePalm&lt;/strong&gt;, a single-shot detector, finds a &lt;em&gt;palm&lt;/em&gt; bounding box in the full frame. Palms, not&#xA;hands: a palm is close to a rigid square; a hand with moving fingers is not.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;A landmark model&lt;/strong&gt; crops that region and regresses 21 keypoints, a hand-presence score and&#xA;handedness.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;In video mode (&lt;code&gt;static_image_mode=False&lt;/code&gt;) the detector barely runs. Landmarks from frame &lt;em&gt;t&lt;/em&gt; define&#xA;the crop for frame &lt;em&gt;t+1&lt;/em&gt;, and the detector wakes only when tracking confidence drops. That shortcut&#xA;is why this pipeline holds 30 fps on a CPU.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/cover.png" />
    </item>
    
    <item>
      <title>Normalizing finger curl: from radians to a 0–1 flexion</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Radians belong to the camera. Newtons belong to the robot. The number that crosses between them is a&#xA;plain percentage — and producing it takes one inverted formula and one clip.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The formula&#xA;    &lt;div id=&#34;the-formula&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-formula&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RAW_STRAIGHT_ANGLE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;3.10&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# ~177.6° — open finger&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RAW_CURLED_ANGLE&lt;/span&gt;   &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1.60&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# ~91.7° — fully curled finger&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flexion&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;straight_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;avg_angle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;straight_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;curled_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flexions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;clip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flexion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;0.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;$$\text{flexion} = \operatorname{clip}\!\left(\frac{\theta_\text{straight} - \bar\theta}{\theta_\text{straight} - \theta_\text{curled}},\; 0,\; 1\right)$$&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-0927e0044df1c84967fec69d742e1c22&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-0927e0044df1c84967fec69d742e1c22&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;line&#39;,&#xA;data: {&#xA;  labels: [&#39;3.30&#39;,&#39;3.20&#39;,&#39;3.10&#39;,&#39;3.00&#39;,&#39;2.90&#39;,&#39;2.80&#39;,&#39;2.70&#39;,&#39;2.60&#39;,&#39;2.50&#39;,&#39;2.40&#39;,&#39;2.30&#39;,&#39;2.20&#39;,&#39;2.10&#39;,&#39;2.00&#39;,&#39;1.90&#39;,&#39;1.80&#39;,&#39;1.70&#39;,&#39;1.60&#39;,&#39;1.50&#39;,&#39;1.40&#39;,&#39;1.30&#39;],&#xA;  datasets: [&#xA;    { label: &#39;Index–pinky window (3.10 → 1.60 rad)&#39;, data: [0,0,0,0.067,0.133,0.2,0.267,0.333,0.4,0.467,0.533,0.6,0.667,0.733,0.8,0.867,0.933,1,1,1,1], borderColor: &#39;#2a78d6&#39;, backgroundColor: &#39;#2a78d6&#39;, borderWidth: 2, pointRadius: 2, tension: 0 },&#xA;    { label: &#39;Thumb window (2.90 → 2.30 rad)&#39;, data: [0,0,0,0,0,0.167,0.333,0.5,0.667,0.833,1,1,1,1,1,1,1,1,1,1,1], borderColor: &#39;#eb6834&#39;, backgroundColor: &#39;#eb6834&#39;, borderWidth: 2, pointRadius: 2, tension: 0 }&#xA;  ]&#xA;},&#xA;options: {&#xA;  plugins: { title: { display: true, text: &#39;Average knuckle angle → normalized flexion&#39; } },&#xA;  scales: {&#xA;    x: { title: { display: true, text: &#39;average knuckle angle (rad) — straight → curled&#39; } },&#xA;    y: { min: 0, max: 1.05, title: { display: true, text: &#39;flexion (0 open · 1 closed)&#39; } }&#xA;  }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The three pieces&#xA;    &lt;div id=&#34;the-three-pieces&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-three-pieces&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Denominator — the range.&lt;/strong&gt; \(3.10 - 1.60 = 1.50\) rad of travel. It sets the scale.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/cover.png" />
    </item>
    
    <item>
      <title>Underactuation: why three knuckle angles become one tendon command</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  &lt;code&gt;avg_angle = np.mean(angles)&lt;/code&gt; looks like noise filtering. It isn&amp;rsquo;t. It is a mechanical decision: a&#xA;human finger has three joints you can move separately, and this robot finger has one string.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Not smoothing — compression&#xA;    &lt;div id=&#34;not-smoothing--compression&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#not-smoothing--compression&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&amp;ldquo;Averaging&amp;rdquo; in a sensor pipeline usually means averaging over &lt;strong&gt;time&lt;/strong&gt; to reduce noise. Nothing here&#xA;keeps history between frames. The mean is taken &lt;strong&gt;across space&lt;/strong&gt; — over the three joints of one&#xA;finger in a single frame — to solve a problem called &lt;strong&gt;underactuation&lt;/strong&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The 3-to-1 problem&#xA;    &lt;div id=&#34;the-3-to-1-problem&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-3-to-1-problem&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Human finger&lt;/th&gt;&#xA;          &lt;th&gt;Robot finger&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Joints&lt;/td&gt;&#xA;          &lt;td&gt;3 (MCP, PIP, DIP)&lt;/td&gt;&#xA;          &lt;td&gt;3 hinges (&lt;code&gt;*_mcp&lt;/code&gt;, &lt;code&gt;*_pip&lt;/code&gt;, &lt;code&gt;*_dip&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Independent actuators&lt;/td&gt;&#xA;          &lt;td&gt;many muscles; joints move semi-independently&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1 flexor tendon, 1 motor&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Degrees of freedom you can command&lt;/td&gt;&#xA;          &lt;td&gt;~3&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;A system with fewer actuators than joints is &lt;strong&gt;underactuated&lt;/strong&gt;. A single tendon threads all three&#xA;joints of each robot finger, so the only command is &lt;em&gt;&amp;ldquo;pull this string with force F&amp;rdquo;&lt;/em&gt; — and the&#xA;joints share that pull according to routing geometry and dynamics.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;The index finger&amp;rsquo;s single flexor tendon threaded through six labelled sites from palm to fingertip&#34;&#xA;    width=&#34;1280&#34;&#xA;    height=&#34;960&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index_hu_2b0b115de9facdf8.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index_hu_2b0b115de9facdf8.png 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index.png&#34;&gt;&lt;figcaption&gt;One string, six via-points, three joints. Pull it and all three knuckles move together.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;So the vision layer must compress &lt;strong&gt;three human measurements into one robot command&lt;/strong&gt;.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/cover.png" />
    </item>
    
    <item>
      <title>What limits the frame rate: MediaPipe, Docker and a MuJoCo viewer on one laptop</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The single-script version felt smoother than the containers. The obvious suspect was Docker. The&#xA;measurements say otherwise — and point at something more interesting: two programs sharing one&#xA;integrated GPU.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Setup&#xA;    &lt;div id=&#34;setup&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#setup&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Laptop with an Intel Comet Lake CPU (12 threads) and Intel UHD Graphics; Linux on Wayland with&#xA;XWayland; USB webcam at 640×480, 30 fps, YUYV. Each row: mean of 150 frames after a 10-frame warm-up,&#xA;no hand in view. Measured 2026-09-16.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The result&#xA;    &lt;div id=&#34;the-result&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-result&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-f3b6ad3cd76a079ab08a776ccfdc0f69&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-f3b6ad3cd76a079ab08a776ccfdc0f69&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;bar&#39;,&#xA;data: {&#xA;  labels: [&#39;Vision alone (container)&#39;, &#39;Vision alone (host venv)&#39;, &#39;+ 1-core CPU burner&#39;, &#39;+ MuJoCo viewer (host, no Docker)&#39;, &#39;+ mujoco_twin (loop capped 30 Hz)&#39;, &#39;+ mujoco_twin (as shipped)&#39;],&#xA;  datasets: [{&#xA;    label: &#39;MediaPipe time per frame (ms)&#39;,&#xA;    data: [19.0, 19.2, 23.5, 54.9, 63.7, 85.6],&#xA;    backgroundColor: [&#39;rgba(42,120,214,0.75)&#39;,&#39;rgba(42,120,214,0.75)&#39;,&#39;rgba(42,120,214,0.75)&#39;,&#39;rgba(235,104,52,0.75)&#39;,&#39;rgba(235,104,52,0.75)&#39;,&#39;rgba(235,104,52,0.75)&#39;]&#xA;  }]&#xA;},&#xA;options: {&#xA;  indexAxis: &#39;y&#39;,&#xA;  plugins: { legend: { display: false }, title: { display: true, text: &#39;MediaPipe inference time by what else is running (30 fps budget = 33 ms)&#39; } },&#xA;  scales: { x: { min: 0, max: 100, title: { display: true, text: &#39;ms per frame&#39; } } }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Condition&lt;/th&gt;&#xA;          &lt;th&gt;Camera read&lt;/th&gt;&#xA;          &lt;th&gt;MediaPipe&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;imshow&lt;/code&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Loop&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Vision alone, &lt;strong&gt;container&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;10.0 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;19.0 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;4.5 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;29.8 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Vision alone, &lt;strong&gt;host venv&lt;/strong&gt; (MediaPipe 0.10.11)&lt;/td&gt;&#xA;          &lt;td&gt;10.6 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;19.2 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;3.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;29.7 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Container vision + &lt;strong&gt;1-core CPU burner&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;8.2 ms&lt;/td&gt;&#xA;          &lt;td&gt;23.5 ms&lt;/td&gt;&#xA;          &lt;td&gt;3.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;28.1 Hz&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Host vision + &lt;strong&gt;plain MuJoCo viewer on host&lt;/strong&gt; (no Docker, no ROS)&lt;/td&gt;&#xA;          &lt;td&gt;1.6 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;54.9 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;5.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;16.0 Hz&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Container vision + &lt;strong&gt;&lt;code&gt;mujoco_twin&lt;/code&gt; capped at 30 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;1.7 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;63.7 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;8.1 ms&lt;/td&gt;&#xA;          &lt;td&gt;13.6 Hz&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Container vision + &lt;strong&gt;&lt;code&gt;mujoco_twin&lt;/code&gt; as shipped&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;1.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;85.6 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;10.2 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;10.2 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;End to end, &lt;code&gt;ros2 topic hz /hand/target_flexions&lt;/code&gt; read &lt;strong&gt;30.0 Hz&lt;/strong&gt; with only &lt;code&gt;vision_tracker&lt;/code&gt; running and&#xA;&lt;strong&gt;10.7 Hz&lt;/strong&gt; once &lt;code&gt;mujoco_twin&lt;/code&gt; started.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/cover.png" />
    </item>
    
    <item>
      <title>Why the thumb needs its own thresholds in hand tracking</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Squeeze your thumb across your palm as hard as you can and, by the finger thresholds, it is only&#xA;half closed. The thumb isn&amp;rsquo;t a finger with a shorter bone — it&amp;rsquo;s a different joint.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Live: thumb tucked across the palm, fingers open — only pull_thumb goes negative at −19.5 N&#34;&#xA;    width=&#34;1570&#34;&#xA;    height=&#34;480&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled_hu_3439f03a327be93f.jpg&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled_hu_3439f03a327be93f.jpg 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled_hu_eed9092295f33e0d.jpg 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled.jpg&#34;&gt;&lt;figcaption&gt;Thumb curled, fingers open. Only &lt;code&gt;pull_thumb&lt;/code&gt; goes negative (−19.5 N, flexion 0.69); every other motor pushes open.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Hinges versus a saddle&#xA;    &lt;div id=&#34;hinges-versus-a-saddle&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#hinges-versus-a-saddle&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Index to pinky&lt;/strong&gt; are chains of &lt;strong&gt;hinge joints&lt;/strong&gt;. A fist rolls them into a tight spiral, each&#xA;knuckle approaching 90°.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The thumb&lt;/strong&gt; hangs from the &lt;strong&gt;carpometacarpal (CMC) saddle joint&lt;/strong&gt; at the wrist. It &lt;em&gt;sweeps&lt;/em&gt;&#xA;across the palm — opposition — instead of simply curling. Much of &amp;ldquo;closing the thumb&amp;rdquo; is rotation&#xA;of the whole thumb, not bending at its knuckles.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;What MediaPipe measures because of it&#xA;    &lt;div id=&#34;what-mediapipe-measures-because-of-it&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#what-mediapipe-measures-because-of-it&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Interior angles come from 3D landmark positions, so the sweep only partly shows up as knuckle bend:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Open&lt;/th&gt;&#xA;          &lt;th&gt;Fully closed&lt;/th&gt;&#xA;          &lt;th&gt;Travel&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Finger — mean of MCP/PIP/DIP&lt;/td&gt;&#xA;          &lt;td&gt;≈ 3.10 rad&lt;/td&gt;&#xA;          &lt;td&gt;≈ 1.60 rad&lt;/td&gt;&#xA;          &lt;td&gt;1.50 rad&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb — mean of CMC/MCP/IP&lt;/td&gt;&#xA;          &lt;td&gt;≈ 2.90 rad&lt;/td&gt;&#xA;          &lt;td&gt;≈ 2.30 rad&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;0.60 rad&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;A fully closed thumb stops near &lt;strong&gt;2.30 rad (~132°)&lt;/strong&gt;. Even its &lt;em&gt;open&lt;/em&gt; angle sits below a finger&amp;rsquo;s —&#xA;a relaxed thumb is never in line with the wrist.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/cover.png" />
    </item>
    
    <item>
      <title>A webcam, some vector geometry, and a hand that moves</title>
      <link>https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/overview/</link>
      <pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/overview/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Everything in this series in one read: how a $20 webcam ends up driving a 15-DOF CAD model in&#xA;real time, why every step is deliberately explicit rather than learned, and what broke along the&#xA;way.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;You hold your hand up to a laptop camera. On the other half of the screen, a robotic hand —&#xA;designed in CAD, never manufactured — closes its fingers at the same moment yours do.&lt;/p&gt;&#xA;&lt;p&gt;There is no glove, no marker, no depth sensor. Just an RGB webcam, two small neural networks,&#xA;about forty lines of vector geometry, and a middleware stack that thinks it is talking to a real&#xA;robot.&lt;/p&gt;&#xA;&lt;p&gt;All of it is open source under AGPL-3.0 and archived with a DOI:&#xA;&lt;a href=&#34;https://doi.org/10.5281/zenodo.22658556&#34;  target=&#34;_blank&#34; rel=&#34;noreferrer&#34;&gt;10.5281/zenodo.22658556&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    A[&#34;📷 Webcam&lt;br&gt;/dev/video0&#34;] --&gt; B[&#34;BlazePalm&lt;br&gt;palm detector&#34;]&#xA;    B --&gt; C[&#34;Landmark regressor&lt;br&gt;21 × (x, y, z)&#34;]&#xA;    C --&gt; D[&#34;Dot-product geometry&lt;br&gt;15 interior angles&#34;]&#xA;    D --&gt; E[&#34;Normalize → flexion&lt;br&gt;0.0 straight · 1.0 curled&#34;]&#xA;    E --&gt; F[&#34;Lerp onto the URDF&#39;s&lt;br&gt;mechanical limits&#34;]&#xA;    F --&gt; G[&#34;/joint_states&#34;]&#xA;    G --&gt; H[&#34;robot_state_publisher&lt;br&gt;→ /tf&#34;]&#xA;    H --&gt; I[&#34;🖥️ RViz digital twin&#34;]&#xA;&lt;/pre&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The rule that shaped the build&#xA;    &lt;div id=&#34;the-rule-that-shaped-the-build&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-rule-that-shaped-the-build&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;There is an easier version of this project. Collect a few thousand frames of a hand next to the&#xA;corresponding CAD poses, train a network to map one to the other, and let gradient descent work out&#xA;the relationship.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/overview/featured.png" />
    </item>
    
    <item>
      <title>How MediaPipe sees a hand — and exactly where it fails</title>
      <link>https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/mediapipe/</link>
      <pubDate>Tue, 08 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/mediapipe/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Twenty-one points, thirty frames a second, on a CPU, from a flat RGB image with no depth&#xA;information. This is how that is possible — and the four failure modes you will meet the first&#xA;time you rely on it.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Every vision-driven robotics project has a moment where the camera stops being a camera and starts&#xA;being a sensor. For this one, that moment is MediaPipe Hands: a webcam frame goes in, and 21&#xA;numbered points in space come out.&lt;/p&gt;&#xA;&lt;p&gt;It is easy to treat that as a black box. It is also a mistake, because the box has a specific&#xA;shape, and its failure modes follow directly from how it was built.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;It is two networks, not one&#xA;    &lt;div id=&#34;it-is-two-networks-not-one&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#it-is-two-networks-not-one&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The single most useful thing to know about MediaPipe Hands is that it is a &lt;strong&gt;cascade&lt;/strong&gt;: a detector&#xA;and a regressor, with completely different jobs.&lt;/p&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart TB&#xA;    A[&#34;📷 Full frame&lt;br&gt;e.g. 640 × 480&#34;] --&gt; B[&#34;Stage 1 — BlazePalm&lt;br&gt;SSD detector&#34;]&#xA;    B --&gt; C[&#34;Oriented palm crop&lt;br&gt;256 × 256&#34;]&#xA;    C --&gt; D[&#34;Stage 2 — Landmark regressor&lt;br&gt;MobileNetV2-style encoder&#34;]&#xA;    D --&gt; E[&#34;63 floats&lt;br&gt;21 landmarks × (x, y, z)&#34;]&#xA;    D --&gt; F[&#34;Presence score&#34;]&#xA;    D --&gt; G[&#34;Handedness&lt;br&gt;left / right&#34;]&#xA;    F --&gt;|&#34;confidence ≥ 0.5&#34;| C&#xA;    F --&gt;|&#34;confidence &lt; 0.5&#34;| B&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;The MediaPipe landmark skeleton overlaid on a hand in the tracker&amp;rsquo;s preview window&#34;&#xA;    width=&#34;800&#34;&#xA;    height=&#34;700&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/mediapipe/mediapipe-landmarks.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/mediapipe/mediapipe-landmarks.png 800w, https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/mediapipe/mediapipe-landmarks.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/mediapipe/mediapipe-landmarks.png&#34;&gt;&lt;figcaption&gt;The regressor&amp;rsquo;s output, drawn back onto the frame: 21 points and the connections between them.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h3 class=&#34;relative group&#34;&gt;Stage 1 — BlazePalm detects palms, never fingers&#xA;    &lt;div id=&#34;stage-1--blazepalm-detects-palms-never-fingers&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#stage-1--blazepalm-detects-palms-never-fingers&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h3&gt;&#xA;&lt;p&gt;This is the design decision the whole system rests on.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/mediapipe/featured.png" />
    </item>
    
  </channel>
</rss>
