Extended Realities, Fiducials

Fiducials

Fiducials are visual markers or reference points designed to be recognizable by computer vision systems. They consist of distinctive patterns, symbols, or shapes (like QR codes ) that can be reliably detected and tracked by a camera.

Fiducials are used in augmented reality and motion tracking to determine position, orientation, or scale of objects in physical space.

Working with fiducials in Processing

NYARToolkit

Tracking Markers

/**
 * Augmented Reality Map Viewer
 * 
 * This sketch uses NyARToolkit to detect markers and display a 3D world map
 * on top of them in augmented reality. It tracks camera input and allows
 * interaction with a 3D map object using mouse coordinates.
 */

// Import necessary libraries
import jp.nyatla.nyar4psg.*;      // NyARToolkit library for Processing
import processing.video.*;         // Video library for camera access

// Global variables
Capture cam;                      // Camera capture object
MultiMarker nya;                  // AR marker detection object
PVector pointer;                  // A Vector that holds coordinates of mousepointer in 3D-space
PImage map;                       // PImage for the world map image
PShape maptable, stand, mapboard; // PShapes for the virtual map stand components

/**
 * Setup function - runs once at the beginning
 */
void setup() {
  // Set up window size and enable 3D rendering
  size(640, 480, P3D);
  
  // Camera setup options - uncomment the one you need based on your system
  print(Capture.list());          // Print available camera options to console
  
  // Camera initialization options for different operating systems
  //cam = new Capture(this, 640, 480);                                      // Use internal cam
  //cam = new Capture(this, width, height, "UGREEN Camera");                // Use external cam with name
  cam = new Capture(this, "pipeline: avfvideosrc device-index=1");         // OSX(Mac), external Camera
  //cam = new Capture(this, "pipeline: ksvideosrc device-index=1");         // Windows, external Camera
  //cam = new Capture(this, 1920, 1080, "pipeline: ksvideosrc device-index=0! image/jpeg, width=1920, height=1080, framerate=30/1 ! jpegdec ! videoconvert"); // Linux option
  // See https://wp.nyu.edu/shanghai-ima-interaction-lab/how-to-capture-camera-in-catalina/ for more camera options
  
  // Initialize the AR marker detection system
  nya = new MultiMarker(this, width, height, "data/camera_para.dat", NyAR4PsgConfig.CONFIG_PSG);
  nya.addARMarker("data/patt.hiro", 80);  // Add a marker pattern with size 80mm
  
  // Load the world map image from the data folder
  map = loadImage("worldmap_smaller.jpg");
  
  // Start the camera capture
  cam.start();
  
  // Create a virtual 3D object group to hold the map components
  createMapObject();
}

/**
 * Helper method to create the 3D map object with stand and board
 */
void createMapObject() {
  // Create a group to hold all map components
  maptable = createShape(GROUP);
  
  // Create the stand (base) of the map holder
  stand = createShape(BOX, 300, 200, 100);
  stand.setFill(color(255, 255, 255, 100));  // Semi-transparent white
  maptable.addChild(stand);
  
  // Create the map board that will display the actual map image
  mapboard = createShape(BOX, 300, -200, 1);
  mapboard.translate(0, 0, 55);              // Position it above the stand
  mapboard.setFill(color(255, 255, 255, 255)); // Solid white background
  mapboard.setTexture(map);                  // Apply the map texture
  maptable.addChild(mapboard);
}

/**
 * Draw function - runs continuously in a loop
 */
void draw() {
  // Check if camera is available
  if (cam.available() != true) {
    return;  // If camera hasn't started yet, skip this frame
  }
  
  // Process camera input
  cam.read();        // Read the latest frame from camera
  nya.detect(cam);   // Analyze camera image for AR markers
  
  // Set black background and draw camera feed
  background(0);
  nya.drawBackground(cam);  // Draw the camera image as background
  
  // Check if marker is detected
  if (!nya.isExist(0)) {
    return;  // If no marker found, skip the rest of this frame
  }
  
  // Convert mouse position to 3D coordinates relative to the marker
  pointer = nya.screen2ObjectCoordSystem(0, mouseX, mouseY);
  
  // Begin drawing in 3D space aligned with the detected marker
  nya.beginTransform(0);
    
    // Draw the map table with the world map
    shape(maptable);
    
    // Draw an interactive pointer at mouse position
    stroke(100, 100, 0);       // Set stroke color to dark yellow
    translate(10, 10, 100);    // Adjust position in 3D space
    fill(200);                 // Light gray fill
    ellipse((int)pointer.x, (int)pointer.y, 20, 20);  // Draw circle at mouse position
  
  // End the 3D transformation
  nya.endTransform();
}

Generative audiovisual objects in augmented reality

BoofCV is a library that provides methods for computer vision. It must also be installed by the Library Manager in its version for Processing. For example, BoofCV has functions to detect and track markers in a video stream. By making the code react when certain markers are detected, the basic operation of Reactable can be understood in a simplified way.

import processing.video.*;
import jp.nyatla.nyar4psg.*;

Capture cam;
MultiMarker nya;

int amount = 11;

Blossom[] blossoms = new Blossom[amount];

void setup() {
  size(640,360,P3D);
  colorMode(HSB);
  //cam=new Capture(this,640,360);          // Internal Cam
  cam=new Capture(this,640,360, "Live! Cam Sync HD VF0770");
  nya=new MultiMarker(this,width,height,"data/camera_para.dat",NyAR4PsgConfig.CONFIG_PSG);
  for (int i=0; i < amount; i++){
    nya.addNyIdMarker(i,80);
    blossoms[i] = new Blossom();
    blossoms[i].setPoints();
  }
  cam.start();
  lights();
}

void draw()
{
  if (cam.available() !=true) {
      return;
  }
  
  cam.read();
  nya.detect(cam);
  nya.drawBackground(cam);


  // loop through all possible markers and attach a generative blossom ------------------------
  for (int i=0; i < amount; i++){
    if((nya.isExist(i))){
      nya.beginTransform(i);
        stroke(0,200);
        strokeWeight(3);                                 // start drawing in 3D of the detected marker
        line(0,0,0,0,0,blossoms[i].getStemHeight());      // draw the stem
        translate(0, 0, blossoms[i].getStemHeight());     // translate the blossom by the length of its stem
        blossoms[i].rotateObject();                       // rotate blossom
        rotateX(blossoms[i].getRotation());
        rotateY(blossoms[i].getRotation());
        blossoms[i].drawObject();                         // draw blossom
      nya.endTransform();                                // end drawing in 3D of the marker
    }
  }
}

Augmented Reality Audiovisual Synth

In this sketch, fiducials are detected and two different objects placed according to their position in AR. Their position and the rotation of their individual 3D matrix is calculated. As an example, their z-positions are sent out via OSC messages.

import jp.nyatla.nyar4psg.*;
import processing.video.*;
import oscP5.*;
import netP5.*;

// Core components for AR marker detection, camera capture, and OSC communication
Capture cam;
MultiMarker nya;
OscP5 oscP5;
NetAddress myRemoteLocation;
PVector origin;
float[] rotation;
float x,y,z;
int currentMarkerID;

void setup(){
  size(640,480,P3D);
  
  // Initialize basic components and reference points
  origin = new PVector(0,0,0);
  rotation = new float[3];
  
  //cam=new Capture(this,640,480);                // use internal cam
  //cam = new Capture(this,  width, height, "Live! Cam Sync HD VF0770"); // use external cam
  cam = new  Capture(this, width, height, "pipeline: avfvideosrc device-index=0, width=640, height=360"); //OSX(Mac), external Camera
  
  // Setup OSC communication with SuperCollider
  oscP5 = new OscP5(this,12000);    // Port number 12000
  myRemoteLocation = new NetAddress("127.0.0.1",57120); // localhost SuperCollider
  nya=new MultiMarker(this,width,height,"data/camera_para.dat",NyAR4PsgConfig.CONFIG_PSG);
  
  // Start camera and register AR markers to track
  cam.start();
  nya.addNyIdMarker(0,80);
  nya.addNyIdMarker(1,80);
}

void draw(){
  if (cam.available() !=true) return;
  
  // Capture and process camera frame for AR detection
  cam.read();
  nya.detect(cam);
  background(0);
  nya.drawBackground(cam);
  
  // Process each marker if detected in the scene
  currentMarkerID = 1;
  for (int i=0; i < 2; i++){
    if((nya.isExist(i))){
      nya.beginTransform(i);
        
        // Get position and rotation of marker in world coordinates
        PMatrix3D transMat = nya.getMatrix(i);
        x = transMat.multX(origin.x,origin.y,origin.z);
        y = transMat.multY(origin.x,origin.y,origin.z);
        z = transMat.multZ(origin.x,origin.y,origin.z);
        
        rotation = getRotation(transMat);
          println("Object " + currentMarkerID + " at world coordinates x: " + x + ", y: " + y + ", z: " + z);
          println("Rotation: (" + rotation[0] + ", " + rotation[1] + ", " + rotation[2] + ")");
      
        // Render different 3D objects based on marker ID and send OSC data
        switch (i){
            case 0:
              strokeWeight(10);
              stroke(255);
              fill(0,150);
              box(100);
              // send z coordinate to SuperCollider
              OscMessage zeroMessage = new OscMessage("/zero");
              zeroMessage.add(z);
              oscP5.send(zeroMessage, myRemoteLocation);
              break;
            case 1:
              strokeWeight(0.7);
              fill(255, 0, 0, 120);
              stroke(255,200);
              sphere(100);
              // send z coordinate to SuperCollider
              OscMessage firstMessage = new OscMessage("/first");
              firstMessage.add(z);
              oscP5.send(firstMessage, myRemoteLocation);
              break;
        }
      nya.endTransform(); 
    }
    currentMarkerID +=1;
  }
}

// Utility function to extract Euler rotation angles from transformation matrix
float[] getRotation(PMatrix3D _m){
  PMatrix3D m = _m;
  float[] rotationXYZ = new float[3];
  float sy = sqrt(m.m00 * m.m00 + m.m10 * m.m10);
  float x, y, z;
  if (sy > 1e-6) {
    x = atan2(m.m21, m.m22);
    y = atan2(-m.m20, sy);
    z = atan2(m.m10, m.m00);
  } else {
    x = atan2(-m.m12, m.m11);
    y = atan2(-m.m20, sy);
    z = 0;
  }
  rotationXYZ[0] = x;
  rotationXYZ[1] = y;
  rotationXYZ[2] = z;
  return rotationXYZ;
  
}

In SuperCollider, two sound objects accepting OSC messages from Processing could be defined. When Processing finds fiducials, this SuperCollider sketch adjusts the sounds according to the distance of the markers.

Last updated