Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a5d5275c4 | |||
| f7fd6fc6a8 | |||
| 64bf8769a1 | |||
| c340c02085 | |||
| dbb6b519e6 | |||
| df57253287 | |||
| 6a7d3eeffc | |||
| f59b9b9094 | |||
| 2eedfaccbc | |||
| b09e34d6e9 | |||
| 9deba45afd | |||
| a0173b1053 | |||
| 0c59839dfa | |||
| 36a6c2267b | |||
| b505524fe1 | |||
| 7dc679371a |
@@ -27,3 +27,6 @@ bin/
|
|||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
map.txt
|
||||||
|
processing-4.3/
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="JavadocDeclaration" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ADDITIONAL_TAGS" value="brief" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
Generated
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="ejml">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="file://$PROJECT_DIR$/lib/ejml-v0.42-libs" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES>
|
||||||
|
<root url="file://$PROJECT_DIR$/lib/ejml-v0.42-libs" />
|
||||||
|
</SOURCES>
|
||||||
|
<jarDirectory url="file://$PROJECT_DIR$/lib/ejml-v0.42-libs" recursive="false" />
|
||||||
|
<jarDirectory url="file://$PROJECT_DIR$/lib/ejml-v0.42-libs" recursive="false" type="SOURCES" />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
Generated
-1
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="openjdk-20" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_20" default="true" project-jdk-name="openjdk-20" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
|||||||
@@ -43,5 +43,9 @@
|
|||||||
<SOURCES />
|
<SOURCES />
|
||||||
</library>
|
</library>
|
||||||
</orderEntry>
|
</orderEntry>
|
||||||
|
<orderEntry type="library" exported="" name="ejml" level="project" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,112 +0,0 @@
|
|||||||
numVerts,60
|
|
||||||
numEdges,50
|
|
||||||
vert,206.0,522.0
|
|
||||||
vert,554.0,422.0
|
|
||||||
vert,525.0,281.0
|
|
||||||
vert,404.0,666.0
|
|
||||||
vert,482.0,615.0
|
|
||||||
vert,545.0,326.0
|
|
||||||
vert,520.0,338.0
|
|
||||||
vert,713.0,810.0
|
|
||||||
vert,401.0,616.0
|
|
||||||
vert,520.0,399.0
|
|
||||||
vert,367.0,375.0
|
|
||||||
vert,599.0,418.0
|
|
||||||
vert,591.0,332.0
|
|
||||||
vert,483.0,810.0
|
|
||||||
vert,602.0,652.0
|
|
||||||
vert,711.0,234.0
|
|
||||||
vert,366.0,275.0
|
|
||||||
vert,611.0,359.0
|
|
||||||
vert,570.0,324.0
|
|
||||||
vert,302.0,249.0
|
|
||||||
vert,259.0,476.0
|
|
||||||
vert,411.0,811.0
|
|
||||||
vert,503.0,375.0
|
|
||||||
vert,168.0,471.0
|
|
||||||
vert,603.0,346.0
|
|
||||||
vert,601.0,491.0
|
|
||||||
vert,165.0,235.0
|
|
||||||
vert,405.0,237.0
|
|
||||||
vert,606.0,811.0
|
|
||||||
vert,165.0,809.0
|
|
||||||
vert,213.0,812.0
|
|
||||||
vert,165.0,521.0
|
|
||||||
vert,223.0,273.0
|
|
||||||
vert,294.0,378.0
|
|
||||||
vert,267.0,616.0
|
|
||||||
vert,493.0,238.0
|
|
||||||
vert,403.0,483.0
|
|
||||||
vert,210.0,380.0
|
|
||||||
vert,330.0,376.0
|
|
||||||
vert,714.0,488.0
|
|
||||||
vert,603.0,537.0
|
|
||||||
vert,715.0,655.0
|
|
||||||
vert,224.0,247.0
|
|
||||||
vert,267.0,723.0
|
|
||||||
vert,503.0,357.0
|
|
||||||
vert,618.0,397.0
|
|
||||||
vert,713.0,539.0
|
|
||||||
vert,334.0,727.0
|
|
||||||
vert,485.0,693.0
|
|
||||||
vert,211.0,346.0
|
|
||||||
vert,300.0,277.0
|
|
||||||
vert,294.0,347.0
|
|
||||||
vert,577.0,423.0
|
|
||||||
vert,531.0,414.0
|
|
||||||
vert,329.0,345.0
|
|
||||||
vert,327.0,615.0
|
|
||||||
vert,367.0,347.0
|
|
||||||
vert,304.0,479.0
|
|
||||||
vert,621.0,379.0
|
|
||||||
vert,487.0,485.0
|
|
||||||
edge,start,1,end,53
|
|
||||||
edge,start,2,end,16
|
|
||||||
edge,start,3,end,21
|
|
||||||
edge,start,5,end,18
|
|
||||||
edge,end,5,start,6
|
|
||||||
edge,start,7,end,15
|
|
||||||
edge,start,9,end,22
|
|
||||||
edge,start,11,end,52
|
|
||||||
edge,start,12,end,24
|
|
||||||
edge,start,14,end,41
|
|
||||||
edge,start,14,end,28
|
|
||||||
edge,start,15,end,35
|
|
||||||
edge,start,17,end,58
|
|
||||||
edge,end,12,start,18
|
|
||||||
edge,start,19,end,42
|
|
||||||
edge,start,19,end,50
|
|
||||||
edge,start,22,end,44
|
|
||||||
edge,end,20,start,23
|
|
||||||
edge,end,17,start,24
|
|
||||||
edge,start,26,end,29
|
|
||||||
edge,end,26,start,27
|
|
||||||
edge,end,7,start,29
|
|
||||||
edge,end,0,start,30
|
|
||||||
edge,end,0,start,31
|
|
||||||
edge,start,32,end,42
|
|
||||||
edge,start,32,end,50
|
|
||||||
edge,end,8,start,36
|
|
||||||
edge,start,36,end,57
|
|
||||||
edge,start,37,end,49
|
|
||||||
edge,end,33,start,37
|
|
||||||
edge,start,38,end,54
|
|
||||||
edge,end,10,start,38
|
|
||||||
edge,end,25,start,40
|
|
||||||
edge,start,40,end,46
|
|
||||||
edge,end,34,start,43
|
|
||||||
edge,start,43,end,47
|
|
||||||
edge,end,6,start,44
|
|
||||||
edge,end,11,start,45
|
|
||||||
edge,end,13,start,48
|
|
||||||
edge,end,49,start,51
|
|
||||||
edge,end,33,start,51
|
|
||||||
edge,end,1,start,52
|
|
||||||
edge,end,9,start,53
|
|
||||||
edge,end,34,start,55
|
|
||||||
edge,end,47,start,55
|
|
||||||
edge,end,54,start,56
|
|
||||||
edge,end,10,start,56
|
|
||||||
edge,end,45,start,58
|
|
||||||
edge,end,4,start,59
|
|
||||||
edge,end,39,start,59
|
|
||||||
+1
-1
@@ -50,7 +50,7 @@ public class Car{
|
|||||||
//With all the views that the car has, get their point list
|
//With all the views that the car has, get their point list
|
||||||
void updateScan(PointGraph map){
|
void updateScan(PointGraph map){
|
||||||
for(View view : views){
|
for(View view : views){
|
||||||
view.look(map);
|
view.calculatePointScan(map);
|
||||||
slam.RANSAC(view);
|
slam.RANSAC(view);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ public class Edge {
|
|||||||
* @param vStart the vertex the edge starts at
|
* @param vStart the vertex the edge starts at
|
||||||
* @param vEnd the vertex the edge ends at
|
* @param vEnd the vertex the edge ends at
|
||||||
*/
|
*/
|
||||||
Edge(Vertex vStart, Vertex vEnd){
|
protected Edge(Vertex vStart, Vertex vEnd){
|
||||||
this.vStart = vStart;
|
this.vStart = vStart;
|
||||||
this.vEnd = vEnd;
|
this.vEnd = vEnd;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,10 +52,17 @@ public class PointGraphWriter {
|
|||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PointGraph loadFile(String filename) throws FileNotFoundException, NumberFormatException {
|
public PointGraph loadFile(String filename) throws NumberFormatException {
|
||||||
PointGraph g = new PointGraph();
|
PointGraph g = new PointGraph();
|
||||||
File file = new File(filename);
|
File file = new File(filename);
|
||||||
Scanner reader = new Scanner(file);
|
Scanner reader;
|
||||||
|
try {
|
||||||
|
reader = new Scanner(file);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e){
|
||||||
|
System.out.println("File not found");
|
||||||
|
return g;
|
||||||
|
}
|
||||||
ArrayList<PointVertex> vertices = new ArrayList<>();
|
ArrayList<PointVertex> vertices = new ArrayList<>();
|
||||||
while(reader.hasNextLine()){
|
while(reader.hasNextLine()){
|
||||||
String line = reader.nextLine();
|
String line = reader.nextLine();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import processing.core.PApplet;
|
|||||||
|
|
||||||
public class PointVertex extends Vertex {
|
public class PointVertex extends Vertex {
|
||||||
private Vector position;
|
private Vector position;
|
||||||
|
|
||||||
private int[] color = new int[]{127, 255, 0, 0};
|
private int[] color = new int[]{127, 255, 0, 0};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-13
@@ -23,7 +23,7 @@ public class Processing extends PApplet {
|
|||||||
processing = this;
|
processing = this;
|
||||||
car = new Car(processing, 100,100,50,40);
|
car = new Car(processing, 100,100,50,40);
|
||||||
size(1000, 1000);
|
size(1000, 1000);
|
||||||
car.addView(360,360);
|
car.addView(90,180);
|
||||||
|
|
||||||
// for(int i = 0; i < 10; i++){
|
// for(int i = 0; i < 10; i++){
|
||||||
// PointVertex vStart = new PointVertex(random(50, 950), random(50, 950));
|
// PointVertex vStart = new PointVertex(random(50, 950), random(50, 950));
|
||||||
@@ -40,7 +40,6 @@ public class Processing extends PApplet {
|
|||||||
car.drawCar(map, SLAMIsHidden);
|
car.drawCar(map, SLAMIsHidden);
|
||||||
strokeWeight(2);
|
strokeWeight(2);
|
||||||
stroke(255);
|
stroke(255);
|
||||||
//car.drive(new int[] {0, 0});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void keyPressed(){
|
public void keyPressed(){
|
||||||
@@ -80,15 +79,10 @@ public class Processing extends PApplet {
|
|||||||
}
|
}
|
||||||
if(key == 'l'){
|
if(key == 'l'){
|
||||||
System.out.println("Attempting to load a map from file");
|
System.out.println("Attempting to load a map from file");
|
||||||
try{
|
PointGraphWriter writer = new PointGraphWriter();
|
||||||
PointGraphWriter writer = new PointGraphWriter();
|
try {
|
||||||
map = writer.loadFile("map.txt");
|
map = writer.loadFile("map.txt");
|
||||||
}
|
} catch (NumberFormatException e) {
|
||||||
catch (FileNotFoundException e){
|
|
||||||
System.out.println("File not found");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
catch (NumberFormatException e){
|
|
||||||
System.out.println("Number format incorrect");
|
System.out.println("Number format incorrect");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -127,7 +121,4 @@ public class Processing extends PApplet {
|
|||||||
PointVertex v = new PointVertex(clickPosition);
|
PointVertex v = new PointVertex(clickPosition);
|
||||||
map.addVertex(v);
|
map.addVertex(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
import Vector.*;
|
import Vector.*;
|
||||||
import processing.core.*;
|
import processing.core.*;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -96,7 +95,7 @@ public class SLAM{
|
|||||||
* @param view a laser scan view
|
* @param view a laser scan view
|
||||||
*/
|
*/
|
||||||
public void RANSAC(View view){
|
public void RANSAC(View view){
|
||||||
unassociatedPoints.addScan(view.getPos(), view.getPoints());
|
unassociatedPoints.addScan(view.getPos(), view.getScan().getPoints());
|
||||||
|
|
||||||
float degreeRange = radians(5); // range to randomly sample readings within
|
float degreeRange = radians(5); // range to randomly sample readings within
|
||||||
int numSampleReadings = 15; // number of readings to randomly sample
|
int numSampleReadings = 15; // number of readings to randomly sample
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package ScanGraph;
|
||||||
|
|
||||||
|
import Graph.Edge;
|
||||||
|
import Vector.Line;
|
||||||
|
import Vector.LineInterface;
|
||||||
|
import Vector.Vector;
|
||||||
|
import processing.core.PApplet;
|
||||||
|
|
||||||
|
import static java.lang.Math.PI;
|
||||||
|
|
||||||
|
public class ScanEdge extends Edge implements LineInterface {
|
||||||
|
|
||||||
|
protected ScanPoint vStart;
|
||||||
|
protected ScanPoint vEnd;
|
||||||
|
protected Line line;
|
||||||
|
public ScanEdge(ScanPoint vStart, ScanPoint vEnd){
|
||||||
|
super(vStart, vEnd);
|
||||||
|
this.vStart = vStart;
|
||||||
|
this.vEnd = vEnd;
|
||||||
|
|
||||||
|
this.line = new Line(vStart.getPos(), vEnd.getPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getDirection(){
|
||||||
|
return line.getDirection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getPosition(){
|
||||||
|
return line.getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLength(){
|
||||||
|
return line.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAngle(){
|
||||||
|
return line.getAngle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector endPoint(){
|
||||||
|
return line.endPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDistance(Vector point){
|
||||||
|
return line.getDistance(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void draw(PApplet proc){
|
||||||
|
line.draw(proc);
|
||||||
|
Vector leftFlange = line.getDirection().rotate2D((float)(-3*PI/4)).normalize().mul(20);
|
||||||
|
Vector rightFlange = line.getDirection().rotate2D((float) (3*PI/4)).normalize().mul(20);
|
||||||
|
Line l1 = new Line(line.endPoint(), line.endPoint().add(leftFlange));
|
||||||
|
Line l2 = new Line(line.endPoint(), line.endPoint().add(rightFlange));
|
||||||
|
l1.draw(proc);
|
||||||
|
l2.draw(proc);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package ScanGraph;
|
||||||
|
|
||||||
|
import Graph.Graph;
|
||||||
|
import Graph.Vertex;
|
||||||
|
import Vector.Vector;
|
||||||
|
import org.ejml.simple.SimpleMatrix;
|
||||||
|
import org.ejml.simple.SimpleSVD;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
public class ScanGraph extends Graph {
|
||||||
|
|
||||||
|
ScanPoint lastPoint;
|
||||||
|
|
||||||
|
public ScanGraph(ScanPoint startingPoint) {
|
||||||
|
super();
|
||||||
|
this.lastPoint = startingPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEdge(ScanPoint vEnd) {
|
||||||
|
addVertex(vEnd);
|
||||||
|
ScanEdge edge = new ScanEdge(this.lastPoint, vEnd);
|
||||||
|
adjList.get((Vertex) this.lastPoint).add(edge);
|
||||||
|
|
||||||
|
this.lastPoint = vEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param newScan the scan to match
|
||||||
|
* @return null if no match can be found, or an existing scan the matches the new scan.
|
||||||
|
* @brief Get a new scan in and try to match it with all other scans in the graph
|
||||||
|
*/
|
||||||
|
private ScanPoint getAssociatedScan(ScanPoint newScan) {
|
||||||
|
ScanMatcher matcher = new ScanMatcher();
|
||||||
|
ScanPoint matchedScan = null;
|
||||||
|
// go through all of our available scans and try to match the new scan with the old scans. If no match can be found return null
|
||||||
|
for (Vertex v : adjList.keySet()) {
|
||||||
|
ScanPoint referenceScan = (ScanPoint) v;
|
||||||
|
matchedScan = matcher.iterativeScanMatch(referenceScan, newScan, 0.1F, 10);
|
||||||
|
|
||||||
|
if(matchedScan != null){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matchedScan;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
package ScanGraph;
|
||||||
|
|
||||||
|
import Vector.Vector;
|
||||||
|
import org.ejml.simple.SimpleMatrix;
|
||||||
|
import org.ejml.simple.SimpleSVD;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static java.lang.Math.abs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A class that can match two point scans together
|
||||||
|
*/
|
||||||
|
public class ScanMatcher{
|
||||||
|
// A 2x2 matrix describing a rotation to apply to the new scan
|
||||||
|
public SimpleMatrix rotationMatrix = null;
|
||||||
|
|
||||||
|
// A 2x1 matrix describing a translation to apply to the new scan
|
||||||
|
public SimpleMatrix translationVector = null;
|
||||||
|
|
||||||
|
public ScanMatcher(){
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief iteratively calculate new rotation and transpose matrices to determien if the two scans match
|
||||||
|
* @param referenceScan the scan to be referenced
|
||||||
|
* @param newScan the scan that will be rotated and moved until it matches the reference scan
|
||||||
|
* @param iterations The number of iterations that the scan matcher will attempt
|
||||||
|
* @param errorThreshold The error threshold that the match will have to meet before considering it a valid match
|
||||||
|
*/
|
||||||
|
public ScanPoint iterativeScanMatch(ScanPoint referenceScan, ScanPoint newScan, float errorThreshold, int iterations){
|
||||||
|
// make a copy of the new scan so we don't modify the original
|
||||||
|
ScanPoint scanBeingMatched = new ScanPoint(newScan);
|
||||||
|
|
||||||
|
// calculate the rotation and translation matrices between the two scans
|
||||||
|
this.calculateRotationAndTranslationMatrices(referenceScan, scanBeingMatched);
|
||||||
|
|
||||||
|
SimpleMatrix cumulativeRotationMatrix = new SimpleMatrix(this.rotationMatrix);
|
||||||
|
SimpleMatrix cumulativeTranslationVector = new SimpleMatrix(this.translationVector);
|
||||||
|
|
||||||
|
// iterate through the scan matching algorithm
|
||||||
|
for (int i = 0; i < iterations; i++) {
|
||||||
|
// calculate the rotation and translation matrices between the two scans
|
||||||
|
this.calculateRotationAndTranslationMatrices(referenceScan, scanBeingMatched);
|
||||||
|
// apply the rotation and translation matrices to the new scan
|
||||||
|
scanBeingMatched = this.applyRotationAndTranslationMatrices(scanBeingMatched);
|
||||||
|
// calculate the error between the new scan and the reference scan
|
||||||
|
float error = this.getError(referenceScan, scanBeingMatched);
|
||||||
|
// if the error is less than the error threshold, then we have a valid match
|
||||||
|
if(error < errorThreshold){
|
||||||
|
this.rotationMatrix = cumulativeRotationMatrix;
|
||||||
|
this.translationVector = cumulativeTranslationVector;
|
||||||
|
return scanBeingMatched;
|
||||||
|
}
|
||||||
|
// otherwise, we need to keep iterating
|
||||||
|
// add the rotation and translation matrices to the cumulative rotation and translation matrices
|
||||||
|
cumulativeRotationMatrix = cumulativeRotationMatrix.mult(this.rotationMatrix);
|
||||||
|
cumulativeTranslationVector = cumulativeTranslationVector.plus(this.translationVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we get to this point, then we have not found a valid match
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute the cross covariance matrix between the new scan and the reference scan
|
||||||
|
* @return a 2x2 matrix containing the cross covariance matrix
|
||||||
|
*/
|
||||||
|
private SimpleMatrix crossCovarianceMatrix(ScanPoint referenceScan, ScanPoint newScan, CorrespondenceMatrix correspondenceMatrix){
|
||||||
|
|
||||||
|
Vector referenceScanAveragePosition = correspondenceMatrix.getAverageOldPosition();
|
||||||
|
Vector newScanAveragePosition = correspondenceMatrix.getAverageNewPosition();
|
||||||
|
|
||||||
|
// compute the cross covariance matrix which is given by the formula:
|
||||||
|
// covariance = the sum from 1 to N of (p_i) * (q_i)^T
|
||||||
|
// where p_i is the ith point in the new scan and q_i is the ith point in the reference scan and N is the number of points in the scan
|
||||||
|
// the cross covariance matrix is a 2x2 matrix
|
||||||
|
float[][] crossCovarianceMatrix = new float[2][2];
|
||||||
|
|
||||||
|
for (int i = 0; i < correspondenceMatrix.getOldPointIndices().size(); i++) {
|
||||||
|
int oldIndex = correspondenceMatrix.getOldPointIndices().get(i);
|
||||||
|
int newIndex = correspondenceMatrix.getNewPointIndices().get(i);
|
||||||
|
Vector oldPoint = referenceScan.getPoints().get(oldIndex);
|
||||||
|
Vector newPoint = newScan.getPoints().get(newIndex);
|
||||||
|
if (oldPoint != null && newPoint != null) {
|
||||||
|
Vector oldPointOffset = oldPoint.sub(referenceScanAveragePosition);
|
||||||
|
Vector newPointOffset = newPoint.sub(newScanAveragePosition);
|
||||||
|
crossCovarianceMatrix[0][0] += oldPointOffset.x * newPointOffset.x;
|
||||||
|
crossCovarianceMatrix[0][1] += oldPointOffset.x * newPointOffset.y;
|
||||||
|
crossCovarianceMatrix[1][0] += oldPointOffset.y * newPointOffset.x;
|
||||||
|
crossCovarianceMatrix[1][1] += oldPointOffset.y * newPointOffset.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SimpleMatrix(crossCovarianceMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute the rotation and translation matrices between the new scan and the reference scan. Then cache them as private variables.
|
||||||
|
* The rotation matrix is a 2x2 matrix and the translation vector is a 2x1 matrix
|
||||||
|
*/
|
||||||
|
public void calculateRotationAndTranslationMatrices(ScanPoint referenceScan, ScanPoint newScan){
|
||||||
|
CorrespondenceMatrix correspondenceMatrix = new CorrespondenceMatrix(newScan, referenceScan);
|
||||||
|
|
||||||
|
// compute the rotation matrix which is given by the formula:
|
||||||
|
// R = V * U^T
|
||||||
|
// where V and U are the singular value decomposition of the cross covariance matrix
|
||||||
|
// the rotation matrix is a 2x2 matrix
|
||||||
|
SimpleMatrix crossCovarianceMatrixSimple = crossCovarianceMatrix(referenceScan, newScan, correspondenceMatrix);
|
||||||
|
SimpleSVD<SimpleMatrix> svd = crossCovarianceMatrixSimple.svd();
|
||||||
|
this.rotationMatrix = svd.getU().mult(svd.getV().transpose());
|
||||||
|
// calculate what the angle of the rotation matrix is
|
||||||
|
float angle = (float) Math.atan2(this.rotationMatrix.get(1, 0), this.rotationMatrix.get(0, 0));
|
||||||
|
// scale the angle by a small amount to make the rotation matrix more accurate
|
||||||
|
angle*= 1.75F;
|
||||||
|
this.rotationMatrix = new SimpleMatrix(new double[][]{{Math.cos(angle), -Math.sin(angle)}, {Math.sin(angle), Math.cos(angle)}});
|
||||||
|
|
||||||
|
// percentage of the local position to use in the translation calculation
|
||||||
|
double weightedAverage = 0.9;
|
||||||
|
|
||||||
|
SimpleMatrix localNewScanAveragePosition = new SimpleMatrix(correspondenceMatrix.getAverageNewPosition().toArray());//this.averageScanPosition(newScan);
|
||||||
|
SimpleMatrix globalNewScanAveragePosition = new SimpleMatrix(this.averageScanPosition(newScan));
|
||||||
|
SimpleMatrix weightedAverageNewScanPostion = localNewScanAveragePosition.scale(weightedAverage).plus(globalNewScanAveragePosition.scale(1-weightedAverage));
|
||||||
|
|
||||||
|
SimpleMatrix localReferenceScanAveragePosition = new SimpleMatrix(correspondenceMatrix.getAverageOldPosition().toArray()); //this.averageScanPosition(referenceScan);
|
||||||
|
SimpleMatrix globalReferenceScanAveragePosition = new SimpleMatrix(this.averageScanPosition(referenceScan));
|
||||||
|
SimpleMatrix weightedAverageReferenceScanPostion = localReferenceScanAveragePosition.scale(weightedAverage).plus(globalReferenceScanAveragePosition.scale(1-weightedAverage));
|
||||||
|
|
||||||
|
|
||||||
|
this.translationVector = weightedAverageReferenceScanPostion.minus(rotationMatrix.mult(weightedAverageNewScanPostion));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleMatrix getRotationMatrix(){
|
||||||
|
return this.rotationMatrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleMatrix getTranslationVector(){
|
||||||
|
return this.translationVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScanPoint applyRotationAndTranslationMatrices(ScanPoint newScan){
|
||||||
|
// copy the new scan so we don't modify the original
|
||||||
|
ScanPoint tempScan = new ScanPoint(newScan);
|
||||||
|
// apply the rotation matrix and translation vector to the new scan
|
||||||
|
for (int i = 0; i < tempScan.getPoints().size(); i++) {
|
||||||
|
Vector point = tempScan.getPoints().get(i);
|
||||||
|
if (point != null) {
|
||||||
|
SimpleMatrix pointMatrix = new SimpleMatrix(point.toArray());
|
||||||
|
SimpleMatrix newPointMatrix = rotationMatrix.mult(pointMatrix).plus(translationVector);
|
||||||
|
tempScan.getPoints().set(i, new Vector((float) newPointMatrix.get(0), (float) newPointMatrix.get(1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tempScan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compute the average position of the scan
|
||||||
|
* @param scan the scan to compute the average position of
|
||||||
|
* @return a 2x1 matrix containing the x,y coordinates of the average position of the scan
|
||||||
|
*/
|
||||||
|
private SimpleMatrix averageScanPosition(ScanPoint scan){
|
||||||
|
Vector averagePosition = new Vector(0, 0);
|
||||||
|
int invalidPoints = 0;
|
||||||
|
for (Vector point : scan.getPoints()) {
|
||||||
|
if (point != null) {
|
||||||
|
averagePosition = averagePosition.add(point);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
invalidPoints++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SimpleMatrix(averagePosition.div(scan.getPoints().size() - invalidPoints).toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getError(ScanPoint referenceScan, ScanPoint newScan){
|
||||||
|
// calculate the error between the new scan and the reference scan
|
||||||
|
// q is reference scan and p is new scan
|
||||||
|
// error is given as abs(Q_mean - R * P_mean)
|
||||||
|
// where Q_mean is the average position of the reference scan
|
||||||
|
// P_mean is the average position of the new scan
|
||||||
|
// R is the rotation matrix
|
||||||
|
|
||||||
|
SimpleMatrix newScanAveragePosition = averageScanPosition(newScan);
|
||||||
|
SimpleMatrix referenceScanAveragePosition = averageScanPosition(referenceScan);
|
||||||
|
SimpleMatrix error = referenceScanAveragePosition.minus(rotationMatrix.mult(newScanAveragePosition));
|
||||||
|
return (float) abs(error.elementSum());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A class to hold the correspondence matrix between two scans
|
||||||
|
* The correspondence matrix is a 3xN matrix where N is the number of valid points in the scan.
|
||||||
|
* This calculates the closest point in the old scan for each point in the new scan and gets rid of redundant closest points.
|
||||||
|
*/
|
||||||
|
class CorrespondenceMatrix{
|
||||||
|
private ArrayList<Integer> oldPointIndices = new ArrayList<>();
|
||||||
|
private ArrayList<Integer> newPointIndices = new ArrayList<>();
|
||||||
|
private ArrayList<Float> distances = new ArrayList<>();
|
||||||
|
|
||||||
|
private Vector averageOldPosition = new Vector(0, 0);
|
||||||
|
private Vector averageNewPosition = new Vector(0, 0);
|
||||||
|
|
||||||
|
CorrespondenceMatrix(ScanPoint newScan, ScanPoint oldScan){
|
||||||
|
this.calculateCorrespondenceMatrix(newScan, oldScan);
|
||||||
|
this.calculateAveragePositions(newScan, oldScan);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Integer> getOldPointIndices(){
|
||||||
|
return this.oldPointIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Integer> getNewPointIndices(){
|
||||||
|
return this.newPointIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Float> getDistances(){
|
||||||
|
return this.distances;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getAverageOldPosition(){
|
||||||
|
return this.averageOldPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector getAverageNewPosition(){
|
||||||
|
return this.averageNewPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateAveragePositions(ScanPoint newScan, ScanPoint oldScan){
|
||||||
|
int invalidPoints = 0;
|
||||||
|
for (int i = 0; i < this.oldPointIndices.size(); i++){
|
||||||
|
int oldIndex = this.oldPointIndices.get(i);
|
||||||
|
int newIndex = this.newPointIndices.get(i);
|
||||||
|
Vector oldPoint = oldScan.getPoints().get(oldIndex);
|
||||||
|
Vector newPoint = newScan.getPoints().get(newIndex);
|
||||||
|
if (oldPoint != null && newPoint != null) {
|
||||||
|
this.averageOldPosition = this.averageOldPosition.add(oldPoint);
|
||||||
|
this.averageNewPosition = this.averageNewPosition.add(newPoint);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
invalidPoints++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.averageOldPosition = this.averageOldPosition.div(this.oldPointIndices.size() - invalidPoints);
|
||||||
|
this.averageNewPosition = this.averageNewPosition.div(this.newPointIndices.size() - invalidPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate the correspondence matrix between two scans
|
||||||
|
* @param newScan the new scan
|
||||||
|
* @param referenceScan the reference scan
|
||||||
|
*/
|
||||||
|
private void calculateCorrespondenceMatrix(ScanPoint newScan, ScanPoint referenceScan) {
|
||||||
|
for (int newPointIndex = 0; newPointIndex < newScan.getPoints().size(); newPointIndex++) {
|
||||||
|
Vector newPoint = newScan.getPoints().get(newPointIndex);
|
||||||
|
|
||||||
|
// Skip null points in the new scan
|
||||||
|
if (newPoint == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float closestDistance = Float.MAX_VALUE;
|
||||||
|
int closestIndex = -1;
|
||||||
|
|
||||||
|
for (int oldPointIndex = 0; oldPointIndex < referenceScan.getPoints().size(); oldPointIndex++) {
|
||||||
|
Vector oldPoint = referenceScan.getPoints().get(oldPointIndex);
|
||||||
|
|
||||||
|
// Skip null points in the old scan
|
||||||
|
if (oldPoint == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
float distance = newPoint.sub(oldPoint).mag();
|
||||||
|
|
||||||
|
if (distance < closestDistance) {
|
||||||
|
closestDistance = distance;
|
||||||
|
closestIndex = oldPointIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we find a closest point...
|
||||||
|
if (closestIndex != -1) {
|
||||||
|
// check if the oldPointIndex is already in the list of oldPointIndices
|
||||||
|
if(this.oldPointIndices.contains(closestIndex)){
|
||||||
|
int index = this.oldPointIndices.indexOf(closestIndex);
|
||||||
|
// if the index is already in our list, then we need to check if the new point is closer than the old point
|
||||||
|
if(this.distances.get(index) > closestDistance){
|
||||||
|
// if the new point is closer than the old point, then we need to replace the old point with the new point
|
||||||
|
this.oldPointIndices.set(index, closestIndex);
|
||||||
|
this.newPointIndices.set(index, newPointIndex);
|
||||||
|
this.distances.set(index, closestDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the index is not in our list, then we need to add it
|
||||||
|
else{
|
||||||
|
this.oldPointIndices.add(closestIndex);
|
||||||
|
this.newPointIndices.add(newPointIndex);
|
||||||
|
this.distances.add(closestDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package ScanGraph;
|
||||||
|
|
||||||
|
import Graph.Vertex;
|
||||||
|
import Vector.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class ScanPoint extends Vertex{
|
||||||
|
|
||||||
|
private Vector position;
|
||||||
|
private float orientation;
|
||||||
|
private ArrayList<Vector> scan;
|
||||||
|
|
||||||
|
public ScanPoint(Vector scanPosition, float orientation, ArrayList<Vector> scan) {
|
||||||
|
super();
|
||||||
|
this.position = scanPosition;
|
||||||
|
this.orientation = orientation;
|
||||||
|
this.scan = scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor
|
||||||
|
* @param other The scan point to copy
|
||||||
|
*/
|
||||||
|
public ScanPoint(ScanPoint other){
|
||||||
|
super();
|
||||||
|
this.position = new Vector(other.getPos().x, other.getPos().y);
|
||||||
|
this.orientation = other.getOrientation();
|
||||||
|
this.scan = new ArrayList<>(other.getPoints());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a two eleement float array containing the x and y coordinates of the vertex respectively.
|
||||||
|
*/
|
||||||
|
public Vector getPos(){
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getOrientation(){
|
||||||
|
return this.orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayList<Vector> getPoints(){
|
||||||
|
return this.scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package Vector;
|
package Vector;
|
||||||
|
|
||||||
|
import org.ejml.simple.SimpleMatrix;
|
||||||
import processing.core.PApplet;
|
import processing.core.PApplet;
|
||||||
|
|
||||||
import static java.lang.Math.*;
|
import static java.lang.Math.*;
|
||||||
@@ -23,6 +24,18 @@ public class Vector {
|
|||||||
this.z = z;
|
this.z = z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Vector(SimpleMatrix matrix){
|
||||||
|
// initialize x,y if matrix is 2x1 and x,y,z if matrix is 3x1
|
||||||
|
if(matrix.getNumRows() == 2){
|
||||||
|
this.x = (float)matrix.get(0,0);
|
||||||
|
this.y = (float)matrix.get(1,0);
|
||||||
|
}else if(matrix.getNumRows() == 3){
|
||||||
|
this.x = (float)matrix.get(0,0);
|
||||||
|
this.y = (float)matrix.get(1,0);
|
||||||
|
this.z = (float)matrix.get(2,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Vector add(Vector other){
|
public Vector add(Vector other){
|
||||||
return new Vector(this.x + other.x, this.y + other.y, this.z + other.z);
|
return new Vector(this.x + other.x, this.y + other.y, this.z + other.z);
|
||||||
}
|
}
|
||||||
@@ -83,10 +96,18 @@ public class Vector {
|
|||||||
return angle;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The angle of the vector in radians
|
||||||
|
*/
|
||||||
public float angle(){
|
public float angle(){
|
||||||
return (float) atan2(y, x);
|
return (float) atan2(y, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rotate a 2D vector by a given angle
|
||||||
|
* @param angle The angle to rotate the vector by in radians
|
||||||
|
* @return The rotated vector
|
||||||
|
*/
|
||||||
public Vector rotate2D(float angle){
|
public Vector rotate2D(float angle){
|
||||||
float distance = mag();
|
float distance = mag();
|
||||||
float currentAngle = this.angle();
|
float currentAngle = this.angle();
|
||||||
@@ -96,4 +117,8 @@ public class Vector {
|
|||||||
public void draw(PApplet proc){
|
public void draw(PApplet proc){
|
||||||
proc.circle(this.x, this.y, 8);
|
proc.circle(this.x, this.y, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float[] toArray() {
|
||||||
|
return new float[]{x, y};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-18
@@ -2,18 +2,25 @@ import Graph.PointGraph;
|
|||||||
import Vector.Vector;
|
import Vector.Vector;
|
||||||
import processing.core.*;
|
import processing.core.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import ScanGraph.ScanPoint;
|
||||||
|
|
||||||
public class View {
|
public class View {
|
||||||
Vector pose;
|
Vector position;
|
||||||
float angle = 0;
|
float angle = 0;
|
||||||
float FOV;
|
float FOV;
|
||||||
ArrayList<Ray> rays = new ArrayList<>();
|
ArrayList<Ray> rays = new ArrayList<>();
|
||||||
private static PApplet proc;
|
private static PApplet proc;
|
||||||
|
|
||||||
//the x,y position of the view, what angle it's looking at and its FOV
|
/**
|
||||||
|
* @brief Constructor for the View class
|
||||||
|
* @param processing The PApplet that the view will be drawn on
|
||||||
|
* @param newPose The position of the view
|
||||||
|
* @param numberOfRays The number of rays that the view will have
|
||||||
|
* @param FOV The field of view of the view
|
||||||
|
*/
|
||||||
View(PApplet processing, Vector newPose, int numberOfRays, float FOV) {
|
View(PApplet processing, Vector newPose, int numberOfRays, float FOV) {
|
||||||
proc = processing;
|
proc = processing;
|
||||||
this.pose = newPose;
|
this.position = newPose;
|
||||||
this.FOV = FOV;
|
this.FOV = FOV;
|
||||||
this.setRayNum(numberOfRays, FOV, this.angle);
|
this.setRayNum(numberOfRays, FOV, this.angle);
|
||||||
}
|
}
|
||||||
@@ -24,34 +31,42 @@ public class View {
|
|||||||
rays.clear();
|
rays.clear();
|
||||||
float angle = (float) (angleOffset); //the 0.01 fixes some bugs
|
float angle = (float) (angleOffset); //the 0.01 fixes some bugs
|
||||||
for (int i = 0; i < numberOfRays; i++) {
|
for (int i = 0; i < numberOfRays; i++) {
|
||||||
Ray ray = new Ray(pose, angle);
|
Ray ray = new Ray(position, angle);
|
||||||
angle = angle + rayStep;
|
angle = angle + rayStep;
|
||||||
rays.add(ray);
|
rays.add(ray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//sees if the ray will collide with the walls in the wall list
|
/**
|
||||||
public void look(PointGraph map) {
|
* @brief Calculates the points of intersection of the rays with the map
|
||||||
|
* @param map The map that the view is looking at
|
||||||
|
*/
|
||||||
|
public void calculatePointScan(PointGraph map) {
|
||||||
for (Ray ray : rays) {
|
for (Ray ray : rays) {
|
||||||
ray.castRay(map);
|
ray.castRay(map);
|
||||||
if(ray.hasCollided()){
|
if(ray.hasCollided()){
|
||||||
ray.getPoint().draw(proc);
|
ray.getPoint().draw(proc);
|
||||||
// ray.drawRay(proc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//changes the position of the view
|
/**
|
||||||
public void setPos(Vector newPose) {
|
* @brief Sets the position of the view
|
||||||
pose = newPose;
|
* @param newPosition The new position of the view
|
||||||
|
*/
|
||||||
|
public void setPos(Vector newPosition) {
|
||||||
|
position = newPosition;
|
||||||
for (Ray ray : rays) {
|
for (Ray ray : rays) {
|
||||||
ray.setPos(pose);
|
ray.setPos(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//changes the angle of the view
|
/**
|
||||||
public void setAngle(float angle) {
|
* @brief Sets the angle of the view
|
||||||
this.angle = angle;
|
* @param newAngle The new angle of the view
|
||||||
|
*/
|
||||||
|
public void setAngle(float newAngle) {
|
||||||
|
this.angle = newAngle;
|
||||||
for(Ray ray : rays){
|
for(Ray ray : rays){
|
||||||
float angleOffset = ray.getAngle() - this.angle;
|
float angleOffset = ray.getAngle() - this.angle;
|
||||||
ray.setAngle(this.angle+angleOffset);
|
ray.setAngle(this.angle+angleOffset);
|
||||||
@@ -64,24 +79,39 @@ public class View {
|
|||||||
this.setRayNum(this.rays.size(), this.FOV, this.angle);
|
this.setRayNum(this.rays.size(), this.FOV, this.angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The position of the view
|
||||||
|
*/
|
||||||
public Vector getPos() {
|
public Vector getPos() {
|
||||||
return pose;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The angle of the view
|
||||||
|
*/
|
||||||
public float getAngle() {
|
public float getAngle() {
|
||||||
return this.angle;
|
return this.angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The field of view of the view
|
||||||
|
*/
|
||||||
public float getFOV() {
|
public float getFOV() {
|
||||||
return this.FOV;
|
return this.FOV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of rays that the view has
|
||||||
|
*/
|
||||||
public int getRayNum() {
|
public int getRayNum() {
|
||||||
return this.rays.size();
|
return this.rays.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
//gets the point that each ray has collided with
|
/**
|
||||||
public ArrayList<Vector> getPoints() {
|
* @brief Get the most recent scan from the view
|
||||||
|
* @return A ScanPoint object containing the position, angle and points of the view
|
||||||
|
*/
|
||||||
|
public ScanPoint getScan() {
|
||||||
ArrayList<Vector> points = new ArrayList<>();
|
ArrayList<Vector> points = new ArrayList<>();
|
||||||
|
|
||||||
for (Ray ray : rays) {
|
for (Ray ray : rays) {
|
||||||
@@ -92,7 +122,7 @@ public class View {
|
|||||||
points.add(point);
|
points.add(point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return points;
|
return new ScanPoint(this.position,this.angle, points);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import ScanGraph.ScanMatcher;
|
||||||
|
import ScanGraph.ScanPoint;
|
||||||
|
import Vector.Vector;
|
||||||
|
import processing.core.PApplet;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class MatcherVisualizer extends PApplet{
|
||||||
|
|
||||||
|
public static PApplet processing;
|
||||||
|
ScanPoint referenceScan;
|
||||||
|
ScanPoint scanToMatch;
|
||||||
|
ScanPoint scanBeingMatched;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
PApplet.main("MatcherVisualizer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void settings(){
|
||||||
|
processing = this;
|
||||||
|
size(1000, 1000);
|
||||||
|
float connectingAngle = (float) (3 * Math.PI / 9);
|
||||||
|
|
||||||
|
// generate two scans rotated by 45 degrees and append them together
|
||||||
|
Vector descriptor = new Vector(200, 200);
|
||||||
|
Vector position = new Vector(500, 500);
|
||||||
|
ScanPoint scan1 = generateScanPoint(position, descriptor, 12);
|
||||||
|
ScanPoint scan2 = generateScanPoint(position, descriptor.rotate2D(connectingAngle), 12);
|
||||||
|
Vector p1 = scan1.getPoints().get(11);
|
||||||
|
Vector p2 = scan2.getPoints().get(11);
|
||||||
|
ScanPoint scan3 = generateScanPoint(p1, p2.sub(p1), 12);
|
||||||
|
this.referenceScan = appendScanPoints(scan1, scan2);
|
||||||
|
this.referenceScan = appendScanPoints(this.referenceScan, scan3);
|
||||||
|
|
||||||
|
// generate two scans offset by some amount and rotated by 55 degrees and append them together
|
||||||
|
Vector rotated = descriptor.rotate2D((float) Math.PI);
|
||||||
|
Vector offset = new Vector(100, 150);
|
||||||
|
ScanPoint scan4 = generateScanPoint(position.add(offset), rotated, 12);
|
||||||
|
ScanPoint scan5 = generateScanPoint(position.add(offset), rotated.rotate2D(connectingAngle), 12);
|
||||||
|
p1 = scan4.getPoints().get(11);
|
||||||
|
p2 = scan5.getPoints().get(11);
|
||||||
|
ScanPoint scan6 = generateScanPoint(p1, p2.sub(p1), 12);
|
||||||
|
this.scanToMatch = appendScanPoints(scan4, scan5);
|
||||||
|
this.scanToMatch = appendScanPoints(this.scanToMatch, scan6);
|
||||||
|
this.scanBeingMatched = new ScanPoint(this.scanToMatch);
|
||||||
|
}
|
||||||
|
public void draw(){
|
||||||
|
iterativeScanMatch();
|
||||||
|
// background(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generate a scan point from a scan description
|
||||||
|
* @param offset The offset of the scan point from the origin
|
||||||
|
* @param scanDescription A vector which describes the length of the line and direction of the line
|
||||||
|
* @return A scan point with the given offset and scan description
|
||||||
|
*/
|
||||||
|
public static ScanPoint generateScanPoint(Vector offset, Vector scanDescription, int numPoints){
|
||||||
|
// generate a scan point with the given offset and scan description
|
||||||
|
ArrayList<Vector> scan = new ArrayList<>();
|
||||||
|
|
||||||
|
// divide the scan description by the number of points to allow us to scale it back up in the loop
|
||||||
|
Vector directionVector = scanDescription.div(numPoints-1);
|
||||||
|
|
||||||
|
for (int i = 0; i < numPoints; i++) {
|
||||||
|
scan.add(offset.add(directionVector.mul(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScanPoint(new Vector(0, 0), 0, scan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append two scan points together
|
||||||
|
* @param scan1 The first scan point to append
|
||||||
|
* @param scan2 The second scan point to append
|
||||||
|
* @return A scan point that is the combination of the two scan points
|
||||||
|
*/
|
||||||
|
public static ScanPoint appendScanPoints(ScanPoint scan1, ScanPoint scan2){
|
||||||
|
ArrayList<Vector> points = new ArrayList<>();
|
||||||
|
points.addAll(scan1.getPoints());
|
||||||
|
points.addAll(scan2.getPoints());
|
||||||
|
return new ScanPoint(new Vector(0, 0), 0, points);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Draw a scan point to the screen
|
||||||
|
* @param scan The scan point to draw
|
||||||
|
* @param color The color to draw the scan point
|
||||||
|
*/
|
||||||
|
public void drawScan(ScanPoint scan, int[] color) {
|
||||||
|
processing.stroke(color[0], color[1], color[2]);
|
||||||
|
processing.fill(color[0], color[1], color[2]);
|
||||||
|
ArrayList<Vector> points = scan.getPoints();
|
||||||
|
for (int i = 0; i < points.size() - 1; i++) {
|
||||||
|
Vector point = points.get(i);
|
||||||
|
processing.ellipse(point.x, point.y, 5, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void iterativeScanMatch() {
|
||||||
|
background(0);
|
||||||
|
int[] red = {255, 0, 0};
|
||||||
|
int[] green = {0, 255, 0};
|
||||||
|
int[] blue = {0, 0, 255};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
drawScan(this.referenceScan, red);
|
||||||
|
drawScan(this.scanToMatch, green);
|
||||||
|
|
||||||
|
// do a single scan match and calculate the error
|
||||||
|
ScanMatcher matcher = new ScanMatcher();
|
||||||
|
matcher.calculateRotationAndTranslationMatrices(this.referenceScan, this.scanBeingMatched);
|
||||||
|
this.scanBeingMatched = matcher.applyRotationAndTranslationMatrices(this.scanBeingMatched);
|
||||||
|
float error = matcher.getError(this.referenceScan, this.scanBeingMatched);
|
||||||
|
drawScan(this.scanBeingMatched, blue);
|
||||||
|
|
||||||
|
// do an iterative scan match and calculate the error
|
||||||
|
// ScanPoint matchedScan = matcher.iterativeScanMatch(scan1, scan2, 0.01f, 10);
|
||||||
|
|
||||||
|
// float iterativeScanMatchError = matcher.getError(scan1, matchedScan);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import ScanGraph.ScanMatcher;
|
||||||
|
import ScanGraph.ScanPoint;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import processing.core.PApplet;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import Vector.Vector;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static processing.core.PApplet.main;
|
||||||
|
|
||||||
|
class ScanMatcherTest{
|
||||||
|
/**
|
||||||
|
* @brief Generate a scan point from a scan description
|
||||||
|
* @param offset The offset of the scan point from the origin
|
||||||
|
* @param scanDescription A vector which describes the length of the line and direction of the line
|
||||||
|
* @return A scan point with the given offset and scan description
|
||||||
|
*/
|
||||||
|
public ScanPoint generateScanPoint(Vector offset, Vector scanDescription, int numPoints){
|
||||||
|
// generate a scan point with the given offset and scan description
|
||||||
|
ArrayList<Vector> scan = new ArrayList<>();
|
||||||
|
|
||||||
|
// divide the scan description by the number of points to allow us to scale it back up in the loop
|
||||||
|
Vector directionVector = scanDescription.div(numPoints-1);
|
||||||
|
|
||||||
|
for (int i = 0; i < numPoints; i++) {
|
||||||
|
scan.add(offset.add(directionVector.mul(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScanPoint(new Vector(0, 0), 0, scan);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append two scan points together
|
||||||
|
* @param scan1 The first scan point to append
|
||||||
|
* @param scan2 The second scan point to append
|
||||||
|
* @return A scan point that is the combination of the two scan points
|
||||||
|
*/
|
||||||
|
public ScanPoint appendScanPoints(ScanPoint scan1, ScanPoint scan2){
|
||||||
|
ArrayList<Vector> points = new ArrayList<>();
|
||||||
|
points.addAll(scan1.getPoints());
|
||||||
|
points.addAll(scan2.getPoints());
|
||||||
|
return new ScanPoint(new Vector(0, 0), 0, points);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void applyRotationAndTranslationMatrices() {
|
||||||
|
// generate one scan that is level and another that is rotated 45 degrees.
|
||||||
|
Vector scanDescription = new Vector(10, 0);
|
||||||
|
ScanPoint referenceScan = generateScanPoint(new Vector(0, 0), scanDescription, 10);
|
||||||
|
ScanPoint newScan = generateScanPoint(new Vector(0, 0), scanDescription.rotate2D((float) Math.PI / 4), 10);
|
||||||
|
|
||||||
|
// calculate the rotation and translation matrices between the two scans
|
||||||
|
ScanMatcher matcher = new ScanMatcher();
|
||||||
|
matcher.calculateRotationAndTranslationMatrices(referenceScan, newScan);
|
||||||
|
// apply the rotation and translation matrices to the new scan
|
||||||
|
ScanPoint newScanWithRotationAndTranslation = matcher.applyRotationAndTranslationMatrices(newScan);
|
||||||
|
|
||||||
|
// Get the first and last points of the new scan with rotation and translation and calculate the angle between them
|
||||||
|
ArrayList<Vector> points = newScanWithRotationAndTranslation.getPoints();
|
||||||
|
Vector firstPoint = points.get(0);
|
||||||
|
Vector lastPoint = points.get(points.size() - 1);
|
||||||
|
Vector rotatedDirection = lastPoint.sub(firstPoint);
|
||||||
|
float angle = scanDescription.angleDiff(rotatedDirection);
|
||||||
|
|
||||||
|
// The angle between the first and last points should be zero
|
||||||
|
assertEquals(0, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getError() {
|
||||||
|
// generate two scans that are the same. The error should be zero.
|
||||||
|
ScanPoint scan1 = generateScanPoint(new Vector(0, 0), new Vector(10, 10), 12);
|
||||||
|
ScanPoint scan2 = generateScanPoint(new Vector(0, 0), new Vector(10, 10), 12);
|
||||||
|
ScanMatcher matcher = new ScanMatcher();
|
||||||
|
matcher.calculateRotationAndTranslationMatrices(scan1, scan2);
|
||||||
|
assertEquals(0, matcher.getError(scan1, scan2));
|
||||||
|
|
||||||
|
// generate two scans that are the same but one is offset by 10 in the y direction. The error should be 10.
|
||||||
|
scan1 = generateScanPoint(new Vector(0, 0), new Vector(10, 10), 12);
|
||||||
|
scan2 = generateScanPoint(new Vector(0, 10), new Vector(10, 10), 12);
|
||||||
|
matcher.calculateRotationAndTranslationMatrices(scan1, scan2);
|
||||||
|
assertEquals(10, matcher.getError(scan1, scan2));
|
||||||
|
|
||||||
|
// generate two scans that are the same but one is rotated by 45 degrees. The error should be near zero.
|
||||||
|
scan1 = generateScanPoint(new Vector(0, 0), new Vector(10, 10), 12);
|
||||||
|
scan2 = generateScanPoint(new Vector(0, 0), new Vector(10, 10).rotate2D((float) Math.PI / 4), 12);
|
||||||
|
matcher.calculateRotationAndTranslationMatrices(scan1, scan2);
|
||||||
|
assertEquals(0, matcher.getError(scan1, scan2), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void iterativeScanMatch() {
|
||||||
|
float bendAngle = (float) (5 * Math.PI / 9);
|
||||||
|
// generate two scans rotated by 45 degrees and append them together
|
||||||
|
ScanPoint scan1 = generateScanPoint(new Vector(0, 0), new Vector(10, 10), 12);
|
||||||
|
ScanPoint scan2 = generateScanPoint(new Vector(0, 0), new Vector(10, 10).rotate2D(bendAngle), 12);
|
||||||
|
ScanPoint scan3 = appendScanPoints(scan1, scan2);
|
||||||
|
|
||||||
|
|
||||||
|
// generate two scans offset by some amount and rotated by 55 degrees and append them together
|
||||||
|
Vector rotated = (new Vector(10, 10)).rotate2D((float) Math.PI);
|
||||||
|
ScanPoint scan4 = generateScanPoint(new Vector(10, 10), rotated, 12);
|
||||||
|
ScanPoint scan5 = generateScanPoint(new Vector(10, 10), rotated.rotate2D(bendAngle), 12);
|
||||||
|
ScanPoint scan6 = appendScanPoints(scan4, scan5);
|
||||||
|
|
||||||
|
|
||||||
|
// do a single scan match and calculate the error
|
||||||
|
ScanMatcher matcher = new ScanMatcher();
|
||||||
|
matcher.calculateRotationAndTranslationMatrices(scan3, scan6);
|
||||||
|
ScanPoint oneCalcMatch = matcher.applyRotationAndTranslationMatrices(scan6);
|
||||||
|
float singleScanMatchError = matcher.getError(scan3, oneCalcMatch);
|
||||||
|
|
||||||
|
|
||||||
|
// do an iterative scan match and calculate the error
|
||||||
|
ScanPoint matchedScan = matcher.iterativeScanMatch(scan1, scan2, 0.0001f, 10);
|
||||||
|
|
||||||
|
// if it's null something has gone wrong with the algorithm because these scans can easily be matched.
|
||||||
|
assertNotNull(matchedScan);
|
||||||
|
|
||||||
|
float iterativeScanMatchError = matcher.getError(scan1, matchedScan);
|
||||||
|
|
||||||
|
// the iterative scan match should have a lower error than the single scan match
|
||||||
|
assertTrue(iterativeScanMatchError < singleScanMatchError);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user