The default face-api.js uses only one image as reference for face recognition, but through my tests I noticed a pretty high error gap. So I was wondering, how can I manage to increase the number of reference images, in order to reduce the error gap?
Assuming my images are in the imgs/ folder, how can I do this?
Here's my project folder :
Project Folder
Here's the faceRecognition.ts file :
import * as faceapi from 'face-api.js';
import { canvas, faceDetectionNet, faceDetectionOptions, saveFile } from './commons';
const REFERENCE_IMAGE = '../../imgs/test1.jpeg'
const QUERY_IMAGE = '../../test/test.jpeg'
// i want to have many images for the REFERENCE_IMAGE
// in folder imgs, i have 5 images in a want to use all five images for increase
// the result. Actually i have some bad prediction when i use only one image
async function run() {
await faceDetectionNet.loadFromDisk('../../weights')
await faceapi.nets.faceLandmark68Net.loadFromDisk('../../weights')
await faceapi.nets.faceRecognitionNet.loadFromDisk('../../weights')
const referenceImage = await canvas.loadImage(REFERENCE_IMAGE)
const queryImage = await canvas.loadImage(QUERY_IMAGE)
const resultsRef = await faceapi.detectAllFaces(referenceImage, faceDetectionOptions)
.withFaceLandmarks()
.withFaceDescriptors()
const resultsQuery = await faceapi.detectAllFaces(queryImage, faceDetectionOptions)
.withFaceLandmarks()
.withFaceDescriptors()
const faceMatcher = new faceapi.FaceMatcher(resultsRef)
const labels = faceMatcher.labeledDescriptors
.map(ld => ld.label)
const refDrawBoxes = resultsRef
.map(res => res.detection.box)
.map((box, i) => new faceapi.draw.DrawBox(box, { label: labels[i] }))
const outRef = faceapi.createCanvasFromMedia(referenceImage)
refDrawBoxes.forEach(drawBox => drawBox.draw(outRef))
saveFile('referenceImage.jpg', (outRef as any).toBuffer('image/jpeg'))
const queryDrawBoxes = resultsQuery.map(res => {
const bestMatch = faceMatcher.findBestMatch(res.descriptor)
return new faceapi.draw.DrawBox(res.detection.box, { label: bestMatch.toString() })
})
const outQuery = faceapi.createCanvasFromMedia(queryImage)
queryDrawBoxes.forEach(drawBox => drawBox.draw(outQuery))
saveFile('queryImage.jpg', (outQuery as any).toBuffer('image/jpeg'))
}
run()
Can anyone help ?
const path = require('path')
import * as faceapi from 'face-api.js';
import { canvas, faceDetectionNet, faceDetectionOptions, saveFile } from './commons';
async function start() {
await faceDetectionNet.loadFromDisk('../../weights')
await faceapi.nets.faceLandmark68Net.loadFromDisk('../../weights')
await faceapi.nets.faceRecognitionNet.loadFromDisk('../../weights')
const labeledFaceDescriptors = await loadLabeledImages()
const faceMatcher = new faceapi.FaceMatcher(labeledFaceDescriptors, 0.6)
const queryImage = await canvas.loadImage(`test/test.jpeg`)
//absolute link to image
const detections = await faceapi.detectAllFaces(queryImage ).withFaceLandmarks().withFaceDescriptors()
const queryDrawBoxes = detections.map(res => {
const bestMatch = faceMatcher.findBestMatch(res.descriptor)
return new faceapi.draw.DrawBox(res.detection.box, { label: bestMatch.toString() })
})
const outQuery = faceapi.createCanvasFromMedia(queryImage)
queryDrawBoxes.forEach(drawBox => drawBox.draw(outQuery))
saveFile('queryImage.jpg', (outQuery as any).toBuffer('image/jpeg'))
console.log('done, saved results to out/queryImage.jpg')
}
function loadLabeledImages() {
const labels = ['imgs']
return Promise.all(
labels.map(async label => {
const descriptions = []
for (let i = 1; i <= 5; i++) {
const img = await canvas.loadImage(`/imgs/test${i}.jpeg` )
// for example if you are test1 , test2, etc. like image's names
const detections = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor()
descriptions.push(detections.descriptor)
}
return new faceapi.LabeledFaceDescriptors(label, descriptions)
})
)
}
start()
In the line setVotedPosts([...previousVotedPosts, postId]);
I'm trying to get the previous value of votedPosts, but I'm getting back the newest value.
full code : https://github.com/silvertechguy/reddit-clone/blob/main/src/components/vote-buttons.js
App live : https://reddit-clone-official.vercel.app/
const VoteButtons = ({ post }) => {
const [isVoting, setVoting] = useState(false);
const [votedPosts, setVotedPosts] = useState([]);
useEffect(() => {
const votesFromLocalStorage =
JSON.parse(localStorage.getItem("votes")) || [];
setVotedPosts(votesFromLocalStorage);
}, []);
const handleDisablingOfVoting = (postId) => {
const previousVotedPosts = votedPosts;
setVotedPosts([...previousVotedPosts, postId]);
localStorage.setItem(
"votes",
JSON.stringify([...previousVotedPosts, postId])
);
};
const handleClick = async (type) => {
setVoting(true);
// Do calculation to save the vote.
let upVotesCount = post.upVotesCount;
let downVotesCount = post.downVotesCount;
const date = new Date();
if (type === "upvote") {
upVotesCount = upVotesCount + 1;
} else {
downVotesCount = downVotesCount + 1;
}
await db.collection("posts").doc(post.id).set({
title: post.title,
upVotesCount,
downVotesCount,
createdAt: post.createdAt,
updatedAt: date.toUTCString(),
});
// Disable the voting button once the voting is successful.
handleDisablingOfVoting(post.id);
setVoting(false);
};
const checkIfPostIsAlreadyVoted = () => votedPosts.includes(post.id);
Problem
const previousVotedPosts = votedPosts;
In JavaScript, arrays are reference types, so you can't just create a new copy of an array using =.
Try this solution
Clone array using spread syntax(...).
const handleDisablingOfVoting = (postId) => {
const previousVotedPosts = [...votedPosts];
setVotedPosts([...previousVotedPosts, postId]);
localStorage.setItem(
"votes",
JSON.stringify([...previousVotedPosts, postId])
);
};
I have spent a while looking around at similar problems online but I seem to be really struggling to wrap my head around this. I've reviewed a few sources online for help but was unable to apply the logic I seen to my own codebase, I'm hoping somebody out there might be able to help.
I'm running an express server with a pg Pool to handle DB logic.
I am able to successfully log out the information from within the pg.Pool logic however despite banging my head against multiple attempts I have been unable to successfully pass the data onto the clientside.
dbQueries.js
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database:process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT
});
// Main function called in server.js
// step 1. fetches distinct name values from pg table
// step 2. fetches values to get full list of reviews for those distinct names
// step 3. does some data modification to make the data formatted for frontend usecase
const getFormattedReviews = async function(){
console.log('Fetching all unique therapist review names.')
const getDistinct = {
name: 'distinct-reviews',
text: 'SELECT DISTINCT therapist_name FROM reviews'
};
// step 1
const res = await pool.query(getDistinct, (err, res) => {
let data = []
if (err) {
console.log(err.stack);
} else {
// console.log(res.rows);
data = res.rows.map(
// step 2
therapist => getSpecificTherapistReviews(therapist.therapist_name)
)
}
console.log(`\n DEBUG3 - getFormattedReviews data: ${JSON.stringify(data)} \n`)
return data;
});
return res;
}
const getSpecificTherapistReviews = async function(therapist_name){
console.log(`Fetching reviews for: ${therapist_name}.`)
const getSpecificTherapistReviews = {
name: `${therapist_name}-reviews`,
text: `SELECT * FROM reviews WHERE therapist_name LIKE '%${therapist_name}%'`
};
const res = await pool.query(getSpecificTherapistReviews, (err, res) => {
let data = []
if (err) {
console.log(err.stack);
} else {
// console.log(res.rows);
// step 3
data = filteringDataForFrontend(res.rows);
}
console.log(`\n DEBUG2 - GetSpecificTherapistReviews ${JSON.stringify(data)} \n`)
return data;
});
return res;
}
const filteringDataForFrontend = function(data){
console.log(`Filtering Data for Frontend.`)
// Based on length of the reviews array, each review = 1 object
const total_reviews = data.length;
// Underlying logic assumes consistent data across all entries for these values
const therapist_name = data[0].therapist_name;
const type = data[0].type;
const image = data[0].image;
const location = data[0].location;
// Summing the rating values across multiple review entries
const ratings = data.reduce((acc, obj) => ({
rating_friendliness: acc.rating_friendliness + obj.rating_friendliness,
rating_techniques: acc.rating_techniques + obj.rating_techniques,
rating_progression: acc.rating_progression + obj.rating_progression,
rating_cost: acc.rating_progression + obj.rating_progression,
rating_listening: acc.rating_listening + obj.rating_listening,
rating_overall: acc.rating_overall + obj.rating_overall
})
)
// Placeholder as string, most likely restructure to an array of objects
const comments = data.reduce ((acc, obj) => ({
feedback_comments: acc.feedback_comments + obj.feedback_comments
})
)
// Filtered data for returning
const filteredData = {
therapist_name,
type,
image,
location,
rating_friendliness: ratings.rating_friendliness / total_reviews,
rating_techniques: ratings.rating_techniques / total_reviews,
rating_progression: ratings.rating_progression / total_reviews,
rating_cost: ratings.rating_cost / total_reviews,
rating_listening: ratings.rating_listening / total_reviews,
rating_overall: ratings.rating_overall / total_reviews,
feedback_comments: comments.feedback_comments
}
console.log(`\n DEBUG 1 - filteredData -> ${JSON.stringify(filteredData)} \n`)
return filteredData;
}
module.exports = {
getFormattedReviews,
};
An ideal setup I would like to have on the server.js side of things running express would be:
server.js
const express = require('express');
const DB = require('./dbQueries.js');
const app = express();
const port = process.env.SERVER_PORT || 8000;
app.get('/get-reviews', async (req, res) => {
const data = await DB.getFormattedReviews();
console.log(`data check ${data}`);
res.send({data});
});
Currently the endpoint is logging 'data check undefined'.
DEBUG checks 1 & 2 successfully appear to log information, however I spotted that DEBUG 3 only logs DEBUG3 - getFormattedReviews data: [{},{},{}] so perhaps I'm doing something wrong around there?
Any help/insight appreciated.
Thanks #Abraham & #Labkovsky for the suggestions -> Ill review them properly during the week.
I managed to get the basic fuctionality up and running with this code - it likely needs some refactoring but for reference:
dbQueries.js
const getFormattedReviews = async function(){
const getDistinct = {
name: 'distinct-reviews',
text: 'SELECT DISTINCT therapist_name FROM reviews'
};
const res = await new Promise(resolve => {
pool.query(getDistinct, (err, res) => {
let data = []
if (err) {
console.log(err.stack);
} else {
// console.log(res.rows);
data = res.rows.map(
async therapist => await getSpecificTherapistReviews(therapist.therapist_name)
)
// Promise.all(data).then(results => console.log(`\n DEBUG3 - getFormattedReviews data: ${JSON.stringify(results)} \n`))
}
Promise.all(data).then(results => resolve(results));
});
});
return res;
}
const getSpecificTherapistReviews = async function(therapist_name){
// console.log(`Fetching reviews for: ${therapist_name}.`)
const getSpecificTherapistReviews = {
name: `${therapist_name}-reviews`,
text: `SELECT * FROM reviews WHERE therapist_name LIKE '%${therapist_name}%'`
};
const res = await new Promise(resolve => {
pool.query(getSpecificTherapistReviews, (err, res) => {
let data = []
if (err) {
console.log(err.stack);
} else {
// console.log(res.rows);
data = filteringDataForFrontend(res.rows);
}
// console.log(`\n DEBUG2 - GetSpecificTherapistReviews ${JSON.stringify(data)} \n`)
resolve(data);
});
});
return res;
}
const filteringDataForFrontend = function(data){
// Based on length of the reviews array, each review = 1 object
const total_reviews = data.length;
// Underlying logic assumes consistent data across all entries for these values
const therapist_name = data[0].therapist_name;
const type = data[0].type;
const image = data[0].image;
const location = data[0].location;
// Summing the rating values across multiple review entries
const ratings = data.reduce((acc, obj) => ({
rating_friendliness: acc.rating_friendliness + obj.rating_friendliness,
rating_techniques: acc.rating_techniques + obj.rating_techniques,
rating_progression: acc.rating_progression + obj.rating_progression,
rating_cost: acc.rating_progression + obj.rating_progression,
rating_listening: acc.rating_listening + obj.rating_listening,
rating_overall: acc.rating_overall + obj.rating_overall
})
)
// Placeholder as string, most likely restructure to an array of objects
const comments = data.reduce ((acc, obj) => ({
feedback_comments: acc.feedback_comments + obj.feedback_comments
})
)
// Filtered data for returning
const filteredData = {
therapist_name,
type,
image,
location,
total_reviews,
rating_friendliness: ratings.rating_friendliness / total_reviews,
rating_techniques: ratings.rating_techniques / total_reviews,
rating_progression: ratings.rating_progression / total_reviews,
rating_cost: ratings.rating_cost / total_reviews,
rating_listening: ratings.rating_listening / total_reviews,
rating_overall: ratings.rating_overall / total_reviews,
feedback_comments: comments.feedback_comments
}
// console.log(`\n DEBUG 1 - filteredData -> ${JSON.stringify(filteredData)} \n`)
return filteredData;
}
module.exports = {
getFormattedReviews,
};
server.js
const express = require('express');
const DB = require('./dbQueries.js');
const app = express();
const port = process.env.SERVER_PORT || 8000;
app.get('/get-reviews', async (req, res) => {
const data = await DB.getFormattedReviews();
// data.then(data => console.log(`data2 check ${JSON.stringify(data)}`))
res.send(data);
});
Ultimately I think my limited understanding of Promises/Async/Await & Promise.all failed me a bit here, the Promise.all in the getFormattedReviews was the missing trick.
Some of the code was rewritten with some new Promise syntax which can likely be redacted however I noted when doing this with the getFormattedReviews method that it broke sending the data to the /get-reviews endpoint. Something I'll investigate later.
You are awaiting a callback. I don't think that works.
Try wrapping in a Promise. That might be the issue.
Full disclosure: I did not read your code very in depth...
const res = await new Promise(resolve => {
pool.query(getSpecificTherapistReviews, (err, res) => {
let data = []
if (err) {
console.log(err.stack);
} else {
// console.log(res.rows);
// step 3
data = filteringDataForFrontend(res.rows);
}
console.log(`\n DEBUG2 - GetSpecificTherapistReviews ${JSON.stringify(data)} \n`)
resolve(data);
});
})
I have an app the read values from external devices. After this, these values are written in the database.
I have values like acceleration, gyroscope, magnetometer and pressure.
The acceleration, gyroscope and magnetometer are read together with a time in this way:
(for example for acceleration)
const buf = Buffer.from(characteristic.value, "base64");
const [time, ...acc] = [0,2,4,6].map(index => buf.readInt16LE(index));
this.setState(state => ({
time,
acc,
array_acc_sx: [
...state.array_acc_sx,
[time , acc ]
]
}));
For the pressure I can't do the same, because the pressure doesn't have the time automatically.
So I have think set a variable timeP equal to the time of the acc,gyr,mag.
But in this way the set time starts before the reading of values of pressure, so the result is something like this:
"PL":
"[740,740,740,740,700,700,660,660,580,580,580,
560,500,500,500,500,500,440,400,400,340,340,320,300,
280,260,260,260,200,180,160,160,140,
// there start the time + pressure values.
[140,[0,0,0,0,0]],[160,[0,0,0,0,0]],[160,[0,0,0,0,0]],
[180,[0,0,0,0,0]],[200,[0,0,0,0,0]],[260,[0,0,0,0,0]],
[260,[0,0,0,0,0]],[260,[0,0,0,0,0]],[280,[0,0,0,0,0]],
[300,[0,0,0,0,0]],[320,[0,0,0,0,0]],[340,[0,0,0,0,0]],
[340,[0,0,0,0,0]],[400,[0,0,0,0,0]],[400,[0,0,0,0,0]],
[440,[0,0,0,0,0]],[500,[0,0,0,0,0]],[500,[0,0,0,0,0]],
.....
this is the code that I used:
async setupNotifications2(device) {
const service = this.serviceGeneral();
/* Accelerometro + Giroscopio + Magnetometro */
device.monitorCharacteristicForService(
service,
this.AccGyrMg,
(error, characteristic) => {
if (error) {
this.error(error.message);
return;
}
const buf = Buffer.from(characteristic.value, "base64");
const [time, ...acc] = [0,2,4,6].map(index => buf.readInt16LE(index));
this.setState(state => ({
time,
acc,
array_acc_sx: [
...state.array_acc_sx,
[time , acc ]
]
}));
//console.log("this.state.time - ", this.state.time)
this.setState({timeP: this.state.time})
const [ ...gyr] = [8,10,12].map(index => buf.readInt16LE(index));
this.setState(state => ({
time,
gyr,
array_gyr_sx: [
...state.array_gyr_sx,
[time, gyr]
]
}));
const [ ...mg] = [14,16,18].map(index => buf.readInt16LE(index));
this.setState(state => ({
time,
mg,
array_mg_sx: [
...state.array_mg_sx,
[time, mg]
]
}));
}
);
/* Pressione */
device.monitorCharacteristicForService(
service,
this.Pressure,
(error, characteristic) => {
if (error) {
this.error(error.message);
return;
}
console.log("TimeP - ", this.state.timeP)
const buf = Buffer.from(characteristic.value, "base64");
const [...pressure_sx] = [0, 2, 4, 6, 8].map(index => buf.readUInt16LE(index));
this.setState(state => ({...state.timeP, pressure_sx,array_pressure_sx: [this.state.timeP, ...state.array_pressure_sx, [this.state.timeP, pressure_sx] ]
}));
}
);
In your opinion how can I do to solve this kind of problem? thank you.
You could use a setInterval() function to record the time of each state:
var Time = 0;
function resetT()
{
Time=0;
clearInterval(stateT)
console.log("state_reset")
}
function stateInitiate(t)
{
//t will be your unit of time (1 = a millisecond, 1000 = a second)
setInterval(stateT,t)
}
function stateT()
{
Time++;
}
function init()
{
device.monitorCharacteristicForService(
service,
this.Pressure,
(error, characteristic) => {
if (error) {
this.error(error.message);
return;
}
console.log("TimeP - ", Time)//variable time
const buf = Buffer.from(characteristic.value, "base64");
const [...pressure_sx] = [0, 2, 4, 6, 8].map(index => buf.readUInt16LE(index));
this.setState(state => ({...state.timeP, pressure_sx,array_pressure_sx: [this.state.timeP, ...state.array_pressure_sx, [this.state.timeP, pressure_sx] ]
}));
}
);
stateInitiate(1)//adds 1ms every ms
}
init()//begin sequence
Let me know if this was what you were looking for... :)
I have an API call in api.js:
export const getGraphData = (domain, userId, testId) => {
return axios({
url: `${domain}/api/${c.embedConfig.apiVersion}/member/${userId}/utests/${testId}`,
method: 'get',
});
};
I have a React helper that takes that data and transforms it.
import { getGraphData } from './api';
const dataObj = (domain, userId, testId) => {
const steps = getGraphData(domain, userId, testId)
.then((result) => {
return result.attributes;
});
console.log(steps);
// const steps = test.get('steps');
const expr = /select/;
// build array of steps that we have results in
const resultsSteps = [];
steps.forEach((step) => {
// check for types that contain 'select', and add them to array
if (expr.test(step.get('type'))) {
resultsSteps.push(step);
}
});
const newResultsSteps = [];
resultsSteps.forEach((item, i) => {
const newMapStep = new Map();
const itemDescription = item.get('description');
const itemId = item.get('id');
const itemOptions = item.get('options');
const itemAnswers = item.get('userAnswers');
const newOptArray = [];
itemOptions.forEach((element) => {
const optionsMap = new Map();
let elemName = element.get('value');
if (!element.get('value')) { elemName = element.get('caption'); }
const elemPosition = element.get('position');
const elemCount = element.get('count');
optionsMap.name = elemName;
optionsMap.position = elemPosition;
optionsMap.value = elemCount;
newOptArray.push(optionsMap);
});
newMapStep.chartType = 'horizontalBar';
newMapStep.description = itemDescription;
newMapStep.featured = 'false';
newMapStep.detailUrl = '';
newMapStep.featuredStepIndex = i + 1;
newMapStep.id = itemId;
newMapStep.isValid = 'false';
newMapStep.type = 'results';
const listForNewOptArray = List(newOptArray);
newMapStep.data = listForNewOptArray;
newMapStep.userAnswers = itemAnswers;
newResultsSteps.push(newMapStep);
});
return newResultsSteps;
};
export default dataObj;
The issue is steps, when logged outside the .then() returns a Promise {<pending>}. If I log results.attributes inside the .then(), I see the data fully returned.
You need to wait until your async call is resolved. You can do this by chaining on another then:
getGraphData(domain, userId, testId)
.then((result) => {
return result.attributes;
})
.then(steps => {
// put the rest of your method here
});
You can also look at async/await if your platform supports it which would allow code closer to your original
const steps = await getGraphData(domain, userId, testId)
.then((result) => {
return result.attributes;
});
// can use steps here
You have 2 options to transform your fetched data :
1st option : create a async function that returns a promise with the modified data :
const dataObj = (domain, userId, testId) => {
return getGraphData(domain, userId, testId).then((result) => {
const steps = result.attributes;
const expr = /select/;
// build array of steps that we have results in
const resultsSteps = [];
steps.forEach((step) => {
// check for types that contain 'select', and add them to array
if (expr.test(step.get('type'))) {
resultsSteps.push(step);
}
});
const newResultsSteps = [];
resultsSteps.forEach((item, i) => {
const newMapStep = new Map();
const itemDescription = item.get('description');
const itemId = item.get('id');
const itemOptions = item.get('options');
const itemAnswers = item.get('userAnswers');
const newOptArray = [];
itemOptions.forEach((element) => {
const optionsMap = new Map();
let elemName = element.get('value');
if (!element.get('value')) {
elemName = element.get('caption');
}
const elemPosition = element.get('position');
const elemCount = element.get('count');
optionsMap.name = elemName;
optionsMap.position = elemPosition;
optionsMap.value = elemCount;
newOptArray.push(optionsMap);
});
newMapStep.chartType = 'horizontalBar';
newMapStep.description = itemDescription;
newMapStep.featured = 'false';
newMapStep.detailUrl = '';
newMapStep.featuredStepIndex = i + 1;
newMapStep.id = itemId;
newMapStep.isValid = 'false';
newMapStep.type = 'results';
const listForNewOptArray = List(newOptArray);
newMapStep.data = listForNewOptArray;
newMapStep.userAnswers = itemAnswers;
newResultsSteps.push(newMapStep);
});
return newResultsSteps;
});
};
With es7 async/await syntax it should be :
const dataObj = async (domain, userId, testId) => {
const result = await getGraphData(domain, userId, testId);
const steps = result.attributes;
... modify the data
}
Then keep in mind that this function returns a promise, you'll need to wait for it to get the result, example in a react component :
componentDidMount(){
dataObj('mydomain', 'myuserId', 'mytestId').then((res) => {
this.setState({ data: res });
}
}
The component will update when the promise is resolve, you can then use the data (you'll need to handle the undefined data state in render method)
2nd option : Create a sync function to modify the data :
const dataObj = (steps) => {
const expr = /select/;
const resultsSteps = [];
steps.forEach((step) => {
...
}
return newResultsSteps;
};
To have the same result as option 1 in our component we'll use it like this :
componentDidMount(){
getGraphData('mydomain', 'myuserId', 'mytestId').then((res) => {
const modifiedData = dataObj(res);
this.setState({ data: modifiedData });
}
}
That's how promises work. The data is not ready when you are trying to use it so you should move all your processing into the .then. The reason your variable is a Promise {<pending>} is because you can chain other things onto it.
Something like:
steps.then((steps) => {
...
});