// ================================================== // Working name: Time Blobs // Aaron Steed, John Holder, Hedley Roberts // All singing, all dancing blob detection implementations // ================================================== import processing.video.*; Capture myCapture; BlobDetection blobDetection; BlobBuffer [][][] blobBuffer; Buttons [] buttons; PImage swap,swapSmall; //Image buffers float edgeThreshold = 300.0f; PFont buttonFont; int ratio = 4; //denominator for scaling of image before scanning int saveCount; int totalFrames = 6800; //index of last frame int playheadStart = 6750; int targetWidth = 640; int targetHeight = 480; int playhead = 6750; //in exhibit started at 1000 for 4 figure format - because Macs count from the left and think 10 follows 1 int white = 0; int blobFrames = 5; int blobNumber = 100; int thresholdLevelsMax = 12; int thresholdLevels = 5; int blobFramesMax = 20; float threeDDepth = 10; int tunnelAlpha = 255; int whiteSwitch = 0; int edgeBlobSwitch = 0; int depthFillSwitch = 0; boolean lines = false; //edge trails setting boolean keyOK = true; //compensation for pressure pads being held down boolean webcam = false; //Sets Web-Cam version or Frame Loader version - edit here to "true" if using both modes boolean drawEdges = true; //draws edges boolean saveImg = false; //Save frames to disk boolean playing = true; //Cycle thru frames on disk boolean drawImg = true; //Draw capture image boolean twoDimensional = true; //Project blobs into 3D boolean tunnel = false; //Map blobs in 3D as tunnels boolean tunnelShift = false; //Ignore massive shifts in tunnel rendering boolean tunnelOutline = false; //Draw outline of tunnel boolean tunnelFill = true; //Map capture on to tunnel walls boolean blobs = true; //Draw blobs void setup() { size(640,480,P3D); //do not use OPENGL with frame-loader, it crashes OPENGL if(webcam){ //String s = "TRUST 120 SPACEC@M-WDM"; String s = "Creative WebCam Live! Pro #2-WDM"; //String s = "Logitech QuickCam Communicate-WDM"; myCapture = new Capture(this, s, targetWidth/ratio, targetHeight/ratio, 12); } blobDetection = new BlobDetection(targetWidth/ratio,targetHeight/ratio); blobDetection.setIsoValue(edgeThreshold); blobBuffer = new BlobBuffer[blobFramesMax][thresholdLevelsMax][blobNumber]; for (int i1 = 0; i1 < blobFramesMax; i1++){ for (int i2 = 0; i2 < thresholdLevelsMax; i2++){ for (int i3 = 0; i3 < blobNumber; i3++){ blobBuffer[i1][i2][i3] = new BlobBuffer(0,0,0,0); } } } swapSmall = loadImage("c.gif"); stepforward(); buttons = new Buttons[4]; String [] buttonNames = new String[4]; buttonNames[0] = "blobs/edges"; buttonNames[1] = "edge trails"; buttonNames[2] = "3D"; buttonNames[3] = "background"; for (int i = 0; i < buttons.length; i++){ buttons[i] = new Buttons(40+110*i,450,100,30,buttonNames[i]); } buttonFont = loadFont("CenturyGothic-48.vlw"); println("P:toggle play, S:toggle save images, +:frame forward, -:frame back, 1&2:bleach camera image, 3&4:blobFrames-/+,"); println("I:toggle edges, R:save frame, D:toggle image, T:toggle 3D, O:toggle tunnel 5&6:tunnel Alpha"); println("7&8:threhold levels C:toggle tunnel shift U:toggle tunnel outline F:tunnel fill 9&0:3D depth Z:Blobs off/on"); println("W:webcam/timelapse"); } void draw() { if(tunnel){ ambientLight(100,100,100); pointLight(150,150,150,width/2,height/2,0); directionalLight(155, 155, 155, 0, 0, -1); } if (webcam){ if(myCapture.available()) { myCapture.read(); prepareBlobBuffer(); /* //depth trouble shooting translate(width/2,0,0); rotateX((PI/height)*mouseY); rotateY((PI/width)*mouseX); */ render(); } } if (saveImg && playing){ saveFrames(); } if (playing){ stepforward(); } for (int i = 0; i < buttons.length; i++){ buttons[i].draw(); } } // ================================================== // Events // ================================================== public void mousePressed(){ int b = -1; for (int i = 0; i < buttons.length; i++){ if(buttons[i].over()){ //println(i); b = i; } } switch(b){ case 0: lines = false; edgeBlobSwitch = (edgeBlobSwitch+1)%3; switch(edgeBlobSwitch){ case 0: drawEdges = true; blobs = true; break; case 1: drawEdges = false; blobs = true; break; case 2: drawEdges = true; blobs = false; break; } break; case 1: if (lines){ drawImg = true; blobs = true; drawEdges = true; twoDimensional = true; lines = false; } else{ lines = true; drawImg = false; blobs = false; drawEdges = true; twoDimensional = true; } break; case 2: lines = false; depthFillSwitch = (depthFillSwitch+1)%3; switch(depthFillSwitch){ case 0: twoDimensional = true; break; case 1: blobs = true; twoDimensional = false; tunnel = false; break; case 2: blobs = true; twoDimensional = false; tunnel = true; break; } break; case 3: lines = false; drawImg = !drawImg; break; } render(); } void keyReleased(){ keyOK = true; } void keyPressed(){ if (keyOK){ switch(key){ /* //reintroduce to code for webcam use case 'w': case 'W': if(webcam){ webcam = false; playing = true; } else{ webcam = true; playing = false; } */ case '1': case '!': white = constrain(white-10,0,255); break; case '2': case '"': white = constrain(white+10,0,255); break; case 's': case 'S': saveImg = !saveImg; println("save image:"+saveImg); break; case 'p': case 'P': playing = !playing; println("play:"+playing); break; case 'i': case 'I': drawEdges = !drawEdges; break; case '+': case '=': stepforward(); break; case '-': case '_': stepback(); break; case 'r': case 'R': stepforward(); saveFrames(); break; case '3': case '£': blobFrames = constrain(blobFrames-1,2,blobFramesMax); break; case '4': case '$': blobFrames = constrain(blobFrames+1,2,blobFramesMax); break; case 'd': case 'D': drawImg = !drawImg; break; case 't': case 'T': twoDimensional = !twoDimensional; break; case 'o': case 'O': tunnel = !tunnel; break; case '5': case '%': tunnelAlpha = constrain(tunnelAlpha-10,0,255); break; case '6': case '^': tunnelAlpha = constrain(tunnelAlpha+10,0,255); break; case '7': case '&': thresholdLevels = constrain(thresholdLevels-1,1,thresholdLevelsMax); break; case '8': case '*': thresholdLevels = constrain(thresholdLevels+1,1,thresholdLevelsMax); break; case 'c': case 'C': tunnelShift = !tunnelShift; break; case 'u': case 'U': tunnelOutline = !tunnelOutline; break; case 'f': case 'F': tunnelFill = !tunnelFill; break; case '9': case '(': threeDDepth = constrain(threeDDepth-5,5,200); break; case '0': case ')': threeDDepth = constrain(threeDDepth+5,5,200); break; case 'z': case 'Z': blobs = !blobs; break; case 'l': case 'L': lines = !lines; break; } render(); } keyOK = false; } // METHODS ========================================== void saveFrames(){ if (saveCount < totalFrames){ String saveNumber = str(saveCount); while (saveNumber.length() < 5){ saveNumber = "0".concat(saveNumber); } save("tl_"+saveNumber+".tif"); saveCount++; } } // ================================================== // playhead // ================================================== void stepforward(){ prepareBlobBuffer(); swap = null; swap = loadImage("importa ("+playhead+").jpg"); if (playhead == (totalFrames-1) && saveImg){ println("All done buddy!"); } render(); //println(playhead); playhead++; if (playhead == totalFrames){ playhead = playheadStart; } } void stepback(){ prepareBlobBuffer(); swap = null; swap = loadImage("importa ("+playhead+").jpg"); render(); playhead--; if (playhead == playheadStart){ playhead = totalFrames; } } void prepareBlobBuffer(){ //roll back blob frames for (int i1 = 0; i1 < blobFrames-1; i1++){ for (int i2 = 0; i2 < thresholdLevels; i2++){ for (int i3 = 0; i3 < blobNumber; i3++){ blobBuffer[i1][i2][i3].x = blobBuffer[i1+1][i2][i3].x; blobBuffer[i1][i2][i3].y = blobBuffer[i1+1][i2][i3].y; blobBuffer[i1][i2][i3].w = blobBuffer[i1+1][i2][i3].w; blobBuffer[i1][i2][i3].h = blobBuffer[i1+1][i2][i3].h; blobBuffer[i1][i2][i3].cx = blobBuffer[i1+1][i2][i3].cx; blobBuffer[i1][i2][i3].cy = blobBuffer[i1+1][i2][i3].cy; blobBuffer[i1][i2][i3].valid = blobBuffer[i1+1][i2][i3].valid; } } } //invalidate fresh meat for (int i1 = 0; i1 < thresholdLevels; i1++){ for (int i2 = 0; i2 < blobNumber; i2++){ blobBuffer[blobFrames-1][i1][i2].valid = false; } } } // ================================================== // render // ================================================== void render(){ if (!lines){ background(0); } else{ fill(0,0,0,10); rect(0,0,width,height); } if(webcam){ swapSmall = myCapture; swap = myCapture; } else { swapSmall.copy(swap,0,0,swap.width,swap.height,0,0,swapSmall.width,swapSmall.height); } fill(255,255,255,white); if (drawImg){ if(!twoDimensional){ pushMatrix(); translate(0,0,(0-blobFrames+1)*threeDDepth); image(swap,0,0,width,height); rect(0,0,width,height); popMatrix(); } else{ image(swap,0,0,width,height); rect(0,0,width,height); } } for(int i = 0; i < thresholdLevels; i++){ blobDetection.setIsoValue((i*90.0)+200.0); blobDetection.apply(swapSmall, 1); stroke(180,180,20,200); if(drawEdges){ blobDetection.draw(); } blobDetection.drawBlobs(i); } if (blobs){ if (twoDimensional){ drawBuffer2D(); } else{ drawBuffer3D(); } } } // ================================================== void drawBuffer2D(){ for (int i1 = 0; i1 < blobFrames; i1++){ for (int i2 = 0; i2 < thresholdLevels; i2++){ for (int i3 = 0; i3 < blobNumber; i3++){ noFill(); stroke(255,0,0,(255.0/blobFrames)*i1); if(blobBuffer[i1][i2][i3].valid){ blobBuffer[i1][i2][i3].draw(); } } } } } void drawBuffer3D(){ if (!tunnel){ //tunnel of squares for (int i1 = 0; i1 < blobFrames; i1++){ for (int i2 = 0; i2 < thresholdLevels; i2++){ for (int i3 = 0; i3 < blobNumber; i3++){ noFill(); stroke(255,0,0,(255.0/blobFrames)*i1); if(blobBuffer[i1][i2][i3].valid){ pushMatrix(); translate(0,0,0-((blobFrames-1)-i1)*threeDDepth); blobBuffer[i1][i2][i3].draw(); popMatrix(); } } } } } else{ //tunnel filled in for (int i1 = 0; i1 < blobFrames-1; i1++){ for (int i2 = 0; i2 < thresholdLevels; i2++){ for (int i3 = 0; i3 < blobNumber; i3++){ if(tunnelOutline){ stroke(0,0,0,tunnelAlpha); } else{ noStroke(); } if (tunnelFill){ fill(255,255,255,tunnelAlpha); } else{ noFill(); } boolean drawStuff = false; if(blobBuffer[i1][i2][i3].valid && blobBuffer[i1+1][i2][i3].valid && tunnelShift){ drawStuff = true; } if(blobBuffer[i1][i2][i3].valid && blobBuffer[i1+1][i2][i3].valid && !tunnelShift){ int shiftSensitivity = 1; if(dist(blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y,blobBuffer[i1+1][i2][i3].x,blobBuffer[i1+1][i2][i3].y) < shiftSensitivity && dist(blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y,blobBuffer[i1+1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1+1][i2][i3].y) < shiftSensitivity && dist(blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h,blobBuffer[i1+1][i2][i3].x,blobBuffer[i1+1][i2][i3].y+blobBuffer[i1][i2][i3].h) < shiftSensitivity && dist(blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h,blobBuffer[i1+1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1+1][i2][i3].y+blobBuffer[i1][i2][i3].h) < shiftSensitivity){ drawStuff = true; } } if(drawStuff){ if(webcam){ swap = myCapture; } //left hand panel beginShape(QUADS); texture(swap); vertex(blobBuffer[i1+1][i2][i3].x,blobBuffer[i1+1][i2][i3].y+blobBuffer[i1+1][i2][i3].h,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); vertex(blobBuffer[i1+1][i2][i3].x,blobBuffer[i1+1][i2][i3].y,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y); vertex(blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y); vertex(blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); endShape(); //top panel beginShape(QUADS); texture(swap); vertex(blobBuffer[i1+1][i2][i3].x+blobBuffer[i1+1][i2][i3].w,blobBuffer[i1+1][i2][i3].y,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y); vertex(blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); vertex(blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); vertex(blobBuffer[i1+1][i2][i3].x,blobBuffer[i1+1][i2][i3].y,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y); endShape(); //right hand panel beginShape(QUADS); texture(swap); vertex(blobBuffer[i1+1][i2][i3].x+blobBuffer[i1+1][i2][i3].w,blobBuffer[i1+1][i2][i3].y,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y); vertex(blobBuffer[i1+1][i2][i3].x+blobBuffer[i1+1][i2][i3].w,blobBuffer[i1+1][i2][i3].y+blobBuffer[i1+1][i2][i3].h,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); vertex(blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); vertex(blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y); endShape(); //bottom panel beginShape(QUADS); texture(swap); vertex(blobBuffer[i1+1][i2][i3].x,blobBuffer[i1+1][i2][i3].y+blobBuffer[i1+1][i2][i3].h,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); vertex(blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x,blobBuffer[i1][i2][i3].y); vertex(blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h,0-((blobFrames-1)-(i1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y); vertex(blobBuffer[i1+1][i2][i3].x+blobBuffer[i1+1][i2][i3].w,blobBuffer[i1+1][i2][i3].y+blobBuffer[i1+1][i2][i3].h,0-((blobFrames-1)-(i1+1))*threeDDepth,blobBuffer[i1][i2][i3].x+blobBuffer[i1][i2][i3].w,blobBuffer[i1][i2][i3].y+blobBuffer[i1][i2][i3].h); endShape(); //blobBuffer[i1][i2][i3].draw(); } } } } } } PImage blendd(PImage one, PImage two){ for (int iy = 0; iy < one.height; iy++){ for (int ix = 0; ix < one.width; ix++){ color c1 = one.pixels[ix + iy * one.width]; color c2 = two.pixels[ix + iy * two.width]; int r1=c1>>16&0xff; int g1=c1>>8&0xff; int b1=c1&0xff; int r2=c2>>16&0xff; int g2=c2>>8&0xff; int b2=c2&0xff; one.pixels[ix + iy * one.width] = color ((r1+r2)>>1,(g1+g2)>>1,(b1+b2)>>1); } } one.updatePixels(); return one; } // ================================================== // class Buttons // ================================================== class Buttons{ int x,y,w,h; String myName; Buttons(int x, int y, int w, int h, String myName){ this.x = x; this.y = y; this.w = w; this.h = h; this.myName = myName; } void draw(){ if (over()){ fill (255,100,100); } else{ fill (200,50,50); } stroke(0); rect(x,y,w,h); fill(0); textFont(buttonFont,15); text(myName,x+5,y+15); } boolean over(){ if (mouseX <= x+w && mouseX >= x && mouseY <= y+h && mouseY >= y){ return true; } else{ return false; } } } // ================================================== // class BlobBuffer // ================================================== class BlobBuffer{ float x,y,w,h,cx,cy; boolean valid; BlobBuffer(float x, float y, float w, float h){ this.x = x; this.y = y; this.w = w; this.h = h; cx = 0.0; cy = 0.0; } void update(float nx, float ny, float nw, float nh, float ncx, float ncy){ x = nx; y = ny; w = nw; h = nh; cx = ncx; cy = ncy; valid = true; } void draw(){ beginShape(); vertex(x,y); vertex(x+w,y); vertex(x+w,y+h); vertex(x,y+h); endShape(); //rect(x,y,w,h); } } //V3GA'S SLIGHTLY ADAPTED CODE - INCLUDING BLOB COUNTING PATCHES // ================================================== // 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(int level) { noFill(); //rect(xMin*width,yMin*height, (xMax-xMin)*width, (yMax-yMin)*height); blobBuffer[blobFrames-1][level][id].update(xMin*width,yMin*height, (xMax-xMin)*width, (yMax-yMin)*height, x, y); } 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 = constrain(blobNumber+1,0,99); } vy += stepy; } // for y vx += stepx; } // for x nbLineToDraw /= 2; } // findBlob() // ------------------ void findBlob(int iBlob, int x, int y) { //maximum blob patch iBlob = constrain(iBlob,1,blobMaxNumber-1); // 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(int level) { 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, 8); 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