Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ DEFAULT_TOKEN_EXPIRATION_SECS=500
DISTANCE_SCORE_THRESHOLD=0.6

# docker image name of the algorithm to use for face vectorization
FACE_VECTORIZE_ALGORITHM=cwolff/face_recognition
FACE_VECTORIZE_ALGORITHM=cwolff/faceanalysis_facerecognition

# face-api configuration, only used if FACE_VECTORIZE_ALGORITHM is set to "FaceApi"
FACE_API_GROUP_ID=
FACE_API_ACCESS_KEY=
FACE_API_REGION=
FACE_API_ENDPOINT=

# connection string for your docker daemon, on windows this should be tcp://0.0.0.0:2375
DOCKER_DAEMON=unix://var/run/docker.sock
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Tests
stage: test
script: make test
before_script: docker pull cwolff/faceanalysis_facerecognition
before_script: docker pull "$(grep '^FACE_VECTORIZE_ALGORITHM=' .env | cut -d'=' -f2-)"

stages:
- lint
Expand Down
17 changes: 12 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
SHELL = /bin/bash
build_tag := $(shell grep '^BUILD_TAG=' .env | cut -d'=' -f2-)
docker_repo := $(shell grep '^DOCKER_REPO=' .env | cut -d'=' -f2-)
prod_db := $(shell grep '^DB_DIR=' .env | cut -d'=' -f2-)
prod_data := $(shell grep '^DATA_DIR=' .env | cut -d'=' -f2-)

.PHONY: build-dev build-prod build-algorithms release-server release-algorithms pylint flake8 mypy lint test

Expand Down Expand Up @@ -40,17 +42,22 @@ mypy: build-dev
lint: pylint flake8 mypy

test: build-dev
$(eval data_dir := $(shell mktemp -d))
$(eval db_dir := $(shell mktemp -d))
$(eval test_data := $(shell mktemp -d))
$(eval test_db := $(shell mktemp -d))
$(eval queue_name := $(shell echo "faceanalysisq$$RANDOM"))
$(eval db_name := $(shell echo "faceanalysisdb$$RANDOM"))
DATA_DIR="$(data_dir)" DB_DIR="$(db_dir)" IMAGE_PROCESSOR_QUEUE="$(queue_name)" MYSQL_DATABASE="$(db_name)" \
DATA_DIR="$(test_data)" DB_DIR="$(test_db)" IMAGE_PROCESSOR_QUEUE="$(queue_name)" MYSQL_DATABASE="$(db_name)" \
docker-compose run --rm api nose2 --verbose --with-coverage; \
exit_code=$$?; \
docker-compose down; \
rm -rf $(data_dir); \
rm -rf $(db_dir); \
rm -rf $(test_data); \
rm -rf $(test_db); \
exit $$exit_code

createdb: build-prod
mkdir -p $(prod_db)
docker-compose run --rm api python3 /app/main.py createdb

server: build-prod
mkdir -p $(prod_data)
docker-compose up
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This repository contains an API for face detection and matching. The algorithm b
- [Azure FaceAPI](https://azure.microsoft.com/en-us/services/cognitive-services/face/)
- [face_recognition](https://github.com/ageitgey/face_recognition)
- [FaceNet](https://github.com/davidsandberg/facenet)
- [InsightFace (coming soon)](https://github.com/deepinsight/insightface)
- [InsightFace](https://github.com/deepinsight/insightface)

More information on customizing and implementing new face detection algorithms can be found [here](./algorithms/README.md).

Expand All @@ -21,7 +21,8 @@ More information on customizing and implementing new face detection algorithms c
2. Clone this repo.
3. Review the default configuration values in `.env`.
4. To run tests type `make test` from within the top level directory.
5. To run in production type `make server` from within the top level directory.
5. To initialize the database, type `make createdb`. This only needs to be executed once.
6. To start the server, run `make server`.

## API definition

Expand Down
1 change: 1 addition & 0 deletions algorithms/FaceApi/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
bin/
obj/
Properties/
*.csproj.user

63 changes: 48 additions & 15 deletions algorithms/FaceApi/Cli.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,18 @@ static void Main(string[] args)
static async Task MainAsync(string[] args)
{
var settings = new Settings(args);
if (!settings.TryParse(out string apiKey, out string apiEndpoint, out double matchThreshold, out PredictionMode predictionMode))
if (!settings.TryParse(out IFaceIdentifier faceIdentifier, out double matchThreshold))
{
await Console.Error.WriteLineAsync("Missing api-key and api-endpoint settings");
return;
}

var faceIdentifier = new FaceIdentifier(apiKey, apiEndpoint, predictionMode);

if (settings.TryParseForTraining(out string trainSetRoot))
{
var trainedGroupId = await faceIdentifier.Train(trainSetRoot);
await Console.Out.WriteLineAsync(trainedGroupId ?? "Training failed");
}
else if (settings.TryParseForEvaluation(out string evaluationGroupId, out string pairsTxtPath, out string imagesRoot))
else if (settings.TryParseForEvaluation(out string evaluationGroupId, out string pairsTxtPath, out string imagesRoot, out bool ignoreMissingEmbeddings))
{
var pairs = new Pairs(pairsTxtPath, imagesRoot).Parse();
var stats = new PairStats();
Expand All @@ -37,7 +35,14 @@ await Task.WhenAll(pairs.Select(async pair =>
try
{
var areSame = await faceIdentifier.Predict(evaluationGroupId, matchThreshold, pair.ImagePath1, pair.ImagePath2);
stats.Record(areSame, pair.AreSame);
if (areSame.HasValue)
{
stats.Record(areSame.Value, pair.AreSame);
}
else if (!ignoreMissingEmbeddings)
{
stats.Record(!pair.AreSame, pair.AreSame);
}
}
catch (Exception ex)
{
Expand All @@ -52,7 +57,7 @@ await Task.WhenAll(pairs.Select(async pair =>
else if (settings.TryParseForPrediction(out string predictionGroupId, out string imagePath1, out string imagePath2))
{
var areSame = await faceIdentifier.Predict(predictionGroupId, matchThreshold, imagePath1, imagePath2);
await Console.Out.WriteLineAsync(areSame.ToString());
await Console.Out.WriteLineAsync(areSame.HasValue ? areSame.ToString() : "Failed to find faces");
}
else
{
Expand All @@ -73,21 +78,17 @@ public Settings(string[] args)
Args = args;
}

public bool TryParse(out string apiKey, out string apiEndpoint, out double matchThreshold, out PredictionMode predictionMode)
public bool TryParse(out IFaceIdentifier faceIdentifier, out double matchThreshold)
{
if (ApiKey == null || ApiEndpoint == null)
if (ApiKey == null || ApiEndpoint == null || FaceIdentifier == null)
{
apiKey = null;
apiEndpoint = null;
faceIdentifier = null;
matchThreshold = DefaultMatchThreshold;
predictionMode = DefaultPredictionMode;
return false;
}

apiKey = ApiKey;
apiEndpoint = ApiEndpoint;
faceIdentifier = FaceIdentifier;
matchThreshold = MatchThreshold;
predictionMode = PredictionMode;
return true;
}

Expand All @@ -103,19 +104,21 @@ public bool TryParseForTraining(out string trainSetRoot)
return true;
}

public bool TryParseForEvaluation(out string groupId, out string pairsTxtPath, out string trainSetRoot)
public bool TryParseForEvaluation(out string groupId, out string pairsTxtPath, out string trainSetRoot, out bool ignoreMissingEmbeddings)
{
if (!Evaluation || GroupId == null || Args.Length != 2 || !File.Exists(Args[0]) || !Directory.Exists(Args[1]))
{
groupId = null;
pairsTxtPath = null;
trainSetRoot = null;
ignoreMissingEmbeddings = IgnoreMissingEmbeddings;
return false;
}

groupId = GroupId;
pairsTxtPath = Args[0];
trainSetRoot = Args[1];
ignoreMissingEmbeddings = IgnoreMissingEmbeddings;
return true;
}

Expand All @@ -135,11 +138,34 @@ public bool TryParseForPrediction(out string groupId, out string imagePath1, out
return true;
}

private IFaceIdentifier FaceIdentifier
{
get
{
switch (PredictionMode)
{
case PredictionMode.FindSimilar:
return new FindSimilarFaceIdentifier(ApiKey, ApiEndpoint);
case PredictionMode.Identify:
return new IdentifyFaceIdentifier(ApiKey, ApiEndpoint);
case PredictionMode.Verify:
return new VerifyFaceIdentifier(ApiKey, ApiEndpoint);
default:
return null;
}
}
}

private bool Evaluation
{
get => Environment.GetEnvironmentVariable("FACE_API_EVALUATE") == "true";
}

private bool IgnoreMissingEmbeddings
{
get => Environment.GetEnvironmentVariable("FACE_API_IGNORE_MISSING_EMBEDDINGS") == "true";
}

private string ApiKey
{
get => Environment.GetEnvironmentVariable("FACE_API_KEY");
Expand Down Expand Up @@ -200,4 +226,11 @@ private string GroupId
get => Environment.GetEnvironmentVariable("FACE_API_GROUP_ID");
}
}

enum PredictionMode
{
FindSimilar,
Identify,
Verify
}
}
Loading