diff --git a/src/Car.java b/src/Car.java index b449ecd..31c602b 100644 --- a/src/Car.java +++ b/src/Car.java @@ -50,7 +50,7 @@ public class Car{ for(View view : views){ ArrayList pointList = view.getPoints(); - slam.addPoints(pointList); + slam.RANSAC(pointList, view.getFOV() / view.getRayNum()); } } diff --git a/src/Processing.java b/src/Processing.java index 6abbf3f..1f66aae 100644 --- a/src/Processing.java +++ b/src/Processing.java @@ -18,7 +18,7 @@ public class Processing extends PApplet { processing = this; car = new Car(processing, 100,100,50,40); size(1000, 1000); - car.addView(60,6); + car.addView(180,180); for(int i = 0; i < 20; i++){ Wall wall = new Wall(processing, new PVector((int)random(40, 1840), (int)random(40, 960)), (int)random(360), (int)random(100, 1000)); objects.add(wall); diff --git a/src/SLAM.java b/src/SLAM.java index ec7fa81..dadc677 100644 --- a/src/SLAM.java +++ b/src/SLAM.java @@ -1,19 +1,95 @@ +import static processing.core.PApplet.radians; import processing.core.*; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import static processing.core.PApplet.pow; public class SLAM{ - ArrayList points = new ArrayList<>(); + ArrayList lines = new ArrayList<>(); private static PApplet proc; SLAM(PApplet processing){ proc = processing; } - public void addPoints(ArrayList newPoints){ - Line line = new Line(proc, newPoints); + /** + * @param set the set to take a sub sample of + * @param indexRange the range within to take the sub sample + * @param subSampleSize the size of the sub sample + * @return A random subset of the set within an indexRange and of size: subSampleSize + */ + private List randomSample(ArrayList set, int indexRange, int subSampleSize){ + // select a random laser data reading + int randomIdx = (int) proc.random(set.size() - 1); // index of starter reading + PVector point = set.get(randomIdx); // point of starter reading + + // get a random sample of size numSampleReadings within degreeRange degrees of this laser reading. + List subSample = set.subList(randomIdx - indexRange, randomIdx + indexRange); // get the sub-sample + Collections.shuffle(subSample); // shuffle the list + List randomSample = subSample.subList(0, subSampleSize); // get our random sample + if (!randomSample.contains(point)) { + randomSample.add(point); + } + + return randomSample; + } + + /** + * @param originalList the list which the randomSample of points originated from + * @param randomSample a random subsampling of points from the originalList + * @param maxRange the maximum distance away from the line of best fit of the subSample of points for a given point's consensus to count. + * @param consensus the number of points that have to give their consensus for the line of best fit to count as a valid feature. + */ + private void extractFeature(ArrayList originalList, List randomSample, float maxRange, int consensus){ + // get a line of best fit for this list. + Line bestFit = new Line(proc, randomSample); + int count = 0; + ArrayList newRandomSample = new ArrayList<>(); + for (PVector v : randomSample) { + if (bestFit.getDistance(v) <= maxRange) { + count++; + newRandomSample.add(v); + } + } + // if the count is above the consensus, add the line to our list and remove the points that gave the consensus. + if (count >= consensus) { + bestFit = new Line(proc, newRandomSample.subList(0, newRandomSample.size() - 1)); + lines.add(bestFit); + // remove the associated readings from the total available readings. + for (PVector v : newRandomSample) { + originalList.remove(v); + } + } + } + public void RANSAC(ArrayList newPoints, float raysPerDegree){ + float degreeRange = radians(10/2); // range to randomly sample readings within + int indexRange = (int) (degreeRange / raysPerDegree); + int numSampleReadings = 10; // number of readings to randomly sample + // constrain numSampleReadings so that it cant be higher than possible + if(numSampleReadings >= 2 * indexRange){ + numSampleReadings = 2 * indexRange; + } + int consensus = 6; // the number of points that need to lie near a line for it to be considered valid. + float maxRange = 10; // the maximum distance a point can be away from the line for it to count as a consensus + + // this for loop determines the maximum number of trials we're willing to do. + for(int j = 0; j < 20; j++) { + // if there aren't enough points left in the set to form a consensus, we're done. + if(newPoints.size() < consensus){ + break; + } + + // get a random sub sample of newPoints within the index range of a given size + List randomSample = this.randomSample(newPoints, indexRange, numSampleReadings); + + // check if the sub sample forms a valid line and remove the randomSample points if it does. + extractFeature(newPoints, randomSample, maxRange, consensus); + + } + } } @@ -36,13 +112,13 @@ class Line{ * attempt to find the line of best fit for the given points * @param points the points to get the line of best for */ - Line(PApplet processing, ArrayList points){ + Line(PApplet processing, List points){ bestFit(points); proc = processing; } // least squares line of best fit algorithm - private void bestFit(ArrayList points){ + private void bestFit(List points){ // get the mean of all the points PVector mean = new PVector(); for(PVector point : points){ @@ -75,4 +151,12 @@ class Line{ public PVector getPosition(){ return position; } + + /** + * @param point + * @return the smallest distance from the point to this line + */ + public float getDistance(PVector point){ + return (point.sub(position).cross(direction)).mag() / direction.mag(); + } } \ No newline at end of file