What is Facial Recognition?
Facial recognition is a system of processes or algorithms that translates the characteristics of a face from a digital source into numbers that can be used to later identify the face.
Face detection, and the ethics surrounding the practice, has become an important topic in tech and a valid topic in political discussions. Facial recognition has a few different names (i.e image processing, computer vision) and falls into the larger category of artificial intelligence and machine learning. It has grown hand-in-hand with the rise of data science practices, the popular programming language Python, and the aggregation of massive data stores. One of the data science techniques that can be used to identify a face is a neural network.
Related: How to build a Facial Recognition App/Website with Python and Flaskre
Neural networks are a bit beyond the scope of this article, but we should have the general idea before using the API. If we were to submit an image to a neural network it would be processed at different levels. The network would be trying to answer a question, for example, “Is there a face in this image?“. Sections of the photograph would be selected and the system would gather information on whether the lighting and colors reveal edges, curves—or an object. This information means nothing unless the system has something to compare it with.
The system, hypothetically, has patterns of data points that have been positively identified as resembling faces. If the data points collected from sections of the submitted photograph are consistent with the data point patterns of faces, then the neural network can answer, “Yes, there is a face in this photograph!”, or, “I don’t think there is anything here”. This is typically done with ‘confidence intervals’ that are between 0 and 1. We will be able to glimpse some of these data points in the response object from the Kairos Facial Recognition API.
What is the Best Facial Recognition API?
After reviewing over 30+ APIs, we have found these to be the best face detection and recognition APIs:
- Kaiors Face Recognition API
- Animetrics Face Recognition API
- Lambda Labs API
- Inferdo Face Detection API
- Luxand.cloud Face Recognition API
- EyeRecognize Face Detection API
- Face++ Face Detection API
- Macgyver Face Recognition with Deep Learning API
- Microsoft Computer Vision API
- BetaFace Face Recognition API
Kairos API
Kairos is a leading face recognition AI company with an ethical approach to identity, that reflects our globally diverse communities. Through computer vision and deep learning, we recognize faces in videos, photos, and the real-world—our innovative API platform simplifies how developers and businesses integrate human identity into their software. We help our customers combat identity fraud, meet regulatory compliance, and enable more profitable customer experiences. –Kairos Face Recognition Overview
In using the Kairos API, we remove the need to build our own face database and understand complicated statistical algorithms. This can save a company a lot of time and money. In addition Kairos is simple, and the nine endpoints listed in the dashboard are easy to understand.
With Kairos, images that contain faces (that you hope to identify later) are put into galleries. For example, if I had three pictures of three different friends, I would add each of them to the gallery ‘Friends’. Later, I could use different endpoints (‘/recognize’, ‘/verify’) to identify my friends in group pictures or different individual pictures. I could check all my galleries, or submit an image and have the API search a specific gallery.
Kairos only accepts a few parameters across its endpoints:
- “image”
- “gallery_name”
- “subject_id”
- “selector”
This means that we can quickly build uncomplicated HTTP requests.
Furthermore, the “image” parameter accepts a publicly accessible URL, file upload or base64 encoded photo.
I hope to show you how simple it is to create galleries, upload pictures, and recognize images in the example javascript app below. We will build a ReactJS frontend that allows users to upload images to either the ‘friend’ gallery or the ‘foe’ gallery. Next, the frontend will send the image data to our node.js backend that will fire off the request to the Kairos API for processing and return the data to the user in the frontend. We will enroll subjects in both galleries, then identify our friends and foes in later images.
How to Build a Facial Recognition App with JavaScript
Prerequisites
In order the complete the application there are a few items that need to be completed.
First, you will need a RapidAPI account. Second, after setting up an account you will need to subscribe to the Kairos API. With the Basic subscription plan, you get 1000 API calls a month, and after that, it’s $0.0001 each request. That will be plenty for us in this example. Next, make sure that you have:
- Node >= 8.10 and npm >= 5.6 on your machine
- Some familiarity with how to open and use a command-line tool
- An internet connection
- A code editor (I recommend VS Code)
- Some familiarity with Javascript, specifically React and Node.
I will be using the bash terminal on macOS but most of the commands should be the same with the Windows command prompt. We will also need React >=16.8.0, however, if you are following the instructions, we will be downloading an acceptable version in the process.
Steps
Setting Up the Project
1.Open up a new terminal
2. Navigate to a good directory using cd
.
3. Make a new directory named face_recognition:
mkdir face_recognition
4. Move into that directory:
cd face_recognition
5. Run npx create-react-app client
. Npx is a program that ships with npm. We are creating a new React project using the popular project Create React App and placing it in a folder named ‘client’. After a few minutes, you should see the new folder appear in face_recognition
.
6. In face_recognition
, make a new directory server
and cd
into the empty folder.
7. Initialize a new npm module:
npm init -y
The -y
flag fills out the information that we would be prompted to answer that describes the package. Filling out this information is unnecessary in this tutorial. We now have our front end folder and our server folder ready for installations and modifications!
Set Up Express Server
1. Open up your code editor. I am using VS Code with the command line interface installed, so if I am in the root of our project (face_recognition) I can execute code .
and VS Code will open in that directory. Keep the terminal open because we will be installing packages soon.
2. In the terminal, navigate into server
and install express and dotenv:
npm install express dotenv --save
We will use express as our web application and dotenv to store our RapidAPI key so we don’t accidentally commit it to a repository. We will not be pushing the code base to Github, but it’s a good habit to have.
2. Install nodemon for development:
npm install nodemon --save-dev
3. Add a dev script to server/package.json
that will make it easier for us to use nodemon in development.
// server/package.json { ... "scripts": { "start": "node server.js", "dev": "nodemon server.js -e js", // our new script ... }
4. Make a new file server.js
in the folder server
. Add the below code to it.
// server.js const express = require('express'); //const path = require('path'); // Import routes // Create App const app = express(); const port = process.env.PORT || 3001; // Adds a parsed json body object to req app.use(express.json({ limit: '10000kb' })); // Future route // app.use('/api/', router); // DEPLOYMENT: for express to find static assets in the React build // app.use(express.static(path.resolve('client', 'build'))); // DEPLOYMENT: so express can return an actual webpage // app.get('*', (req, res) => { res.sendFile(path.resolve('client', 'build', 'index.html')); // }); // console.log that your server is up and running app.listen(port, () => console.log(`Listening on port ${port}`));
The majority of the code is commented out because it’s not time for it to be used. The code blocks that are commented out and labeled “DEPLOYMENT” will not be used in this app, but would be used if you were to deploy to a server. They allow the express application to find the React frontend and serve static files.
In this file, so far, we are creating the express app, adding JSON parsing middleware, and telling the app to listen for requests on port 3001. Easy enough! Next, we’ll go to the front end for general set up, and make sure that it can communicate with our backend.
Set Up Frontend
In the client
folder there is another package.json
file. However, it has different contents and is not related to the package.json
in the server
folder because, although our client
and server
folders are in the same directory, they are separate entities.
What we are going to do is open up a path of communication between the two folders by adding a directive in package.json
. The directive will proxy the requests that we make on the frontend to port 3001 (the port we specified in the express application).
1.Add proxy to front-end
// client/package.json { ... }, "proxy": "http://locahost:3001" }
Now, when we send requests on the front end to the root url they will be received by our backend.
2. We are going to do some basic clean up in our client
folder and install a few packages that we will use later. Open App.css
and fill it with the code below:
.App { display:flex; flex-direction: column; min-height: 100vh; } .App-header { background-color: #282c34; min-height: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } main { flex-grow: 1; text-align: center; padding: 25px; } footer { padding: 2px; min-height: 100px; display: flex!important; flex-direction: column; justify-content: center; background: #EEEEEE; font-size: small; text-align: center; } header { padding: 25px; }
Explaining the CSS is beyond the scope of the article. The code above will give us a nice layout to build our app.
3. Create React App ships with some example content that is displayed in App.js
, but we are going to replace it with our own set up. Open App.js
and add the code:
import React from 'react'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <h1>Friend or Foe</h1> </header> <main> </main> <footer> Page created by yournamehere </footer> </div> ); } export default App;
4. We are almost ready to fire up our app. Before we do that, let’s install a few packages that we are going to use extensively for styling. Double-check that your terminal is in the client
folder before executing the next command.
npm install react-bootstrap bootstrap
5. Import the bootstrap CSS into the page and add content to the <main>
tag of our app.
// App.js ... import 'bootstrap/dist/css/bootstrap.min.css' import { Container, Row, Col } from 'react-bootstrap' function App() { return ( <div className="App"> ... <main> <Container> <Row className="justify-content-md-center"> <Col md="6"> <h1>Test person form</h1> </Col> <Col md="6"> <h1>Add person form</h1> </Col> </Row> </Container> </main> ... </div> ); } export default App;
We are now ready to start our application and see what we are working with!
5. In client
run npm start
. The development server will start up and our app will be viewable at http://localhost:3000.
Your page should look like this:
Build Input Form
As can be seen from the headings, we are going to have a form where an image is tested for recognition and another form where we add images to galleries. This is going to be the same ReactJS component, but we are going to add conditionals that decide how the component is rendered. We are going to use React hooks to store our form data.
1. Make a directory in the src
folder named components
. Inside that folder create another folder ImageUploadForm
. Inside ImageUploadForm
create two files;
ImageUploadForm.js
ImageUploadForm.module.css
In ImageUploadForm.js
we are going to start to build a form, using the react-bootstrap library, that has three inputs. These inputs correspond to three of the basic parameters I mentioned earlier (gallery_name, image, subject_id).
2. Add the code below that structures our form.
import React from 'react' import { Form } from 'react-bootstrap' import classes from './ImageUploadForm.module.css' const ImageUploadForm = (props) => { return ( <Form className="my-4"> <Form.Row className="text-left"> select gallery </Form.Row> <Form.Row className="text-left"> enter subject name </Form.Row> <Form.Row> select or input image </Form.Row> </Form> ) } export default ImageUploadForm
3. Import our new form into App.js
and add it to our columns.
// App.js ... import ImageUploadForm from './components/ImageUploadForm/ImageUploadForm' ... ... <main> <Container> <Row className="justify-content-md-center"> <Col md="6"> <h1>Test person form</h1> <ImageUploadForm /> </Col> <Col md="6"> <h1>Add person form</h1> <ImageUploadForm /> </Col> </Row> </Container> </main> ... ...
That shouldn’t change the look of the app, but it will when we start adding inputs.
The first input we are going to add are radio buttons that select the gallery name. Our app is going to have two galleries: “friend” and “foe”.
We will need to import the { useState }
hook at the top of the page and declare our hook at the top of the components. If you are new to hooks, they are essentially a simple way to hold and change state in a component.
4. Replace “select gallery” with the code below.
// Image UploadForm.js import React, { useState } from 'react' ... const ImageUploadForm = (props) => { let [galleryName, setGalleryName]= useState('friend') // set starting value to 'friend' ... ... ... <Form.Group> <Form.Label>Gallery</Form.Label> <Form.Text className="text-muted"> Select upload gallery. </Form.Text> <Form.Check type="radio" label="Friend" checked={galleryName === "friend"} onChange={() => setGalleryName("friend")} /> <Form.Check type="radio" label="Foe" checked={galleryName === "foe"} onChange={() => setGalleryName("foe")} /> </Form.Group> ... ... ... ... }
Next, we are going to do something very similar to store the subject_id (name) variable, but we only want to show this input if we are adding someone to a gallery. Therefore, we will pass a ‘prop’ to each form that identifies which endpoint the form will be utilizing.
If we are testing a person we will set the endpoint prop to ‘recognize’, and if we are adding a person we will set the endpoint to ‘enroll’.
5. In App.js
two variables above the App()
function:
const recognize = "recognize"
const enroll = "enroll"
{enroll}
and the ‘Test person’ endpoint prop a value of {recognize}
. It may seem weird to have variables that match their own string value, but we are going to use these values again and want to avoid hard coding those strings in four different places.// App.js ... const enroll = "enroll" const recognize = "recognize" ... ... ... ... <Col md="6"> <h1>Test person form</h1> <ImageUploadForm endpoint={recognize} /> </Col> <Col md="6"> <h1>Add person form</h1> <ImageUploadForm endpoint={enroll} /> </Col> ... ... ... ...
Back in ImageUploadForm.js
we can check this prop and if it equals ‘enroll’ then we will display the ‘Name’ input.
7. Add the ‘Name’ input with a conditional to ImageUploadForm.js
in replace of the row that has ‘enter subject name’. Remember to add the hook that declares the variable and set function.
... ... ... ... {props.endpoint === 'enroll' ? <Form.Row className="text-left"> <Form.Group> <Form.Label>Name</Form.Label> <FormControl type="text" placeholder="Subject name" onChange={(e) => setName(e.target.value)} value={name} required /> </Form.Group> </Form.Row> : null } ... ... ... ...
After adding the conditional input row your forms should look like this:
The last input is the image input, but it will require a little more work.
Adding the Image Input
The image input comes in a few different forms. The Kairos API “image” parameter accepts a publicly accessible URL, file upload or base64 encoded photo. Our form is going to take an image upload, translate it to base64, and submit that string, or it will accept the URL string and submit that as the image parameter.
Therefore, we need two different inputs. In order to accomplish this, we are going to create two tabs in each column, one for URLs, and one for image uploads. We will then pass another prop specifying whether the form accepts ‘file’ or ‘text’.
1.Import the Tab
component from react-bootstrap and duplicate the image forms. Each column should have two Tabs
, two ImageUploadForms
, and one form should have a prop type='file'
while the other has a prop type='text'
. We are going to add a placeholder prop for our text forms. App.js
should have the following code:
import React from 'react' import './App.css' import 'bootstrap/dist/css/bootstrap.min.css' import { Container, Row, Col, Tab, Tabs } from 'react-bootstrap' import ImageUploadForm from './components/ImageUploadForm/ImageUploadForm' const recognize = "recognize" const enroll = "enroll" function App() { return ( <div className="App"> <header className="App-header"> <h1>Friend or Foe</h1> </header> <main> <Container fluid> <Row className="justify-content-md-around"> <Col md="5"> <h2>Test Person</h2> <Tabs defaultActiveKey="upload" id="uncontrolled-tab-example"> <Tab eventKey="upload" title="Upload"> <ImageUploadForm label="Select Image to Test" type="file" endpoint={recognize} /> </Tab> <Tab eventKey="url" title="URL"> <ImageUploadForm label="Enter Image URL to Test" type="text" placeholder="Image address URL" endpoint={recognize} /> </Tab> </Tabs> </Col> <Col md="5" className="bg-light"> <h2>Add Person</h2> <Tabs defaultActiveKey="uploadAdd" id="uncontrolled-tab-example"> <Tab eventKey="uploadAdd" title="Upload"> <ImageUploadForm label="Select Image to Add" type="file" endpoint={enroll} /> </Tab> <Tab eventKey="urlAdd" title="URL"> <ImageUploadForm label="Enter Image URL to Add" type="text" placeholder="Image address URL" endpoint={enroll} /> </Tab> </Tabs> </Col> </Row> </Container> </main> <footer> Page created by yournamehere </footer> </div> ); } export default App;
Now, you should be able to switch back and forth between the ‘Upload’ tab and the ‘URL’ tab. Nothing changes in the form yet because we have not added the image input.
2. In ImageUploadForm.js
add the image input where it says ‘select or input image’. We will also need to import Button
from react-bootstrap and create the file
variable in state.
... import { Form, Button, //import button FormControl ) from 'react-bootstrap' ... const ImageUploadForm = (props) => { ... let [file, setFile] = useState(null) // add file to state ... ... ... <div className="input-group d-flex justify-content-center mb-3"> <div className="input-group-prepend"> <Button type="submit" variant="primary">Upload</Button> </div> <div className={props.type === 'file' ? "custom-file" : ''}> <label className={props.type === 'file' ? "custom-file-label text-left" : 'text-center'}>{!file ? props.label : file.name}</label> <input required type={props.type} className={props.type === 'file' ? "custom-file-input" : "form-control"} placeholder={props.placeholder} /> </div> </div> ... ... ... ... }
You can now see that the input changes between a text image upload and a file image upload. This is because we are changing values in the code based on the props that the component is being passed.
Add Preview
It would be nice if our users could see the image that they are about to upload. Currently, our image input isn’t added to state. We are going to build a file staging function that operates conditionally:
- If the image is uploaded, the file is converted to base64 and a local URL is created for the preview
- If the image input is a URL the string then we set the appropriate variables to the string
In addition, we will have an <image />
element that is waiting to receive a value for it’s src
attribute.
1.In ImageUploadForm.js
add another value to the state:
let [fileSrc, setFileSrc] = useState('')
2. Import the useCallback
hook at the top of the page next to useState
import React, { useState, useCallback } from 'react'
useCallback
hook memoizes the function. This is beyond the scope of the article, and might be a little unnecessary for such a small app and function.fileStaging
function to ImageUploadForm.js
.... const fileStaging = useCallback((e) => { if (props.type === 'file') { setFile(e.target.files[0]) let reader = new FileReader(); try { reader.readAsDataURL(e.target.files[0]) } catch (e) { setFileSrc('') } reader.addEventListener("load", function() { setFileSrc(reader.result) }, false) } else { setFileSrc(e.target.value) } }, [props.type]) ...
3. Add the image preview to the bottom of the form.
... ... ... ... <h2>Image Preview</h2> {fileSrc ? <figure><img className={classes.Image} alt="" src={fileSrc} /></figure> : <p style={{ color: "#CCC" }}>No image to preview</p>} </Form> ... ... ...
4. Add the onChange handler for the function to the image input. The full file should now have the code below:
import React, { useState, useCallback } from 'react' import { Form, FormControl, Button, } from 'react-bootstrap' import classes from './ImageUploadForm.module.css' const ImageUploadForm = (props) => { let [galleryName, setGalleryName] = useState('friend') // set starting value to 'friend' let [name, setName] = useState('') let [file, setFile] = useState(null) let [fileSrc, setFileSrc] = useState('') const fileStaging = useCallback((e) => { if (props.type === 'file') { setFile(e.target.files[0]) let reader = new FileReader(); try { reader.readAsDataURL(e.target.files[0]) } catch (e) { setFileSrc('') } reader.addEventListener("load", function() { setFileSrc(reader.result) }, false) } else { setFileSrc(e.target.value) } }, [props.type]) return ( <Form className="my-4"> <Form.Row className="text-left"> <Form.Group> <Form.Label>Gallery</Form.Label> <Form.Text className="text-muted"> Select upload gallery. </Form.Text> <Form.Check type="radio" label="Friend" checked={galleryName === "friend"} onChange={() => setGalleryName("friend")} /> <Form.Check type="radio" label="Foe" checked={galleryName === "foe"} onChange={() => setGalleryName("foe")} /> </Form.Group> </Form.Row> {props.endpoint === 'enroll' ? <Form.Row className="text-left"> <Form.Group> <Form.Label>Name</Form.Label> <FormControl type="text" placeholder="Subject name" onChange={(e) => setName(e.target.value)} value={name} required /> </Form.Group> </Form.Row> : null } <Form.Row> <div className="input-group d-flex justify-content-center mb-3"> <div className="input-group-prepend"> <Button type="submit" variant="primary">Upload</Button> </div> <div className={props.type === 'file' ? "custom-file" : ''}> <label className={props.type === 'file' ? "custom-file-label text-left" : 'text-center'}>{!file ? props.label : file.name}</label> <input required type={props.type} className={props.type === 'file' ? "custom-file-input" : "form-control"} placeholder={props.placeholder} onChange={(e) => fileStaging(e)} /> </div> </div> </Form.Row> <h2>Image Preview</h2> {fileSrc ? <figure><img className={classes.Image} alt="" src={fileSrc} /></figure> : <p style={{ color: "#CCC" }}>No image to preview</p>} </Form> ) } export default ImageUploadForm
Images can now be previewed!
ImageUploadForm.module.css
..Image { max-width: 50% }
One of the final things that needs to be implemented is a submit function that sends the data we have added to our backend. Currently, we do not have a route set up on the backend to receive the data. Yet we can still get the submit function set up in the way that we want the backend to receive the data.
Add Submit Function
1.We will use the popular library axios to send our data to the backend. In the terminal, while in face_recognition/client
install axios:
npm install axios --save
Remember, this component will be used for two kinds of data submission. We will need to create a data payload object to send as a POST request and the contents of that data object will depend on which type of form is being used to submit the data.
Thankfully, a prop is being passed in the form that distinguishes between the two types: endpoint
.
The function conditionally adds the subject_id
value if the endpoint prop has a value of ‘enroll’.
2. Add the handleSubmit
function to ImageUploadForm.js
underneath the fileStaging
function.
... const handleSubmit = (e) => { e.preventDefault() // will stop the page from refreshing on submit // set alert variable to null // constructs data payload let data = { gallery_name: galleryName, image: fileSrc } // adds subject_id if we are enrolling a new person if (props.endpoint === 'enroll') { data = { ...data, // spread operater - add all the properties in the previously declared data object to this object subject_id: name } } console.log(data) // log the data to make sure that it is beng sent the way we want axios.post(`/api/upload/${props.endpoint}`, data) .then(response => { // set alert variable }) .catch(e => { set alert variable }) } ...
3. Add the onSubmit handler to the <Form>
tag.
<Form onSubmit={handleSubmit} className="my-4">
Build Route for Uploads
In the submit function we specified a URL to send the data to (`/api/upload/${props.endpoint}`). We used a string literal that uses back ticks—not quotes—to dynamically change the last part of the URL. There are only two values that we are giving the ‘endpoint’ prop: ‘enroll’ and ‘recognize’.
However, we need to build those routes on the backend.
1. In the terminal navigate to face_recognition/server
and run the command npm run dev
. We set-up this command earlier in the example.
Listening on port 3001
should be logged to the console. The nodemon server is now listening for changes in our files. This will help us develop and catch mistakes faster.
2. In server
, make a directory src
and inside of that folder make another directory routes
.
3. In routes
, make the file upload.js
and add the two routes that we talked about earlier.
// upload.js const router = require('express').Router(); router.route('/enroll').post( async (req, res) => { // enroll route }) router.route('/recognize').post( async (req, res) => { // recognize route }) module.exports = router;
4. The express application does not know about this file. In server.js
import the router and set up the route middleware.
// server.js const express = require('express'); const path = require('path'); require('dotenv').config() // Import route const uploadRouter = require('./src/routes/upload') // NEW // Create App const app = express(); const port = process.env.PORT || 3001; // Adds a parsed json body object to req app.use(express.json({ limit: '10000kb' })); // Set route middleware app.use('/api/upload', uploadRouter) // NEW // Needed for express to find static assets //app.use(express.static(path.resolve('client', 'build'))); // Needed so express can return an actual webpage //app.get('*', (req, res) => { // res.sendFile(path.resolve('client', 'build', 'index.html')); //}); // console.log that your server is up and running app.listen(port, () => console.log(`Listening on port ${port}`));
Add RapidAPI Credentials and API Calls to the Route
Back in the RapidAPI marketplace, pull up the Kairos Face Recognition dashboard. Locate the two endpoints that we will use (enroll and recognize).
Notice the headers are the same in both endpoints. Furthermore, the HTTP request URL is mostly the same with the exception of the last word.
1.Pull the header information into the route and create a header
object. Add another variable named baseUrl
and assign it the URL section that doesn’t change between the two routes.
const router = require('express').Router(); const headers = { "content-type":"application/json", "x-rapidapi-host":"kairosapi-karios-v1.p.rapidapi.com", "x-rapidapi-key": process.env.KAIROS_API_KEY, "accept":"application/json" } const baseUrl = "https://kairosapi-karios-v1.p.rapidapi.com/" router.route('/enroll').post( async (req, res) => { ...
Take note that I didn’t paste in my api-key. We are going to use the dotenv library that we installed earlier to add this
2. Create a file in the server
folder named .env
. Add your api-key to that file.
// .env KAIROS_API_KEY=apikey
If we were pushing this up to a Git repository, we would add ‘.env’ to the .gitignore
file so our API key isn’t public or shared among other collaborators.
3. Require the dotenv file in server.js
, so our variables are loaded into the app. Underneath the imports add the line:
require('dotenv').config()
Now that we have the header object and base URL, it’s easy to build axios calls.
4. Install axios on the backend and import it into upload.js
.
On the frontend, we formed the data to have key values that match what the Kairos API expects as parameter values (subject_id, gallery_name, image). This will make it easy for us to take the request body and simply place it into the request to the Kairos API.
5. Add the Kairos API calls to each route.
const router = require('express').Router(); const axios = require('axios') router.route('/enroll').post( async (req, res) => { let data = { ...req.body } const url = baseUrl + "enroll" try { const response = await axios({ "method": "POST", url, headers, data }) } catch(e) { console.log(e); res.status(500).send(); } }) router.route('/recognize').post( async (req, res) => { let data = { ...req.body } const url = baseUrl + "recognize" try { const response = await axios({ "method": "POST", url, headers, data }) } catch(e) { console.log(e); res.status(500).send(); } }) module.exports = router;
We get the data from the front end and submit it to the Kairos API, but we don’t send any useful information back. We must inspect the response objects that we get from Kairos.
Building Useful Responses
We can test the endpoints in the RapidAPI dashboard and examine the response objects.
When we enroll a subject, and it’s successful, the response looks like:
{ "face_id": "68267b4f16394218826", "images": [ { "attributes": { // demographic information }, "transaction": { "confidence": 0.99648, // I left this here because it's the confidence level ID that was discussed at beginning of article } } ], }
If the image is low resolution, or if it does not contain a face, the response fails. The API does not return an error status, it returns an error object.
{ "Errors":[ { "ErrCode":5001 "Message":"invalid url was sent" } ] }
We need to check the response for the ‘Errors’ parameter, and if it exists we need to send that error back to the front end. If it succeeds, we can send back whatever information we would like to.
For the enroll endpoint, we will simply send back a nice success message.
However, the recognize response has other information we want to use. If the Kairos API recognizes a face in an image it will return a ‘candidates’ object with the subject_id of the person it recognizes.
Furthermore, if it does not recognize the face, it will not have a candidates object. Knowing this, we can build string responses that send back data in an easy to display format.
1. Insert the remaining code into upload.js
. The file should have the contents below:
const router = require('express').Router(); const axios = require("axios"); const headers = { "content-type":"application/json", "x-rapidapi-host":"kairosapi-karios-v1.p.rapidapi.com", "x-rapidapi-key": process.env.KAIROS_API_KEY, "accept":"application/json" } const baseUrl = "https://kairosapi-karios-v1.p.rapidapi.com/" router.route('/enroll').post( async (req, res) => { let data = { ...req.body } const url = baseUrl + "enroll" try { const response = await axios({ "method": "POST", url, headers, data }) const params = Object.keys(response.data) // check for errors if (params.includes('Errors')) { console.log(response.data) return res.status(400).send({variant:"danger", text: response.data.Errors[0].Message}) } // return a simple string if the add was successful res.send({variant:'success', text: `Success! ${data.subject_id} added.`}) } catch(e) { console.log(e); res.status(500).send({variant: 'danger', text: 'Server error.'}); } }) router.route('/recognize').post( async (req, res) => { let data = { ...req.body } const url = baseUrl + "recognize" try { const response = await axios({ "method": "POST", url, headers, data }) const params = Object.keys(response.data) if (params.includes('Errors')) { return res.status(400).send({variant: "danger", text: response.data.Errors[0].Message}) } let subjects = [] // check the response for candidates response.data.images.map(item => { console.log(item) if (item.candidates) { subjects.push(item.candidates[0].subject_id) } }) // build string responses that contain the subjects that were found, or not found in the image if (subjects.length < 1) { return res.send({variant: "info", text: "No faces were recognized. Exercise caution."}) } if (data.gallery_name === 'foe') { const foeString = `Be careful we recognize ${subjects.join(' ')} as foe!` res.send({variant: 'danger', text: foeString}) } else { const friendString = `Hoozah! We recognize friends! That's ${subjects.join(' and ')}` res.send({variant: 'success', text: friendString}) } } catch(e) { console.log(e); res.status(500).send({variant: 'danger', text: 'Server error.'}); } }) module.exports = router;
Create the Message Component In The Frontend
There is not a way for us to display useful messages or data on the front end. Yet, you may have noticed how we are structuring the response objects in upload.js
:
{ variant: 'danger', text: 'Server error.' }
The Message component will take in a string ‘variant’ that corresponds to allowed property values for the Alert components variant prop. The Alert component is part of the bootstrap library and can have values of ‘success’, ‘danger’, ‘primary’, ‘info’, ‘light’, ‘dark’, and a few others.
1.In components
create the Message
directory. Inside of that folder add the file Message.js
.
2. Add the below code to Message.js
import React from 'react' import { Alert } from 'react-bootstrap' const message = (props) => { const message = props.message return ( <div> {message && <Alert variant={props.message.variant}>{props.message.text}</Alert>} </div> ) } export default message
It is almost time to add our subjects and test our app!
Finishing Up the ImageUploadForm
The final touches to the form component are:
- import the Message component
- declare a variable for the message component
- set the variable value in the
handleSubmit
function, and; - display the message component when it has a value
The below code is what the final ImageUploadForm.js
should contain.
I’ll add comments next to the lines that were added.
import React, { useState, useCallback } from 'react' import { Form, Button, FormControl} from 'react-bootstrap' import classes from './ImageUploadForm.module.css' import axios from 'axios' import Message from '../Message/Message' // NEW const ImageUploadForm = (props) => { let [alert, setAlert] = useState(null) // NEW let [file, setFile] = useState(null) let [fileSrc, setFileSrc] = useState('') let [galleryName, setGalleryName] = useState('friend') let [name, setName] = useState('') const fileStaging = useCallback((e) => { if (props.type === 'file') { setFile(e.target.files[0]) let reader = new FileReader(); try { reader.readAsDataURL(e.target.files[0]) } catch (e) { setAlert({variant:'danger', text:'Please select valid image.'}) // NEW setFileSrc('') } reader.addEventListener("load", function() { setFileSrc(reader.result) }, false) } else { setFileSrc(e.target.value) } }, [props.type]) const handleSubmit = (e) => { e.preventDefault() setAlert(null) // NEW let data = { gallery_name: galleryName, image: fileSrc } if (props.endpoint === 'enroll') { data = { ...data, subject_id: name } } axios.post(`/api/upload/${props.endpoint}`, data) .then(response => { setAlert(response.data) // NEW }) .catch(e => { setAlert(e.response.data) // NEW }) } return ( <Form onSubmit={handleSubmit} className="my-4"> {alert && <Message message={alert} />} // NEW <Form.Row className="text-left"> <Form.Group> <Form.Label>Gallery</Form.Label> <Form.Text className="text-muted"> Select upload gallery. </Form.Text> <Form.Check type="radio" label="Friend" checked={galleryName === "friend"} onChange={() => setGalleryName("friend")} /> <Form.Check type="radio" label="Foe" checked={galleryName === "foe"} onChange={() => setGalleryName("foe")} /> </Form.Group> </Form.Row> {props.endpoint === 'enroll' ? <Form.Row className="text-left"> <Form.Group> <Form.Label>Name</Form.Label> <FormControl type="text" placeholder="Subject name" onChange={(e) => setName(e.target.value)} value={name} required /> </Form.Group> </Form.Row> : null } <Form.Row> <div className="input-group d-flex justify-content-center mb-3"> <div className="input-group-prepend"> <Button type="submit" variant="primary">Upload</Button> </div> <div className={props.type === 'file' ? "custom-file" : ''}> <label className={props.type === 'file' ? "custom-file-label text-left" : 'text-center'}>{!file ? props.label : file.name}</label> <input required type={props.type} className={props.type === 'file' ? "custom-file-input" : "form-control"} placeholder={props.placeholder} onChange={(e) => fileStaging(e)} /> </div> </div> </Form.Row> <h2>Image Preview</h2> {fileSrc ? <figure><img className={classes.Image} alt="" src={fileSrc} /></figure> : <p style={{ color: "#CCC" }}>No image to preview</p>} </Form> ) } export default ImageUploadForm
Test
Time to use our app! To recap, we will be creating two galleries with Kairos. Kairos saves the gallery’s name once it is used and creates a new gallery if the name has not been used. What is great about that is we don’t need a database to store the subject_ids, face dimensions, or images: Kairos does that for us.
I am going to be pulling image addresses off of Google to test the app and will doing a LOTR themed testing.
First, let’s add Gollum to the ‘foe’ gallery:
- image address: https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic.independent.co.uk%2Fs3fs-public%2Fthumbnails%2Fimage%2F2012%2F12%2F06%2F19%2FAndy-Serkis-1-ap.jpg&f=1&nofb=1
- name: Gollum
Second, upload two friends, Aragorn and Gandalf:
- image address: https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2Fimages1.fanpop.com%2Fimages%2Fphotos%2F2200000%2FAragorn-screencaps-viggo-mortensen-2256968-960-404.jpg&f=1&nofb=1
- name: Aragorn
- image address: https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2Fwww.media4.hw-static.com%2Fwp-content%2Fuploads%2F55042617.jpg&f=1&nofb=1
- name: Gandalf
Third, check the gallery ‘foe’ with a different image the contains Gollum:
- image address: https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2Fartistthinks.files.wordpress.com%2F2012%2F07%2Ffcfd1425.jpg&f=1&nofb=1
Fourth, see if our app can recognize Gandalf and Aragorn in a different image:
- image address: https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.q-0RjCtPHdXDnWu96DyfIwHaDW%26pid%3DApi&f=1
Conclusion
I had a lot of fun working with this API and I hope that the example app showed you how to build something even better.
Remember, there are still many ethical concerns surrounding facial recognition and they should be considered before building something that could compromise someone’s safety.
That being said, I hope you found this article helpful and leave a comment or question if you like!
Leave a Reply