updated
This commit is contained in:
@@ -1,52 +1,200 @@
|
||||
<template>
|
||||
<div>
|
||||
<button v-on:click="sendMessage()">Send Message</button>
|
||||
<video ref="video" autoplay></video>
|
||||
<canvas ref="canvas"></canvas>
|
||||
<!-- The Modal -->
|
||||
<div id="myModal" class="modal">
|
||||
<!-- Modal content -->
|
||||
<div class="modal-content">
|
||||
<p>Loading video...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<br />
|
||||
<br />
|
||||
<center><h2>Your Room </h2>
|
||||
<h4 class="text-muted">Code: {{message.roomId}}</h4></center>
|
||||
<div id="canvas-div" style="text-align: center">
|
||||
<canvas
|
||||
ref="canvas"
|
||||
class="top"
|
||||
id="2d"
|
||||
v-bind:width="windowWidth - 30"
|
||||
height="500"
|
||||
style="
|
||||
background-color: white;
|
||||
border-style: solid;
|
||||
border-width: 0.5px;
|
||||
border-radius: 15px;
|
||||
height: 50%;
|
||||
background: url('https://i.ibb.co/X3sHrcM/Pngtree-summer-sandy-beach-background-986369.png');
|
||||
">
|
||||
</canvas>
|
||||
</div>
|
||||
<center>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card" style="width: 18rem">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Your Camera</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">
|
||||
(Only you can see this)
|
||||
</h6>
|
||||
<div class="card-text">
|
||||
<div id="video" style="display: inline-block; width: 100%">
|
||||
<video
|
||||
ref="video"
|
||||
autoplay
|
||||
class="top"
|
||||
style="display: inline-block; height: 100%; width: 100%"
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="card" style="width: 18rem">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Settings:</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">Edit your player:</h6>
|
||||
<div class="card-text">
|
||||
<label for="customRange3" class="form-label"><b>Colour</b></label>
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="1"
|
||||
max="8"
|
||||
step="1"
|
||||
v-model="colorSelect"
|
||||
@change="updateColor"
|
||||
id="customRange3"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
type="text"
|
||||
v-model="username"
|
||||
placeholder="Enter a username"
|
||||
@change="updateUsername"
|
||||
/>
|
||||
<br />
|
||||
Color: {{ colorSelect }}
|
||||
<br />
|
||||
Username: {{ username }}
|
||||
<br />
|
||||
Video Loaded: {{ videoLoaded }}
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as posenet from "@tensorflow-models/posenet";
|
||||
import * as utils from "../utils";
|
||||
import { useWindowSize } from "vue-window-size";
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const { width, height } = useWindowSize();
|
||||
return {
|
||||
windowWidth: width,
|
||||
windowHeight: height,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
connection: null,
|
||||
message: {},
|
||||
people: {},
|
||||
resultWidth: 0,
|
||||
resultHeight: 0,
|
||||
colorSelect: 1,
|
||||
username: "",
|
||||
videoLoaded: false,
|
||||
windowWidth: window.innerWidth,
|
||||
modal: {},
|
||||
span: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
console.log("Starting connection to websocket server");
|
||||
console.log(this.$route.params.id);
|
||||
this.connection = new WebSocket(
|
||||
"ws://50.100.180.37:4000/" + this.$route.params.id
|
||||
);
|
||||
//console.log(this.$route.params.id);
|
||||
this.connection = new WebSocket("wss://dance.cubehostingmc.com:4000/");
|
||||
|
||||
this.connection.onopen = function (event) {
|
||||
this.connection.onopen = (event) => {
|
||||
console.log(event);
|
||||
console.log("Successfully connected to the echo WebSocket Server");
|
||||
};
|
||||
|
||||
this.connection.onmessage = function (event) {
|
||||
console.log(event); // When we recieve a message
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.modal = document.getElementById("myModal");
|
||||
this.span = document.getElementsByClassName("close")[0];
|
||||
|
||||
this.modal.style.display = "block";
|
||||
window.addEventListener("resize", () => {
|
||||
this.windowWidth = window.innerWidth;
|
||||
});
|
||||
this.ctx = this.$refs.canvas.getContext("2d");
|
||||
this.net = await posenet.load();
|
||||
this.net = await posenet.load({
|
||||
architecture: "ResNet50",
|
||||
outputStride: 32,
|
||||
inputResolution: 250,
|
||||
multiplier: 1,
|
||||
quantBytes: 2,
|
||||
});
|
||||
this.streamPromise = await this.initWebcamStream();
|
||||
this.detectPose();
|
||||
this.connection.onmessage = (event) => {
|
||||
let data = JSON.parse(event.data);
|
||||
this.game = data.game;
|
||||
this.playerArr = data.playerArr; // When we recieve a message
|
||||
//utils.assignAllUniversePairs(this.playerArr, this.ctx)
|
||||
//let players = this.playerArr;
|
||||
let numPlayers = this.playerArr.length;
|
||||
let width = this.ctx.canvas.width;
|
||||
let x_spacing = width / (numPlayers + 1);
|
||||
for (let i = 0; i < numPlayers; i++) {
|
||||
let x = (i + 1) * x_spacing;
|
||||
let y = this.ctx.canvas.height / 2;
|
||||
let com = utils.getCenterOfMass(this.playerArr[i].pose.keypoints);
|
||||
this.playerArr[i].universePairs = utils.keypointsToUniverse(this.playerArr[i].pose.keypoints, com, [x, y])
|
||||
this.playerArr[i].pose.keypoints.forEach(keypoint => {
|
||||
if (keypoint.part == "nose") {
|
||||
this.playerArr[i].head = utils.getUniversePoint(keypoint, com, [x, y])
|
||||
}
|
||||
})
|
||||
}
|
||||
this.message.playerArr = this.playerArr;
|
||||
};
|
||||
this.gameCycle();
|
||||
},
|
||||
methods: {
|
||||
sendMessage() {
|
||||
console.log(this.connection);
|
||||
//console.log(this.connection);
|
||||
this.message.playerId = this.getCookie("playerId");
|
||||
this.message.gameId = this.$route.params.id;
|
||||
console.log(this.message);
|
||||
this.message.colour = this.getCookie("playerColour");
|
||||
this.message.name = this.username;
|
||||
if (!this.message.game) {
|
||||
this.message.game = {};
|
||||
}
|
||||
utils.assignAllUniversePairs(this.message.playerArr, this.ctx)
|
||||
//this.message.playerArr = this.playerArr;
|
||||
this.message.game.name = this.getCookie("game");
|
||||
//console.log(this.message);
|
||||
let str = JSON.stringify(this.message);
|
||||
|
||||
this.connection.send(str);
|
||||
|
||||
},
|
||||
getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
@@ -69,7 +217,7 @@ export default {
|
||||
return navigator.mediaDevices
|
||||
.getUserMedia({
|
||||
audio: false, // don't capture audio
|
||||
video: { facingMode: "environment" }, // use the rear camera if there is
|
||||
video: { facingMode: "user" }, // use the rear camera if there is
|
||||
})
|
||||
.then((stream) => {
|
||||
// set <video> source as the webcam input
|
||||
@@ -83,6 +231,8 @@ export default {
|
||||
return new Promise((resolve) => {
|
||||
// when video is loaded
|
||||
video.onloadedmetadata = () => {
|
||||
this.videoLoaded = true;
|
||||
this.modal.style.display = "none";
|
||||
// calculate the video ratio
|
||||
this.videoRatio = video.offsetHeight / video.offsetWidth;
|
||||
// add event listener on resize to reset the <video> and <canvas> sizes
|
||||
@@ -105,43 +255,119 @@ export default {
|
||||
);
|
||||
}
|
||||
},
|
||||
setResultSize () {
|
||||
setResultSize() {
|
||||
// get the current browser window size
|
||||
let clientWidth = document.documentElement.clientWidth
|
||||
let clientWidth = document.documentElement.clientWidth;
|
||||
// set max width as 600
|
||||
this.resultWidth = Math.min(600, clientWidth)
|
||||
this.resultWidth = Math.min(600, clientWidth);
|
||||
// set the height according to the video ratio
|
||||
this.resultHeight = this.resultWidth * this.videoRatio
|
||||
this.resultHeight = this.resultWidth * this.videoRatio;
|
||||
// set <video> width and height
|
||||
/*
|
||||
Doesn't use vue binding :width and :height,
|
||||
because the initial value of resultWidth and resultHeight
|
||||
will affect the ratio got from the initWebcamStream()
|
||||
*/
|
||||
let video = this.$refs.video
|
||||
video.width = this.resultWidth
|
||||
video.height = this.resultHeight
|
||||
let video = this.$refs.video;
|
||||
video.width = this.resultWidth;
|
||||
video.height = this.resultHeight;
|
||||
},
|
||||
timeout(ms){
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
timeout(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
},
|
||||
async detectPose() {
|
||||
const imageScaleFactor = 0.5;
|
||||
const flipHorizontal = false;
|
||||
const outputStride = 16;
|
||||
this.pose = await this.net.estimateSinglePose(this.$refs.video, imageScaleFactor, flipHorizontal, outputStride)
|
||||
console.log(this.pose);
|
||||
utils.drawSkeleton(this.pose.keypoints, 0.3, this.ctx);
|
||||
//this.renderPose();
|
||||
// await this.timeout(1000/20);
|
||||
// return this.detectPose();
|
||||
requestAnimationFrame(() => {
|
||||
this.detectPose();
|
||||
})
|
||||
}
|
||||
const imageScaleFactor = 0.5;
|
||||
const flipHorizontal = true;
|
||||
const outputStride = 16;
|
||||
this.pose = await this.net.estimateSinglePose(
|
||||
this.$refs.video,
|
||||
imageScaleFactor,
|
||||
flipHorizontal,
|
||||
outputStride
|
||||
);
|
||||
|
||||
let playerId = this.getCookie("playerId");
|
||||
let roomId = this.$route.params.id;
|
||||
let send = {
|
||||
roomId: roomId,
|
||||
playerArr: [{ playerId: playerId, pose: this.pose }],
|
||||
};
|
||||
this.message = send;
|
||||
this.sendMessage();
|
||||
},
|
||||
updateColor() {
|
||||
this.setCookie("playerColour", this.colorSelect, 1);
|
||||
},
|
||||
updateUsername() {
|
||||
this.setCookie("playerUsername", this.username, 1);
|
||||
},
|
||||
setCookie(cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
},
|
||||
draw() {
|
||||
utils.clearCanvas(this.ctx);
|
||||
if (this.playerArr) {
|
||||
utils.drawPlayers(this.playerArr, this.ctx);
|
||||
}
|
||||
|
||||
if (this.game != undefined) {
|
||||
utils.drawGame(this.game, this.ctx);
|
||||
}
|
||||
},
|
||||
async gameCycle() {
|
||||
this.draw();
|
||||
this.detectPose();
|
||||
await this.timeout(100);
|
||||
return this.gameCycle();
|
||||
//requestAnimationFrame(() => {
|
||||
// this.gameCycle();
|
||||
//})
|
||||
},
|
||||
getColour(colourSelect){
|
||||
const colours = {0: "red", 1: "green", 2:"blue", 3:"orange", 4:"purple", 5:"pink", 6:"black", 7:"brown", 8:"aqua"}
|
||||
return colours[colourSelect];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
58
src/components/HelloWorld.vue
Normal file
58
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HelloWorld',
|
||||
props: {
|
||||
msg: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,23 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
Create Room: <button v-on:click="createRoom" style="border: solid">Create Room</button>
|
||||
<div class="cont">
|
||||
<main class="form-signin">
|
||||
|
||||
<center><img class="mb-4" :src="logo" alt="" width="30%" height="60%" style="width:60%; height 60%"></center>
|
||||
<h1 class="h3 mb-3 fw-normal text-center">Welcome to Chillspace!</h1>
|
||||
<label for="inputEmail" class="visually-hidden">Create a New Game:</label>
|
||||
<br>
|
||||
Join Room: <input type="text" v-model="room.id" style="border: solid"> <button v-on:click="joinRoom" style="border: solid">Join Room</button>
|
||||
<center><img :src="voleyball" @click="changeGame('0')" id="game-0" style="opacity: 0.25;"> <img :src="soccer" @click="changeGame('1')" id="game-1" style="opacity: 1;"> <img :src="dance" @click="changeGame('2')" id="game-2" style="opacity: 1;"></center>
|
||||
<button v-on:click="createRoom" class="w-100 btn btn-lg btn-primary" style="margin-top:15px;">Create Room</button>
|
||||
<br>
|
||||
<label for="input" class="visually-hidden" style="margin-top: 15px;">Join an Existing Room:</label>
|
||||
<input type="text" id="input" class="form-control" placeholder="Game ID" v-model="room.id">
|
||||
<div class="checkbox mb-3">
|
||||
</div>
|
||||
<button v-on:click="joinRoom" class="w-100 btn btn-lg btn-primary">Join Room</button>
|
||||
|
||||
<p class="mt-5 mb-3 text-muted"><a href="https://github.com/alex-alexiev" class="text-secondary">Alex Alexiev</a>, <a href="https://github.com/jslightham" class="text-secondary">Johnathon Slightham</a>, and <a href="https://github.com/lcarnegie" class="text-secondary">Luca Carnegie</a></p>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,23 +26,43 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
room: {},
|
||||
logo: require('@/assets/logo.jpg'),
|
||||
dance: require('@/assets/dance.jpg'),
|
||||
soccer: require('@/assets/soccer.jpg'),
|
||||
voleyball: require('@/assets/volleyball.jpg'),
|
||||
game: 0,
|
||||
};
|
||||
}, created() {
|
||||
this.setCookie("game", 0, 0);
|
||||
},
|
||||
methods: {
|
||||
createRoom() {
|
||||
this.axios.get("http://50.100.180.37:4000/rooms/add").then((res) => {
|
||||
this.axios.get("https://dance.cubehostingmc.com:4000/rooms/add").then((res) => {
|
||||
console.log("create");
|
||||
let id = res.data._id;
|
||||
let playerId = res.data.playerId;
|
||||
let playerColour = res.data.colour;
|
||||
this.setCookie("playerId", playerId, 1);
|
||||
this.setCookie("playerColour", playerColour, 1);
|
||||
this.setCookie("game", this.game, 1);
|
||||
console.log(res.data);
|
||||
this.$router.push({path: `game/${id}`});
|
||||
});
|
||||
}).catch(e => {console.log(e)});
|
||||
},
|
||||
joinRoom() {
|
||||
console.log(this.room);
|
||||
this.axios.post("http://50.100.180.37:4000/rooms/join", this.room).then((res) => {
|
||||
let playerId = res.data._id;
|
||||
this.setCookie("playerId", playerId, 1);
|
||||
this.$router.push({path: `game/${this.room.id}`});
|
||||
this.axios.post("https://dance.cubehostingmc.com:4000/rooms/join", this.room).then((res) => {
|
||||
if (res.data){
|
||||
console.log(res.data);
|
||||
let playerId = res.data._id;
|
||||
let playerColour = res.data.colour;
|
||||
this.setCookie("playerId", playerId, 1);
|
||||
this.setCookie("playerColour", playerColour, 1);
|
||||
console.log(res.data);
|
||||
this.$router.push({path: `game/${this.room.id}`});
|
||||
} else {
|
||||
alert("Please enter a valid room!");
|
||||
}
|
||||
});
|
||||
},
|
||||
setCookie(cname, cvalue, exdays) {
|
||||
@@ -36,9 +71,80 @@ export default {
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
},
|
||||
changeGame(game) {
|
||||
this.game = game;
|
||||
this.setCookie("game", game, 1);
|
||||
if (game == '0') {
|
||||
document.getElementById('game-0').style.opacity = "0.25";
|
||||
document.getElementById('game-1').style.opacity = "1";
|
||||
document.getElementById('game-2').style.opacity = "1";
|
||||
}
|
||||
if (game == '1') {
|
||||
document.getElementById('game-0').style.opacity = "1";
|
||||
document.getElementById('game-1').style.opacity = "0.25";
|
||||
document.getElementById('game-2').style.opacity = "1";
|
||||
}
|
||||
if (game == '2') {
|
||||
document.getElementById('game-0').style.opacity = "1";
|
||||
document.getElementById('game-1').style.opacity = "1";
|
||||
document.getElementById('game-2').style.opacity = "0.25";
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cont {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.form-signin {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
padding: 15px;
|
||||
margin: auto;
|
||||
}
|
||||
.form-signin .checkbox {
|
||||
font-weight: 400;
|
||||
}
|
||||
.form-signin .form-control {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
height: auto;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.form-signin .form-control:focus {
|
||||
z-index: 2;
|
||||
}
|
||||
.form-signin input[type="email"] {
|
||||
margin-bottom: -1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.form-signin input[type="password"] {
|
||||
margin-bottom: 10px;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
126
src/components/MyCamera.vue
Normal file
126
src/components/MyCamera.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div id="my-camera">
|
||||
<div class="resultFrame">
|
||||
<video ref="video" autoplay></video>
|
||||
<canvas ref="canvas" :width="resultWidth" :height="resultHeight"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as posenet from "@tensorflow-models/posenet";
|
||||
import * as utils from "../utils";
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
data () {
|
||||
return {
|
||||
// store the promises of initialization
|
||||
streamPromise: null,
|
||||
// control the UI visibilities
|
||||
isVideoStreamReady: false,
|
||||
isModelReady: false,
|
||||
initFailMessage: '',
|
||||
pose: [],
|
||||
resultHeight: 0,
|
||||
resultWidth: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initWebcamStream () {
|
||||
// if the browser supports mediaDevices.getUserMedia API
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
return navigator.mediaDevices.getUserMedia({
|
||||
audio: false, // don't capture audio
|
||||
video: { facingMode: 'environment' } // use the rear camera if there is
|
||||
})
|
||||
.then(stream => {
|
||||
// set <video> source as the webcam input
|
||||
let video = this.$refs.video
|
||||
try {
|
||||
video.srcObject = stream
|
||||
} catch (error) {
|
||||
// support older browsers
|
||||
video.src = URL.createObjectURL(stream)
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
// when video is loaded
|
||||
video.onloadedmetadata = () => {
|
||||
// calculate the video ratio
|
||||
this.videoRatio = video.offsetHeight / video.offsetWidth
|
||||
// add event listener on resize to reset the <video> and <canvas> sizes
|
||||
window.addEventListener('resize', this.setResultSize)
|
||||
// set the initial size
|
||||
this.setResultSize()
|
||||
this.isVideoStreamReady = true
|
||||
console.log('webcam stream initialized')
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('failed to initialize webcam stream', error)
|
||||
throw (error)
|
||||
})
|
||||
} else {
|
||||
return Promise.reject(new Error('Your browser does not support mediaDevices.getUserMedia API'))
|
||||
}
|
||||
},
|
||||
setResultSize () {
|
||||
// get the current browser window size
|
||||
let clientWidth = document.documentElement.clientWidth
|
||||
// set max width as 600
|
||||
this.resultWidth = Math.min(600, clientWidth)
|
||||
// set the height according to the video ratio
|
||||
this.resultHeight = this.resultWidth * this.videoRatio
|
||||
// set <video> width and height
|
||||
/*
|
||||
Doesn't use vue binding :width and :height,
|
||||
because the initial value of resultWidth and resultHeight
|
||||
will affect the ratio got from the initWebcamStream()
|
||||
*/
|
||||
let video = this.$refs.video
|
||||
video.width = this.resultWidth
|
||||
video.height = this.resultHeight
|
||||
},
|
||||
timeout(ms){
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
},
|
||||
async detectPose() {
|
||||
const imageScaleFactor = 0.5;
|
||||
const flipHorizontal = true;
|
||||
const outputStride = 16;
|
||||
this.pose = await this.net.estimateSinglePose(this.$refs.video, imageScaleFactor, flipHorizontal, outputStride)
|
||||
console.log(this.pose);
|
||||
utils.drawSkeleton(this.pose.keypoints, 0.3, this.ctx);
|
||||
//this.renderPose();
|
||||
// await this.timeout(1000/20);
|
||||
// return this.detectPose();
|
||||
requestAnimationFrame(() => {
|
||||
this.detectPose();
|
||||
})
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.ctx = this.$refs.canvas.getContext("2d");
|
||||
this.net = await posenet.load();
|
||||
this.streamPromise = await this.initWebcamStream()
|
||||
this.detectPose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
.resultFrame {
|
||||
display: grid;
|
||||
video {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
}
|
||||
canvas {
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
48
src/components/MyCanvas.vue
Normal file
48
src/components/MyCanvas.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>my canvas</h1>
|
||||
<canvas ref="canv"></canvas>
|
||||
<button @click="drawRect">Add Rect</button>
|
||||
<button @click="subWidth">-</button>
|
||||
<button @click="addWidth">+</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'my-canvas',
|
||||
data() {
|
||||
return {
|
||||
rectWidth: 200,
|
||||
width: 0,
|
||||
height: 0,
|
||||
pose: []
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.ctx = this.$refs.canv.getContext("2d");
|
||||
},
|
||||
|
||||
methods: {
|
||||
drawRect() {
|
||||
this.ctx.clearRect(0, 0, 400, 200);
|
||||
this.ctx.beginPath();
|
||||
this.ctx.rect(20, 20, this.rectWidth, 100);
|
||||
this.ctx.stroke();
|
||||
},
|
||||
addWidth(){
|
||||
this.rectWidth += 10
|
||||
this.drawRect()
|
||||
},
|
||||
subWidth() {
|
||||
this.rectWidth -= 10
|
||||
this.drawRect()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
Reference in New Issue
Block a user