mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-26 01:51:40 +00:00
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:
BIN
chameleon-client/src/assets/chessboard.png
Normal file
BIN
chameleon-client/src/assets/chessboard.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
140
chameleon-client/src/assets/robotIcon.svg
Normal file
140
chameleon-client/src/assets/robotIcon.svg
Normal file
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg3713"
|
||||
width="219"
|
||||
height="230"
|
||||
viewBox="0 0 219 230"
|
||||
sodipodi:docname="robotIcon.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||
<metadata
|
||||
id="metadata3719">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs3717">
|
||||
<linearGradient
|
||||
id="linearGradient937"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop933"
|
||||
offset="0"
|
||||
style="stop-color:#228a42;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop935"
|
||||
offset="1"
|
||||
style="stop-color:#5cb34a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient931"
|
||||
inkscape:collect="always">
|
||||
<stop
|
||||
id="stop927"
|
||||
offset="0"
|
||||
style="stop-color:#228a42;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop929"
|
||||
offset="1"
|
||||
style="stop-color:#5cb34a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient925">
|
||||
<stop
|
||||
style="stop-color:#228a42;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop921" />
|
||||
<stop
|
||||
style="stop-color:#5cb34a;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop923" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient925"
|
||||
id="linearGradient4820"
|
||||
x1="108.25"
|
||||
y1="186.25"
|
||||
x2="109.5"
|
||||
y2="21"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient937"
|
||||
id="linearGradient4822"
|
||||
x1="108.25"
|
||||
y1="186.25"
|
||||
x2="109.5"
|
||||
y2="21"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient931"
|
||||
id="linearGradient4824"
|
||||
x1="108.25"
|
||||
y1="186.25"
|
||||
x2="109.5"
|
||||
y2="21"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
id="namedview3715"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="110.62282"
|
||||
inkscape:cy="130.60249"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg3713"
|
||||
showguides="false"
|
||||
inkscape:snap-page="false"
|
||||
inkscape:snap-others="true"
|
||||
inkscape:snap-object-midpoints="true" />
|
||||
<path
|
||||
style="opacity:1;fill:url(#linearGradient4824);fill-opacity:1;stroke:#000000;stroke-width:0.49889764;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 167,156.45722 -41,-2 -14,-17 V 55.457215 Z"
|
||||
id="path3725-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:url(#linearGradient4822);stroke:#020000;stroke-width:0.49889764;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1;opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 76.866,158.246 13,-0.496 19.567,11.61661 19.567,-11.39151 13,0.2709 -32.5675,19.39302 z"
|
||||
id="path3742"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
<path
|
||||
style="fill:url(#linearGradient4820);stroke:#020000;stroke-width:0.49889764;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1;opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
d="m 51.864904,156.45722 41,-2 13.999996,-17 V 55.457209 Z"
|
||||
id="path3725-6-5"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
165
chameleon-client/src/components/3D/MiniMap.vue
Normal file
165
chameleon-client/src/components/3D/MiniMap.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row style="width: 400px;" align="center">
|
||||
<canvas id="canvasId" width="800" height="800"/>
|
||||
</v-row>
|
||||
<v-row style="width: 400px;" align="center" justify="middle">
|
||||
<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">X</th>
|
||||
<th class="text-center">Y</th>
|
||||
<th class="text-center">Angle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(target, index) in targets" :key="index">
|
||||
<td>{{ index}}</td>
|
||||
<td>{{ target.pose.translation.x.toFixed(2) }}</td>
|
||||
<td>{{ target.pose.translation.y.toFixed(2) }}</td>
|
||||
<td>{{ target.pose.rotation.radians.toFixed(2) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</template>
|
||||
</v-simple-table>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MiniMap",
|
||||
props: {
|
||||
targets: Array,
|
||||
horizontalFOV: Number
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ctx: undefined,
|
||||
canvas: undefined,
|
||||
x: 0,
|
||||
y: 0,
|
||||
targetWidth: 40,
|
||||
targetHeight: 6
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
targets: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.draw();
|
||||
}
|
||||
},
|
||||
horizontalFOV() {
|
||||
this.draw();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
draw() {
|
||||
this.clearBoard();
|
||||
this.drawPlayer();
|
||||
for (let index in this.targets) {
|
||||
this.drawTarget(index, this.targets[index].pose);
|
||||
}
|
||||
},
|
||||
drawTarget(index, target) {
|
||||
// first save the untranslated/unrotated context
|
||||
let x = 800 - (160 * target.translation.x); // getting meters as pixels
|
||||
let y = 400 - (160 * target.translation.y);
|
||||
this.ctx.save();
|
||||
this.ctx.beginPath();
|
||||
// move the rotation point to the center of the rect
|
||||
this.ctx.translate(y + this.targetWidth / 2, x + this.targetHeight / 2); // wpi lib makes x forward and back and y left to right
|
||||
// rotate the rect
|
||||
this.ctx.rotate(target.rotation.radians);
|
||||
|
||||
// draw the rect on the transformed context
|
||||
// Note: after transforming [0,0] is visually [x,y]
|
||||
// so the rect needs to be offset accordingly when drawn
|
||||
this.ctx.rect(-this.targetWidth / 2, -this.targetHeight / 2, this.targetWidth, this.targetHeight);
|
||||
|
||||
this.ctx.fillStyle = "#01a209";
|
||||
this.ctx.fill();
|
||||
|
||||
// restore the context to its untranslated/unrotated state
|
||||
this.ctx.restore();
|
||||
this.ctx.fillStyle = "whitesmoke";
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(y + this.targetWidth / 2, x + this.targetHeight / 2, 3, 0, 2 * Math.PI, true);
|
||||
this.ctx.fill();
|
||||
this.ctx.fillText(index, y - 30, x - 5);
|
||||
|
||||
},
|
||||
drawPlayer() {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(400, 820);
|
||||
this.ctx.lineTo(400 + this.hLen, 650);
|
||||
this.ctx.lineTo(400 - this.hLen, 650);
|
||||
this.ctx.closePath();
|
||||
this.ctx.fillStyle = this.grad;
|
||||
this.ctx.fill();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(400, 820);
|
||||
this.ctx.lineTo(400 + this.hLen, 650);
|
||||
this.ctx.stroke();
|
||||
this.ctx.moveTo(400, 820);
|
||||
this.ctx.lineTo(400 - this.hLen, 650);
|
||||
this.ctx.stroke();
|
||||
|
||||
},
|
||||
clearBoard() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // clearing the canvas
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hLen: {
|
||||
get() {
|
||||
return Math.tan(this.horizontalFOV / 2 * Math.PI / 180) * 150;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
const canvas = document.getElementById("canvasId"); // getting the canvas element
|
||||
const ctx = canvas.getContext("2d"); // getting the canvas context
|
||||
this.canvas = canvas; // setting the canvas as a vue variable
|
||||
this.ctx = ctx; // setting the canvas context as a vue variable
|
||||
this.grad = this.ctx.createLinearGradient(400, 800, 400, 600);
|
||||
this.grad.addColorStop(0, "rgb(119,119,119)");
|
||||
this.grad.addColorStop(0.05, "rgba(14,92,22,0.96)");
|
||||
this.grad.addColorStop(0.8, "rgba(43,43,43,0.48)");
|
||||
|
||||
// setting canvas context values for drawing
|
||||
|
||||
|
||||
this.ctx.font = "26px Arial";
|
||||
this.ctx.strokeStyle = "whitesmoke";
|
||||
this.ctx.lineWidth = 2;
|
||||
|
||||
this.$nextTick(function () {
|
||||
this.drawPlayer();
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#canvasId {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background-color: #2b2b2b;
|
||||
border-radius: 5px;
|
||||
border: 2px solid grey;
|
||||
box-shadow: 0 0 5px 1px;
|
||||
}
|
||||
|
||||
th {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -6,7 +6,7 @@
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field dark v-model="localValue" class="mt-0 pt-0" hide-details single-line type="number"
|
||||
style="width: 70px"/>
|
||||
style="width: 70px" :step="step"/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
@@ -15,7 +15,7 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'NumberInput',
|
||||
props: ['name', 'value'],
|
||||
props: ['name', 'value', 'step'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
@@ -37,7 +37,8 @@ export default new Vuex.Store({
|
||||
isBinary: 0,
|
||||
calibrationMode: 0,
|
||||
videoModeIndex:0,
|
||||
streamDivisor:0
|
||||
streamDivisor:0,
|
||||
is3D:false
|
||||
},
|
||||
cameraSettings: {},
|
||||
resolutionList: [],
|
||||
|
||||
@@ -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>
|
||||
86
chameleon-client/src/views/CameraViewes/3D.vue
Normal file
86
chameleon-client/src/views/CameraViewes/3D.vue
Normal 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>
|
||||
@@ -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']"
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user