First commit

This commit is contained in:
charlesbvll 2020-10-08 20:15:00 +02:00
commit 9877eb16fd
23 changed files with 32168 additions and 0 deletions

127
BlobDetection.pde Normal file
View File

@ -0,0 +1,127 @@
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
class BlobDetection {
PImage findConnectedComponents(PImage input, boolean onlyBiggest) {
PImage result = input.copy();
int [] labels = new int [result.width * result.height];
// **********************************************************************
// First pass: label the pixels and store labels' equivalences
// **********************************************************************
List<TreeSet<Integer>> labelsEquivalences = new ArrayList<TreeSet<Integer>>();
ArrayList<Integer> meter = new ArrayList<Integer>();
int currLabel = 0;
// First loop - for height
for (int y = 0; y < result.height; ++y) {
TreeSet<Integer> colourAdjacent = new TreeSet<Integer>();
// Second loop - for width
for (int x = 0; x < result.width; ++x) {
if (brightness(result.pixels[y*result.width+x]) == 255) {
colourAdjacent.clear();
for (int i = x-1; i <= x+1; i++) {
// Checks first row
if (y != 0) {
if (0 <= i && i < result.width && labels[(y-1)*result.width+i] != 0) {
colourAdjacent.add(labels[(y-1)*result.width+i]);
}
} else {
// Do nothing since we are at the first line and we cannot consider the y-1 th line...
}
}
if (colourAdjacent.isEmpty()) {
TreeSet tree_set = new TreeSet<Integer>();
tree_set.add(++currLabel);
labelsEquivalences.add(tree_set);
meter.add(1);
labels[y*result.width+x] = currLabel;
} else {
if (colourAdjacent.size()>1) {
for (Integer i : colourAdjacent) {
labelsEquivalences.get(i-1).addAll(colourAdjacent);
}
}
int first = colourAdjacent.first();
meter.set(first-1, meter.get(first-1)+1);
labels[y*result.width+x] = first;
}
}
}
}
// **********************************************************************
// Second pass: re-label the pixels by their equivalent class
// if onlyBiggest = = true, count the number of pixels for each label
// **********************************************************************
// Merge all equivalence classes of labels
for (int labEq = 0; labEq < labelsEquivalences.size(); ++labEq) {
TreeSet<Integer> tree_set = labelsEquivalences.get(labEq);
if (tree_set.size()>1) {
TreeSet<Integer> acc = new TreeSet<Integer>();
for (Integer i : tree_set) {
TreeSet<Integer> other = labelsEquivalences.get(i-1);
if (tree_set != other) {
acc.addAll(other);
}
}
tree_set.addAll(acc);
for (Integer i : tree_set) {
labelsEquivalences.set(i-1, tree_set);
}
}
}
// Evaluate size of blob
int[] blobSize = new int[labelsEquivalences.size()];
for (int labEq = 0; labEq<labelsEquivalences.size(); ++labEq) {
TreeSet<Integer> tree_set = labelsEquivalences.get(labEq);
int total = 0;
for (Integer i : tree_set) {
total += meter.get(i-1);
}
blobSize[labEq] = total;
}
// **********************************************************************
// Finally:
// if onlyBiggest = = false, output an image with each blob colored in one uniform color
// if onlyBiggest = = true, output an image with the biggest blob in white and others in black
// **********************************************************************
int[] colorArray = new int[blobSize.length];
if (onlyBiggest) {
int maximum = -1;
for (int i = 0; i < blobSize.length; i++) {
maximum = max(maximum, blobSize[i]);
}
for (int i = 0; i < blobSize.length; i++) {
colorArray[i] = (blobSize[i] == maximum) ? color(255) : color(0);
}
} else {
for (TreeSet<Integer> tree_set : labelsEquivalences) {
int randomColor = color(random(255), random(255), random(255));
for (Integer i : tree_set) {
colorArray[i-1] = randomColor;
}
}
}
// Fill the map with color according to their label
for (int i = 0; i < result.width*result.height; ++i) {
if (labels[i] != 0) {
result.pixels[i] = colorArray[labels[i]-1];
}
}
return result;
}
}

70
Cylinder.pde Normal file
View File

@ -0,0 +1,70 @@
final float cylinderBaseSize = 20;
final float cylinderHeight = 50;
final int cylinderResolution = 40;
final color defaultCylinderColour = color(220, 60, 60);
class Cylinder {
PShape closedCylinder = new PShape();
PShape openCylinder = new PShape();
PShape topDisk = new PShape();
//#############################################
//-----------CONSTRUCTOR OF CYLINDER-----------
//#############################################
Cylinder(color cylinderColour) {
// Initialise the Cylinder
closedCylinder = new PShape();
openCylinder = new PShape();
topDisk = new PShape();
float angle;
float[] x = new float[cylinderResolution + 1];
float[] z = new float[cylinderResolution + 1];
//Get the x and y position on a circle for all the sides
for(int i = 0; i < x.length; i++) {
angle = (TWO_PI / cylinderResolution) * i;
x[i] = sin(angle) * cylinderBaseSize;
z[i] = cos(angle) * cylinderBaseSize;
}
//#############################################
//-----------SHAPE OF OPEN CYLINDER------------
//#############################################
openCylinder = createShape();
openCylinder.beginShape(QUAD_STRIP);
openCylinder.fill(cylinderColour);
//Draw the border of the cylinder
for(int i = 0; i < x.length; i++) {
openCylinder.vertex(x[i] , 0, z[i]);
openCylinder.vertex(x[i], -cylinderHeight, z[i]);
}
openCylinder.endShape();
//#############################################
//-----------DISK OF CLOSED CYLINDER-----------
//#############################################
topDisk = createShape();
topDisk.beginShape(TRIANGLE_FAN);
topDisk.fill(cylinderColour);
for (int i = 0; i< x.length; i++) {
topDisk.vertex(x[i], -cylinderHeight, z[i]);
}
topDisk.endShape();
// MERGE TOP DISK WITH OPEN CYLINDER
closedCylinder = createShape(GROUP);
closedCylinder.addChild(openCylinder);
closedCylinder.addChild(topDisk);
}
//#############################################
//----------------DISPLAY METHOD---------------
//#############################################
void display() {
gameSurface.shape(closedCylinder);
}
}

140
DrawMethods.pde Normal file
View File

@ -0,0 +1,140 @@
//#############################################
//------------------DRAW GAME------------------
//#############################################
void drawGame() {
gameSurface.beginDraw();
basic_settings();
drawBoard();
drawSphere();
drawParticleSystem();
gameSurface.endDraw();
}
//#############################################
//--------------DRAW SCORE_BOARD---------------
//#############################################
void drawScoreBoard() {
scoreBoard.beginDraw();
scoreBoard.background(scoreColor);
scoreBoard.text("Total Score : ", 10, 25);
scoreBoard.text(Float.toString(getPoints()), 20, 45);
scoreBoard.text("Velocity : ", 10, 65);
scoreBoard.text(Float.toString(playable_sphere.velocity.mag()), 20, 85);
scoreBoard.text("Last score : ", 10, 105);
scoreBoard.text(Float.toString(getLastPoints()), 20, 125);
scoreBoard.endDraw();
}
//#############################################
//----------------DRAW TOP_VEIW----------------
//#############################################
void drawTopView() {
topView.beginDraw();
topView.background(topViewBoard);
float ellipseX = (playable_sphere.location().x + boxLength/2) * square_for_score/boxLength;
float ellipseY = (playable_sphere.location().z + boxLength/2) * square_for_score/boxLength;
float ellipseWidth = 2*radius_sphere*square_for_score/boxLength;
float ellipseHeight = ellipseWidth;
topView.fill(colorSphereTopView);
topView.ellipse(ellipseX, ellipseY, ellipseWidth, ellipseHeight);
if (particle_system!=null) {
particle_system.particleTopView();
}
topView.endDraw();
}
//#############################################
//---------------DRAW BAR_CHART----------------
//#############################################
void drawBarChart() {
barChart.beginDraw();
barChart.background(colorBarChart);
barChart.rectMode(CORNER);
barChart.fill(255);
score_scale = 0.5 + hs_scroll_bar.getPos();
rectIndex = 0;
if (lastSecond != second() && !gamePaused) {
scores.add(getPoints());
}
// Display the chart
for (Float score_index : scores) {
float scaling = (Math.abs(score_index) > 2) ? 2*(log(Math.abs(score_index))) : score_index;
float dimRect = rectSize*score_scale;
float coordX = rectIndex*rectSize*score_scale;
for (int col = 0; col <= Math.abs(scaling); ++col) {
float coordY = col*rectSize*score_scale;
if(score_index < 0) {
barChart.rect(coordX, coordY + score_graphic/2, dimRect, dimRect);
} else {
barChart.rect(coordX, -coordY + score_graphic/2, dimRect, dimRect);
}
}
rectIndex++;
}
lastSecond = second();
barChart.endDraw();
}
//#############################################
//----------AUX METHODS FOR DRAW_GAME----------
//#############################################
//---------------BASIC SETTINGS----------------
void basic_settings(){
gameSurface.noStroke();
gameSurface.directionalLight(directionalColor.x, directionalColor.y, directionalColor.z, 0, 1, 0);
gameSurface.ambientLight(ambientColor, ambientColor, ambientColor);
gameSurface.background(backgroundColor);
}
//-------------SETUP OF THE BOARD--------------
void drawBoard(){
gameSurface.translate(width/2, height/2, -score_graphic);
gameSurface.rotateX(thetaX);
gameSurface.rotateY(thetaY);
gameSurface.rotateZ(thetaZ);
gameSurface.fill(boardColor);
gameSurface.box(boxLength, boxThickness, boxLength);
}
//------------SETUP OF THE SPHERE--------------
void drawSphere(){
if(!gamePaused) {
playable_sphere.update();
}
playable_sphere.checkEdges();
playable_sphere.display();
}
//--------SETUP OF THE PARTICLE SYSTEM---------
void drawParticleSystem(){
for (int i = particle_system.particles.size(); i > 0; --i) {
PVector position = particle_system.particles.get(i-1);
gameSurface.pushMatrix();
gameSurface.translate(position.x,0 , position.z ); //translate origin back to left upper corner
cylinder.display();
gameSurface.popMatrix();
}
if(particle_system.origin != null) {
gameSurface.pushMatrix();
gameSurface.translate(particle_system.origin.x , -cylinderHeight , particle_system.origin.z ); //translate origin back to left upper corner
gameSurface.shape(robotnik);
gameSurface.popMatrix();
}
if(!particle_system.particles.isEmpty() && !gamePaused) {
particle_system.run();
}
}

54
GameSettings.pde Normal file
View File

@ -0,0 +1,54 @@
final int window_size_x = 800;
final int window_size_y = 800;
final float depth = -1000;
final int score_graphic = 150;
final int square_for_score = score_graphic-10;
// Colors drawGame
final color backgroundColor = color(216, 223, 239);
final color boardColor = color(78, 122, 207);
// Color bottom of window
final color bottomColor = color(162, 165, 200);
// Color drawScore
final color scoreColor = color(132, 60, 101);
// Color drawTopView
final color topViewBoard = color(56, 91, 185);
final color colorSphereTopView = color(0, 0, 0);
final color colorVillainTopView = color(150,20,20);
// Color drawBarChart
final color colorBarChart = color(125,128,190);
final PVector directionalColor = new PVector(50, 100, 200);
final float ambientColor = 200;
final PVector topViewBoardColor = new PVector(56, 91, 185);
final int fps = 60;
final float boxLength = 500;
final float boxThickness = 20;
final float gravityConstant = 0.1;
final float normalForce = 1;
final float mu = 0.01;
final float frictionMagnitude = normalForce * mu;
final float radius_sphere = 25;
float angularSpeed = 1;
float defaultGain = 1;
float thetaX = 0;
float thetaY = 0;
float thetaZ = 0;
float oldThetaX = 0;
float oldThetaY = 0;
float oldThetaZ = 0;
PShape robotnik;
PImage texture;
boolean gamePaused = false;
boolean shiftPressed = false;
float score_scale = 1;
float rectIndex = 0;
float lastSecond = 0;
float rectSize = 8;

110
HScrollbar.pde Normal file
View File

@ -0,0 +1,110 @@
class HScrollbar {
float barWidth; //Bar's width in pixels
float barHeight; //Bar's height in pixels
float xPosition; //Bar's x position in pixels
float yPosition; //Bar's y position in pixels
float sliderPosition, newSliderPosition; //Position of slider
float sliderPositionMin, sliderPositionMax; //Max and min values of slider
boolean mouseOver; //Is the mouse over the slider?
boolean locked; //Is the mouse clicking and dragging the slider now?
/**
* @brief Creates a new horizontal scrollbar
*
* @param x The x position of the top left corner of the bar in pixels
* @param y The y position of the top left corner of the bar in pixels
* @param w The width of the bar in pixels
* @param h The height of the bar in pixels
*/
HScrollbar (float x, float y, float w, float h) {
barWidth = w;
barHeight = h;
xPosition = x;
yPosition = y;
sliderPosition = xPosition + barWidth/2 - barHeight/2;
newSliderPosition = sliderPosition;
sliderPositionMin = xPosition;
sliderPositionMax = xPosition + barWidth - barHeight;
}
/**
* @brief Updates the state of the scrollbar according to the mouse movement
*/
void update() {
if (isMouseOver()) {
mouseOver = true;
}
else {
mouseOver = false;
}
if (mousePressed && mouseOver) {
locked = true;
}
if (!mousePressed) {
locked = false;
}
if (locked) {
newSliderPosition = constrain(mouseX - barHeight/2, sliderPositionMin, sliderPositionMax);
}
if (abs(newSliderPosition - sliderPosition) > 1) {
sliderPosition = sliderPosition + (newSliderPosition - sliderPosition);
}
}
/**
* @brief Clamps the value into the interval
*
* @param val The value to be clamped
* @param minVal Smallest value possible
* @param maxVal Largest value possible
*
* @return val clamped into the interval [minVal, maxVal]
*/
float constrain(float val, float minVal, float maxVal) {
return min(max(val, minVal), maxVal);
}
/**
* @brief Gets whether the mouse is hovering the scrollbar
*
* @return Whether the mouse is hovering the scrollbar
*/
boolean isMouseOver() {
if (mouseX > xPosition && mouseX < xPosition+barWidth &&
mouseY > yPosition && mouseY < yPosition+barHeight) {
return true;
}
else {
return false;
}
}
/**
* @brief Draws the scrollbar in its current state
*/
void display() {
noStroke();
fill(204);
rect(xPosition, yPosition, barWidth, barHeight);
if (mouseOver || locked) {
fill(0, 0, 0);
}
else {
fill(102, 102, 102);
}
rect(sliderPosition, yPosition, barHeight, barHeight);
}
/**
* @brief Gets the slider position
*
* @return The slider position in the interval [0,1] corresponding to [leftmost position, rightmost position]
*/
float getPos() {
return (sliderPosition - xPosition)/(barWidth - barHeight);
}
}

14
HoughComparator.pde Normal file
View File

@ -0,0 +1,14 @@
class HoughComparator implements java.util.Comparator<Integer> {
int[] accumulator;
public HoughComparator(int[] accumulator) {
this.accumulator = accumulator;
}
@Override
public int compare(Integer l1, Integer l2) {
if (accumulator[l1] > accumulator[l2] || (accumulator[l1] == accumulator[l2] && l1 < l2))
return -1;
return 1;
}
}

384
ImageProcessing.pde Normal file
View File

@ -0,0 +1,384 @@
//********Merge with ImgProcessing******** //<>//
import gab.opencv.*;
//********Merge with ImgProcessing********
import processing.video.*;
class ImageProcessing extends PApplet {
Movie cam;
OpenCV opencv;
TwoDThreeD converter;
PVector rotation;
PImage img;
/********SETTINGS FOR HSB TRESHOLD********/
final int Hmin = 105, Hmax = 140;
final int Smin = 35, Smax = 255;
final int Bmin = 40, Bmax = 255;
/*****************************************/
BlobDetection blobDetection;
List<PVector> detectedCorners;
void settings() {
size(455, 255, P2D);
}
void setup() {
opencv = new OpenCV(this, 100, 100);
blobDetection = new BlobDetection();
//Video
cam = new Movie (this, "testvideo.avi"); //Put the absolute path here !!
cam.loop();
}
void draw() {
if (cam.available() == true) cam.read();
img = cam.get();
converter =new TwoDThreeD(img.width, img.height, 0);
img.resize(img.width/2, img.height/2);
image(img, 0, 0);
img = thresholdHSB(img, Hmin, Hmax, Smin, Smax, Bmin, Bmax );
img = convolute(img);
img = blobDetection.findConnectedComponents(img, false);
img = scharr(img);
img = threshold(img, 100); // PImage threshold(PImage img, int threshold) the threshold value can be changed
plotLines(hough(img, 4), img);
ArrayList<PVector> lines = hough(img, 4);
detectedCorners = new QuadGraph().findBestQuad(lines, width, height, width*height, width*height/64, false);
stroke(0);
for (PVector vector : detectedCorners) {
fill(color(255, 255, 255));
ellipse(vector.x, vector.y, 30, 30);
}
}
PVector getRotation() {
if (detectedCorners == null) {
return new PVector(0, 0, 0);
}
if (detectedCorners.size() == 4) {
for (PVector corner : detectedCorners) {
corner.set(corner.x, corner.y, 1);
}
rotation = converter.get3DRotations(detectedCorners);
if ((rotation.x) <= - PI/3) rotation.set(rotation.x + PI, rotation.y, rotation.z);
else if ((rotation.x) >= PI/3) rotation.set(rotation.x - PI, rotation.y, rotation.z);
}
return rotation;
}
//*******************************
// CONVOLUTION
//*******************************
PImage convolute(PImage img) {
float[][] kernel = {
{ 9, 12, 9},
{ 12, 15, 12},
{ 9, 12, 9}};
float normFactor = 99.f;
// create a greyscale image (type: ALPHA) for output
PImage result = createImage(img.width, img.height, ALPHA);
// kernel size N = 3
for (int i = 1; i < img.width -1; i++) {
for (int j = 1; j< img.height -1; j++) {
int tot = 0;
int c = 0;
for (int l = 0; l<3; l++) {
for (int h = 0; h<3; h++) {
c = i-1 + img.width*(j-1+h) +l;
tot += kernel[h][l] * brightness(img.pixels[c]);
}
}
result.pixels[j * img.width + i] = color(tot/normFactor);
}
}
return result;
}
//*******************************
// THRESHOLD
//*******************************
// Inverted Binary Threshold
PImage threshold(PImage img, int threshold) {
// create a new, initially transparent, 'result' image
PImage result = createImage(img.width, img.height, RGB);
for (int i = 0; i < img.width * img.height; i++) {
if (brightness(img.pixels[i]) < threshold) {
result.pixels[i] = color(0);
} else {
result.pixels[i] = color(255);
}
}
return result;
}
// HUE Method
PImage applyHue(PImage img, int min, int max) {
// create a new, initially transparent, 'result' image
PImage result = createImage(img.width, img.height, RGB);
for (int i = 0; i < img.width * img.height; i++) {
float h = hue(img.pixels[i]);
if (min < h && max > h) {
result.pixels[i] = img.pixels[i];
} else {
result.pixels[i] = color(h);
}
}
return result;
}
// Threhold method for HUE, BRIGHTNESS and SATURATION
PImage thresholdHSB(PImage img, int minH, int maxH, int minS, int maxS, int minB, int maxB) {
PImage result = createImage(img.width, img.height, RGB);
for (int i = 0; i < img.width * img.height; i++) {
float hue = hue(img.pixels[i]);
float bri = brightness(img.pixels[i]);
float sat = saturation(img.pixels[i]);
if (hue >= minH && hue <= maxH && bri <= maxB && bri >= minB && sat >= minS && sat <= maxS)
result.pixels[i] = color(255);
else
result.pixels[i] = color(0);
}
return result;
}
//*******************************
// SCHARR
//*******************************
PImage scharr(PImage img) {
float[][] vKernel = {
{ 3, 0, -3 },
{ 10, 0, -10 },
{ 3, 0, -3 } };
float[][] hKernel = {
{ 3, 10, 3 },
{ 0, 0, 0 },
{ -3, -10, -3 } };
PImage result = createImage(img.width, img.height, ALPHA);
// clear the image
for (int i = 0; i < img.width * img.height; i++) {
result.pixels[i] = color(0);
}
float max=0;
float[] buffer = new float[img.width * img.height];
// ***********************************
// Implement here the double convolution
// ***********************************
for (int j = 1; j< img.height -1; j++) {
for (int i = 1; i < img.width -1; i++) {
float sum_v = 0;
float sum_h = 0;
int c = 0;
for (int l = 0; l<3; l++) {
for (int h = 0; h<3; h++) {
c = i -1 + img.width * (j-1+h) +l;
sum_v += vKernel[h][l] * brightness(img.pixels[c]);
sum_h += hKernel[h][l] * brightness(img.pixels[c]);
}
}
float sum = sqrt(pow(sum_h, 2) + pow(sum_v, 2));
buffer[j * img.width + i] = sum;
if (max <= sum) max = sum;
}
}
for (int y = 1; y < img.height - 1; y++) { // Skip top and bottom edges
for (int x = 1; x < img.width - 1; x++) { // Skip left and right
int val=(int) ((buffer[y * img.width + x] / max)*255);
result.pixels[y * img.width + x]=color(val);
}
}
return result;
}
//*******************************
// HOUGH
//*******************************
// **********************************************************************
// Step_1 - Draw the lines requiered - Compute and Store the polar representation
// of lines passing through edge pixels
// **********************************************************************
ArrayList<PVector> hough(PImage edgeImg, int nLines) {
float discretizationStepsPhi = 0.07f;
float discretizationStepsR = 2.8f;
ArrayList<Integer> bestCandidates=new ArrayList<Integer>();
// dimensions of the accumulator
int phiDim = (int) (Math.PI / discretizationStepsPhi +1);
//The max radius is the image diagonal, but it can be also negative
int rDim = (int) ((sqrt(edgeImg.width*edgeImg.width +
edgeImg.height*edgeImg.height) * 2) / discretizationStepsR +1);
// our accumulator
int[] accumulator = new int[phiDim * rDim];
// pre-compute the sin and cos values
float[] tabSin = new float[phiDim];
float[] tabCos = new float[phiDim];
float ang = 0;
float inverseR = 1.f / discretizationStepsR;
for (int accPhi = 0; accPhi < phiDim; ang += discretizationStepsPhi, accPhi++) {
// we can also pre-multiply by (1/discretizationStepsR) since we need it in the Hough loop
tabSin[accPhi] = (float) (Math.sin(ang) * inverseR);
tabCos[accPhi] = (float) (Math.cos(ang) * inverseR);
}
// Fill the accumulator: on edge points (ie, white pixels of the edge
// image), store all possible (r, phi) pairs describing lines going
// through the point.
for (int y = 0; y < edgeImg.height; y++) {
for (int x = 0; x < edgeImg.width; x++) {
// Are we on an edge?
if (brightness(edgeImg.pixels[y * edgeImg.width + x]) != 0) {
// ...determine here all the lines (r, phi) passing through
// pixel (x,y), convert (r,phi) to coordinates in the
// accumulator, and increment accordingly the accumulator.
// Be careful: r may be negative, so you may want to center onto
// the accumulator: r += rDim / 2
for (int phi=0; phi<phiDim; ++phi) {
int accR = (int) ((x*tabCos[phi]+y*tabSin[phi])+rDim/2);
++accumulator[phi*rDim+accR];
}
}
}
}
// **********************************************************************
// Step_2 - Display the accumulator
// **********************************************************************
/*PImage houghImg = createImage(rDim, phiDim, ALPHA);
for (int i = 0; i < accumulator.length; i++) {
houghImg.pixels[i] = color(min(255, accumulator[i]));
}
// You may want to resize the accumulator to make it easier to see:
houghImg.resize(400, 400);
houghImg.updatePixels();
image(houghImg,img.width+50,0);*/
final int minVotes=50;
final int REGION_SIZE = 10;
// Step 2 - Week 11 - Find Local Maxima
for (int elem = 0; elem < accumulator.length; ++elem) {
if (accumulator[elem] > minVotes && isMaxOverArea(accumulator, elem, REGION_SIZE, phiDim, rDim)) {
bestCandidates.add(elem);
}
}
// Sort the lsit of bestCandidates with the HoughComparator class
bestCandidates.sort(new HoughComparator(accumulator));
// Construction of the arrayList of the lines for the return
ArrayList<PVector> lines = new ArrayList<PVector>();
// New method to find the lines (do not need to check if " > minVotes"
// since the array bestCandidate already checked that condition
for (int i=0; i < bestCandidates.size() && i < nLines; ++i) {
int idx = bestCandidates.get(i);
// first, compute back the (r, phi) polar coordinates:
int accPhi = (int) (idx / (rDim));
int accR = idx - (accPhi) * (rDim);
float r = (accR - (rDim) * 0.5f) * discretizationStepsR;
float phi = accPhi * discretizationStepsPhi;
lines.add(new PVector(r, phi));
}
return lines;
}
private boolean isMaxOverArea(int[] accumulator, int idx, int REGION_SIZE, int phiDim, int rDim) {
int threshold=accumulator[idx];
for (int dx=Math.max(0, idx%rDim-REGION_SIZE); dx<Math.min(rDim, idx%rDim+REGION_SIZE); ++dx) {
for (int dy=Math.max(0, idx/rDim-REGION_SIZE); dy<Math.min(phiDim, idx/rDim+REGION_SIZE); ++dy) {
if (accumulator[dx+dy*rDim]>threshold) {
return false;
}
}
}
return true;
}
// **********************************************************************
// Step_3 - Plot lines on the top of the image
// **********************************************************************
void plotLines(ArrayList<PVector> lines, PImage edgeImg) {
for (int idx = 0; idx < lines.size(); idx++) {
PVector line=lines.get(idx);
float r = line.x;
float phi = line.y;
// Cartesian equation of a line: y = ax + b
// in polar, y = (-cos(phi)/sin(phi))x + (r/sin(phi))
// => y = 0 : x = r / cos(phi)
// => x = 0 : y = r / sin(phi)
// compute the intersection of this line with the 4 borders of the image
int x0 = 0;
int y0 = (int) (r / sin(phi));
int x1 = (int) (r / cos(phi));
int y1 = 0;
int x2 = edgeImg.width;
int y2 = (int) (-cos(phi) / sin(phi) * x2 + r / sin(phi));
int y3 = edgeImg.width;
int x3 = (int) (-(y3 - r / sin(phi)) * (sin(phi) / cos(phi)));
// Finally, plot the lines
//stroke(255, 0, 0);
stroke(204, 102, 0);
if (y0 > 0) {
if (x1 > 0)
line(x0, y0, x1, y1);
else if (y2 > 0)
line(x0, y0, x2, y2);
else
line(x0, y0, x3, y3);
} else {
if (x1 > 0) {
if (y2 > 0)
line(x1, y1, x2, y2);
else
line(x1, y1, x3, y3);
} else
line(x2, y2, x3, y3);
}
}
}
}

146
ParticleSystem.pde Normal file
View File

@ -0,0 +1,146 @@
final PVector normalX = new PVector(1, 0, 0);
class ParticleSystem {
ArrayList<PVector> particles;
PVector originVillain;
PVector origin;
float points;
float previousPoints = 0;
ParticleSystem() {
originVillain = new PVector(0,-boxThickness/2 - cylinderHeight,0);
particles = new ArrayList<PVector>();
setupVillain();
}
//#############################################
//------------SETUP OF THE VILLAIN------------
//#############################################
void setupVillain() {
points = 0;
robotnik = loadShape("robotnik.obj");
robotnik.scale(50);
robotnik.rotateX(PI);
texture = loadImage("robotnik.png");
robotnik.setTexture(texture);
}
//#############################################
// Main method that will add and adjacent cylinder to the whole system
//#############################################
void addParticle() {
PVector center;
int numAttempts = 100;
for(int i=0; i<numAttempts; i++) {
// Pick a cylinder and its center.
int index = int(random(particles.size()));
center = particles.get(index).copy();
// Try to add an adjacent cylinder.
float angle = random(TWO_PI);
center.x += sin(angle) * 2*cylinderBaseSize;
center.z += cos(angle) * 2*cylinderBaseSize;
if(checkPosition(center)) {
particles.add(new PVector(center.x , 0, center.z));
points -= 1;
break;
}
}
}
//#############################################
// Check if a position is available, i.e.
// - would not overlap with particles that are already created
// (for each particle, call checkOverlap())
// - is inside the board boundaries
//#############################################
boolean checkPosition(PVector center) {
boolean result = true;
pushMatrix();
translate(width/2, height/2);
if ( (center.x > (boxLength/2 - cylinderBaseSize)) || (center.x < - boxLength/2 + cylinderBaseSize) || (center.z > (boxLength/2 - cylinderBaseSize)) || (center.z < - boxLength/2 + cylinderBaseSize) ) result = false;
for(PVector v : particles)
if (checkOverlap(v, center)) result = false;
popMatrix();
return result;
}
//#############################################
// Check if a particle with center c1 and another particle with center c2 overlap.
//#############################################
boolean checkOverlap(PVector c1, PVector c2) {
double distance = sqrt((c1.x - c2.x)*(c1.x - c2.x) + (c1.z - c2.z)*(c1.z - c2.z));
return distance < 2*cylinderBaseSize;
}
//#############################################
// Do the following two things :
// - Add a new cylinder at a regular time interval
// - Move the bad guy so that it always faces the movement of the ball
//#############################################
void run() {
playable_sphere.checkCylinderCollision();
if(frameCount % 30 == 0 ) {
addParticle(); //interval set to 0.5 seconds
}
// Rotation of the villain according to the position of the ball
float sphereZ = playable_sphere.location().z - originVillain.copy().z;
float sphereX = playable_sphere.location().x - originVillain.copy().x;
float posNormZ = normalX.copy().normalize().z;
float posNormX = normalX.copy().normalize().x;
float angleVillain = (float) Math.atan2(sphereX*posNormZ - sphereZ*posNormX, sphereX*posNormX + sphereZ*posNormZ);
normalX.x = playable_sphere.location().x - originVillain.x;
normalX.z = playable_sphere.location().z - originVillain.z;
normalX.y = playable_sphere.location().y - originVillain.y;
robotnik.rotateY(angleVillain);
}
//#############################################
// Draw the position of the cylinder in the square top view box
//#############################################
void particleTopView() {
topView.noStroke();
for (int i = 0; i < particles.size(); ++i){
PVector particle_vector = particles.get(i);
float ellipseX = (particle_vector.x + boxLength/2) * square_for_score/boxLength;
float ellipseY = (particle_vector.z + boxLength/2) * square_for_score/boxLength;
float ellipseDim = (2*cylinderBaseSize) * square_for_score/boxLength;
if(i==0){
topView.fill(colorVillainTopView);
topView.ellipse(ellipseX, ellipseY, ellipseDim, ellipseDim);
} else {
topView.fill(defaultCylinderColour);
topView.ellipse(ellipseX, ellipseY, ellipseDim, ellipseDim);
}
}
}
}
//#############################################
// Checks if we can add a cylinder given a x and y coordinates
//#############################################
boolean canAddCylinder(float x, float y) {
return !( (x > (width/2 + boxLength/2 - cylinderBaseSize)) || (x < width/2 - boxLength/2 + cylinderBaseSize) || (y > (height/2 + boxLength/2 - cylinderBaseSize)) || (y < height/2 - boxLength/2 + cylinderBaseSize) );
}
//#############################################
// Set up the origin of the villain in order to place it on the board.
//#############################################
void addCenterOfVillain() {
//when a new origin is set, remove all the cylinders and add a new cylinder at the origin
particle_system.particles.clear();
particle_system.previousPoints = particle_system.points;
particle_system.points = 0;
scores = new ArrayList<Float>();
if(canAddCylinder(mouseX, mouseY)){
PVector origin = new PVector(mouseX - width/2 , 0 , mouseY - height/2); //origin in upper left corner
particle_system.particles.add(origin);
particle_system.origin = origin;
} else {
particle_system.origin = null;
}
}

385
QuadGraph.pde Normal file
View File

@ -0,0 +1,385 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
class QuadGraph {
boolean verbose=false;
List<int[]> cycles = new ArrayList<int[]>();
int[][] graph;
List<PVector> findBestQuad(List<PVector> lines, int width, int height, int max_quad_area, int min_quad_area, boolean verbose) {
this.verbose=verbose;
build(lines, width, height); //<>//
findCycles(verbose);
ArrayList<PVector> bestQuad=new ArrayList<PVector>();
float bestQuadArea=0;
for (int [] cy : cycles) {
ArrayList<PVector> quad= new ArrayList<PVector>();
PVector l1 = lines.get(cy[0]);
PVector l2 = lines.get(cy[1]);
PVector l3 = lines.get(cy[2]);
PVector l4 = lines.get(cy[3]);
quad.add(intersection(l1, l2));
quad.add(intersection(l2, l3));
quad.add(intersection(l3, l4));
quad.add(intersection(l4, l1));
quad=sortCorners(quad);
PVector c1 = quad.get(0);
PVector c2 = quad.get(1);
PVector c3 = quad.get(2);
PVector c4 = quad.get(3);
if (isConvex(c1, c2, c3, c4) &&
nonFlatQuad(c1, c2, c3, c4)) {
float quadArea=validArea(c1, c2, c3, c4, max_quad_area, min_quad_area);
if (quadArea>0 && quadArea>bestQuadArea) {
bestQuadArea=quadArea;
bestQuad=quad;
}
}
}
if (bestQuadArea>0)
return bestQuad;
else
return new ArrayList<PVector>();
}
void build(List<PVector> lines, int width, int height) {
int n = lines.size();
// The maximum possible number of edges is n * (n - 1)/2
graph = new int[n * (n - 1)/2][2];
int idx =0;
for (int i = 0; i < lines.size(); i++) {
for (int j = i + 1; j < lines.size(); j++) {
if (intersect(lines.get(i), lines.get(j), width, height)) {
graph[idx][0]=i;
graph[idx][1]=j;
idx++;
}
}
}
}
/** Returns true if polar lines 1 and 2 intersect
* inside an area of size (width, height)
*/
boolean intersect(PVector line1, PVector line2, int width, int height) {
double sin_t1 = Math.sin(line1.y);
double sin_t2 = Math.sin(line2.y);
double cos_t1 = Math.cos(line1.y);
double cos_t2 = Math.cos(line2.y);
float r1 = line1.x;
float r2 = line2.x;
double denom = cos_t2 * sin_t1 - cos_t1 * sin_t2;
int x = (int) ((r2 * sin_t1 - r1 * sin_t2) / denom);
int y = (int) ((-r2 * cos_t1 + r1 * cos_t2) / denom);
if (0 <= x && 0 <= y && width >= x && height >= y)
return true;
else
return false;
}
PVector intersection(PVector line1, PVector line2) {
double sin_t1 = Math.sin(line1.y);
double sin_t2 = Math.sin(line2.y);
double cos_t1 = Math.cos(line1.y);
double cos_t2 = Math.cos(line2.y);
float r1 = line1.x;
float r2 = line2.x;
double denom = cos_t2 * sin_t1 - cos_t1 * sin_t2;
int x = (int) ((r2 * sin_t1 - r1 * sin_t2) / denom);
int y = (int) ((-r2 * cos_t1 + r1 * cos_t2) / denom);
return new PVector(x,y);
}
void findCycles(boolean verbose) {
cycles.clear();
for (int i = 0; i < graph.length; i++) {
for (int j = 0; j < graph[i].length; j++) {
findNewCycles(new int[] {graph[i][j]});
}
}
if (verbose) {
for (int[] cy : cycles) {
String s = "" + cy[0];
for (int i = 1; i < cy.length; i++) {
s += "," + cy[i];
}
System.out.println(s);
}
}
}
void findNewCycles(int[] path)
{
int n = path[0];
int x;
int[] sub = new int[path.length + 1];
for (int i = 0; i < graph.length; i++)
for (int y = 0; y <= 1; y++)
if (graph[i][y] == n)
// edge refers to our current node
{
x = graph[i][(y + 1) % 2];
if (!visited(x, path))
// neighbor node not on path yet
{
sub[0] = x;
System.arraycopy(path, 0, sub, 1, path.length);
// explore extended path
findNewCycles(sub);
} else if ((path.length == 4) && (x == path[path.length - 1]))
// cycle found
{
int[] p = normalize(path);
int[] inv = invert(p);
if (isNew(p) && isNew(inv))
{
cycles.add(p);
}
}
}
}
// check of both arrays have same lengths and contents
Boolean equals(int[] a, int[] b)
{
Boolean ret = (a[0] == b[0]) && (a.length == b.length);
for (int i = 1; ret && (i < a.length); i++)
{
if (a[i] != b[i])
{
ret = false;
}
}
return ret;
}
// create a path array with reversed order
int[] invert(int[] path)
{
int[] p = new int[path.length];
for (int i = 0; i < path.length; i++)
{
p[i] = path[path.length - 1 - i];
}
return normalize(p);
}
// rotate cycle path such that it begins with the smallest node
int[] normalize(int[] path)
{
int[] p = new int[path.length];
int x = smallest(path);
int n;
System.arraycopy(path, 0, p, 0, path.length);
while (p[0] != x)
{
n = p[0];
System.arraycopy(p, 1, p, 0, p.length - 1);
p[p.length - 1] = n;
}
return p;
}
// compare path against known cycles
// return true, iff path is not a known cycle
Boolean isNew(int[] path)
{
Boolean ret = true;
for (int[] p : cycles)
{
if (equals(p, path))
{
ret = false;
break;
}
}
return ret;
}
// return the int of the array which is the smallest
int smallest(int[] path)
{
int min = path[0];
for (int p : path)
{
if (p < min)
{
min = p;
}
}
return min;
}
// check if vertex n is contained in path
Boolean visited(int n, int[] path)
{
Boolean ret = false;
for (int p : path)
{
if (p == n)
{
ret = true;
break;
}
}
return ret;
}
/** Check if a quad is convex or not.
*
* Algo: take two adjacent edges and compute their cross-product.
* The sign of the z-component of all the cross-products is the
* same for a convex polygon.
*
* See http://debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/clockwise.htm
* for justification.
*
* @param c1
*/
boolean isConvex(PVector c1, PVector c2, PVector c3, PVector c4) {
PVector v21= PVector.sub(c1, c2);
PVector v32= PVector.sub(c2, c3);
PVector v43= PVector.sub(c3, c4);
PVector v14= PVector.sub(c4, c1);
float i1=v21.cross(v32).z;
float i2=v32.cross(v43).z;
float i3=v43.cross(v14).z;
float i4=v14.cross(v21).z;
if ( (i1>0 && i2>0 && i3>0 && i4>0)
|| (i1<0 && i2<0 && i3<0 && i4<0))
return true;
else if(verbose)
System.out.println("Eliminating non-convex quad");
return false;
}
/** Compute the area of a quad, and check it lays within a specific range
*/
float validArea(PVector c1, PVector c2, PVector c3, PVector c4, float max_area, float min_area) {
float i1=c1.cross(c2).z;
float i2=c2.cross(c3).z;
float i3=c3.cross(c4).z;
float i4=c4.cross(c1).z;
float area = Math.abs(0.5f * (i1 + i2 + i3 + i4));
if (area < max_area && area > min_area){
return area;
}
return 0;
}
/** Compute the (cosine) of the four angles of the quad, and check they are all large enough
* (the quad representing our board should be close to a rectangle)
*/
boolean nonFlatQuad(PVector c1, PVector c2, PVector c3, PVector c4) {
// cos(70deg) ~= 0.3
float min_cos = 0.5f;
PVector v21= PVector.sub(c1, c2);
PVector v32= PVector.sub(c2, c3);
PVector v43= PVector.sub(c3, c4);
PVector v14= PVector.sub(c4, c1);
float cos1=Math.abs(v21.dot(v32) / (v21.mag() * v32.mag()));
float cos2=Math.abs(v32.dot(v43) / (v32.mag() * v43.mag()));
float cos3=Math.abs(v43.dot(v14) / (v43.mag() * v14.mag()));
float cos4=Math.abs(v14.dot(v21) / (v14.mag() * v21.mag()));
if (cos1 < min_cos && cos2 < min_cos && cos3 < min_cos && cos4 < min_cos)
return true;
else {
if(verbose)
System.out.println("Flat quad");
return false;
}
}
ArrayList<PVector> sortCorners(ArrayList<PVector> quad) {
// 1 - Sort corners so that they are ordered clockwise
PVector a = quad.get(0);
PVector b = quad.get(2);
PVector center = new PVector((a.x+b.x)/2, (a.y+b.y)/2);
Collections.sort(quad, new CWComparator(center));
// 2 - Sort by upper left most corner
PVector origin = new PVector(0, 0);
float distToOrigin = 1000;
for (PVector p : quad) {
if (p.dist(origin) < distToOrigin) distToOrigin = p.dist(origin);
}
while (quad.get(0).dist(origin) != distToOrigin)
Collections.rotate(quad, 1);
return quad;
}
}
class CWComparator implements Comparator<PVector> {
PVector center;
public CWComparator(PVector center) {
this.center = center;
}
@Override
public int compare(PVector b, PVector d) {
if (Math.atan2(b.y-center.y, b.x-center.x)<Math.atan2(d.y-center.y, d.x-center.x))
return -1;
else return 1;
}
}

116
Sphere.pde Normal file
View File

@ -0,0 +1,116 @@
class Mover {
PVector location;
PVector velocity;
PVector gravityForce;
//Rotation angles of the sphere
float sphere_rotX = 0;
float sphere_rotZ = 0;
//Add textures
private PImage img;
private PShape globe;
//Related to the geometry of the object and have an impact on its moment inertia
final float K_INERTIA = 2/3;
Mover() {
location = new PVector(0, -(radius_sphere + boxThickness/2), 0);
velocity = new PVector(0, 0, 0);
gravityForce = new PVector(0, 0, 0);
//Adding texture of a pool ball
img = loadImage("PoolBall.jpeg");
globe = createShape();
globe = createShape(SPHERE, radius_sphere);
globe.setStroke(false);
globe.setTexture(img);
}
//#############################################
// Update the position of the ball so that it has a real movement
//#############################################
void update() {
gravityForce = new PVector(sin(thetaZ) * gravityConstant, 0, -sin(thetaX) * gravityConstant);
velocity.add(gravityForce);
PVector friction = velocity.copy();
friction.mult(-1);
friction.normalize();
friction.mult(frictionMagnitude);
velocity.add(friction);
location.add(velocity);
//Update rotation angles of the sphere
sphere_rotX = (-velocity.z)/radius_sphere;
sphere_rotZ = (velocity.x)/radius_sphere;
checkCylinderCollision();
}
//#############################################
//Displays the ball and makes it move with real rolling effect
//#############################################
void display() {
gameSurface.noStroke();
gameSurface.pushMatrix();
gameSurface.translate(location.x, location.y, location.z);
//Add rolling effect to the ball
if (!gamePaused) {
gameSurface.rotateX(PI/2);
globe.rotateX(sphere_rotX);
globe.rotateY(sphere_rotZ);
}
gameSurface.shape(globe);
gameSurface.popMatrix();
}
//#############################################
//---AVOID THE BALL TO GO OUT OF THE BOARD-----
//#############################################
void checkEdges() {
if (location.x + radius_sphere > boxLength/2) {
velocity.x *= -1;
location.x = boxLength/2 - radius_sphere;
} else if (location.x - radius_sphere < -boxLength/2) {
velocity.x *= -1;
location.x = -boxLength/2 + radius_sphere;
}
if (location.z + radius_sphere > boxLength/2) {
velocity.z *= -1;
location.z = boxLength/2 - radius_sphere;
} else if (location.z - radius_sphere < -boxLength/2) {
velocity.z *= -1;
location.z = -boxLength/2 + radius_sphere;
}
}
//#############################################
//-------CHECK COLLISIONS WITH CYLINDERS-------
//#############################################
void checkCylinderCollision() {
for (int i = 0; i < particle_system.particles.size(); ++i) {
PVector cylinderPosition = particle_system.particles.get(i);
PVector ballCylVect = new PVector(location.x - cylinderPosition.x, 0, location.z - cylinderPosition.z);
if (ballCylVect.mag() <= (radius_sphere + cylinderBaseSize)) {
if (cylinderPosition == particle_system.origin) {
particle_system.particles.clear();
particle_system.origin = null;
} else {
//remove the cylinder if the ball hits it
particle_system.particles.remove(i);
PVector normal = ballCylVect.normalize();
float veloNorm = PVector.dot(velocity, normal)*2;
PVector vector = PVector.mult(normal, veloNorm);
velocity = PVector.sub(velocity, vector);
particle_system.points += defaultGain * playable_sphere.velocity.mag();;
}
}
}
}
PVector location() {
return location.copy();
}
}

144
TangibleGame.pde Normal file
View File

@ -0,0 +1,144 @@
PGraphics gameSurface; //<>//
PGraphics scoreBoard;
PGraphics topView;
PGraphics barChart;
Mover playable_sphere;
Cylinder cylinder;
ParticleSystem particle_system;
HScrollbar hs_scroll_bar;
ImageProcessing imgproc;
ArrayList<PVector> cylindersPosition = new ArrayList<PVector>();
ArrayList<Float> scores = new ArrayList<Float>();
void settings() {
size(window_size_x, window_size_y, P3D);
}
void setup() {
//has to be in the main game process
//********Merge with ImgProcessing********
imgproc = new ImageProcessing();
String []args = {"Image processing window"};
PApplet.runSketch(args, imgproc);
//********Merge with ImgProcessing********
noStroke();
frameRate(fps);
playable_sphere = new Mover();
cylinder = new Cylinder(defaultCylinderColour);
particle_system = new ParticleSystem();
gameSurface = createGraphics(width, height - score_graphic, P3D);
scoreBoard = createGraphics(square_for_score, square_for_score, P2D);
topView = createGraphics(square_for_score, square_for_score, P2D);
barChart = createGraphics(width - 2*square_for_score - 20, square_for_score, P2D);
hs_scroll_bar = new HScrollbar(2*score_graphic, height-25, score_graphic*3/4, 15);
}
void draw() {
//has to be in the main game process
//********Merge with ImgProcessing********
PVector rotation = imgproc.getRotation();
changeAngleForRotation(rotation);
//********Merge with ImgProcessing********
background(bottomColor);
drawGame();
image(gameSurface, 0, 0);
drawScoreBoard();
image(scoreBoard, 150, height-score_graphic+5);
drawTopView();
image(topView, 5, height-score_graphic+5);
drawBarChart();
image(barChart, 2*square_for_score+10+5, height-score_graphic+5);
hs_scroll_bar.update();
hs_scroll_bar.display();
}
void changeAngleForRotation(PVector rotation)
{
if (!shiftPressed) {
thetaX = rotation.x;
thetaZ = rotation.z;
if (thetaX <= -PI/3) thetaX = -PI/3;
else if (thetaX >= PI/3) thetaX = PI/3;
if (thetaZ <= -PI/3) thetaZ = -PI/3;
else if (thetaZ >= PI/3) thetaZ = PI/3;
}
}
void mouseWheel(MouseEvent evenement) {
float e = evenement.getCount();
if (e < 1 ) {
angularSpeed+=0.1;
} else {
angularSpeed-=0.1;
}
if (angularSpeed >= 1.5) {
angularSpeed = 1.5;
} else if (angularSpeed <= 0.3) {
angularSpeed = 0.3;
}
}
void mousePressed() {
if (shiftPressed) {
addCenterOfVillain();
}
}
void keyPressed() {
if (key == CODED && keyCode == SHIFT) {
shiftPressed = true;
oldThetaX = thetaX;
oldThetaY = thetaY;
oldThetaZ = thetaZ;
thetaX = -PI/2;
thetaY = 0;
thetaZ = 0;
pauseGame();
}
}
void keyReleased() {
if (key == CODED && keyCode == SHIFT ) {
shiftPressed = false;
thetaX = oldThetaX;
thetaY = oldThetaY;
thetaZ = oldThetaZ;
resumeGame();
}
}
void pauseGame() {
gamePaused = true;
}
void resumeGame() {
gamePaused = false;
}
//#############################################
// Getters for points & previousPoints variables
//#############################################
float getPoints() {
if (particle_system == null) return 0;
return particle_system.points;
}
float getLastPoints() {
if (particle_system == null) return 0;
return particle_system.previousPoints;
}

355
TwoDThreeD.pde Normal file
View File

@ -0,0 +1,355 @@
import java.util.List;
import processing.core.PVector;
import org.opencv.core.Mat;
import org.opencv.core.CvType;
import org.opencv.core.Core;
class TwoDThreeD {
// default focal length, well suited for most webcams
float f = 700;
// intrisic camera matrix
float [][] K = {{f, 0, 0},
{0, f, 0},
{0, 0, 1}};
float [][] invK;
PVector invK_r1, invK_r2, invK_r3;
Mat opencv_A, w, u, vt;
double [][] V;
// Real physical coordinates of the Lego board in mm
//float boardSize = 380.f; // large Duplo board
// float boardSize = 255.f; // smaller Lego board
// the 3D coordinates of the physical board corners, clockwise
float [][] physicalCorners = {
{-128, 128, 0, 1},
{128, 128, 0, 1},
{128, -128, 0, 1},
{-128, -128, 0, 1}
};
//Filtering variables: low-pass filter based on arFilterTrans from ARToolKit v5 */
float[] q;
float sampleRate;
float cutOffFreq;
float alpha;
public TwoDThreeD(int width, int height, float sampleRate) {
// set the offset to the center of the webcam image
K[0][2] = 0.5f * width;
K[1][2] = 0.5f * height;
//compute inverse of K
Mat opencv_K= new Mat(3, 3, CvType.CV_32F);
opencv_K.put(0, 0, K[0][0]);
opencv_K.put(0, 1, K[0][1]);
opencv_K.put(0, 2, K[0][2]);
opencv_K.put(1, 0, K[1][0]);
opencv_K.put(1, 1, K[1][1]);
opencv_K.put(1, 2, K[1][2]);
opencv_K.put(2, 0, K[2][0]);
opencv_K.put(2, 1, K[2][1]);
opencv_K.put(2, 2, K[2][2]);
Mat opencv_invK=opencv_K.inv();
invK = new float[][]{
{ (float)opencv_invK.get(0, 0)[0], (float)opencv_invK.get(0, 1)[0], (float)opencv_invK.get(0, 2)[0] },
{ (float)opencv_invK.get(1, 0)[0], (float)opencv_invK.get(1, 1)[0], (float)opencv_invK.get(1, 2)[0] },
{ (float)opencv_invK.get(2, 0)[0], (float)opencv_invK.get(2, 1)[0], (float)opencv_invK.get(2, 2)[0] }};
invK_r1=new PVector(invK[0][0], invK[0][1], invK[0][2]);
invK_r2=new PVector(invK[1][0], invK[1][1], invK[1][2]);
invK_r3=new PVector(invK[2][0], invK[2][1], invK[2][2]);
opencv_A=new Mat(12, 9, CvType.CV_32F);
w=new Mat();
u=new Mat();
vt=new Mat();
V= new double[9][9];
q=new float[4];
q[3]=1;
this.sampleRate=sampleRate;
if (sampleRate>0) {
cutOffFreq=sampleRate/2;
alpha= (1/sampleRate)/(1/sampleRate + 1/cutOffFreq);
}
}
PVector get3DRotations(List<PVector> points2D) {
// 1- Solve the extrinsic matrix from the projected 2D points
double[][] E = solveExtrinsicMatrix(points2D);
// 2 - Re-build a proper 3x3 rotation matrix from the camera's
// extrinsic matrix E
PVector firstColumn=new PVector((float)E[0][0], (float)E[1][0], (float)E[2][0]);
PVector secondColumn=new PVector((float)E[0][1], (float)E[1][1], (float)E[2][1]);
firstColumn.normalize();
secondColumn.normalize();
PVector thirdColumn=firstColumn.cross(secondColumn);
float [][] rotationMatrix={{firstColumn.x, secondColumn.x, thirdColumn.x},
{firstColumn.y, secondColumn.y, thirdColumn.y},
{firstColumn.z, secondColumn.z, thirdColumn.z}};
if (sampleRate>0)
filter(rotationMatrix, false);
// 3 - Computes and returns Euler angles (rx, ry, rz) from this matrix
return rotationFromMatrix(rotationMatrix);
}
double[][] solveExtrinsicMatrix(List<PVector> points2D) {
// p ~= K · [R|t] · P
// with P the (3D) corners of the physical board, p the (2D)
// projected points onto the webcam image, K the intrinsic
// matrix and R and t the rotation and translation we want to
// compute.
//
// => We want to solve: (K^(-1) · p) X ([R|t] · P) = 0
float[][] projectedCorners = new float[4][3];
if(points2D.size() >= 4)
for (int i=0; i<4; i++) {
// TODO:
// store in projectedCorners the result of (K^(-1) · p), for each
// corner p found in the webcam image.
// You can use PVector dot function for computing dot product between K^(-1) lines and p.
//Do not forget to normalize the result
PVector point =points2D.get(i);
projectedCorners[i][0]=point.dot(invK_r1)/point.dot(invK_r3);
projectedCorners[i][1]=point.dot(invK_r2)/point.dot(invK_r3);
projectedCorners[i][2]=1;
}
// 'A' contains the cross-product (K^(-1) · p) X P
float[][] A= new float[12][9];
for (int i=0; i<4; i++) {
A[i*3][0]=0;
A[i*3][1]=0;
A[i*3][2]=0;
// note that we take physicalCorners[0,1,*3*]: we drop the Z
// coordinate and use the 2D homogenous coordinates of the physical
// corners
A[i*3][3]=-projectedCorners[i][2] * physicalCorners[i][0];
A[i*3][4]=-projectedCorners[i][2] * physicalCorners[i][1];
A[i*3][5]=-projectedCorners[i][2] * physicalCorners[i][3];
A[i*3][6]= projectedCorners[i][1] * physicalCorners[i][0];
A[i*3][7]= projectedCorners[i][1] * physicalCorners[i][1];
A[i*3][8]= projectedCorners[i][1] * physicalCorners[i][3];
A[i*3+1][0]= projectedCorners[i][2] * physicalCorners[i][0];
A[i*3+1][1]= projectedCorners[i][2] * physicalCorners[i][1];
A[i*3+1][2]= projectedCorners[i][2] * physicalCorners[i][3];
A[i*3+1][3]=0;
A[i*3+1][4]=0;
A[i*3+1][5]=0;
A[i*3+1][6]=-projectedCorners[i][0] * physicalCorners[i][0];
A[i*3+1][7]=-projectedCorners[i][0] * physicalCorners[i][1];
A[i*3+1][8]=-projectedCorners[i][0] * physicalCorners[i][3];
A[i*3+2][0]=-projectedCorners[i][1] * physicalCorners[i][0];
A[i*3+2][1]=-projectedCorners[i][1] * physicalCorners[i][1];
A[i*3+2][2]=-projectedCorners[i][1] * physicalCorners[i][3];
A[i*3+2][3]= projectedCorners[i][0] * physicalCorners[i][0];
A[i*3+2][4]= projectedCorners[i][0] * physicalCorners[i][1];
A[i*3+2][5]= projectedCorners[i][0] * physicalCorners[i][3];
A[i*3+2][6]=0;
A[i*3+2][7]=0;
A[i*3+2][8]=0;
}
for (int i=0; i<12; i++)
for (int j=0; j<9; j++)
opencv_A.put(i, j, A[i][j]);
Core.SVDecomp(opencv_A, w, u, vt);
for (int i=0; i<9; i++)
for (int j=0; j<9; j++)
V[j][i]=vt.get(i, j)[0];
double[][] E = new double[3][3];
//E is the last column of V
for (int i=0; i<9; i++) {
E[i/3][i%3] = V[i][V.length-1] / V[8][V.length-1];
}
return E;
}
PVector rotationFromMatrix(float[][] mat) {
// Assuming rotation order is around x,y,z
PVector rot = new PVector();
if (mat[1][0] > 0.998) { // singularity at north pole
rot.z = 0;
float delta = (float) Math.atan2(mat[0][1], mat[0][2]);
rot.y = -(float) Math.PI/2;
rot.x = -rot.z + delta;
return rot;
}
if (mat[1][0] < -0.998) { // singularity at south pole
rot.z = 0;
float delta = (float) Math.atan2(mat[0][1], mat[0][2]);
rot.y = (float) Math.PI/2;
rot.x = rot.z + delta;
return rot;
}
rot.y =-(float)Math.asin(mat[2][0]);
rot.x = (float)Math.atan2(mat[2][1]/Math.cos(rot.y), mat[2][2]/Math.cos(rot.y));
rot.z = (float)Math.atan2(mat[1][0]/Math.cos(rot.y), mat[0][0]/Math.cos(rot.y));
return rot;
}
int filter(float m[][], boolean reset) {
float[] q= new float[4];
float alpha, oneminusalpha, omega, cosomega, sinomega, s0, s1;
mat2Quat(m, q);
if (nomalizeQuaternion(q)<0) return -1;
if (reset) {
this.q[0] = q[0];
this.q[1] = q[1];
this.q[2] = q[2];
this.q[3] = q[3];
} else {
alpha = this.alpha;
oneminusalpha = 1.0 - alpha;
// SLERP for orientation.
cosomega = q[0]*this.q[0] + q[1]*this.q[1] + q[2]*this.q[2] + q[3]*this.q[3]; // cos of angle between vectors.
if (cosomega < 0.0) {
cosomega = -cosomega;
q[0] = -q[0];
q[1] = -q[1];
q[2] = -q[2];
q[3] = -q[3];
}
if (cosomega > 0.9995) {
s0 = oneminusalpha;
s1 = alpha;
} else {
omega = acos(cosomega);
sinomega = sin(omega);
s0 = sin(oneminusalpha * omega) / sinomega;
s1 = sin(alpha * omega) / sinomega;
}
this.q[0] = q[0]*s1 + this.q[0]*s0;
this.q[1] = q[1]*s1 + this.q[1]*s0;
this.q[2] = q[2]*s1 + this.q[2]*s0;
this.q[3] = q[3]*s1 + this.q[3]*s0;
nomalizeQuaternion(this.q);
}
if (quat2Mat(this.q, m) < 0) return (-2);
return (0);
}
int nomalizeQuaternion(float[] q) {// Normalise quaternion.
float mag2 = q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3];
if (mag2==0) return (-1);
float mag = sqrt(mag2);
q[0] /= mag;
q[1] /= mag;
q[2] /= mag;
q[3] /= mag;
return (0);
}
int mat2Quat(float m[][], float q[]) {
float t, s;
t = m[0][0] + m[1][1] + m[2][2] + 1.0;
if (t > 0.0001) {
s = sqrt(t) * 2.0;
q[0] = (m[1][2] - m[2][1]) / s;
q[1] = (m[2][0] - m[0][2]) / s;
q[2] = (m[0][1] - m[1][0]) / s;
q[3] = 0.25 * s;
} else {
if (m[0][0] > m[1][1] && m[0][0] > m[2][2]) { // Column 0:
s = sqrt(1.0 + m[0][0] - m[1][1] - m[2][2]) * 2.0;
q[0] = 0.25 * s;
q[1] = (m[0][1] + m[1][0] ) / s;
q[2] = (m[2][0] + m[0][2] ) / s;
q[3] = (m[1][2] - m[2][1] ) / s;
} else if (m[1][1] > m[2][2]) { // Column 1:
s = sqrt(1.0 + m[1][1] - m[0][0] - m[2][2]) * 2.0;
q[0] = (m[0][1] + m[1][0] ) / s;
q[1] = 0.25 * s;
q[2] = (m[1][2] + m[2][1] ) / s;
q[3] = (m[2][0] - m[0][2] ) / s;
} else { // Column 2:
s = sqrt(1.0 + m[2][2] - m[0][0] - m[1][1]) * 2.0;
q[0] = (m[2][0] + m[0][2] ) / s;
q[1] = (m[1][2] + m[2][1] ) / s;
q[2] = 0.25 * s;
q[3] = (m[0][1] - m[1][0] ) / s;
}
}
return 0;
}
int quat2Mat( float q[], float m[][] )
{
float x2, y2, z2;
float xx, xy, xz;
float yy, yz, zz;
float wx, wy, wz;
x2 = q[0] * 2.0;
y2 = q[1] * 2.0;
z2 = q[2] * 2.0;
xx = q[0] * x2;
xy = q[0] * y2;
xz = q[0] * z2;
yy = q[1] * y2;
yz = q[1] * z2;
zz = q[2] * z2;
wx = q[3] * x2;
wy = q[3] * y2;
wz = q[3] * z2;
m[0][0] = 1.0 - (yy + zz);
m[1][1] = 1.0 - (xx + zz);
m[2][2] = 1.0 - (xx + yy);
m[1][0] = xy - wz;
m[0][1] = xy + wz;
m[2][0] = xz + wy;
m[0][2] = xz - wy;
m[2][1] = yz - wx;
m[1][2] = yz + wx;
return 0;
}
}

BIN
data/PoolBall.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
data/board1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
data/board2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
data/board3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
data/board4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
data/nao.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

BIN
data/nao_blob.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

12
data/robotnik.mtl Normal file
View File

@ -0,0 +1,12 @@
# File produced by Open Asset Import Library (http://www.assimp.sf.net)
# (assimp v3.1.187496374)
newmtl _1_-_Default-material
Kd 0.6 0.6 0.6
Ka 0.588235 0.588235 0.588235
Ks 0.9 0.9 0.9
Ke 0 0 0
Ns 0.43
illum 2
map_Kd robotnik.png

30111
data/robotnik.obj Normal file

File diff suppressed because it is too large Load Diff

BIN
data/robotnik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
data/testvideo.avi Normal file

Binary file not shown.