Blob tracking and collision detection

Hey!
I’m having trouble with a collision detection program at the moment.

Essentially I’m using blob tracking with colours (from a depth camera image), and I want to know when a blob collides or overlaps another blob, and change colours of both blobs on collision.

The blob tracking system is based off Daniel Shiffman’s CV tutorials: https://www.youtube.com/watch?v=r0lvsMPGEoY, and currently my collision detection is based off this: https://processing.org/examples/bouncybubbles.html. However they clash with each other and I’m stuck:

Shiffman:

if (!found){
          Blob b = new Blob (x,y);
          currentBlobs.add(b);
        }

where new = Blob() does not expect (int,int) when using this:

Collision:

 Blob (float x, float y, float din, float idin, Blob [] oin){
    minx = x;
    miny = y;
    maxx = x;
    maxy = y;
    diameter = din;
    id = idin;
    others = oin;
  }
void collide (){
     for (int i = id + 1; i < numBlobs; i++){
      //distance x
      float dx = others [i].maxx - minx;
      //distance y
      float dy = others [i].maxy - miny;
      
      //distance between centre point of 2 blobs
      float distance = sqrt (dx*dx + dy*dy);
      
      //radius of both blobs combined
      float minDist = others[i].diameter/2 + diameter/2;
      
      //if the distance in smaller than the minimum distance then do something
      if (distance < minDist){
        fill(255,0,0);
      } else { 
        fill(0,255,0);
      }
    }
  }

So i either need to know how to remedy this, or,

if there’s a way to say

distance = sqrt(x1*x2+y1*y2)

where (x1,y1) are centre co-ordinated of one blob,
and (x2,y2) are centre co-ordinated of another, without having extra floats associated with Blob b…

Thanks!

1 Like

Can you clarify what you mean by this? If we’re talking blobs, maybe you’re looking for polygon collision detection?

Yes sorry, I realised while I was writing it out that I wasn’t even sure how to explain!

I’m converting the blobs found into circles and using circle collision detection at the moment, so finding the distance between two centre points and subtracting their combined radius’. Indicated by this piece of code in void collide:

//distance between centre point of 2 blobs
      float distance = sqrt (dx*dx + dy*dy);
      
      //radius of both blobs combined
      float minDist = others[i].diameter/2 + diameter/2;

However, I can’t seem to get this piece of code working when I add it to my blob tracking program, as to do this tracking, the class blob expects:

Blob(float x, float y, float din, float idin, Blob [] oin)

and I’m not sure how to translate din, idin and oin when i call for blobs to be created in the main sketch as the original call:

Blob b = new Blob (x,y)

probably needs to include ( x, y, din, idin, blob) as constructors? As it it is Blob (int, int) does not exist, and when I add the floats din and idin they cannot be resolved to a variable.

The option you quoted was me attempting to figure out how to do it without declaring these variables in the blob class - but I have no idea how to compare one blob to another in a potentially endless array… I believe that I would run into the same problem in polygonal collision detection? I could be wrong… I’m so lost with this.

All of the examples I’ve come across, including those in your link say all you need to know is the x,y , width and height of your object(s). My biggest confusion is then how do I find these values when all the objects I’m looking at exist in the same endless array.

I’m still confused - what do the arguments din, idin, and oin refer to?

I’m really not sure how else to explain here… Those arguments are pulled directly from the example I originally linked:
https://processing.org/examples/bouncybubbles.html

and I’ve pasted all aspects of the class code that references them.
The only thing I can think of that might be relevant is that in bouncy bubbles, the class is called in the main sketch like so:

 for (int i = 0; i < numBalls; i++) {
    balls[i] = new Ball(random(width), random(height), random(30, 70), i, balls);
  }

whereas in my sketch it is called like so:

Blob b = new Blob (x,y)

But i have variable sizes and diameters so I don’t want to constrain this…

So let me clarify, you pasted this:

class Ball {
  
  float x, y;
  float diameter;
  float vx = 0;
  float vy = 0;
  int id;
  Ball[] others;
 
  Ball(float xin, float yin, float din, int idin, Ball[] oin) {
    x = xin;
    y = yin;
    diameter = din;
    id = idin;
    others = oin;
  } 
  
  void collide() {
    // Do collision stuff
    }   
  }

Into your code, and renamed it Blob?

Essentially… With some slight adjustments as seen above.

I’ve realised 2 things:

  1. I need to identify float diameter somewhere.
  2. The blob tracking uses an arraylist, whereas this uses just an array.

WORKING!

Over the past 2 days I have been relentlessly googling and trawling old forums trying to get this collision detection working!

I finally found this version, and implemented the piece of code proposed by Quark:

 for (int i = 0; i < blobs.size() - 1; i++) {
      for (int j = i + 1; j <blobs.size(); j++) {    
        if (dist(blobs.get(i).minx, blobs.get(i).miny, blobs.get(j).minx, blobs.get(j).miny) <= 100) {
        fill (255,0,0);
      } else {
        fill (255);
    }
  }
      }

It only works when I put it inside the class Blob, but, when it detects collision, ALL the blobs turn red?? I only want the blobs that have collided to turn red… Any ideas?

I also tried to turn it into a boolean so I could allocate colour/behaviour in the main sketch to future-proof the concept when I eventually want to load different images or behaviours…

boolean checkCollision() {
      for (int i = 0; i < blobs.size() - 1; i++) {
      for (int j = i + 1; j <blobs.size(); j++) {    
        if (dist(blobs.get(i).minx, blobs.get(i).miny, blobs.get(j).minx, blobs.get(j).miny) <= 100) {
        return true;
      } else {
        return false;
    }
  }
      }
    }

but it came up with the error “This method must return a result of type boolean”

1 Like

think about your for loop,
even you must loop through all that you might want only return ONE
true or false with following logic:
if the IF is true for first time brake and return true.
ELSE finish all loop and then return false.

int imax =5, jmax=5;

boolean check() {
  for ( int i = 0; i < imax; i++) {
    for ( int j = 0; j < jmax; j++) {
      if ( something_is_true ) return true;
      //else return false;  // ERROR: This method must return a result of type boolean
    }
  }
  return false;             // move to here, outside the for loop!
}

but that is not only a logical question, also the
syntax check will look for possible situations where
no clear return is defined.

Thanks! That fixed the boolean problem, I didn’t realise it should’ve been outside the for loop.

It’s still changing the colour of ALL the blobs though, rather than just the two that are touching - and honestly I have no idea why or where to start to fix that?

In this online sketch below, a line() is drawn linking each Bubble which are touching each other: :link:

You would just need to change the code a little to replace line() w/ fill() I guess: :flushed:

More tips about circle-circle collision checking on the link below: :large_blue_circle:

I don’t understand your example… I’ve seen you post it before in an old forum post but I tried understanding and applying what I could of it and it has stopped detecting collisions completely with your .isIntersecting code.
From what I gather they’re different methods to the same end - and through looking at that code I still don’t see what is changing the colour of all the blobs vs. just the colliding blobs.

It’s probably something small and silly I just can’t see it!

This is the code:

timport org.openkinect.processing.*;

//BLOB
int blobCounter = 0;
int maxLife = 200;
float threshold = 25;
float distThreshold = 25;

ArrayList<Blob> blobs = new ArrayList<Blob>();

int trackColor;

// Kinect Library object
Kinect kinect;
PImage img;
float minThresh = 340;
float maxThresh = 475;

void setup() {
  size(640, 480);
  kinect = new Kinect(this);
  kinect.initDepth();
  img = createImage(kinect.width, kinect.height, RGB);
  

  trackColor = color(255,255,255);

}


void draw() {

  background(0);
   img.loadPixels();
  //minThresh = map(mouseX, 0, width, 0, 2048);
  //maxThresh = map(mouseY, 0, height, 0, 2048);
  //used to determine min and max for best visuals
   //create image based on the raw depth
  // Get the raw depth as array of integers
  
  //KINECT
 //=========================================================
  int[] depth = kinect.getRawDepth();


  for (int x = 0; x < kinect.width; x++) {
    for (int y = 0; y < kinect.height; y++) {
      int offset = x + y * kinect.width;
      int d = depth[offset];
      
      if (d > minThresh && d < maxThresh) {
        img.pixels[offset] = color(255); 
      }else { img.pixels[offset] = color(0); 
      }
      
      
    }
  }
 img.updatePixels();
 image(img,0,0);
 /*fill(255);
 textSize(32);
 text(minThresh + " " + maxThresh, 10, 64);*/
 
 
 
 
 
 //BLOB=====================================================================
 ArrayList<Blob> currentBlobs = new ArrayList<Blob>();
 
 //begin loop to walk through every pixel
 for (int x = 0; x < kinect.width; x++ ) {
   for (int y = 0; y < kinect.height; y++){
     int loc = x + y * kinect.width;
     //what is current color
     color currentColor = img.pixels[loc];
      float r1 = red(currentColor);
      float g1 = green(currentColor);
      float b1 = blue(currentColor);
      float r2 = red(trackColor);
      float g2 = green(trackColor);
      float b2 = blue(trackColor);

      float d = distSq(r1, g1, b1, r2, g2, b2); 
      
      if (d < threshold*threshold){
        
        boolean found = false;
        for (Blob b : currentBlobs){
          if (b.isNear(x,y)){
            b.add(x,y);
            found = true;
            break;
          }
        }
        
        if (!found){
          Blob b = new Blob (x,y);
          currentBlobs.add(b);
        }
      }
   }
 }
 
 /*for (int i = currentBlobs.size()-1; i>=0; i++){
   if(currentBlobs.get(i).size() <500){
     currentBlobs.remove(i);
   }
 }*/
 
 //There is no blobs!
 if (blobs.isEmpty() && currentBlobs.size() > 0){
   println ("Adding Blobs!");
   for (Blob b : currentBlobs){
     b.id = blobCounter;
     blobs.add(b);
     blobCounter++;
   }
 } else if (blobs.size() <= currentBlobs.size()){
   //match whatever blobs you can match)
   for (Blob b : blobs){
     float recordD = 1000;
     Blob matched = null;
     for (Blob cb : currentBlobs){
       PVector centerB = b.getCenter();
       PVector centerCB = cb.getCenter();
       float d = PVector.dist(centerB, centerCB);
       if (d < recordD && !cb.taken){
          recordD = d; 
          matched = cb;
        }
      }
      matched.taken = true;
      b.become(matched);
    }

    // Whatever is leftover make new blobs
    for (Blob b : currentBlobs) {
      if (!b.taken) {
        b.id = blobCounter;
        blobs.add(b);
        blobCounter++;
      }
    }
  } else if (blobs.size() > currentBlobs.size()) {
    for (Blob b : blobs) {
      b.taken = false;
    }


    // Match whatever blobs you can match
    for (Blob cb : currentBlobs) {
      float recordD = 1000;
      Blob matched = null;
      for (Blob b : blobs) {
        PVector centerB = b.getCenter();
        PVector centerCB = cb.getCenter();         
        float d = PVector.dist(centerB, centerCB);
        if (d < recordD && !b.taken) {
          recordD = d; 
          matched = b;
        }
      }
      if (matched != null) {
        matched.taken = true;
       // matched.counter = 100;
        matched.become(cb);
      }
    }

    for (int i = blobs.size() - 1; i >= 0; i--) {
      Blob b = blobs.get(i);
      if (!b.taken) {
        //if (b.checkCounter()){
        blobs.remove(i);
     // }
    }
  }
  }

   **for (Blob b : blobs) {
**     b.show();
**     
**      if (b.checkCollision() == true){
**        b.isColliding();
**      } else if (b.checkCollision() == false){
**        b.noCollision();
**    }  b.display();
    
  **}
  
   }

float distSq(float x1, float y1, float x2, float y2) {
  float d = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
  return d;
}

float distSq(float x1, float y1, float z1, float x2, float y2, float z2) {
  float d = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) +(z2-z1)*(z2-z1);
  return d;
}
...

with the asterix-ed area being where all the fun happens.
And this is the Blob class:

// Daniel Shiffman
// http://codingtra.in

class Blob {
  float minx;
  float miny;
  float maxx;
  float maxy;
  float distance;
  
    
  int id = 0;
  
  int counter = 100;
 
  
  boolean taken = false;

  Blob(float x, float y) {
    minx = x;
    miny = y;
    maxx = x;
    maxy = y;
  }
    
   
      
  void show() {
    
    stroke(0);
    fill(255, 100);
    strokeWeight(2);
    rectMode(CORNERS);
    rect(minx, miny, maxx, maxy);
    
    textAlign(CENTER);
    textSize(40);
    fill(0,255,0);
    text(id, minx + (maxx-minx)*0.5, maxy - 10);
    textSize(25);
    text(counter, minx + (maxx-minx)*0.5, miny - 10);
  }

  boolean checkCounter(){
  counter--;
  if (counter < 0){
    return true; 
  } else { 
      return false;
    }
}
  
  void add(float x, float y) {
    minx = min(minx, x);
    miny = min(miny, y);
    maxx = max(maxx, x);
    maxy = max(maxy, y);
  }
  
  void become(Blob other) {
    minx = other.minx;
    maxx = other.maxx;
    miny = other.miny;
    maxy = other.maxy;
  }

  float size() {
    return (maxx-minx)*(maxy-miny);
  }
  
  PVector getCenter() {
    float x = (maxx - minx)* 0.5 + minx;
    float y = (maxy - miny)* 0.5 + miny;    
    return new PVector(x,y); 
  }
  
**  void display(){
**    float x = (maxx - minx)* 0.5 + minx;
**    float y = (maxy - miny)* 0.5 + miny; 
**    ellipse(x,y,(maxx-minx) + 30, (maxy-miny) + 30);
 ** }

** void noCollision(){
**  fill(0,255,0);
** }
  
**void isColliding(){
**  fill(255,0,0);
**}


**boolean checkCollision() {
**  float cx = maxx-minx;
**  float cy = maxy-miny;
**    for (int i = 0; i < blobs.size() - 1; i++) {
**       for (int j = i + 1; j <blobs.size(); j++) {    
**          if (dist(blobs.get(i).minx, blobs.get(i).miny, blobs.get(j).minx, blobs.get(j).miny) <= 80) {
**        return true;
**      } else {
**      }
**  }
**      } return false;
**    }


  boolean isNear(float x, float y) {

    float cx = max(min(x, maxx), minx);
    float cy = max(min(y, maxy), miny);
    float d = distSq(cx, cy, x, y);

    if (d < distThreshold*distThreshold) {
      return true;
    } else {
      return false;
    }
  }
}

What is going wrong to make all the blobs change colour???

I’m still desperately trying to figure out why everything changes colour, rather than only the two (or more) that are colliding! I’ve tried moving things around and even tried using a different tracking mechanism but nothing works!
Please help - I need only the ones that are colliding to change!

not sure but i would expect something like

void display(){
    float x = (maxx - minx)* 0.5 + minx;
    float y = (maxy - miny)* 0.5 + miny;
    fill(0,255,0);
    if ( checkCollision() ) fill(255,0,0);
    ellipse(x,y,(maxx-minx) + 30, (maxy-miny) + 30);
 }

//void noCollision(){
//  fill(0,255,0);
// }
  
//void isColliding(){
//  fill(255,0,0);
//}


boolean checkCollision() {
//...

that toggles the color for each drawn circle

Unfortunately that’s not it - still changes the colour of all the blobs…

did that code ever work?

dist(blobs.get(i).minx, blobs.get(i).miny, blobs.get(j).minx, blobs.get(j).miny) <= 80

Yes (As far as I’m aware). It’s not accurate but the colour change wasn’t happening at all without that code… and now it is. I also put in:

 println("COLLISION!" + b);

to see if I could test it and that resulted in lines like:

COLLISION!Kinect_Blobs_collision_working_allblobschangecolour$Blob@ec5ea3d
COLLISION!Kinect_Blobs_collision_working_allblobschangecolour$Blob@5f927027
COLLISION!Kinect_Blobs_collision_working_allblobschangecolour$Blob@4b16827f
COLLISION!Kinect_Blobs_collision_working_allblobschangecolour$Blob@6a231d22

in the console… (yes I know my naming game could use some work!)

I can alter the code to work with webcam input rather than Kinect if that’s easier to troubleshoot?