Building a WebRTC powered camera for the mobile web is pretty simple. All you need to do is prompt the user to gain access to their webcam, feed the webcam stream to a waiting <video> tag, and then provide a method for taking photos. Here’s how I might do that in Nuxt. First, I would add the <video> tag to my <template> . The ref property allows me to easily access my <video> tag within any Vue methods I’ll create. I’m also adding an <img> tag which will receive our final image (for saving) upon completion.

<template>

<video ref="video" autoplay playsinline></video>

<img ref="photo">

</template>

Then, in an startCamera method, we’ll prompt the user to gain access to their webcam and send the stream to the <video> tag, catching errors along the way. The constraints for my app are pretty simple. I don’t need audio so I’ll set it to false and I’ve chosen the facingMode of the video to be environment so the back facing camera is used.

startCamera() {

navigator.mediaDevices.getUserMedia({

audio: false,

video: {

facingMode: 'environment'

}

}).then((stream) => {

this.$refs.video.srcObject = stream

}).then((error) => {

// do something with error

})

}

I choose to call this method as soon as my Vue component is mounted . When the user allows access, the camera will immediately begin streaming to the <video> tag. Next we’ll want another method called takePhoto to, you guessed it, capture photos. The process of taking photos from a webcam stream involves drawing the current <video> tag image to a waiting HTML5 canvas. That canvas can be visible to the user or simply offlined to your code. I’ll choose the latter. In addition, I will grab the video height and width to help establish the size of my canvas.

takePhoto() {

let video = this.$refs.video

let videoWidth = video.videoWidth

let videoHeight = video.videoHeight this.photo = document.createElement('canvas') this.photo.width = videoWidth

this.photo.height = videoHeight let photoContext = this.photo.getContext('2d') photoContext.drawImge(video, 0, 0, videoWidth, videoHeight) // classify photo

}

Note, this is a very simplified example. I tend to also use some dynamic resizing depending on what the final format of the photo should be. A good library for handling that is Javascript Load Image by blueimp. Once your photo is captured, you’re ready to send it to Watson for classification. You can either merge that logic into takePhoto method or create a new one just for classifying.

We’ve got our Express endpoint awaiting images and a way to capture photos from the user’s webcam. How do we send those new images to the endpoint for classifying? I choose to use the handy FormData web api to generate a new form post on the fly. However, we can’t just send the canvas over, we’ll need to convert it to a blob (or file-like object.) Luckily, this is all pretty damn “EASY.”

photo.toBlob((blob) => {

let fd = new FormData() fd.append('virgilPhoto', blob) fetch('/api', {

method: 'POST',

body: fd

}).then((response) => {

return response.json()

}).then((data) => {

this.classes = data.images[0].classifiers[0].classes // compose text on photo

})

}

I now have the data required to begin composing my final image. However, IBM Watson will actually send over several classifications (including color) for your photo with scores. I would like to grab the highest scoring class which isn’t a color. In order to do this, I’ve set up three computed properties with Vue to filter, sort, and expose the top class. First, let’s filter out any classes which include the word “color” with the es6 array filter method.

filteredClasses() {

return this.classes.filter(c => !c.class.includes('color'))

}

Then we can sort the remaining classes by their score, making sure to reverse the array to order from highest to lowest.

sortedClasses() {

return this.filteredClasses.sort(function(a, b) {

return a.score - b.score

}).reverse()

}

Finally, we can then pluck the highest score from the top of the array and make it’s containing class string upper cased and ready for Helvetica bold.

topClass() {

return this.sortedClasses[0].class.toUpperCase()

}