// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // BlobDetection by v3ga // August 2004 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // // // uses : // - fastblur by Quasimondo // - scrollbar by Casey Reas // // keys : // - 'i' to toggle on/off drawing video frame on screen. // // // ================================================== // Globals // ================================================== // BlobDetection // ----------------------- import processing.video.*; Capture myCapture; BlobDetection blobDetection; float edgeThreshold = 300.0f; float edgeThresholdMin = 0.0f; float edgeThresholdMax = 600.0f; boolean drawImage = true; // Only draws edges boolean saveImg = false; // Save frames to disk // Video // ----------------------- int targetWidth = 80; int targetHeight = 60; boolean newFrame; int ratio = 6; // Adjust here // GUI // ----------------------- HScrollbar scrollbar; // allow to adjust edgeThreshold // ================================================== // Main // ================================================== void setup() { // >Size size(targetWidth*ratio,targetWidth*ratio); // >BlobDetect blobDetection = new BlobDetection(targetWidth,targetHeight); blobDetection.setIsoValue(edgeThreshold); // >GUI scrollbar = new HScrollbar(0, height-7, width, 10, 1); // >Video String s = "TRUST 120 SPACEC@M-WDM"; //change to appropriate capture device myCapture = new Capture(this, s, targetWidth, targetHeight, 12); } void draw() { // >Clear Screen (only needed if we do not draw video image...) background(255); // > Blob Detection // > Results in blobDetection.blob array // > See Blob class for properties blobDetection.setIsoValue(scrollbar.getPos()/width*edgeThresholdMax); blobDetection.apply(myCapture, 1); // > Draw Image if (drawImage) blobDetection.drawImageScreen(); // > Draw Edges strokeWeight(2); stroke(0,255,0); blobDetection.draw(); // > Draw Blobs & Infos strokeWeight(1); stroke(255,0,0); blobDetection.drawBlobs(); blobDetection.print(); // > Save Frames if (saveImg) saveFrame(); noStroke(); scrollbar.update(); scrollbar.draw(); } // ================================================== // Events // ================================================== void captureEvent(Capture myCapture){ myCapture.read(); } void keyPressed() { switch(key) { case 'i': case 'I': drawImage = !drawImage; break; } } // ================================================== // class EdgeVertex // ================================================== class EdgeVertex { float x,y; EdgeVertex(float x, float y){this.x=x;this.y=y;} }; // ================================================== // class Metaballs2D // ================================================== class Metaballs2D { // Isovalue // ------------------ float isovalue; // Grid // ------------------ int resx,resy; float stepx,stepy; float[] gridValue; int nbGridValue; // Voxels // ------------------ int[] voxel; int nbVoxel; // EdgeVertex // ------------------ EdgeVertex[] edgeVrt; int nbEdgeVrt; // Lines // what we pass to the renderer // ------------------ int[] lineToDraw; int nbLineToDraw; // Constructor // ------------------ Metaballs2D(){} // init(int, int) // ------------------ void init(int resx, int resy) { this.resx = resx; this.resy = resy; this.stepx = 1.0f/( (float)(resx-1)); this.stepy = 1.0f/( (float)(resy-1)); // Allocate gridValue nbGridValue = resx*resy; gridValue = new float[nbGridValue]; // Allocate voxels nbVoxel = nbGridValue; voxel = new int[nbVoxel]; // Allocate EdgeVertices edgeVrt = new EdgeVertex [2*nbVoxel]; nbEdgeVrt = 0; // Allocate Lines lineToDraw = new int[2*nbVoxel]; nbLineToDraw = 0; // Precompute some values int x,y,n,index; n = 0; for (x=0 ; x0) { if ( (toCompute & 1) > 0) // Edge 0 { t = (isovalue - gridValue[offset]) / (gridValue[offset+1] - gridValue[offset]); edgeVrt[voxel[offset]].x = vx*(1.0f-t) + t*(vx+stepx); } if ( (toCompute & 2) > 0) // Edge 3 { t = (isovalue - gridValue[offset]) / (gridValue[offset+resx] - gridValue[offset]); edgeVrt[voxel[offset]+1].y = vy*(1.0f-t) + t*(vy+stepy); } } // toCompute vy += stepy; } // for y vx += stepx; } // for x nbLineToDraw /= 2; } // getSquareIndex(int,int) // ------------------ int getSquareIndex(int x, int y) { int squareIndex = 0; int offy = resx*y; int offy1 = resx*(y+1); if (gridValue[x+offy] < isovalue) squareIndex |= 1; if (gridValue[x+1+offy] < isovalue) squareIndex |= 2; if (gridValue[x+1+offy1] < isovalue) squareIndex |= 4; if (gridValue[x+offy1] < isovalue) squareIndex |= 8; return squareIndex; } // setIsoValue(float) // ------------------ void setIsoValue(float newIso){this.isovalue = newIso;} // draw() // ------------------ void draw() { int iA,iB; int iLine; for (iLine = 0 ; iLine>16; G = (pixel & 0x0000FF00)>>8; B = (pixel & 0x000000FF); gridValue[offset] = (float) (R+G+B); } } // getSquareIndex() // ------------------ int getSquareIndex(int x, int y) { int squareIndex = 0; int offy = resx*y; int offy1 = resx*(y+1); if (gridValue[x+offy] < isovalue) squareIndex |= 1; if (gridValue[x+1+offy] < isovalue) squareIndex |= 2; if (gridValue[x+1+offy1] < isovalue) squareIndex |= 4; if (gridValue[x+offy1] < isovalue) squareIndex |= 8; return squareIndex; } // copyToImg() // ------------------ void copyRenderTargetToImg(PImage imgToCopy) { System.arraycopy(imgToCopy.pixels, 0, img.pixels,0, imgToCopy.width*imgToCopy.height); img.updatePixels(); } // blurImg() // ------------------ void blurImg(int radius) { fastblur(this.img, radius); } // drawImage() // ------------------ void drawImageScreen() { image(img, 0,0,width, height); } }; // ================================================== // class Blob // ================================================== class Blob { int id; float x,y; // position of its center float w,h; // width & height float xMin, xMax, yMin, yMax; Blob(){} void draw() { noFill(); rect(xMin*width,yMin*height, (xMax-xMin)*width, (yMax-yMin)*height); } void update() { w = (xMax-xMin)*width; h = (yMax-yMin)*height; x = 0.5f*(xMax+xMin)*width; y = 0.5f*(yMax+yMin)*height; } void print() { text("Blob "+id, xMax*width, yMax*height-28.0f); text("x="+(int)x+";y="+(int)y, xMax*width, yMax*height-14.0f); text("w="+(int)w+";h="+(int)h, xMax*width, yMax*height); } }; // ================================================== // class BlobDetection // ================================================== class BlobDetection extends EdgeDetection { int blobMaxNumber = 100; int blobNumber; Blob[] blob; // a stack of Blobs boolean[] gridVisited; PFont font; BlobDetection(int imgWidth, int imgHeight) { super(imgWidth, imgHeight); gridVisited = new boolean[nbGridValue]; blob = new Blob[blobMaxNumber]; blobNumber = 0; font = loadFont("ArialMT-48.vlw"); for (int i=0 ; i offset in the grid offset = x + resx*y; // > if we were already there, just go the next square! if (gridVisited[offset] == true) continue; // > squareIndex squareIndex = getSquareIndex(x,y); // >Found something if (squareIndex > 0 && squareIndex < 15) { findBlob(blobNumber,x,y); blobNumber++; } vy += stepy; } // for y vx += stepx; } // for x nbLineToDraw /= 2; } // findBlob() // ------------------ void findBlob(int iBlob, int x, int y) { // Reset Blob values blob[iBlob].id = iBlob; blob[iBlob].xMin = 1000.0f; blob[iBlob].xMax = -1000.0f; blob[iBlob].yMin = 1000.0f; blob[iBlob].yMax = -1000.0f; // Find it !! computeEdgeVertex(iBlob, x,y); // > This is just a temp patch (somtimes 'big' blobs are detected on the grid edges) if (blob[iBlob].xMin>=1000.0f || blob[iBlob].xMax<=-1000.0f || blob[iBlob].yMin>=1000.0f || blob[iBlob].yMax<=-1000.0f) { blobNumber--; } else blob[iBlob].update(); } // computeEdgeVertex() // ------------------ void computeEdgeVertex(int iBlob, int x, int y) { //println("x = " + x + "; y = "+y); // offset int offset = x+resx*y; // Mark voxel as visited if (gridVisited[offset] == true) return; gridVisited[offset]=true; // int iEdge, offx, offy, offAB; int[] edgeOffsetInfo; int squareIndex = getSquareIndex(x,y); float vx = (float)x*stepx; float vy = (float)y*stepy; int n = 0; while ( (iEdge = MetaballsTable.edgeCut[squareIndex][n++]) != -1) { edgeOffsetInfo = MetaballsTable.edgeOffsetInfo[iEdge]; offx = edgeOffsetInfo[0]; offy = edgeOffsetInfo[1]; offAB = edgeOffsetInfo[2]; lineToDraw[nbLineToDraw++] = voxel[(x+offx) + resx*(y+offy)] + offAB; } int toCompute = MetaballsTable.edgeToCompute[squareIndex]; float t = 0.0f; float value = 0.0f; if (toCompute>0) { if ( (toCompute & 1) > 0) // Edge 0 { t = (isovalue - gridValue[offset]) / (gridValue[offset+1] - gridValue[offset]); value = vx*(1.0f-t) + t*(vx+stepx); edgeVrt[voxel[offset]].x = value; if (value < blob[iBlob].xMin ) blob[iBlob].xMin = value; if (value > blob[iBlob].xMax ) blob[iBlob].xMax = value; } if ( (toCompute & 2) > 0) // Edge 3 { t = (isovalue - gridValue[offset]) / (gridValue[offset+resx] - gridValue[offset]); value = vy*(1.0f-t) + t*(vy+stepy); edgeVrt[voxel[offset]+1].y = value; if (value < blob[iBlob].yMin ) blob[iBlob].yMin = value; if (value > blob[iBlob].yMax ) blob[iBlob].yMax = value; } } // toCompute // Propagate to neightbors : use of Metaballs.neighborsTable byte neighborVoxel = MetaballsTable.neightborVoxel[squareIndex]; if (x0) computeEdgeVertex(iBlob, x+1,y); if (x>0 && (neighborVoxel & (1<<1))>0) computeEdgeVertex(iBlob, x-1,y); if (y0) computeEdgeVertex(iBlob, x,y+1); if (y>0 && (neighborVoxel & (1<<3))>0) computeEdgeVertex(iBlob, x,y-1); } // drawBlob() // ------------------ void drawBlobs() { for (int n=0 ; n 0) { stroke(0,0,255); rect(vx,vy,wx,wy); } vy += wy; } // for y vx += wx; } // for x } // print() // ------------------ void print() { fill(255,0,0); textFont(font, 14); for (int n=0 ; n // ================================================== void fastblur(PImage img,int radius){ if (radius<1){ return; } int w=img.width; int h=img.height; int wm=w-1; int hm=h-1; int wh=w*h; int div=radius+radius+1; int r[]=new int[wh]; int g[]=new int[wh]; int b[]=new int[wh]; int rsum,gsum,bsum,x,y,i,p,p1,p2,yp,yi,yw; int vmin[] = new int[max(w,h)]; int vmax[] = new int[max(w,h)]; int[] pix=img.pixels; int dv[]=new int[256*div]; for (i=0;i<256*div;i++){ dv[i]=(i/div); } yw=yi=0; for (y=0;y>16; gsum+=(p & 0x00ff00)>>8; bsum+= p & 0x0000ff; } for (x=0;x>16; gsum+=((p1 & 0x00ff00)-(p2 & 0x00ff00))>>8; bsum+= (p1 & 0x0000ff)-(p2 & 0x0000ff); yi++; } yw+=w; } for (x=0;x // ================================================== class HScrollbar { int swidth, sheight; // width and height of bar int xpos, ypos; // x and y position of bar float spos, newspos; // x position of slider int sposMin, sposMax; // max and min values of slider int loose; // how loose/heavy boolean over; // is the mouse over the slider? boolean locked; float ratio; HScrollbar (int xp, int yp, int sw, int sh, int l) { swidth = sw; sheight = sh; int widthtoheight = sw - sh; ratio = (float)sw / (float)widthtoheight; xpos = xp; ypos = yp-sheight/2; spos = xpos + swidth/2 - sheight/2; newspos = spos; sposMin = xpos; sposMax = xpos + swidth - sheight; loose = l; } void update() { if(over()) { over = true; } else { over = false; } if(mousePressed && over) { locked = true; } if(!mousePressed) { locked = false; } if(locked) { newspos = constrain(mouseX-sheight/2, sposMin, sposMax); } if(abs(newspos - spos) > 1) { spos = spos + (newspos-spos)/loose; } } int constrain(int val, int minv, int maxv) { return min(max(val, minv), maxv); } boolean over() { if(mouseX > xpos && mouseX < xpos+swidth && mouseY > ypos && mouseY < ypos+sheight) { return true; } else { return false; } } void draw() { fill(255); rect(xpos, ypos, swidth, sheight); if(over || locked) { fill(200, 0, 0); } else { fill(200, 0, 0); } rect(spos, ypos, sheight, sheight); } float getPos() { // convert spos to be values between // 0 and the total width of the scrollbar return spos * ratio; } }