//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //BlobDetection by v3ga //May 2005 // // // Adopted by Thomas M. Brand, March-April 2007, http://www.lowres.ch // - .mov or capture processing // - displaying center position of blob and channel texutally // - render result window to new movie (mjpeg) // - advanced sending osc: gate on/off based on previous // blob positions -> blobs must be 'tracked' in order // to assign the same channel to the same finger in the // next frame. if the deviation of the previous position // is too high, a gate off is sent (finger up/scratch finished). // - handling of assignment of available voices (poliphony) to blobs // - adjusting parameters relevant for recognition while processing (with gui) // - show/hide controls (standard feature of controlP5!!) // - show calibration help for beamer and camera adjustment // // oscP5 and controlP5 by Andreas Schlegel, http://www.sojamo.de // these libs are really GREAT!! // still needs testing and refactoring.. //=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- import processing.video.*; import blobDetection.*; import oscP5.*; import pressure.*; import controlP5.*; // some configuration int iSW=640; //sreen int iSH=480; int iCW=320; //capture int iCH=240; int iMW=320; //movie int iMH=240; boolean Rotate180=false; boolean ShowDebug=false; boolean FromWebcam=false; boolean OscEnabled=false; boolean CreateMovie=false; boolean BinCut=true; boolean ShowCalibrationHelp=false; Capture cam; int iFramerate=5; //for capture cam and moviemaker Movie mov; String sMov="tangible-test.mov"; MovieMaker movmaker; String sNewMov="tangible-test-created.jpeg"; //set this to a high value (20 = 2 * 10 fingers) //if using osc, synth must provide 20 independant tone generators/ oscilators int iPolyphony=20; int[] iFreeChannels; OscP5 oscP5; int receiveAtPort=3333; int sendToPort=10000; String host="127.0.0.1"; String oscP5event="oscEvent"; ControlP5 controlP5; int sliderBinCut=160; float sliderBlobLuminosity=0.85; int sliderMinBlobSize=0; int sliderMaxBlobSize=150; int sliderBlurRadius=3; BlobDetection theBlobDetection; boolean newFrame=false; PImage img; PFont font; String sFont="BitstreamVeraSansMono-Roman-20.vlw"; int iFontSize=10; //how many pixels a pressure point can deviate from previous one to be //recognized as pressed from the same finger int iDeviation=3; Pressure p; Vector vPressure,vPressurePrevious; // ================================================== // setup() // ================================================== void setup() { // Size of applet size(iSW,iSH); System.out.println("key combinations:"); System.out.println("ALT+h hide and show controllers"); System.out.println("ALT+s save controllers to an xml file"); System.out.println("ALT+l load controllers from an xml file"); System.out.println("ALT+k bring the key menu to the front. use the arrow keys .."); System.out.println("hold the ALT key and move controllers by dragging them."); if(OscEnabled) { initOsc(); } if(CreateMovie) { initMovieMaker(); } //build control and calibration help gui initGui(); //used for channel assignement / tone on/off handling vPressure=new Vector(); vPressurePrevious=new Vector(); iFreeChannels = new int[iPolyphony]; initChannels(); font = loadFont(sFont); textFont(font, iFontSize); if(FromWebcam) { img = new PImage(iCW,iCH); theBlobDetection = new BlobDetection(iCW, iCH); cam = new Capture(this, iCW, iCH, iFramerate); } else { img = new PImage(iMW,iMH); mov = new Movie(this,sMov); theBlobDetection = new BlobDetection(iMW,iMH); mov.loop(); } theBlobDetection.setPosDiscrimination(true); theBlobDetection.setBlobDimensionMin(sliderMinBlobSize,sliderMinBlobSize); theBlobDetection.setBlobDimensionMax(sliderMaxBlobSize,sliderMaxBlobSize); theBlobDetection.setBlobMaxNumber(200); theBlobDetection.setThreshold(sliderBlobLuminosity); // will detect bright areas whose luminosity > x.xf; } // ================================================== // draw() // ================================================== void draw() { if (newFrame) { newFrame=false; //img = new PImage(iCW, iCH); //img = new PImage(iSW, iSH); if(FromWebcam) { img = new PImage(iCW, iCH); } else { img = new PImage(iMW,iMH); } if(Rotate180) //copy each pixel to its 180 degrees rotated place in the pixel array { int h2=0; int hh=0; int ww=0; if(FromWebcam) { hh=iCH; ww=iCW; } else { hh=iMH; ww=iMW; } for (int h=hh; h>0;h--) { int w2=0; for (int w=ww; w>0;w--) { if(FromWebcam) { img.copy(cam,w,h,1,1, w2,h2,1,1); } else { img.copy(mov,w,h,1,1, w2,h2,1,1); } w2++; } h2++; } } else //copy as a whole withouth rotation { if(FromWebcam) { img.copy(cam, 0, 0, iCW, iCH, 0, 0, iCW, iCH); } else { img.copy(mov, 0, 0, iMW, iMH, 0, 0, iMW, iMH); } } img.updatePixels(); //prepare image for better blobdetection results //using radius from slider fastblur(img, sliderBlurRadius); if(BinCut) { binaryCut(); } //show image (scale to applet size) image(img,0,0,iSW,iSH); //set params from slider theBlobDetection.setThreshold(sliderBlobLuminosity); theBlobDetection.setBlobDimensionMin(sliderMinBlobSize,sliderMinBlobSize); theBlobDetection.setBlobDimensionMax(sliderMaxBlobSize,sliderMaxBlobSize); theBlobDetection.computeBlobs(img.pixels); drawBlobsAndEdges(false,true); handlePressureMatrix(); //check if calibration should be displayed if(ShowCalibrationHelp) { strokeWeight(30); stroke(255,50,50); noFill(); rect( 0, 0, width, height ); strokeWeight(5); line( 0, 0, width, height ); line( 0, height, width, 0 ); line( width/4, 0, width/4, height ); line( width/2, 0, width/2, height ); line( 3*width/4, 0, 3*width/4, height ); line( 0, height/2, width, height/2 ); } //check if we have to write a movie if(CreateMovie) { if(movmaker==null) { //not jet initialized initMovieMaker(); } //add window's pixels to movie cool! movmaker.add(); } else { //check if was started and we have to stop if(movmaker!=null) { movmaker.stop(); System.out.println("movie recording finished!"); movmaker=null; } } } } void handlePressureMatrix() { vPressurePrevious = vPressure; vPressure = new Vector(); noFill(); Blob b; EdgeVertex eA,eB; int iPressurePreviousSize = vPressurePrevious.size(); for (int n=0 ; n 0) { Pressure p = new Pressure(iXCenterAbsolute,iYCenterAbsolute,iWAbsolute*iHAbsolute,iFree,false); //update freechannel setChannel(iFree,0); vPressure.add(p); debug("New Pressure Point:"+iXCenterAbsolute+"/"+iYCenterAbsolute+" Channel assigned: "+iFree); } else { //blocked, polyphony reached, set channel to 0 //Pressure p = new Pressure(iXCenterAbsolute,iYCenterAbsolute,iWAbsolute*iHAbsolute,0,true); //vPressure.add(p); debug("Pressure Point:"+iXCenterAbsolute+"/"+iYCenterAbsolute+" accepted but polyphony reached! not added to vector."); } } } debug("Pressure Points in this frame: "+vPressure.size()+" diff to previous: "+(vPressure.size()-iPressurePreviousSize)); //now handle current pressure vector handlePressureVectorChannels(); debug("updated channels."); if (OscEnabled) { if(oscP5 == null) { //not yet initialized initOsc(); } handlePressureVectorOsc(); debug("osc done.."); } debug("frame done."); } void handlePressureVectorOsc() { //handle pitch and cutoff int i=0; for (Enumeration e = vPressure.elements(); e.hasMoreElements(); i++) { Pressure p = (Pressure)e.nextElement(); int iChannel=p.iChannel; float fPitch = (float)p.x/iSW; sendOsc("/Pitch"+iChannel,fPitch); debug("sent osc pitch"+iChannel+" "+fPitch); float fCutoff = (float)p.y/iSH; sendOsc("/P Cutoff"+iChannel,fCutoff); debug("sent osc cutoff"+iChannel+" "+fCutoff); } //handle gate on/off for (i=0;i 140) //get value from slider if(fGrey > sliderBinCut) { img.pixels[i] = color(255,255,255); } else { //leave as is for the moment img.pixels[i] = iPixel; } } img.updatePixels(); } // ================================================== // drawBlobsAndEdges() // ================================================== void drawBlobsAndEdges(boolean drawBlobs, boolean drawEdges) { noFill(); Blob b; EdgeVertex eA,eB; int iPolyCount=0; 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