diff --git a/README.md b/README.md index a121d8c..2a0396a 100644 --- a/README.md +++ b/README.md @@ -2,47 +2,64 @@ CompreFace Python SDK makes face recognition into your application even easier. -## Table of content +# Table of content - [Requirements](#requirements) - [Installation](#installation) - [Usage](#usage) - - [Initialization](#initialization) - - [Example Add an Example of a Subject](#example-add-an-example-of-a-subject) - - [Example Recognize Faces from a Given Image](#example-recognize-faces-from-a-given-image) - - [Example List of All Saved Subjects](#example-list-of-all-saved-subjects) - - [Example Delete All Examples of the Subject by Name](#example-delete-all-examples-of-the-subject-by-name) - - [Example Delete an Example of the Subject by ID](#example-delete-an-example-of-the-subject-by-id) - - [Example Compare Faces from a Given Image](#example-compare-faces-from-a-given-image) - - [Example Detect Faces from a Given Image](#example-detect-faces-from-a-given-image) - - [Example Verify Face from a Given Images](#example-verify-face-from-a-given-images) + - [Initialization](#initialization) + - [Adding faces into a face collection](#adding-faces-into-a-face-collection) + - [Recognition](#recognition) + - [Webcam demo](#webcam-demo) - [Reference](#reference) - - [CompreFace Global Object](#compreFace-global-object) - - [Options structure](#options-structure) - - [Face Recognition Service](#face-recognition-service) + - [CompreFace Global Object](#compreFace-global-object) + - [Methods](#methods) + - [Options structure](#options-structure) + - [Face Recognition Service](#face-recognition-service) + - [Recognize Faces from a Given Image](#recognize-faces-from-a-given-image) + - [Get Face Collection](#get-face-collection) - [Add an Example of a Subject](#add-an-example-of-a-subject) - - [Recognize Faces from a Given Image](#recognize-faces-from-a-given-image) - - [List of All Saved Subjects](#list-of-all-saved-subjects) + - [List of All Saved Examples of the Subject](#list-of-all-saved-examples-of-the-subject) - [Delete All Examples of the Subject by Name](#delete-all-examples-of-the-subject-by-name) - [Delete an Example of the Subject by ID](#delete-an-example-of-the-subject-by-id) - [Verify Faces from a Given Image](#verify-faces-from-a-given-image) + - [Get Subjects](#get-subjects) + - [Add a Subject](#add-a-subject) + - [List Subjects](#list-subjects) + - [Rename a Subject](#rename-a-subject) + - [Delete a Subject](#delete-a-subject) + - [Delete All Subjects](#delete-all-subjects) - [Face Detection Service](#face-detection-service) + - [Detect](#detect) - [Face Verification Service](#face-verification-service) + - [Verify](#verify) +- [Contributing](#contributing) + - [Report Bugs](#report-bugs) + - [Submit Feedback](#submit-feedback) +- [License info](#license-info) -## Requirements +# Requirements Before using our SDK make sure you have installed CompreFace and Python on your machine. 1. [CompreFace](https://github.com/exadel-inc/CompreFace#getting-started-with-compreface) 2. [Python](https://www.python.org/downloads/) (Version 3.7+) -### CompreFace compatibility matrix +## CompreFace compatibility matrix -| CompreFace Python SDK version | CompreFace 0.5.x | -| ------------------------------| ---------------- | -| 0.1.0 | ✔ | +| CompreFace Python SDK version | CompreFace 0.5.x | CompreFace 0.6.x | +| ------------------------------| ---------------- | ---------------- | +| 0.1.0 | ✔ | :yellow_circle: | +| 0.6.x | :yellow_circle: | ✔ | +Explanation: -## Installation +* ✔ SDK supports all functionality from CompreFace. +* :yellow_circle: SDK works with this CompreFace version. +In case if CompreFace version is newer - SDK won't support new features of CompreFace. In case if CompreFace version is older - new SDK features will fail. +* ✘ There are major backward compatibility issues. It is not recommended to use these versions together + + +# Installation It can be installed through pip: @@ -50,11 +67,11 @@ It can be installed through pip: pip install compreface-sdk ``` -## Usage +# Usage All these examples you can find in repository inside [examples](/examples) folder. -### Initialization +## Initialization To start using Python SDK you need to import `CompreFace` object from 'compreface-sdk' dependency. @@ -69,6 +86,7 @@ However, before recognizing you need first to add faces into the face collection from compreface import CompreFace from compreface.service import RecognitionService from compreface.collections import FaceCollection +from compreface.collections.face_collections import Subjects DOMAIN: str = 'http://localhost' PORT: str = '8000' @@ -79,9 +97,11 @@ compre_face: CompreFace = CompreFace(DOMAIN, PORT) recognition: RecognitionService = compre_face.init_face_recognition(API_KEY) face_collection: FaceCollection = recognition.get_face_collection() + +subjects: Subjects = recognition.get_subjects() ``` -### Example. Add an Example of a Subject +## Adding faces into a face collection Here is example that shows how to add an image to your face collection from your file system: @@ -92,109 +112,25 @@ subject: str = 'Jonathan Petit' face_collection.add(image_path=image_path, subject=subject) ``` -### Example. Recognize Faces from a Given Image +## Recognition This code snippet shows how to recognize unknown face. ```python -image_path: "str or bytes" - -recognition.recognize(image_path=image_path) -``` - -### Example. List of All Saved Subjects - -This code shows how to retrieve a list of subject examples saved in a Face Collection: - -```python -face_collection.list() -``` - -### Example. Delete All Examples of the Subject by Name - -This code shows how to delete all image examples of the subject: - -```python -subject: str = 'Jonathan Petit' - -print(face_collection.delete_all(subject)) - -``` - -### Example. Delete an Example of the Subject by ID - -This example to delete an image by ID: - -```python -faces: list = face_collection.list().get('faces') - -if(len(faces) != 0): - last_face: dict = faces[len(faces) - 1] - print(face_collection.delete(last_face.get('image_id'))) -else: - print('No subject found') - -``` - -### Example. Compare Faces from a Given Image - -This example shows how to compare unknown face with existing face in face collection: - -```python -image_path: "str or bytes" - -face: dict = next(item for item in face_collection.list().get('faces') if item['subject'] == - 'Jonathan Petit') - -image_id = face.get('image_id') - -face_collection.verify(image_path=image_path, image_id=image_id) -``` - -### Example. Detect Faces from a Given Image - -Here is example to detect faces from a given image. - -```python -from compreface import CompreFace -from compreface.service import DetectionService - -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -DETECTION_API_KEY: str = 'your_face_detection_key' - -compre_face: CompreFace = CompreFace(DOMAIN, PORT) - -detection: DetectionService = compre_face.init_face_detection(DETECTION_API_KEY) - image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' -detection.detect(image_path=image_path) +recognition.recognize(image_path=image_path) ``` -### Example. Verify Face from a Given Images -Here is example to verify face from a given images. +## Webcam demo +Webcam demo shows how to use CompreFace Recognition and Detection services using Python SDK. +In both cases, age, gender and mask plugins are applied. -```python -from compreface import CompreFace -from compreface.service import VerificationService +Follow this [link](/webcam_demo) to see the instructions. -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -VERIFICATION_API_KEY: str = 'your_face_verification_key' +# Reference -compre_face: CompreFace = CompreFace(DOMAIN, PORT) - -verify: VerificationService = compre_face.init_face_verification(VERIFICATION_API_KEY) - -image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' - -verify.verify(source_image_path=image_path, target_image_path=image_path) -``` - -## Reference - -### CompreFace Global Object +## CompreFace Global Object Global CompreFace Object is used for initializing connection to CompreFace and setting default values for options. Default values will be used in every service method if applicable. @@ -237,7 +173,7 @@ compre_face: CompreFace = CompreFace(domain=DOMAIN, port=PORT, options={ }) ``` -**Methods:** +### Methods 1. ```CompreFace.init_face_recognition(api_key)``` @@ -293,7 +229,7 @@ VERIFICATION_API_KEY: str = 'your_face_verification_key' verify: VerificationService = compre_face.init_face_verification(VERIFICATION_API_KEY) ``` -### Options structure +## Options structure Options is optional field in every request that contains an image. If the option’s value is set in the global object and passed as a function argument then the function argument value will be used. @@ -334,7 +270,7 @@ recognition.recognize(image_path=image_path, options={ }) ``` -### Face Recognition Service +## Face Recognition Service Face recognition service is used for face identification. This means that you first need to upload known faces to face collection and then recognize unknown faces among them. @@ -342,40 +278,15 @@ When you upload an unknown face, the service returns the most similar faces to i Also, face recognition service supports verify endpoint to check if this person from face collection is the correct one. For more information, see [CompreFace page](https://github.com/exadel-inc/CompreFace). -#### Add an Example of a Subject - -This creates an example of the subject by saving images. You can add as many images as you want to train the system. - -```python -FaceCollection.add(image_path, subject, options) -``` - -| Argument | Type | Required | Notes | -| ------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------- | -| image_path | image | required | Image can pass from url, local path or bytes. Max size is 5Mb | -| subject | string | required | is the name you assign to the image you save | -| options | object | optional | `DetProbOptionsDict` object can be used in this method. See more [here](#options-structure). | - -Response body on success: - -```json -{ - "image_id": "6b135f5b-a365-4522-b1f1-4c9ac2dd0728", - "subject": "subject1" -} -``` - -| Element | Type | Description | -| -------- | ------ | -------------------------- | -| image_id | UUID | UUID of uploaded image | -| subject | string | Subject of the saved image | - ### Recognize Faces from a Given Image -Recognizes faces from the uploaded image. +*[Example](examples/recognize_face_from_image.py)* + +Recognizes all faces from the image. +The first argument is the image location, it can be an url, local path or bytes. ```python -RecognitionService.recognize(image_path, options) +recognition.recognize(image_path, options) ``` | Argument | Type | Required | Notes | @@ -383,56 +294,60 @@ RecognitionService.recognize(image_path, options) | image_path | image | required | Image can pass from url, local path or bytes. Max size is 5Mb | | options | object | optional | `AllOptionsDict` object can be used in this method. See more [here](#options-structure). | -Response body on success: +Response: ```json { - "result": [ - { - "age": [25, 32], - "gender": "female", - "embedding": [9.424854069948196e-4, "...", -0.011415496468544006], - "box": { - "probability": 1.0, - "x_max": 1420, - "y_max": 1368, - "x_min": 548, - "y_min": 295 - }, - "landmarks": [ - [814, 713], - [1104, 829], - [832, 937], - [704, 1030], - [1017, 1133] - ], - "subjects": [ - { - "similarity": 0.97858, - "subject": "subject1" - } - ], - "execution_time": { - "age": 28.0, - "gender": 26.0, - "detector": 117.0, - "calculator": 45.0 - } + "result" : [ { + "age" : { + "probability": 0.9308982491493225, + "high": 32, + "low": 25 + }, + "gender" : { + "probability": 0.9898611307144165, + "value": "female" + }, + "mask" : { + "probability": 0.9999470710754395, + "value": "without_mask" + }, + "embedding" : [ 9.424854069948196E-4, "...", -0.011415496468544006 ], + "box" : { + "probability" : 1.0, + "x_max" : 1420, + "y_max" : 1368, + "x_min" : 548, + "y_min" : 295 + }, + "landmarks" : [ [ 814, 713 ], [ 1104, 829 ], [ 832, 937 ], [ 704, 1030 ], [ 1017, 1133 ] ], + "subjects" : [ { + "similarity" : 0.97858, + "subject" : "subject1" + } ], + "execution_time" : { + "age" : 28.0, + "gender" : 26.0, + "detector" : 117.0, + "calculator" : 45.0, + "mask": 36.0 } - ], - "plugins_versions": { - "age": "agegender.AgeDetector", - "gender": "agegender.GenderDetector", - "detector": "facenet.FaceDetector", - "calculator": "facenet.Calculator" + } ], + "plugins_versions" : { + "age" : "agegender.AgeDetector", + "gender" : "agegender.GenderDetector", + "detector" : "facenet.FaceDetector", + "calculator" : "facenet.Calculator", + "mask": "facemask.MaskDetector" } } ``` | Element | Type | Description | | -------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| age | array | detected age range. Return only if [age plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | -| gender | string | detected gender. Return only if [gender plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | +| age | object | detected age range. Return only if [age plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | +| gender | object | detected gender. Return only if [gender plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | +| mask | object | detected mask. Return only if [face mask plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled. | | embedding | array | face embeddings. Return only if [calculator plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | | box | object | list of parameters of the bounding box for this face | | probability | float | probability that a found face is actually a face | @@ -444,16 +359,62 @@ Response body on success: | execution_time | object | execution time of all plugins | | plugins_versions | object | contains information about plugin versions | +### Get Face Collection + +```python +recognition.get_face_collection() +``` + +Returns Face collection object + +Face collection could be used to manage known faces, e.g. add, list, or delete them. + +Face recognition is performed for the saved known faces in face collection, so before using the `recognize` method you need to save at least one face into the face collection. + +More information about face collection and managing examples [here](https://github.com/exadel-inc/CompreFace/blob/master/docs/Rest-API-description.md#managing-subject-examples) + +**Methods:** + +#### Add an Example of a Subject + +*[Example](examples/add_example_of_a_subject.py)* + +This creates an example of the subject by saving images. You can add as many images as you want to train the system. Image should +contain only one face. + +```python +face_collection.add(image_path, subject, options) +``` + +| Argument | Type | Required | Notes | +| ------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------- | +| image_path | image | required | Image can pass from url, local path or bytes. Max size is 5Mb | +| subject | string | required | is the name you assign to the image you save | +| options | object | optional | `DetProbOptionsDict` object can be used in this method. See more [here](#options-structure). | + +Response: + +```json +{ + "image_id": "6b135f5b-a365-4522-b1f1-4c9ac2dd0728", + "subject": "subject1" +} +``` + +| Element | Type | Description | +| -------- | ------ | -------------------------- | +| image_id | UUID | UUID of uploaded image | +| subject | string | Subject of the saved image | -### List of All Saved Subjects +#### List of All Saved Examples of the Subject To retrieve a list of subjects saved in a Face Collection: ```python -FaceCollection.list() +face_collection.list() ``` -Response body on success: +Response: ``` { @@ -472,47 +433,46 @@ Response body on success: | image_id | UUID | UUID of the face | | subject | string | of the person, whose picture was saved for this api key | -### Delete All Examples of the Subject by Name +#### Delete All Examples of the Subject by Name + +*[Example](examples/delete_all_examples_of_subject.py)* To delete all image examples of the : ```python -FaceCollection.delete_all(subject) +face_collection.delete_all(subject) ``` | Argument | Type | Required | Notes | | --------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | subject | string | optional | is the name you assign to the image you save. If this parameter is absent, all faces in Face Collection will be removed | -Response body on success: +Response: ``` -[ - { - "image_id": , - "subject": - }, - ... -] +{ + "deleted": +} ``` -| Element | Type | Description | -| -------- | ------ | ----------------------------------------------------------------- | -| image_id | UUID | UUID of the removed face | -| subject | string | of the person, whose picture was saved for this api key | +| Element | Type | Description | +| -------- | ------- | ------------------------ | +| deleted | integer | Number of deleted faces | -### Delete an Example of the Subject by ID +#### Delete an Example of the Subject by ID + +*[Example](examples/delete_example_by_id.py)* To delete an image by ID: ```python -FaceCollection.delete(image_id) +face_collection.delete(image_id) ``` | Argument | Type | Required | Notes | | --------- | ------ | -------- | ------------------------------------------------------------ | image_id | UUID | required | UUID of the removing face | -Response body on success: +Response: ``` { @@ -526,10 +486,12 @@ Response body on success: | image_id | UUID | UUID of the removed face | | subject | string | of the person, whose picture was saved for this api key | -### Verify Faces from a Given Image +#### Verify Faces from a Given Image + +*[Example](examples/verification_face_from_image.py)* ```python -FaceCollection.verify(image_path, image_id, options) +face_collection.verify(image_path, image_id, options) ``` Compares similarities of given image with image from your face collection. @@ -541,44 +503,218 @@ Compares similarities of given image with image from your face collection. | image_id | UUID | required | UUID of the verifying face | | options | string | Object | `ExpandedOptionsDict` object can be used in this method. See more [here](#options-structure). | -Response body on success: +Response: -``` +```json { - "result": [ - { - "box": { - "probability": , - "x_max": , - "y_max": , - "x_min": , - "y_min": - }, - "similarity": + "result" : [ { + "age" : { + "probability": 0.9308982491493225, + "high": 32, + "low": 25 }, - ... + "gender" : { + "probability": 0.9898611307144165, + "value": "female" + }, + "mask" : { + "probability": 0.9999470710754395, + "value": "without_mask" + }, + "embedding" : [ 9.424854069948196E-4, "...", -0.011415496468544006 ], + "box" : { + "probability" : 1.0, + "x_max" : 1420, + "y_max" : 1368, + "x_min" : 548, + "y_min" : 295 + }, + "landmarks" : [ [ 814, 713 ], [ 1104, 829 ], [ 832, 937 ], [ 704, 1030 ], [ 1017, 1133 ] ], + "subjects" : [ { + "similarity" : 0.97858, + "subject" : "subject1" + } ], + "execution_time" : { + "age" : 28.0, + "gender" : 26.0, + "detector" : 117.0, + "calculator" : 45.0, + "mask": 36.0 + } + } ], + "plugins_versions" : { + "age" : "agegender.AgeDetector", + "gender" : "agegender.GenderDetector", + "detector" : "facenet.FaceDetector", + "calculator" : "facenet.Calculator", + "mask": "facemask.MaskDetector" + } +} +``` + +| Element | Type | Description | +| ------------------------------ | ------- | ------------------------------------------------------------ | +| age | object | detected age range. Return only if [age plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| gender | object | detected gender. Return only if [gender plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| mask | object | detected mask. Return only if [face mask plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled. | +| embedding | array | face embeddings. Return only if [calculator plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| box | object | list of parameters of the bounding box for this face | +| probability | float | probability that a found face is actually a face | +| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face | +| landmarks | array | list of the coordinates of the frame containing the face-landmarks. Return only if [landmarks plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| similarity | float | similarity that on that image predicted person | +| execution_time | object | execution time of all plugins | +| plugins_versions | object | contains information about plugin versions | + +### Get Subjects + +```python +recognition.get_subjects() +``` + +Returns subjects object + +Subjects object allows working with subjects directly (not via subject examples). + +More information about subjects [here](https://github.com/exadel-inc/CompreFace/blob/master/docs/Rest-API-description.md#managing-subjects) + +**Methods:** + +#### Add a Subject + +*[Example](examples/add_subject.py)* + +Create a new subject in Face Collection. +```python +subjects.add(subject) +``` + +| Argument | Type | Required | Notes | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------| +| subject | string | required | is the name of the subject. It can be any string | + +Response: + +```json +{ + "subject": "subject1" +} +``` + +| Element | Type | Description | +| -------- | ------ | -------------------------- | +| subject | string | is the name of the subject | + +#### List Subjects + +*[Example](examples/get_list_of_all_subjects.py)* + +Returns all subject related to Face Collection. +```python +subjects.list() +``` + +Response: + +```json +{ + "subjects": [ + "", + "" ] } ``` -| Element | Type | Description | -| -------------------------- | ------- | ---------------------------------------------------- | -| box | object | list of parameters of the bounding box for this face | -| probability | float | probability that a found face is actually a face | -| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face | -| similarity | float | similarity that on that image predicted person | -| subject | string | name of the subject in Face Collection | +| Element | Type | Description | +| -------- | ------ | -------------------------- | +| subjects | array | the list of subjects in Face Collection | + +#### Rename a Subject -### Face Detection Service +*[Example](examples/update_existing_subject.py)* + +Rename existing subject. If a new subject name already exists, subjects are merged - all faces from the old subject name are reassigned to the subject with the new name, old subject removed. + +```python +subjects.rename(subject, new_name) +``` + +| Argument | Type | Required | Notes | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------| +| subject | string | required | is the name of the subject that will be updated | +| new_name | string | required | is the name of the subject. It can be any string | + +Response: + +```json +{ + "updated": "true|false" +} +``` + +| Element | Type | Description | +| -------- | ------ | -------------------------- | +| updated | boolean | failed or success | + +#### Delete a Subject + +*[Example](examples/delete_subject_by_name.py)* + +Delete existing subject and all saved faces. +```python +subjects.delete(subject) +``` + +| Argument | Type | Required | Notes | +| ------------------ | ------ | -------- | ------------------------------------------------------------------------| +| subject | string | required | is the name of the subject. | + +Response: + +```json +{ + "subject": "subject1" +} +``` + +| Element | Type | Description | +| -------- | ------ | -------------------------- | +| subject | string | is the name of the subject | + +#### Delete All Subjects + +*[Example](examples/delete_all_subjects.py)* + +Delete all existing subjects and all saved faces. +```python +subjects.delete_all() +``` + +Response: + +```json +{ + "deleted": "" +} +``` + +| Element | Type | Description | +| -------- | ------ | -------------------------- | +| deleted | integer | number of deleted subjects | + + +## Face Detection Service Face detection service is used for detecting faces in the image. **Methods:** -#### Detect +### Detect + +*[Example](examples/detect_face_from_image.py)* ```python -DetectionService.detect(image_path, options) +detection.detect(image_path, options) ``` Finds all faces on the image. @@ -588,55 +724,78 @@ Finds all faces on the image. | image_path | image | required | image where to detect faces. Image can pass from url, local path or bytes. Max size is 5Mb | | options | string | Object | `ExpandedOptionsDict` object can be used in this method. See more [here](#options-structure). | -Response body on success: +Response: ```json { - "result": [ - { - "age": [25, 32], - "gender": "female", - "embedding": [-0.03027934394776821, "...", -0.05117142200469971], - "box": { - "probability": 0.9987509250640869, - "x_max": 376, - "y_max": 479, - "x_min": 68, - "y_min": 77 - }, - "landmarks": [ - [156, 245], - [277, 253], - [202, 311], - [148, 358], - [274, 365] - ], - "execution_time": { - "age": 30.0, - "gender": 26.0, - "detector": 130.0, - "calculator": 49.0 - } + "result" : [ { + "age" : { + "probability": 0.9308982491493225, + "high": 32, + "low": 25 + }, + "gender" : { + "probability": 0.9898611307144165, + "value": "female" + }, + "mask" : { + "probability": 0.9999470710754395, + "value": "without_mask" + }, + "embedding" : [ -0.03027934394776821, "...", -0.05117142200469971 ], + "box" : { + "probability" : 0.9987509250640869, + "x_max" : 376, + "y_max" : 479, + "x_min" : 68, + "y_min" : 77 + }, + "landmarks" : [ [ 156, 245 ], [ 277, 253 ], [ 202, 311 ], [ 148, 358 ], [ 274, 365 ] ], + "execution_time" : { + "age" : 30.0, + "gender" : 26.0, + "detector" : 130.0, + "calculator" : 49.0, + "mask": 36.0 } - ], - "plugins_versions": { - "age": "agegender.AgeDetector", - "gender": "agegender.GenderDetector", - "detector": "facenet.FaceDetector", - "calculator": "facenet.Calculator" + } ], + "plugins_versions" : { + "age" : "agegender.AgeDetector", + "gender" : "agegender.GenderDetector", + "detector" : "facenet.FaceDetector", + "calculator" : "facenet.Calculator", + "mask": "facemask.MaskDetector" } } ``` +| Element | Type | Description | +| ------------------------------ | ------- | ------------------------------------------------------------ | +| age | object | detected age range. Return only if [age plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| gender | object | detected gender. Return only if [gender plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| mask | object | detected mask. Return only if [face mask plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled. | +| embedding | array | face embeddings. Return only if [calculator plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| box | object | list of parameters of the bounding box for this face (on processedImage) | +| probability | float | probability that a found face is actually a face (on processedImage) | +| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face (on processedImage) | +| landmarks | array | list of the coordinates of the frame containing the face-landmarks. Return only if [landmarks plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| execution_time | object | execution time of all plugins | +| plugins_versions | object | contains information about plugin versions | + + ## Face Verification Service +*[Example](examples/verify_face_from_image.py)* + Face verification service is used for comparing two images. A source image should contain only one face which will be compared to all faces on the target image. **Methods:** +### Verify + ```python -VerificationService.verify(source_image_path, target_image_path, options) +verify.verify(source_image_path, target_image_path, options) ``` Compares two images provided in arguments. Source image should contain only one face, it will be compared to all faces in the target image. @@ -648,68 +807,101 @@ Compares two images provided in arguments. Source image should contain only one | target_image_path | image | required | reference file to check the source file. Image can pass from url, local path or bytes. Max size is 5Mb | | options | string | Object | `ExpandedOptionsDict` object can be used in this method. See more [here](#options-structure). | -Response body on success: +Response: ```json { - "source_image_face" : { - "age" : [ 25, 32 ], - "gender" : "female", - "embedding" : [ -0.0010271212086081505, "...", -0.008746841922402382 ], - "box" : { - "probability" : 0.9997453093528748, - "x_max" : 205, - "y_max" : 167, - "x_min" : 48, - "y_min" : 0 - }, - "landmarks" : [ [ 92, 44 ], [ 130, 68 ], [ 71, 76 ], [ 60, 104 ], [ 95, 125 ] ], - "execution_time" : {}, - "face_matches": [ - { - "age" : [ 25, 32 ], - "gender" : "female", - "embedding" : [ -0.049007344990968704, "...", -0.01753818802535534 ], + "result" : [{ + "source_image_face" : { + "age" : { + "probability": 0.9308982491493225, + "high": 32, + "low": 25 + }, + "gender" : { + "probability": 0.9898611307144165, + "value": "female" + }, + "mask" : { + "probability": 0.9999470710754395, + "value": "without_mask" + }, + "embedding" : [ -0.0010271212086081505, "...", -0.008746841922402382 ], "box" : { - "probability" : 0.99975, - "x_max" : 308, - "y_max" : 180, - "x_min" : 235, - "y_min" : 98 + "probability" : 0.9997453093528748, + "x_max" : 205, + "y_max" : 167, + "x_min" : 48, + "y_min" : 0 }, - "landmarks" : [ [ 260, 129 ], [ 273, 127 ], [ 258, 136 ], [ 257, 150 ], [ 269, 148 ] ], - "similarity" : 0.97858, + "landmarks" : [ [ 92, 44 ], [ 130, 68 ], [ 71, 76 ], [ 60, 104 ], [ 95, 125 ] ], "execution_time" : { - "age" : 59.0, - "gender" : 30.0, - "detector" : 177.0, - "calculator" : 70.0 + "age" : 85.0, + "gender" : 51.0, + "detector" : 67.0, + "calculator" : 116.0, + "mask": 36.0 } - }], - "plugins_versions" : { - "age" : "agegender.AgeDetector", - "gender" : "agegender.GenderDetector", - "detector" : "facenet.FaceDetector", - "calculator" : "facenet.Calculator" - } - } + }, + "face_matches": [ + { + "age" : { + "probability": 0.9308982491493225, + "high": 32, + "low": 25 + }, + "gender" : { + "probability": 0.9898611307144165, + "value": "female" + }, + "mask" : { + "probability": 0.9999470710754395, + "value": "without_mask" + }, + "embedding" : [ -0.049007344990968704, "...", -0.01753818802535534 ], + "box" : { + "probability" : 0.99975, + "x_max" : 308, + "y_max" : 180, + "x_min" : 235, + "y_min" : 98 + }, + "landmarks" : [ [ 260, 129 ], [ 273, 127 ], [ 258, 136 ], [ 257, 150 ], [ 269, 148 ] ], + "similarity" : 0.97858, + "execution_time" : { + "age" : 59.0, + "gender" : 30.0, + "detector" : 177.0, + "calculator" : 70.0, + "mask": 36.0 + } + }], + "plugins_versions" : { + "age" : "agegender.AgeDetector", + "gender" : "agegender.GenderDetector", + "detector" : "facenet.FaceDetector", + "calculator" : "facenet.Calculator", + "mask": "facemask.MaskDetector" + } + }] } ``` -| Element | Type | Description | -| -------------------------- | ------- | ---------------------------------------------------------------------------------- | -| source_image_face | object | additional info about source image face | -| face_matches | array | result of face verification | -| age | array | detected age range. | -| gender | string | detected gender. Return only if | -| embedding | array | face embeddings. Return only if | -| box | object | list of parameters of the bounding box for this face | -| probability | float | probability that a found face is actually a face | -| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face | -| landmarks | array | list of the coordinates of the frame containing the face-landmarks. Return only if | -| similarity | float | similarity between this face and the face on the source image | -| execution_time | object | execution time of all plugins | -| plugins_versions | object | contains information about plugin versions | +| Element | Type | Description | +| ------------------------------ | ------- | ------------------------------------------------------------ | +| source_image_face | object | additional info about source image face | +| face_matches | array | result of face verification | +| age | object | detected age range. Return only if [age plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| gender | object | detected gender. Return only if [gender plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| mask | object | detected mask. Return only if [face mask plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled. | +| embedding | array | face embeddings. Return only if [calculator plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| box | object | list of parameters of the bounding box for this face | +| probability | float | probability that a found face is actually a face | +| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face | +| landmarks | array | list of the coordinates of the frame containing the face-landmarks. Return only if [landmarks plugin](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md#face-plugins) is enabled | +| similarity | float | similarity between this face and the face on the source image | +| execution_time | object | execution time of all plugins | +| plugins_versions | object | contains information about plugin versions | # Contributing @@ -723,7 +915,7 @@ Contributions are what make the open source community such an amazing place to b After creating your first contributing pull request, you will receive a request to sign our Contributor License Agreement by commenting your pull request with a special message. -### Report Bugs +## Report Bugs Please report any bugs [here](https://github.com/exadel-inc/compreface-python-sdk/issues). @@ -733,7 +925,7 @@ If you are reporting a bug, please specify: - Any details about your local setup that might be helpful in troubleshooting - Detailed steps to reproduce the bug -### Submit Feedback +## Submit Feedback The best way to send us feedback is to file an issue at https://github.com/exadel-inc/compreface-python-sdk/issues. diff --git a/compreface/client/__init__.py b/compreface/client/__init__.py index a60963c..a99b78b 100644 --- a/compreface/client/__init__.py +++ b/compreface/client/__init__.py @@ -20,3 +20,4 @@ from .recognize_face_from_image import RecognizeFaceFromImageClient from .detect_face_from_image import DetectFaceFromImageClient from .verify_face_from_image import VerifyFaceFromImageClient +from .subject_client import SubjectClient diff --git a/compreface/client/subject_client.py b/compreface/client/subject_client.py new file mode 100644 index 0000000..172367b --- /dev/null +++ b/compreface/client/subject_client.py @@ -0,0 +1,81 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ +import json + +import requests + +from compreface.config.api_list import SUBJECTS_CRUD_API +from ..common import ClientRequest + + +class SubjectClient(ClientRequest): + + def __init__(self, api_key: str, domain: str, port: str): + super().__init__() + self.client_url: str = SUBJECTS_CRUD_API + self.api_key: str = api_key + self.url: str = domain + ':' + port + self.client_url + self.headers = {'Content-Type': 'application/json', 'x-api-key': api_key} + + """ + GET request for get all subjects. + + :return: json with subjects from server. + """ + + def get(self) -> dict: + url: str = self.url + result = requests.get(url, headers=self.headers) + return result.json() + + """ + POST request for add subject without an image. + + :param subject: fullname + + :return: json with this subject from server. + """ + + def post(self, subject: dict = '') -> dict: + url: str = self.url + result = requests.post(url, data=json.dumps(subject), headers=self.headers) + return result.json() + + """ + PUT request to CompreFace server for rename existing subject. + + :param subject: fullname + + :return: json from server. + """ + + def put(self, request: dict = '') -> dict: + url: str = self.url + '/' + request.get('api_endpoint') + result = requests.put(url, data=json.dumps(request), headers=self.headers) + return result.json() + + """ + DELETE request to CompreFace server for delete subjects. + + :param subject: fullname + + :return: json from server. + """ + + def delete(self, subject: str = '') -> dict: + url: str = self.url + '/' + subject if subject else self.url + result = requests.delete(url, headers=self.headers) + return result.json() diff --git a/compreface/collections/__init__.py b/compreface/collections/__init__.py index b03f9b8..5cfc3ac 100644 --- a/compreface/collections/__init__.py +++ b/compreface/collections/__init__.py @@ -14,4 +14,4 @@ permissions and limitations under the License. """ -from .face_collections import FaceCollection +from .face_collections import FaceCollection, Subjects diff --git a/compreface/collections/face_collections.py b/compreface/collections/face_collections.py index ae00c73..3acdc62 100644 --- a/compreface/collections/face_collections.py +++ b/compreface/collections/face_collections.py @@ -17,10 +17,15 @@ from compreface.common.typed_dict import AllOptionsDict, ExpandedOptionsDict, DetProbOptionsDict, pass_dict from ..use_cases import ( AddExampleOfSubject, - ListOfAllSavedSubjects, + AddSubject, DeleteAllExamplesOfSubjectByName, + DeleteSubjectByName, + DeleteAllSubjects, DeleteExampleById, - VerificationFaceFromImage + GetSubjects, + UpdateSubject, + VerificationFaceFromImage, + ListOfAllSavedSubjects ) @@ -56,6 +61,13 @@ def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict api_key=api_key ) + def list(self) -> dict: + """ + Get list of collections + :return: + """ + return self.list_of_all_saved_subjects.execute() + def add(self, image_path: str, subject: str, options: DetProbOptionsDict = {}) -> dict: """ Add example to collection @@ -70,12 +82,17 @@ def add(self, image_path: str, subject: str, options: DetProbOptionsDict = {}) - ) return self.add_example.execute(request, pass_dict(options, DetProbOptionsDict) if options == {} else options) - def list(self) -> dict: + def delete(self, image_id: str) -> dict: """ - Get list of collections + Delete example by Id + :param image_id: :return: """ - return self.list_of_all_saved_subjects.execute() + request = DeleteExampleById.Request( + api_key=self.api_key, + image_id=image_id + ) + return self.delete_all_examples_by_id.execute(request) def delete_all(self, subject: str) -> dict: """ @@ -89,18 +106,6 @@ def delete_all(self, subject: str) -> dict: ) return self.delete_all_examples_of_subject_by_name.execute(request) - def delete(self, image_id: str) -> dict: - """ - Delete example by Id - :param image_id: - :return: - """ - request = DeleteExampleById.Request( - api_key=self.api_key, - image_id=image_id - ) - return self.delete_all_examples_by_id.execute(request) - def verify(self, image_path: str, image_id: str, options: ExpandedOptionsDict = {}) -> dict: """ Compare image @@ -114,3 +119,85 @@ def verify(self, image_path: str, image_id: str, options: ExpandedOptionsDict = image_id=image_id ) return self.verify_face_from_image.execute(request, pass_dict(options, ExpandedOptionsDict) if options == {} else options) + + +class Subjects: + def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict = {}): + """Init service with define API Key""" + self.available_services = [] + self.api_key = api_key + self.options = options + self.add_subject: AddSubject = AddSubject( + domain=domain, + port=port, + api_key=api_key + ) + self.update_subject: UpdateSubject = UpdateSubject( + domain=domain, + port=port, + api_key=api_key + ) + self.delete_subject: DeleteSubjectByName = DeleteSubjectByName( + domain=domain, + port=port, + api_key=api_key + ) + self.delete_all_subjects: DeleteAllSubjects = DeleteAllSubjects( + domain=domain, + port=port, + api_key=api_key + ) + self.list_of_all_saved_subjects: GetSubjects = GetSubjects( + domain=domain, + port=port, + api_key=api_key + ) + + def list(self) -> dict: + """ + Get list of subjects + :return: + """ + return self.list_of_all_saved_subjects.execute() + + def add(self, subject: str) -> dict: + """ + Add subject + :param subject: + :return: + """ + request = AddSubject.Request( + subject=subject + ) + return self.add_subject.execute(request) + + def update(self, subject: str, new_name: str) -> dict: + """ + Update subject by name + :param subject: + :param new_name: + :return: + """ + request = UpdateSubject.Request( + subject=new_name, + api_endpoint=subject + ) + return self.update_subject.execute(request) + + def delete(self, subject: str) -> dict: + """ + Delete subject by name + :param subject: + :return: + """ + request = DeleteSubjectByName.Request( + subject=subject + ) + return self.delete_subject.execute(request) + + def delete_all(self) -> dict: + """ + Delete all subjects + :return: + """ + return self.delete_all_subjects.execute() diff --git a/compreface/common/typed_dict.py b/compreface/common/typed_dict.py index aa3f14e..161562c 100644 --- a/compreface/common/typed_dict.py +++ b/compreface/common/typed_dict.py @@ -55,9 +55,11 @@ def check_fields_by_name(name: str, value: Any): for row in values.split(','): if row == ',': pass - if row.find('age') == -1 and row.find('calculator') == -1 and row.find('gender') == -1 and row.find('landmarks') == -1: + if row.find('age') == -1 and row.find('calculator') == -1 and row.find('gender') == -1 \ + and row.find('landmarks') == -1 and row.find('mask') == -1: raise IncorrectFieldException( - "face_plugins must be only contains calculator,age,gender,landmarks. Incorrect value {}".format(row)) + "face_plugins must be only contains calculator,age,gender,landmarks,mask. " + "Incorrect value {}".format(row)) def pass_dict(options: AllOptionsDict, type: DetProbOptionsDict or ExpandedOptionsDict): diff --git a/compreface/config/api_list.py b/compreface/config/api_list.py index 884b2df..52d63e0 100644 --- a/compreface/config/api_list.py +++ b/compreface/config/api_list.py @@ -19,6 +19,7 @@ RECOGNIZE_API: str = RECOGNITION_ROOT_API + '/recognize' RECOGNIZE_CRUD_API: str = RECOGNITION_ROOT_API + '/faces' +SUBJECTS_CRUD_API: str = RECOGNITION_ROOT_API + '/subjects' DETECTION_API: str = '/api/v1/detection/detect' diff --git a/compreface/service/recognition_service.py b/compreface/service/recognition_service.py index 44a3293..18eff3e 100644 --- a/compreface/service/recognition_service.py +++ b/compreface/service/recognition_service.py @@ -18,10 +18,8 @@ from typing import List from ..common import Service -from ..collections import FaceCollection -from ..use_cases import ( - RecognizeFaceFromImage -) +from ..collections import FaceCollection, Subjects +from ..use_cases import RecognizeFaceFromImage class RecognitionService(Service): @@ -42,6 +40,12 @@ def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict api_key=api_key, options=options ) + self.subjects: Subjects = Subjects( + domain=domain, + port=port, + api_key=api_key, + options=options + ) def get_available_functions(self) -> List[str]: """ @@ -69,3 +73,10 @@ def get_face_collection(self) -> FaceCollection: :return: """ return self.face_collection + + def get_subjects(self) -> Subjects: + """ + Get subjects + :return: + """ + return self.subjects diff --git a/compreface/use_cases/__init__.py b/compreface/use_cases/__init__.py index 887e53f..540286f 100644 --- a/compreface/use_cases/__init__.py +++ b/compreface/use_cases/__init__.py @@ -21,3 +21,8 @@ from .recognize_face_from_image import RecognizeFaceFromImage from .verification_face_from_image import VerificationFaceFromImage from .detect_face_from_image import DetectFaceFromImage +from .add_subject import AddSubject +from .get_subjects import GetSubjects +from .update_subject import UpdateSubject +from .delete_subject_by_name import DeleteSubjectByName +from .delete_all_subjects import DeleteAllSubjects diff --git a/compreface/use_cases/add_subject.py b/compreface/use_cases/add_subject.py new file mode 100644 index 0000000..e2f84c4 --- /dev/null +++ b/compreface/use_cases/add_subject.py @@ -0,0 +1,37 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from dataclasses import dataclass, asdict + +from compreface.client.subject_client import SubjectClient + + +class AddSubject: + + @dataclass + class Request: + subject: str + + def __init__(self, domain: str, port: str, api_key: str): + self.subject_client = SubjectClient( + api_key=api_key, + domain=domain, + port=port + ) + + def execute(self, request: Request) -> dict: + result: dict = self.subject_client.post(asdict(request)) + return result diff --git a/compreface/use_cases/delete_all_subjects.py b/compreface/use_cases/delete_all_subjects.py new file mode 100644 index 0000000..a07c5f9 --- /dev/null +++ b/compreface/use_cases/delete_all_subjects.py @@ -0,0 +1,37 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from dataclasses import dataclass + +from compreface.client.subject_client import SubjectClient + + +class DeleteAllSubjects: + + @dataclass + class Request: + pass + + def __init__(self, domain: str, port: str, api_key: str): + self.add_subject = SubjectClient( + api_key=api_key, + domain=domain, + port=port + ) + + def execute(self) -> dict: + result: dict = self.add_subject.delete() + return result diff --git a/compreface/use_cases/delete_subject_by_name.py b/compreface/use_cases/delete_subject_by_name.py new file mode 100644 index 0000000..3f1a856 --- /dev/null +++ b/compreface/use_cases/delete_subject_by_name.py @@ -0,0 +1,37 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from dataclasses import dataclass + +from compreface.client.subject_client import SubjectClient + + +class DeleteSubjectByName: + + @dataclass + class Request: + subject: str + + def __init__(self, domain: str, port: str, api_key: str): + self.subject_client = SubjectClient( + api_key=api_key, + domain=domain, + port=port + ) + + def execute(self, request: Request) -> dict: + result: dict = self.subject_client.delete(request.subject) + return result diff --git a/compreface/use_cases/get_subjects.py b/compreface/use_cases/get_subjects.py new file mode 100644 index 0000000..7631bbf --- /dev/null +++ b/compreface/use_cases/get_subjects.py @@ -0,0 +1,37 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from dataclasses import dataclass + +from compreface.client.subject_client import SubjectClient + + +class GetSubjects: + + @dataclass + class Request: + pass + + def __init__(self, domain: str, port: str, api_key: str): + self.subject_client = SubjectClient( + api_key=api_key, + domain=domain, + port=port + ) + + def execute(self) -> dict: + result: dict = self.subject_client.get() + return result diff --git a/compreface/use_cases/update_subject.py b/compreface/use_cases/update_subject.py new file mode 100644 index 0000000..b3e4c67 --- /dev/null +++ b/compreface/use_cases/update_subject.py @@ -0,0 +1,38 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from dataclasses import dataclass, asdict + +from compreface.client.subject_client import SubjectClient + + +class UpdateSubject: + + @dataclass + class Request: + subject: str + api_endpoint: str + + def __init__(self, domain: str, port: str, api_key: str): + self.subject_client = SubjectClient( + api_key=api_key, + domain=domain, + port=port + ) + + def execute(self, request: Request) -> dict: + result: dict = self.subject_client.put(asdict(request)) + return result diff --git a/examples/add_example_of_a_subject.py b/examples/add_example_of_a_subject.py index 94acea5..ecb97c0 100644 --- a/examples/add_example_of_a_subject.py +++ b/examples/add_example_of_a_subject.py @@ -21,7 +21,7 @@ DOMAIN: str = 'http://localhost' PORT: str = '8000' -RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' compre_face: CompreFace = CompreFace(DOMAIN, PORT, { "det_prob_threshold": 0.8 @@ -33,7 +33,7 @@ face_collection: FaceCollection = recognition.get_face_collection() # Image from local path. -image: str = 'examples/common/jonathan-petit-unsplash.jpg' +image: str = 'common/jonathan-petit-unsplash.jpg' subject: str = 'Jonathan Petit' print(face_collection.add(image, subject)) diff --git a/examples/add_subject.py b/examples/add_subject.py new file mode 100644 index 0000000..a99f6c0 --- /dev/null +++ b/examples/add_subject.py @@ -0,0 +1,33 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import Subjects + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +subjects: Subjects = recognition.get_subjects() + +subject: str = 'Test Subject' + +print(subjects.add(subject)) diff --git a/examples/common/jonathan-petit-unsplash.jpg b/examples/common/jonathan-petit-unsplash.jpg index 2d498be..cc854ff 100644 Binary files a/examples/common/jonathan-petit-unsplash.jpg and b/examples/common/jonathan-petit-unsplash.jpg differ diff --git a/examples/delete_all_examples_of_subject.py b/examples/delete_all_examples_of_subject.py index ea9cba4..57d21a2 100644 --- a/examples/delete_all_examples_of_subject.py +++ b/examples/delete_all_examples_of_subject.py @@ -20,7 +20,7 @@ DOMAIN: str = 'http://localhost' PORT: str = '8000' -RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' compre_face: CompreFace = CompreFace(DOMAIN, PORT) diff --git a/examples/delete_all_subjects.py b/examples/delete_all_subjects.py new file mode 100644 index 0000000..ac11b67 --- /dev/null +++ b/examples/delete_all_subjects.py @@ -0,0 +1,31 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import Subjects + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +subjects: Subjects = recognition.get_subjects() + +print(subjects.delete_all()) diff --git a/examples/delete_example_by_id.py b/examples/delete_example_by_id.py index ee424a6..d7dfc2d 100644 --- a/examples/delete_example_by_id.py +++ b/examples/delete_example_by_id.py @@ -20,7 +20,7 @@ DOMAIN: str = 'http://localhost' PORT: str = '8000' -RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' compre_face: CompreFace = CompreFace(DOMAIN, PORT) diff --git a/examples/delete_subject_by_name.py b/examples/delete_subject_by_name.py new file mode 100644 index 0000000..3bd85e9 --- /dev/null +++ b/examples/delete_subject_by_name.py @@ -0,0 +1,33 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import Subjects + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +subjects: Subjects = recognition.get_subjects() + +subject: str = 'Test Subject' + +print(subjects.delete(subject)) diff --git a/examples/detect_face_from_image.py b/examples/detect_face_from_image.py index e1a1468..45bcc23 100644 --- a/examples/detect_face_from_image.py +++ b/examples/detect_face_from_image.py @@ -20,7 +20,7 @@ DOMAIN: str = 'http://localhost' PORT: str = '8000' -DETECTION_API_KEY: str = '444dc40f-3168-43d5-8cca-108ab401ff5c' +DETECTION_API_KEY: str = 'f4bdcf1f-1ef9-442f-863d-7bcd170723db' compre_face: CompreFace = CompreFace(DOMAIN, PORT, { "limit": 0, @@ -32,6 +32,6 @@ detection: DetectionService = compre_face.init_face_detection( DETECTION_API_KEY) -image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' +image_path: str = 'common/jonathan-petit-unsplash.jpg' print(detection.detect(image_path)) diff --git a/examples/get_list_of_all_subjects.py b/examples/get_list_of_all_subjects.py index 4f12326..644d5ea 100644 --- a/examples/get_list_of_all_subjects.py +++ b/examples/get_list_of_all_subjects.py @@ -16,11 +16,11 @@ from compreface import CompreFace from compreface.service import RecognitionService -from compreface.collections import FaceCollection +from compreface.collections import Subjects DOMAIN: str = 'http://localhost' PORT: str = '8000' -RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' compre_face: CompreFace = CompreFace(DOMAIN, PORT) @@ -28,6 +28,6 @@ recognition: RecognitionService = compre_face.init_face_recognition( RECOGNITION_API_KEY) -face_collection: FaceCollection = recognition.get_face_collection() +subjects: Subjects = recognition.get_subjects() -print(face_collection.list()) +print(subjects.list()) diff --git a/examples/recognize_face_from_image.py b/examples/recognize_face_from_image.py index d216b1b..53f7b49 100644 --- a/examples/recognize_face_from_image.py +++ b/examples/recognize_face_from_image.py @@ -19,7 +19,7 @@ DOMAIN: str = 'http://localhost' PORT: str = '8000' -RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' compre_face: CompreFace = CompreFace(DOMAIN, PORT, { @@ -32,6 +32,6 @@ recognition: RecognitionService = compre_face.init_face_recognition( RECOGNITION_API_KEY) -image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' +image_path: str = 'common/jonathan-petit-unsplash.jpg' print(recognition.recognize(image_path)) diff --git a/examples/update_existing_subject.py b/examples/update_existing_subject.py new file mode 100644 index 0000000..392fd56 --- /dev/null +++ b/examples/update_existing_subject.py @@ -0,0 +1,34 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +from compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import Subjects + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +subjects: Subjects = recognition.get_subjects() + +subject: str = 'Test Subject' +new_name: str = 'Updated Subject' + +print(subjects.update(subject, new_name)) diff --git a/examples/verification_face_from_image.py b/examples/verification_face_from_image.py index 4a763fb..aafb048 100644 --- a/examples/verification_face_from_image.py +++ b/examples/verification_face_from_image.py @@ -20,7 +20,7 @@ DOMAIN: str = 'http://localhost' PORT: str = '8000' -RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' +RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' compre_face: CompreFace = CompreFace(DOMAIN, PORT, { "limit": 0, @@ -31,7 +31,7 @@ recognition: RecognitionService = compre_face.init_face_recognition( RECOGNITION_API_KEY) -image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' +image_path: str = 'common/jonathan-petit-unsplash.jpg' face_collection: FaceCollection = recognition.get_face_collection() diff --git a/examples/verify_face_from_image.py b/examples/verify_face_from_image.py index 6c93cca..265c0be 100644 --- a/examples/verify_face_from_image.py +++ b/examples/verify_face_from_image.py @@ -19,7 +19,7 @@ DOMAIN: str = 'http://localhost' PORT: str = '8000' -VERIFICATION_API_KEY: str = '7aa288a2-9082-4fa2-a975-e175deba1ad2' +VERIFICATION_API_KEY: str = '5c765423-4192-4fe8-9c60-092f495a332a' compre_face: CompreFace = CompreFace(DOMAIN, PORT, { @@ -32,7 +32,6 @@ verify: VerificationService = compre_face.init_face_verification( VERIFICATION_API_KEY) -image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' - +image_path: str = 'common/jonathan-petit-unsplash.jpg' print(verify.verify(image_path, image_path)) diff --git a/setup.py b/setup.py index a4bcf61..9f47001 100644 --- a/setup.py +++ b/setup.py @@ -23,15 +23,15 @@ setup( name='compreface-sdk', packages=find_packages(exclude=("tests",)), - version='0.1.0', + version='0.6.0', license='Apache License 2.0', description='CompreFace Python SDK makes face recognition into your application even easier.', long_description=long_description, long_description_content_type="text/markdown", - author='Artsiom Liubymov aliubymov@exadel.com, Artsiom Khadzkou akhadzkou@exadel.com', - author_email='aliubymov@exadel.com, akhadzkou@exadel.com', + author='Artsiom Liubymov aliubymov@exadel.com, Artsiom Khadzkou akhadzkou@exadel.com, Aliaksei Tauhen atauhen@exadel.com', + author_email='aliubymov@exadel.com, akhadzkou@exadel.com, atauhen@exadel.com', url='https://exadel.com/solutions/compreface/', - download_url='https://github.com/exadel-inc/compreface-python-sdk/archive/refs/tags/0.1.0.tar.gz', + download_url='https://github.com/exadel-inc/compreface-python-sdk/archive/refs/tags/0.6.0.tar.gz', keywords=[ "CompreFace", "Face Recognition", diff --git a/tests/client/test_subject_crud_client.py b/tests/client/test_subject_crud_client.py new file mode 100644 index 0000000..416a02b --- /dev/null +++ b/tests/client/test_subject_crud_client.py @@ -0,0 +1,130 @@ + +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ +import json +import httpretty +import requests +from compreface.client import SubjectClient +from compreface.config.api_list import SUBJECTS_CRUD_API +from tests.client.const_config import DOMAIN, PORT, DETECTION_API_KEY +""" + Server configuration +""" +url: str = DOMAIN + ":" + PORT + SUBJECTS_CRUD_API + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_get(): + httpretty.register_uri( + httpretty.GET, + url, + headers={'x-api-key': DETECTION_API_KEY}, + body='{"subjects": ["Subject", "Subject2"]}' + ) + test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) + response: dict = requests.get(url=url, headers={'x-api-key': DETECTION_API_KEY}).json() + test_response: dict = test_subject.get() + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post(): + httpretty.register_uri( + httpretty.POST, + url, + headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}, + body='{"subject": "Subject"}' + ) + + data = {'subject': 'Subject'} + + response: dict = requests.post( + url=url, data=json.dumps(data), headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}).json() + + test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.post({'subject': 'Subject'}) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post_incorrect_response(): + httpretty.register_uri( + httpretty.POST, + url, + headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}, + body='{"subject": "Subjectss"}' + ) + + data = {'subject': 'Subjectss'} + + response: dict = requests.post( + url=url, data=json.dumps(data), headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}).json() + + httpretty.register_uri( + httpretty.POST, + url, + headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}, + body='{"subject": "Subject"}' + ) + + test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.post({'subject': 'Subject'}) + assert response != test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_delete(): + test_url = url + '/Subject' + httpretty.register_uri( + httpretty.DELETE, + test_url, + headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}, + body='{"subject": "Subject"}' + ) + + response: dict = requests.delete(url=test_url, + headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}).json() + + test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.delete("Subject") + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_put(): + test_url = url + '/Subject' + httpretty.register_uri( + httpretty.PUT, + test_url, + headers={'x-api-key': DETECTION_API_KEY, + 'Content-Type': 'application/json'}, + body='{"subject": "NewSubject"}' + ) + + data = {"subject": "NewSubject"} + + response: dict = requests.put(url=test_url, data=json.dumps(data), headers={'x-api-key': DETECTION_API_KEY}).json() + + test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.put({"subject": "NewSubject", "api_endpoint": "Subject"}) + assert response == test_response diff --git a/webcam_demo/README.md b/webcam_demo/README.md new file mode 100644 index 0000000..5a05065 --- /dev/null +++ b/webcam_demo/README.md @@ -0,0 +1,32 @@ +# Webcam demo + +This is an example of how to use CompreFace face recognition with python sdk. + +# Requirements + +1. [Python](https://www.python.org/downloads/) (Version 3.7+) +2. [CompreFace](https://github.com/exadel-inc/CompreFace#getting-started-with-compreface) +3. [Compreface-python-sdk](https://github.com/exadel-inc/compreface-python-sdk) +4. [Opencv-python](https://pypi.org/project/opencv-python/) + +# Face recognition demo + +To run the demo, open `webcam_demo` folder and run: + +```commandline +python compreface_webcam_recognition_demo.py --api-key your_api_key --host http://localhost --port 8000 +``` +* `--api-key` is your Face Recognition service API key. API key for this demo was created on step 5 of [How to Use CompreFace](https://github.com/exadel-inc/CompreFace/blob/master/docs/How-to-Use-CompreFace.md#how-to-use-compreface). Optional value. By default, the value is `00000000-0000-0000-0000-000000000002` - api key with celebrities demo. +* `--host` is the host where you deployed CompreFace. Optional value. By default, the value is `http://localhost` +* `--port` is the port of CompreFace instance. Optional value. By default, the value is `8000` + +# Face detection demo + +To run the demo, open `webcam_demo` folder and run: + +```commandline +python compreface_webcam_detection_demo.py --api-key your_api_key --host http://localhost --port 8000 +``` +* `--api-key` is your Face Detection service API key. API key for this demo was created on step 5 of [How to Use CompreFace](https://github.com/exadel-inc/CompreFace/blob/master/docs/How-to-Use-CompreFace.md#how-to-use-compreface). +* `--host` is the host where you deployed CompreFace. Optional value. By default, the value is `http://localhost` +* `--port` is the port of CompreFace instance. Optional value. By default, the value is `8000` \ No newline at end of file diff --git a/webcam_demo/compreface_webcam_detection_demo.py b/webcam_demo/compreface_webcam_detection_demo.py new file mode 100644 index 0000000..6af0601 --- /dev/null +++ b/webcam_demo/compreface_webcam_detection_demo.py @@ -0,0 +1,115 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +import argparse +import cv2 +import time +from threading import Thread + +from compreface import CompreFace +from compreface.service import DetectionService + + +def parseArguments(): + parser = argparse.ArgumentParser() + + parser.add_argument("--api-key", help="CompreFace detection service API key", type=str, required=True) + parser.add_argument("--host", help="CompreFace host", type=str, default='http://localhost') + parser.add_argument("--port", help="CompreFace port", type=str, default='8000') + + args = parser.parse_args() + + return args + +class ThreadedCamera: + def __init__(self, api_key, host, port): + self.active = True + self.results = [] + self.capture = cv2.VideoCapture(0) + self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2) + + compre_face: CompreFace = CompreFace(host, port, { + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "face_plugins": "age,gender,mask", + "status": False + }) + + self.detection: DetectionService = compre_face.init_face_detection(api_key) + + self.FPS = 1/30 + + # Start frame retrieval thread + self.thread = Thread(target=self.show_frame, args=()) + self.thread.daemon = True + self.thread.start() + + def show_frame(self): + print("Started") + while self.capture.isOpened(): + (status, frame_raw) = self.capture.read() + self.frame = cv2.flip(frame_raw, 1) + + if self.results: + results = self.results + for result in results: + box = result.get('box') + age = result.get('age') + gender = result.get('gender') + mask = result.get('mask') + if box: + cv2.rectangle(img=self.frame, pt1=(box['x_min'], box['y_min']), + pt2=(box['x_max'], box['y_max']), color=(0, 255, 0), thickness=1) + if age: + age = f"Age: {age['low']} - {age['high']}" + cv2.putText(self.frame, age, (box['x_max'], box['y_min'] + 15), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + if gender: + gender = f"Gender: {gender['value']}" + cv2.putText(self.frame, gender, (box['x_max'], box['y_min'] + 35), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + if mask: + mask = f"Mask: {mask['value']}" + cv2.putText(self.frame, mask, (box['x_max'], box['y_min'] + 55), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + + cv2.imshow('CompreFace demo', self.frame) + time.sleep(self.FPS) + + if cv2.waitKey(1) & 0xFF == 27: + self.capture.release() + cv2.destroyAllWindows() + self.active=False + + def is_active(self): + return self.active + + def update(self): + if not hasattr(self, 'frame'): + return + + _, im_buf_arr = cv2.imencode(".jpg", self.frame) + byte_im = im_buf_arr.tobytes() + data = self.detection.detect(byte_im) + self.results = data.get('result') + + +if __name__ == '__main__': + args = parseArguments() + threaded_camera = ThreadedCamera(args.api_key, args.host, args.port) + while threaded_camera.is_active(): + threaded_camera.update() \ No newline at end of file diff --git a/webcam_demo/compreface_webcam_recognition_demo.py b/webcam_demo/compreface_webcam_recognition_demo.py new file mode 100644 index 0000000..bc04502 --- /dev/null +++ b/webcam_demo/compreface_webcam_recognition_demo.py @@ -0,0 +1,128 @@ +""" + Copyright(c) 2021 the original author or authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https: // www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + or implied. See the License for the specific language governing + permissions and limitations under the License. + """ + +import cv2 +import argparse +import time +from threading import Thread + +from compreface import CompreFace +from compreface.service import RecognitionService + +def parseArguments(): + parser = argparse.ArgumentParser() + + parser.add_argument("--api-key", help="CompreFace recognition service API key", type=str, default='00000000-0000-0000-0000-000000000002') + parser.add_argument("--host", help="CompreFace host", type=str, default='http://localhost') + parser.add_argument("--port", help="CompreFace port", type=str, default='8000') + + args = parser.parse_args() + + return args + +class ThreadedCamera: + def __init__(self, api_key, host, port): + self.active = True + self.results = [] + self.capture = cv2.VideoCapture(0) + self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2) + + compre_face: CompreFace = CompreFace(host, port, { + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "face_plugins": "age,gender", + "status": False + }) + + self.recognition: RecognitionService = compre_face.init_face_recognition(api_key) + + self.FPS = 1/30 + + # Start frame retrieval thread + self.thread = Thread(target=self.show_frame, args=()) + self.thread.daemon = True + self.thread.start() + + def show_frame(self): + print("Started") + while self.capture.isOpened(): + (status, frame_raw) = self.capture.read() + self.frame = cv2.flip(frame_raw, 1) + + if self.results: + results = self.results + for result in results: + box = result.get('box') + age = result.get('age') + gender = result.get('gender') + mask = result.get('mask') + subjects = result.get('subjects') + if box: + cv2.rectangle(img=self.frame, pt1=(box['x_min'], box['y_min']), + pt2=(box['x_max'], box['y_max']), color=(0, 255, 0), thickness=1) + if age: + age = f"Age: {age['low']} - {age['high']}" + cv2.putText(self.frame, age, (box['x_max'], box['y_min'] + 15), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + if gender: + gender = f"Gender: {gender['value']}" + cv2.putText(self.frame, gender, (box['x_max'], box['y_min'] + 35), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + if mask: + mask = f"Mask: {mask['value']}" + cv2.putText(self.frame, mask, (box['x_max'], box['y_min'] + 55), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + + if subjects: + subjects = sorted(subjects, key=lambda k: k['similarity'], reverse=True) + subject = f"Subject: {subjects[0]['subject']}" + similarity = f"Similarity: {subjects[0]['similarity']}" + cv2.putText(self.frame, subject, (box['x_max'], box['y_min'] + 75), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + cv2.putText(self.frame, similarity, (box['x_max'], box['y_min'] + 95), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + else: + subject = f"No known faces" + cv2.putText(self.frame, subject, (box['x_max'], box['y_min'] + 75), + cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + + cv2.imshow('CompreFace demo', self.frame) + time.sleep(self.FPS) + + if cv2.waitKey(1) & 0xFF == 27: + self.capture.release() + cv2.destroyAllWindows() + self.active=False + + def is_active(self): + return self.active + + def update(self): + if not hasattr(self, 'frame'): + return + + _, im_buf_arr = cv2.imencode(".jpg", self.frame) + byte_im = im_buf_arr.tobytes() + data = self.recognition.recognize(byte_im) + self.results = data.get('result') + + +if __name__ == '__main__': + args = parseArguments() + threaded_camera = ThreadedCamera(args.api_key, args.host, args.port) + while threaded_camera.is_active(): + threaded_camera.update() \ No newline at end of file