<?xml version="1.0" encoding="utf-8"?>

<feed xmlns="http://www.w3.org/2005/Atom" >
  <generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator>
  <link href="https://zhul49.github.io/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://zhul49.github.io/" rel="alternate" type="text/html" />
  <updated>2026-03-20T02:31:22+00:00</updated>
  <id>https://zhul49.github.io/</id>

  
    <title type="html">Robert Zhu</title>
  

  
    <subtitle>Robotics + software engineer focused on perception and motion planning. I build end-to-end robot manipulation systems with ROS 2, MoveIt 2, and computer vision.</subtitle>
  

  

  
  
    <entry>
      <title type="html">Hand2Rob</title>
      <link href="https://zhul49.github.io/2026/01/19/hand2rob/" rel="alternate" type="text/html" title="Hand2Rob" />
      <published>2026-01-19T00:00:00+00:00</published>
      <updated>2026-01-19T00:00:00+00:00</updated>
      <id>https://zhul49.github.io/2026/01/19/hand2rob</id>
      <content type="html" xml:base="https://zhul49.github.io/2026/01/19/hand2rob/">&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;Hand2Rob teaches a Franka robot to grasp delicate objects by watching human hand demonstrations captured with a stereo camera pair. MediaPipe and CoTracker extract hand and object keypoints that get triangulated into 3D trajectories the robot can follow directly. The catch is that spatial imitation alone isn’t enough for fragile things, so I integrated a ResKin tactile sensor into custom gripper fingertips to give the robot a sense of force.&lt;/p&gt;

&lt;p&gt;This project builds on Point Policy and Feel The Force by Siddhant Haldar and Lerrel Pinto. Big thanks to them for the original work. I adapted and extended both systems with my own changes to get them running on the Franka Panda, including modifications to the trajectory execution, force control integration, data collection, and the custom gripper hardware.&lt;/p&gt;

&lt;h2 id=&quot;data-collection-and-annotation&quot;&gt;Data Collection and Annotation&lt;/h2&gt;

&lt;div class=&quot;clearfix&quot;&gt;
&lt;video class=&quot;float-right&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
  &lt;source src=&quot;https://zhul49.github.io/img/portfolio/Mediapipe_Pingpong.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

I collect demonstrations by having a human perform the task in front of two calibrated cameras. MediaPipe tracks the hand in real time, extracting semantic keypoints on the fingers and palm that serve as the basis for the robot&apos;s trajectory. I also annotate object keypoints using CoTracker, giving the model a consistent spatial representation of both the hand and the target object across frames. A ResKin tactile sensor mounted on my thumb records contact forces during grasping trials, providing the force labels used for training.
&lt;/div&gt;

&lt;h2 id=&quot;translating-points-to-franka-end-points&quot;&gt;Translating Points to Franka End Points&lt;/h2&gt;

&lt;div class=&quot;clearfix&quot;&gt;
&lt;video class=&quot;float-left&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot;&gt;
  &lt;source src=&quot;https://zhul49.github.io/img/portfolio/handtorobot.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;

The core challenge is mapping a human hand demonstration onto a robot gripper that has far fewer degrees of freedom. The tracked 2D keypoints are triangulated across both camera views to recover 3D positions, and a transformation aligns the human hand&apos;s keypoint cloud to the robot&apos;s end-effector frame. This produces a full 6-DoF pose trajectory and a gripper open/close signal derived from the distance between the thumb and index finger. The result is a set of robot-executable actions that reproduce the intent of the original human demonstration.
&lt;/div&gt;

&lt;h2 id=&quot;robot-execution&quot;&gt;Robot Execution&lt;/h2&gt;

&lt;div style=&quot;display: flex; gap: 16px; margin: 20px auto; flex-wrap: nowrap; align-items: flex-start; max-width: 560px;&quot;&gt;
  &lt;div style=&quot;flex: 1; text-align: center;&quot;&gt;
    &lt;video class=&quot;img-responsive&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; style=&quot;border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12);&quot;&gt;
      &lt;source src=&quot;https://zhul49.github.io/img/portfolio/Egg_cracked.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;/video&gt;
    &lt;p style=&quot;margin-top: 8px; font-size: 0.9em; color: #666;&quot;&gt;Without force feedback&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex: 1; text-align: center;&quot;&gt;
    &lt;video class=&quot;img-responsive&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; style=&quot;border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12);&quot;&gt;
      &lt;source src=&quot;https://zhul49.github.io/img/portfolio/Egg_saved.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;/video&gt;
    &lt;p style=&quot;margin-top: 8px; font-size: 0.9em; color: #666;&quot;&gt;With ResKin force control&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Without force feedback, the robot has no way to modulate its grip strength, it simply closes the gripper until the binary close command is fully executed. For fragile objects like eggs, this can lead to it breaking. Here the robot successfully reaches and grasps the egg using the learned trajectory, but the uncontrolled gripper force crushes it. This failure motivates the need for closed-loop force control during the grasp phase.&lt;/p&gt;

&lt;p&gt;With the ResKin sensor in the loop, the robot can feel how much force it is applying and stop closing once a target threshold is reached. The force controller reads the live tactile signal and adjusts the gripper incrementally, holding the egg securely without exceeding the pressure that would crack it. This demonstrates that combining learned spatial policies with real-time tactile feedback enables safe manipulation of objects that would otherwise be damaged.&lt;/p&gt;

&lt;div style=&quot;display: flex; gap: 4px; margin: 20px auto; flex-wrap: nowrap; align-items: flex-start; max-width: 560px;&quot;&gt;
  &lt;div style=&quot;flex: 1;&quot;&gt;
    &lt;video class=&quot;img-responsive&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; style=&quot;border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12);&quot;&gt;
      &lt;source src=&quot;https://zhul49.github.io/img/portfolio/egg1.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;/video&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex: 1;&quot;&gt;
    &lt;video class=&quot;img-responsive&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; style=&quot;border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12);&quot;&gt;
      &lt;source src=&quot;https://zhul49.github.io/img/portfolio/egg2.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;/video&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Above are two evaluation runs showing the full pipeline end-to-end, the robot approaches the egg, descends to the grasp position, and closes with force-controlled grip. Each camera view is shown separately, with the live force reading overlaid in blue. The consistency across runs demonstrates that the learned policy generalizes reliably from the human demonstrations.&lt;/p&gt;

&lt;h2 id=&quot;system-architecture&quot;&gt;System Architecture&lt;/h2&gt;

&lt;div style=&quot;text-align: center; margin: 20px 0;&quot;&gt;
  &lt;img src=&quot;https://zhul49.github.io/img/portfolio/hand2rob_architecture(1).png&quot; class=&quot;img-responsive&quot; style=&quot;margin: 0 auto; max-width: 600px; border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.10);&quot; alt=&quot;Hand2Rob system architecture&quot; /&gt;
  &lt;p style=&quot;margin-top: 10px; font-size: 0.9em; color: #666;&quot;&gt;&lt;strong&gt;Figure 1.&lt;/strong&gt; Overview of the Hand2Rob pipeline. Stereo camera footage and MediaPipe hand tracking are processed through CoTracker and stereo triangulation to build the dataset. A training policy learns both the end-effector trajectory and force grasping, which are deployed on the Franka robot with ResKin tactile feedback for manipulating fragile objects.&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;cad-models&quot;&gt;CAD Models&lt;/h2&gt;

&lt;div style=&quot;display: flex; gap: 24px; margin: 20px 0; flex-wrap: wrap; align-items: flex-start; justify-content: center;&quot;&gt;
  &lt;div style=&quot;flex: 0 1 260px; text-align: center;&quot;&gt;
    &lt;img src=&quot;https://zhul49.github.io/img/portfolio/gripper_without_sensor.png&quot; class=&quot;img-responsive&quot; style=&quot;margin: 0 auto; border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.10);&quot; alt=&quot;Gripper without sensor&quot; /&gt;
    &lt;p style=&quot;margin-top: 8px; font-size: 0.9em; color: #666;&quot;&gt;Gripper without sensor&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex: 0 1 260px; text-align: center;&quot;&gt;
    &lt;img src=&quot;https://zhul49.github.io/img/portfolio/gripper_with_sensor.png&quot; class=&quot;img-responsive&quot; style=&quot;margin: 0 auto; border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.10);&quot; alt=&quot;Gripper with sensor&quot; /&gt;
    &lt;p style=&quot;margin-top: 8px; font-size: 0.9em; color: #666;&quot;&gt;Gripper with ResKin tactile sensor&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Special thanks to Miguel Pegues for helping me design custom gripper fingertips to house the ResKin magnetometer-based tactile sensor. The left model shows the standard Franka gripper fingers, while the right model integrates a recessed pocket that secures the ResKin sensing pad flush against the contact surface.&lt;/p&gt;</content>

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      

      
        <category term="Python" />
      
        <category term="Imitation Learning" />
      
        <category term="Tactile Sensing" />
      
        <category term="Computer Vision" />
      
        <category term="PyTorch" />
      
        <category term="LibFranka" />
      

      
        <summary type="html">Overview</summary>
      

      
      
    </entry>
  
    <entry>
      <title type="html">Vision-Guided Pen Grasping</title>
      <link href="https://zhul49.github.io/2025/12/18/pengrasp/" rel="alternate" type="text/html" title="Vision-Guided Pen Grasping" />
      <published>2025-12-18T00:00:00+00:00</published>
      <updated>2025-12-18T00:00:00+00:00</updated>
      <id>https://zhul49.github.io/2025/12/18/pengrasp</id>
      <content type="html" xml:base="https://zhul49.github.io/2025/12/18/pengrasp/">&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;This project implements a vision-guided grasping system for a robotic arm that autonomously detects and grasps a pen using an RGB-D camera. The objective was to build a complete perception-to-action pipeline that converts raw camera data into executable robot motion, enabling the robot to locate and grasp a pen without manual alignment or human intervention.&lt;/p&gt;

&lt;p&gt;The task was intentionally constrained to a known object type and workspace, allowing the system to emphasize robustness, accuracy, and correct geometric reasoning rather than general-purpose object recognition.&lt;/p&gt;

&lt;h2 id=&quot;perception-and-localization&quot;&gt;Perception and Localization&lt;/h2&gt;
&lt;p&gt;Perception was implemented using classical computer vision techniques. Since the target object was a &lt;strong&gt;purple pen&lt;/strong&gt;, the RGB image was converted to the HSV color space and color thresholding was applied to segment purple regions from the background. Depth data from the RealSense camera was used to remove background pixels beyond a fixed range, improving robustness under clutter and lighting variation.&lt;/p&gt;

&lt;p&gt;Contours were extracted from the segmented mask, and the most relevant contour was selected based on geometric properties. From this contour, the pen’s image-space centroid and orientation were estimated. The centroid pixel was aligned with the depth image, and the corresponding depth value was used to deproject the pixel into a 3D point in the camera coordinate frame using the camera intrinsics. To reduce sensor noise, multiple measurements were collected over a short time window and averaged.&lt;/p&gt;

&lt;!-- ![Pen detection overlay](/img/portfolio/cabin.png) --&gt;

&lt;h2 id=&quot;coordinate-transformation-and-grasp-execution&quot;&gt;Coordinate Transformation and Grasp Execution&lt;/h2&gt;
&lt;p&gt;The 3D pen position estimated in the camera frame was transformed into the robot base frame using a precomputed camera-to-robot extrinsic calibration. This calibration was represented as a rigid-body transform consisting of a rotation matrix (R) and translation vector (t), applied directly as:&lt;/p&gt;

&lt;p style=&quot;text-align:center; font-size: 1.15em;&quot;&gt;
  P&lt;sub&gt;robot&lt;/sub&gt; = R · P&lt;sub&gt;camera&lt;/sub&gt; + t
&lt;/p&gt;

&lt;p&gt;A small tool offset was then added to account for the physical geometry of the gripper.&lt;/p&gt;

&lt;p&gt;Once the target position was expressed in the robot frame, the &lt;strong&gt;PincherX 100&lt;/strong&gt; arm was controlled using direct API commands. The robot moved to a hover pose above the pen, descended to the grasp location, closed the gripper, lifted the object to verify a successful grasp, and returned to a safe pose. This demonstrated a complete vision-driven manipulation pipeline operating under real sensor noise and hardware constraints.&lt;/p&gt;</content>

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      

      
        <category term="OpenCV" />
      
        <category term="Camera-to-Robot TF" />
      
        <category term="Interbotix PX100" />
      
        <category term="Python" />
      

      
        <summary type="html">Overview This project implements a vision-guided grasping system for a robotic arm that autonomously detects and grasps a pen using an RGB-D camera. The objective was to build a complete perception-to-action pipeline that converts raw camera data into executable robot motion, enabling the robot to locate and grasp a pen without manual alignment or human intervention.</summary>
      

      
      
    </entry>
  
    <entry>
      <title type="html">AutonomousPick-and-Place</title>
      <link href="https://zhul49.github.io/2025/12/18/omnipplace/" rel="alternate" type="text/html" title="Autonomous&lt;br&gt;Pick-and-Place" />
      <published>2025-12-18T00:00:00+00:00</published>
      <updated>2025-12-18T00:00:00+00:00</updated>
      <id>https://zhul49.github.io/2025/12/18/omnipplace</id>
      <content type="html" xml:base="https://zhul49.github.io/2025/12/18/omnipplace/">&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;OmniPlace is an autonomous pick and place system built on &lt;strong&gt;ROS 2&lt;/strong&gt; for a &lt;strong&gt;Franka Panda&lt;/strong&gt; with an &lt;strong&gt;Intel RealSense&lt;/strong&gt; camera. The robot scans a tabletop, detects both &lt;strong&gt;objects&lt;/strong&gt; (squares, rectangles, cylinders) and &lt;strong&gt;targets&lt;/strong&gt; (their flat cross sections), matches each object to the correct target, and executes pick and place until no targets remain.&lt;/p&gt;

&lt;h2 id=&quot;system-diagrams&quot;&gt;System diagrams&lt;/h2&gt;

&lt;div style=&quot;display: flex; gap: 24px; margin: 20px 0; flex-wrap: wrap; align-items: flex-start;&quot;&gt;
  &lt;div style=&quot;flex: 1; min-width: 260px;&quot;&gt;
    &lt;img src=&quot;/img/portfolio/omniplace_setup_diagram.png&quot; class=&quot;img-responsive&quot; alt=&quot;User setup diagram&quot; /&gt;
    &lt;p class=&quot;img-caption&quot;&gt;Hardware setup: objects, camera, and robot arm&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex: 1; min-width: 260px;&quot;&gt;
    &lt;img src=&quot;/img/portfolio/omniplace_subsystem.png&quot; class=&quot;img-responsive&quot; alt=&quot;Subsystem block diagram&quot; /&gt;
    &lt;p class=&quot;img-caption&quot;&gt;Software pipeline from sensing to planning and execution&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&quot;perception-pipeline-yolo-obb-and-detection-stabilization&quot;&gt;Perception pipeline (YOLO-OBB and detection stabilization)&lt;/h2&gt;

&lt;p&gt;Perception is handled by a YOLO-based detector that outputs &lt;strong&gt;oriented bounding boxes&lt;/strong&gt;, providing both object position and in-plane rotation directly from the image. Rather than relying on single-frame detections, the system performs a structured scan of the workspace and aggregates detections across multiple frames.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/portfolio/omniplace_yolo.gif&quot; style=&quot;display:block; margin: 16px auto; max-width: 75%; border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12);&quot; alt=&quot;YOLO OBB detection&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Detections are associated over time by comparing center location, shape class, bounding box dimensions, and orientation. Candidates that do not appear consistently across the scan window are discarded as noise. This temporal filtering produces a stable set of object and target poses that can be safely used for motion planning.&lt;/p&gt;

&lt;p&gt;To train the YOLO model, a &lt;strong&gt;Python-based synthetic data generation pipeline&lt;/strong&gt; was developed. The script programmatically generated large datasets of labeled images containing geometric objects with randomized pose, scale, orientation, lighting, and background variation. This approach made it possible to rapidly scale the training dataset without manual labeling and ensured strong coverage of object orientations required for reliable OBB prediction.&lt;/p&gt;

&lt;p&gt;Objects and targets are distinguished using a height-based heuristic. Targets are flat cross-sections placed on the table, while objects have nonzero height. This separation allows the system to independently build object and target sets and perform shape-based matching between them.&lt;/p&gt;

&lt;h2 id=&quot;motion-planning-and-task-execution&quot;&gt;Motion Planning and Task Execution&lt;/h2&gt;

&lt;p&gt;Motion planning is handled through a custom Python interface built on top of MoveIt 2, designed to simplify interaction with the robot while still exposing fine control when needed. Instead of calling MoveIt APIs directly throughout the codebase, the system is structured around a small set of modular wrappers that separate state queries, planning logic, and environment management.&lt;/p&gt;

&lt;p&gt;The motion planning interface provides utilities for querying the robot’s current state, generating collision-aware trajectories, and executing both Cartesian and joint-space motions. A dedicated planning scene manager dynamically adds and removes collision objects corresponding to detected items and targets, ensuring that planned motions remain valid as the workspace changes. All motion commands are funneled through a single high-level interface that exposes actions such as moving to poses, executing grasps, and controlling the gripper, keeping task logic clean and readable.&lt;/p&gt;

&lt;p&gt;Task execution is coordinated by a central control script that drives the full pick-and-place pipeline. When triggered, the robot first moves to a known home configuration and performs camera calibration using an ArUco marker to establish consistent transforms between the camera, marker, and robot base. The perception pipeline then scans the workspace, producing a stable set of object and target poses that are visualized in RViz and added to the planning scene.&lt;/p&gt;

&lt;p&gt;Objects are matched to targets based on shape and size, and the robot executes pick-and-place operations one pair at a time. After each attempt, the system re-scans the workspace rather than assuming a static scene. This design allows the robot to recover from failed grasps, handle objects being moved during execution, and remain robust to detection ordering changes. The task continues until no valid targets remain, at which point the robot safely returns to its home position.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/portfolio/tf_frames.png&quot; style=&quot;display:block; margin: 16px auto; max-width: 70%; border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12);&quot; alt=&quot;TF Tree diagram&quot; /&gt;&lt;/p&gt;</content>

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      

      
        <category term="ROS 2" />
      
        <category term="MoveIt 2" />
      
        <category term="YOLO-OBB" />
      
        <category term="TF2 Frames" />
      
        <category term="Franka Panda" />
      
        <category term="3D Pose Estimation" />
      

      
        <summary type="html">Overview OmniPlace is an autonomous pick and place system built on ROS 2 for a Franka Panda with an Intel RealSense camera. The robot scans a tabletop, detects both objects (squares, rectangles, cylinders) and targets (their flat cross sections), matches each object to the correct target, and executes pick and place until no targets remain.</summary>
      

      
      
    </entry>
  
    <entry>
      <title type="html">Jack-in-the-Box Physics Simulation</title>
      <link href="https://zhul49.github.io/2025/12/18/jackinthebox/" rel="alternate" type="text/html" title="Jack-in-the-Box Physics Simulation" />
      <published>2025-12-18T00:00:00+00:00</published>
      <updated>2025-12-18T00:00:00+00:00</updated>
      <id>https://zhul49.github.io/2025/12/18/jackinthebox</id>
      <content type="html" xml:base="https://zhul49.github.io/2025/12/18/jackinthebox/">&lt;h2 id=&quot;project-summary&quot;&gt;Project Summary&lt;/h2&gt;
&lt;p&gt;This project simulates a jack-in-the-box mechanism to study the interaction between rigid-body motion and internal impacts. The system’s motion is computed from analytical dynamics, with collisions between the jack and the box resolved using impulse-based methods.&lt;/p&gt;

&lt;p&gt;The result is a compact simulation that highlights the role of coordinate frames, equations of motion, and contact dynamics in mechanical behavior.&lt;/p&gt;

&lt;h2 id=&quot;frames-dynamics-and-impacts&quot;&gt;Frames, Dynamics, and Impacts&lt;/h2&gt;
&lt;p&gt;The system is represented using multiple coordinate frames. A fixed world frame defines the inertial reference. A box frame moves and rotates with the enclosure. A jack frame rotates inside the box and defines the positions of the four tip masses. Points are mapped between frames using rigid-body transformations of the form&lt;/p&gt;

&lt;p style=&quot;text-align:center;&quot;&gt;
  p&lt;sub&gt;world&lt;/sub&gt; = R · p&lt;sub&gt;body&lt;/sub&gt; + t
&lt;/p&gt;

&lt;p&gt;Expressing the jack in the box frame simplifies contact detection, since the box walls are fixed in that frame.&lt;/p&gt;

&lt;p&gt;The motion of the box and jack is derived using the Euler–Lagrange formulation,&lt;/p&gt;

&lt;p style=&quot;text-align:center;&quot;&gt;
  d/dt(∂L/∂q̇) − ∂L/∂q = Q
&lt;/p&gt;

&lt;p&gt;which governs smooth translational and rotational motion between collisions. These equations are numerically integrated forward in time.&lt;/p&gt;

&lt;p&gt;When a jack tip contacts a wall, the collision is treated as an instantaneous event. Instead of integrating through contact, velocities are updated using an impulse-based formulation. The post-impact generalized velocities satisfy&lt;/p&gt;

&lt;p style=&quot;text-align:center;&quot;&gt;
  ∂L/∂q̇⁺ = ∂L/∂q̇⁻ + Jᵀλ
&lt;/p&gt;

&lt;p&gt;where (J) is the contact Jacobian and (λ) is the impulse magnitude. The impulse is solved by enforcing the contact constraint together with an energy consistency condition, resulting in realistic momentum transfer between the internal jack and the box. These repeated impacts produce rotation, bouncing, and jitter of the enclosure.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/portfolio/jackframes.png&quot; alt=&quot;Jack Frames&quot; /&gt;&lt;/p&gt;</content>

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      

      
        <category term="Euler–Lagrange" />
      
        <category term="Rigid-Body Dynamics" />
      
        <category term="Impulse Impacts" />
      
        <category term="Coordinate Frames" />
      

      
        <summary type="html">Project Summary This project simulates a jack-in-the-box mechanism to study the interaction between rigid-body motion and internal impacts. The system’s motion is computed from analytical dynamics, with collisions between the jack and the box resolved using impulse-based methods.</summary>
      

      
      
    </entry>
  
    <entry>
      <title type="html">EKF SLAM on Turtlebot3</title>
      <link href="https://zhul49.github.io/2025/01/18/navigation-slam-homework/" rel="alternate" type="text/html" title="EKF SLAM on Turtlebot3" />
      <published>2025-01-18T00:00:00+00:00</published>
      <updated>2025-01-18T00:00:00+00:00</updated>
      <id>https://zhul49.github.io/2025/01/18/navigation-slam-homework</id>
      <content type="html" xml:base="https://zhul49.github.io/2025/01/18/navigation-slam-homework/">&lt;h2 id=&quot;demo&quot;&gt;Demo&lt;/h2&gt;

&lt;div style=&quot;text-align: center; margin: 20px 0;&quot;&gt;
  &lt;video class=&quot;img-responsive img-centered&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; style=&quot;border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12);&quot;&gt;
    &lt;source src=&quot;https://zhul49.github.io/img/portfolio/Closed_loop_run.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;/video&gt;
  &lt;p style=&quot;margin-top: 8px; font-size: 0.9em; color: #666;&quot;&gt;Red = ground truth &amp;nbsp;·&amp;nbsp; Blue = odometry &amp;nbsp;·&amp;nbsp; Green = SLAM estimate and landmark map&lt;/p&gt;
&lt;/div&gt;

&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;

&lt;p&gt;This project builds feature-based EKF SLAM on a TurtleBot3 from scratch. Five ROS 2 packages make up the system:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;turtlelib&lt;/strong&gt; — Pure C++ library for SE(2) transforms, differential drive kinematics, and the EKF. No ROS dependency; fully unit-tested with Catch2.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;nusim&lt;/strong&gt; — 2D physics simulator with Gaussian wheel noise, wheel slip, lidar ray-casting, and collision detection.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;nuturtle_description&lt;/strong&gt; — Multi-colour TurtleBot3 URDF with a parametric &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff_params.yaml&lt;/code&gt; for wheel radius, track width, and collision geometry.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;nuturtle_control&lt;/strong&gt; — Turtle interface node, odometry node, and a circle-driving node.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;nuslam&lt;/strong&gt; — Landmark detection pipeline and EKF SLAM node.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;algorithms&quot;&gt;Algorithms&lt;/h2&gt;

&lt;h3 id=&quot;odometry&quot;&gt;Odometry&lt;/h3&gt;

&lt;p&gt;The odometry node integrates wheel encoder deltas using the constant-curvature arc model from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DiffDrive&lt;/code&gt; class. It publishes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nav_msgs/Odometry&lt;/code&gt; and broadcasts the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;odom → base_footprint&lt;/code&gt; TF, with an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initial_pose&lt;/code&gt; service to reset the origin.&lt;/p&gt;

&lt;div style=&quot;display: flex; gap: 16px; margin: 20px 0; flex-wrap: nowrap; align-items: flex-start;&quot;&gt;
  &lt;div style=&quot;flex: 0 0 30%; text-align: center;&quot;&gt;
    &lt;video class=&quot;img-responsive&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; style=&quot;border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12); width: 100%;&quot;&gt;
      &lt;source src=&quot;https://zhul49.github.io/img/portfolio/Irl_Turtle_spin.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;/video&gt;
    &lt;p style=&quot;margin-top: 8px; font-size: 0.9em; color: #666;&quot;&gt;Physical TurtleBot3 driving in a circle&lt;/p&gt;
  &lt;/div&gt;
  &lt;div style=&quot;flex: 0 0 68%; text-align: center;&quot;&gt;
    &lt;video class=&quot;img-responsive&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; style=&quot;border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.12); width: 100%;&quot;&gt;
      &lt;source src=&quot;https://zhul49.github.io/img/portfolio/Rviz_Turtle_spin.mp4&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;/video&gt;
    &lt;p style=&quot;margin-top: 8px; font-size: 0.9em; color: #666;&quot;&gt;Odometry estimate visualized in RViz&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&quot;landmark-detection&quot;&gt;Landmark Detection&lt;/h3&gt;

&lt;p&gt;Landmarks are cylinders detected from 2D lidar scans in three stages:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Clustering&lt;/strong&gt; — consecutive scan points within a distance threshold form a cluster; clusters with fewer than 4 points are discarded.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Circle fitting&lt;/strong&gt; — the Hyper algebraic fit finds the centre and radius of each cluster. Clusters with implausible radii are rejected.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Arc classification&lt;/strong&gt; — uses the inscribed angle theorem: for a true circular arc, the angle ∠P₁PP₂ is nearly constant across all points P. Clusters whose mean falls between 90°–135° with standard deviation below 0.15 rad are classified as circles.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;ekf-slam&quot;&gt;EKF SLAM&lt;/h3&gt;

&lt;p&gt;The EKF maintains a joint state vector [θ, x, y, mx₁, my₁, …, mxN, myN] and a single covariance matrix over robot pose and all landmark positions simultaneously.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Prediction&lt;/strong&gt; — wheel encoder deltas propagate through the nonlinear motion model; the Jacobian is computed analytically.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Correction&lt;/strong&gt; — each detected landmark is matched to a known map entry by world-frame Euclidean distance, then used to correct the full state via a range-bearing Jacobian.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Initialisation&lt;/strong&gt; — on first sighting, a landmark is placed using the inverse measurement model and the EKF correction is skipped for that frame, avoiding a degenerate update with uninformative covariance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;tf-tree&quot;&gt;TF Tree&lt;/h2&gt;

&lt;div style=&quot;text-align: center; margin: 20px 0;&quot;&gt;
  &lt;a href=&quot;https://zhul49.github.io/img/portfolio/tf_tree.png&quot; target=&quot;_blank&quot; title=&quot;Click to view full size&quot;&gt;
    &lt;img src=&quot;https://zhul49.github.io/img/portfolio/tf_tree.png&quot; style=&quot;width: 100%; border-radius: 8px; box-shadow: 0 4px 14px rgba(0,0,0,0.10); cursor: zoom-in;&quot; alt=&quot;TF Tree&quot; /&gt;
  &lt;/a&gt;
  &lt;p style=&quot;margin-top: 8px; font-size: 0.85em; color: #999;&quot;&gt;Click to view full size&lt;/p&gt;
&lt;/div&gt;</content>

      
      
      
      
      

      <author>
          <name></name>
        
        
      </author>

      

      
        <category term="ROS 2" />
      
        <category term="C++" />
      
        <category term="Unsupervised Learning" />
      
        <category term="SLAM" />
      
        <category term="EKF" />
      
        <category term="TurtleBot3" />
      

      
        <summary type="html">Demo</summary>
      

      
      
    </entry>
  
</feed>
