Add solvePNP, 3d tab on the UI, and some other misc bug fixes (#35)

* Rebase solvePNP on master

* added 3D tab minimap and csv reader

* More solvePNP

* Create draw pipe for pnp data

* SolvePNP piping work

* Move sorting into solvepnppipe

* Create calibration pipeline

* Update CalibrateSolvePNPPipeline.java

* add camera tilt angle

* Add calibration slider and snapshot button to 3D view

* Mirror updates in the socket handler

* add 3d calibration mode to the pipeline manager

* created calibration functions in ui and backend

* Start plumbing calibration

* Add snapshot and other handling to the RequestHandler

* added select resolution before starting calibration

* Rename solvePNPPipe to bounding box solve pnp pipe

* Update BoundingBoxSolvePNPPipe.java

* Add Mat serializer and CameraCalibrationConfig

* Begun calibration saving, fixed UI/Backend snapshot count mismatch

* Add (unplumbed) option to set checkerboard size

This will allow users to change the units their calibration is in

* Create chessboard.png

* Fix calibration NPE

* changed string serialization to a json send

* bug fixed cancellation button

* Fix spelling of snapshot in 3d.vue

* Plumb resolution change

* Set resolution during config, start on config serialization

* Update .gitignore

* Config fixes

* Start transition away from cvpipeline3d

* fix NPE on uncalibrated cameras

* clear list on fail

* Fix video mode index error

* ignore getters in camera calibration config

* Create json constructor for jsonmat

* get solvePNP mostly returning sane values

* Fix solvePNP bug and add unit test

* FIx calibration mat truncation

* added capture amount model upload and minimap data

* Standardize on meters in calibration and bounding box

* fix json out of bounds and handle null calibration more gracefully

* don't put text on calibrate image, go back to inches

* convert distance to meters

this means calibration will need to be in inches

* Actually save raw contor

* Update GroupContoursPipe.java

* Add all calibration return to camera capture

* hard code 2019 target

* bugfixed draw2d added fail calib popup, merge end and cancel

added the res index to the calib start

* Clarify error message and draw more fancy rectangles

* Cleanup memory in solvepnp

* re did minimap component

* fix npe if left/right is null

* remove references to 2d

* try-catch running the current pipeline

* Add method to find corners using the harris corner detector

* Possibly fix left/right missmatch

* Fix 3D Tab error

* FIx file permissions, mat serializer adjustments

* fixed mini map for field coordinates

* mini map changes fov

* Update SolvePNPPipe.java

* get rid of target corners

* some memory leak fixes

* fixed mini map location

* added position under minimap

* changed player fov look

* put all targets in the web send

* re did target send to ui added target tables, bugfix calibration

* fixed y position

* Add tilt angle to capture properties

* maybe fix y axis in minimap

* Add square size to onCalibrationEnding

* Possibly add square size to UI

* fix NPE with pitch

* Fix bug with sending multiple targets

* Only instantiate 3d stuff if we are in 3d mode

* Fix array list exceptions

* Fix bug in sort contors

list was truncated too early

* added download chess, tilt setting and ordinal tilt,

* added square size connection

* removed unused code

* Update pom version to 2.1-RELEASE

* Send camera calibrations to UI

* Stream pose list to a LIst

* Only stream necessary parts of the aux list entry

* Make broadcastMessage synchronized to prevent ConcurrentModificationExceptions

* added fps counter changed squaresize steps bug fixes in tables

* bugfix camera settings cam wont change

Authored-by: oriagranat9 <oriagranat9@gmail.com>
This commit is contained in:
Matt
2019-12-31 04:53:20 -08:00
committed by oriagranat9
parent d8c027dae7
commit 1decd2c3d7
70 changed files with 3029 additions and 297 deletions

View File

@@ -75,6 +75,7 @@
<v-tab>Threshold</v-tab>
<v-tab>Contours</v-tab>
<v-tab>Output</v-tab>
<v-tab>3D</v-tab>
</v-tabs>
<div v-else style="height: 48px"></div>
<div style="padding-left:30px">
@@ -96,10 +97,40 @@
<div v-else style="height: 58px"></div>
<!-- camera image stream -->
<div class="videoClass">
<img id="CameraStream" v-if="cameraList.length > 0" :src="streamAddress" @click="onImageClick"
crossorigin="Anonymous"/>
<span v-else>No Cameras Are connected</span>
<h5 id="Point">{{point}}</h5>
<v-row align="center">
<img id="CameraStream" style="display: block;margin: auto; width: 70%;height: 70%;"
v-if="cameraList.length > 0"
:src="streamAddress" @click="onImageClick"
crossorigin="Anonymous"/>
<span v-else>No Cameras Are connected</span>
</v-row>
<v-row justify="end">
<span style="margin-right: 45px">FPS:{{fps.toFixed(2)}}</span>
</v-row>
<v-row align="center">
<v-simple-table
style="text-align: center;background-color: transparent; display: block;margin: auto"
dense dark>
<template v-slot:default>
<thead>
<tr>
<th class="text-center">Target</th>
<th class="text-center">Pitch</th>
<th class="text-center">Yaw</th>
<th class="text-center">Area</th>
</tr>
</thead>
<tbody>
<tr v-for="(value, index) in targets" :key="index">
<td>{{ index}}</td>
<td>{{ parseFloat(value.pitch).toFixed(2) }}</td>
<td>{{ parseFloat(value.yaw).toFixed(2) }}</td>
<td>{{ parseFloat(value.area).toFixed(2) }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-row>
</div>
</div>
</v-col>
@@ -154,6 +185,7 @@
import ThresholdTab from './CameraViewes/ThresholdTab'
import ContoursTab from './CameraViewes/ContoursTab'
import OutputTab from './CameraViewes/OutputTab'
import pnpTab from './CameraViewes/3D'
import CVselect from '../components/cv-select'
import CVicon from '../components/cv-icon'
import CVinput from '../components/cv-input'
@@ -165,6 +197,7 @@
ThresholdTab,
ContoursTab,
OutputTab,
pnpTab,
CVselect,
CVicon,
CVinput
@@ -319,19 +352,21 @@
return "ContoursTab";
case 3:
return "OutputTab";
case 4:
return "pnpTab";
}
return "";
}
},
point: {
targets: {
get: function () {
let p = this.$store.state.point.calculated;
let fps = this.$store.state.point.fps;
if (p !== undefined) {
return `Pitch: ${parseFloat(p['pitch']).toFixed(2)}, Yaw: ${parseFloat(p['yaw']).toFixed(2)}, Area: ${parseFloat(p['area']).toFixed(2)}, FPS: ${parseFloat(fps).toFixed(2)}`
} else {
return undefined;
}
return this.$store.state.point.targets;
}
},
fps: {
get() {
return this.$store.state.point.fps;
}
},
currentCameraIndex: {
@@ -384,17 +419,16 @@
text-align: center;
}
.videoClass img {
max-height: 70vh;
max-width: 70%;
.tableClass {
padding-top: 5px;
width: 70%;
object-fit: cover;
vertical-align: middle;
text-align: center;
}
#Point {
padding-top: 5px;
th {
width: 80px;
text-align: center;
color: #f4f4f4;
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div>
<v-row align="center" justify="start" dense>
<v-col :cols="6">
<CVswitch v-model="value.is3D" name="Enable 3D" @input="handleData('is3D')"/>
</v-col>
<v-col>
<CVnumberInput name="Max Height:" v-model="value.targetHeight" @input="handleData('targetHeight')"/>
<CVnumberInput name="Max Width:" v-model="value.targetWidth" @input="handleData('targetWidth')"/>
</v-col>
<!-- <v-col>-->
<!-- <input type="file" ref="file" style="display: none" accept=".csv" @change="readFile">-->
<!-- <v-btn @click="$refs.file.click()" small>-->
<!-- upload model-->
<!-- </v-btn>-->
<!-- </v-col>-->
</v-row>
<mini-map class="miniMapClass" :targets="targets" :horizontal-f-o-v="horizontalFOV"/>
</div>
</template>
<script>
import miniMap from '../../components/3D/MiniMap';
import CVswitch from '../../components/cv-switch';
import CVnumberInput from '../../components/cv-number-input';
import Papa from 'papaparse';
export default {
name: "solvePNP",
props: ['value'],
components: {
CVswitch,
CVnumberInput,
miniMap
},
data() {
return {
is3D: false,
isPNPCalibration: false,
}
},
methods: {
handleData(val) {
this.handleInput(val, this.value[val]);
this.$emit('update')
},
readFile(event) {
let file = event.target.files[0];
Papa.parse(file, {
complete: this.onParse,
skipEmptyLines: true
});
},
onParse(result) {
// console.log(result.data);
this.axios.post("http://" + this.$address + "/api/vision/pnpModel", result.data);
},
},
computed: {
targets: {
get() {
return this.$store.state.point.targets;
}
},
horizontalFOV: {
get() {
let index = this.$store.state.cameraSettings.resolution;
let FOV = this.$store.state.cameraSettings.fov;
let resolution = this.$store.state.resolutionList[index];
let diagonalView = FOV * (Math.PI / 180);
let diagonalAspect = Math.hypot(resolution.width, resolution.height);
return Math.atan(Math.tan(diagonalView / 2) * (resolution.width / diagonalAspect)) * 2 * (180 / Math.PI)
}
}
}
}
</script>
<style scoped>
.miniMapClass {
width: 50% !important;
height: 50% !important;
}
</style>

View File

@@ -3,7 +3,7 @@
<CVselect name="SortMode" v-model="value.sortMode"
:list="['Largest','Smallest','Highest','Lowest','Rightmost','Leftmost','Centermost']"
@input="handleData('sortMode')"/>
<CVswitch name="Output multiple" v-model="value.multiple" @input="handleData('multiple')"></CVswitch>
<CVswitch name="Output multiple" v-model="value.multiple" @input="handleData('multiple')"/>
<span>Calibrate:</span>
<v-divider dark color="white"/>
<CVselect name="Calibration Mode" v-model="value.calibrationMode" :list="['None','Single point','Dual point']"

View File

@@ -1,8 +1,54 @@
<template>
<div>
<CVselect name="Camera" :list="cameraList" v-model="currentCameraIndex"/>
<CVnumberinput name="Diagonal FOV" v-model="cameraSettings.fov"/>
<v-btn style="margin-top:10px" small color="#4baf62" @click="sendCameraSettings">Save Camera Settings</v-btn>
<div>
<CVselect name="Camera" :list="cameraList" v-model="currentCameraIndex" @input="handleInput('currentCamera',currentCameraIndex)"/>
<CVnumberinput name="Diagonal FOV" v-model="cameraSettings.fov"/>
<br>
<CVnumberinput name="Camera pitch" v-model="cameraSettings.tilt" :step="0.01"/>
<br>
<v-btn style="margin-top:10px" small color="#4baf62" @click="sendCameraSettings">Save Camera Settings
</v-btn>
</div>
<div style="margin-top: 15px">
<span>3D Calibration</span>
<v-divider color="white" style="margin-bottom: 10px"/>
<v-row>
<v-col>
<CVselect name="Resolution" v-model="resolutionIndex" :list="resolutionList"/>
</v-col>
<v-col>
<CVnumberinput name="Square Size (mm)" v-model="squareSize"/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-btn small :color="calibrationModeButton.color" @click="sendCalibrationMode"
:disabled="checkResolution">
{{calibrationModeButton.text}}
</v-btn>
</v-col>
<v-col>
<v-btn small :color="cancellationModeButton.color" @click="sendCalibrationFinish"
:disabled="checkCancelation">
{{cancellationModeButton.text}}
</v-btn>
</v-col>
<v-col>
<v-btn color="whitesmoke" small><a style="color: black; text-decoration: none"
:href="require('../../assets/chessboard.png')"
download="Calibration Board.png">Download Checkerboard</a>
</v-btn>
</v-col>
</v-row>
<v-row v-if="isCalibrating">
<v-col>
<span>Snapshot Amount: {{snapshotAmount}}</span>
</v-col>
</v-row>
</div>
<v-snackbar v-model="snack">
<span>Calibration Failed</span>
</v-snackbar>
</div>
</template>
@@ -17,7 +63,22 @@
CVnumberinput
},
data() {
return {}
return {
isCalibrating: false,
resolutionIndex: undefined,
calibrationModeButton: {
text: "Start Calibration",
color: "green"
},
cancellationModeButton: {
text: "Cancel Calibration",
color: "red"
},
squareSize: 2.54,
snapshotAmount: 0,
hasEnough: false,
snack: false
}
},
methods: {
sendCameraSettings() {
@@ -30,9 +91,69 @@
}
)
},
sendCalibrationMode() {
const self = this;
let data = {};
let connection_string = "/api/settings/";
if (self.isCalibrating === true) {
connection_string += "snapshot"
} else {
connection_string += "startCalibration";
data['resolution'] = this.resolutionIndex;
data['squareSize'] = this.squareSize;
self.hasEnough = false;
}
this.axios.post("http://" + this.$address + connection_string, data).then(
function (response) {
if (response.status === 200) {
if (self.isCalibrating) {
self.snapshotAmount = response.data['snapshotCount'];
self.hasEnough = response.data['hasEnough'];
if (self.hasEnough === true) {
self.cancellationModeButton.text = "Finish Calibration";
self.cancellationModeButton.color = "green";
}
} else {
self.calibrationModeButton.text = "Take Snapshot";
self.isCalibrating = true;
}
}
}
);
},
sendCalibrationFinish() {
const self = this;
let connection_string = "/api/settings/endCalibration";
let data = {};
data['squareSize'] = this.squareSize;
self.axios.post("http://" + this.$address + connection_string, data).then(
function (response) {
if (response.status === 500) {
self.snack = true;
}
self.isCalibrating = false;
self.hasEnough = false;
self.snapshotAmount = 0;
self.calibrationModeButton.text = "Start Calibration";
self.cancellationModeButton.text = "Cancel Calibration";
self.cancellationModeButton.color = "red";
}
);
}
},
computed: {
checkResolution() {
return this.resolutionIndex === undefined;
},
checkCancelation() {
if (this.isCalibrating) {
return false
} else if (this.checkResolution) {
return true;
} else {
return true
}
},
currentCameraIndex: {
get() {
return this.$store.state.currentCameraIndex;
@@ -49,6 +170,15 @@
this.$store.commit('cameraList', value);
}
},
resolutionList: {
get() {
let tmp_list = [];
for (let i of this.$store.state.resolutionList) {
tmp_list.push(`${i['width']} X ${i['height']} at ${i['fps']} FPS, ${i['pixelFormat']}`)
}
return tmp_list;
}
},
cameraSettings: {
get() {
return this.$store.state.cameraSettings;