~fabrixxm/facetool

fb1df77fd5edcd771c5778c9a9715a90a32d8b28 — fabrixxm 8 months ago master
First commit
4 files changed, 109 insertions(+), 0 deletions(-)

A Containerfile
A README.md
A facetool
A facetool.py
A  => Containerfile +9 -0
@@ 1,9 @@
FROM animcogn/face_recognition:cpu

VOLUME /known

RUN  pip install climatik

COPY facetool.py /facetool.py

CMD [ "python3", "/facetool.py" ]
\ No newline at end of file

A  => README.md +6 -0
@@ 1,6 @@
Simple CLI around [face_recognition](https://github.com/ageitgey/face_recognition)

- `detect <file>` : find faces in `file`. returns on line per face found: `filename,x,y,w,h`
- `learn <file> <name>` : add first found face encoding to list of known faces with name `name`
- `recognize <file>` : find known faces in `file`. returns one line per match: `filename,distance,name`


A  => facetool +7 -0
@@ 1,7 @@
#!/bin/bash

MACHINEHOME=$HOME
[[ $(podman machine list --format json | jq '.|length') -gt 0 ]] && MACHINEHOME=/home/core/mnt
MACHINEPWD=${PWD/$HOME/$MACHINEHOME}

podman run --rm -v$MACHINEHOME:$HOME -v$MACHINEPWD:/app facetool /app/facetool.py "$@"

A  => facetool.py +87 -0
@@ 1,87 @@
#!/usr/bin/env python3

from typing import Tuple
from pathlib import Path
import pickle
import face_recognition.api as face_recognition
import PIL.Image

import numpy as np
import climatik


KNOWN_DB=Path(__file__).parent / 'known'

def add_known_face(known_people_folder:Path, face_image:Path, name:str) -> None:
    if not known_people_folder.exists():
        known_people_folder.mkdir()

    img = face_recognition.load_image_file(face_image.as_posix())
    encodings = face_recognition.face_encodings(img)

    if len(encodings) > 1:
        print(f"WARNING: More than one face found in {face_image}. Only considering the first face.")
    if len(encodings) == 0:
        print(f"WARNING: No faces found in {face_image}. Ignoring file.")
        return

    datfile = known_people_folder / f"{name}.dat"
    with datfile.open('wb') as f:
        pickle.dump(encodings[0], f)


def load_known_people(known_people_folder:Path) -> Tuple:
    known_names = []
    known_face_encodings = []
   
    for file in known_people_folder.glob("*.dat"):
        name = file.name.replace(".dat", "")
        with file.open('rb') as f:
            encoding = pickle.load(f)
        known_names.append(name)
        known_face_encodings.append(encoding)

    return known_names, known_face_encodings


@climatik.command()
def learn(image:Path, name:str):
    add_known_face(KNOWN_DB, image, name)


@climatik.command()
def detect(image:Path, model:str='hog', upsample:int=0):
    """
    @param model: "hog" or "cnn"
    """
    unknown_image = face_recognition.load_image_file(image.as_posix())
    face_locations = face_recognition.face_locations(unknown_image, number_of_times_to_upsample=upsample, model=model)
    for face_location in face_locations:
        top, right, bottom, left = face_location
        print("{},{},{},{},{}".format(image, min(left,right), min(top,bottom), abs(right-left), abs(top-bottom)))


@climatik.command()
def recognize(image:Path, tolerance:float=0.6):
    known_names, known_face_encodings = load_known_people(KNOWN_DB)
    unknown_image = face_recognition.load_image_file(image.as_posix())

    # Scale down image if it's giant so things run a little faster
    if max(unknown_image.shape) > 1600:
        pil_img = PIL.Image.fromarray(unknown_image)
        pil_img.thumbnail((1600, 1600), PIL.Image.LANCZOS)
        unknown_image = np.array(pil_img)

    unknown_encodings = face_recognition.face_encodings(unknown_image)

    for unknown_encoding in unknown_encodings:
        distances = face_recognition.face_distance(known_face_encodings, unknown_encoding)
        result = list(distances <= tolerance)

        for is_match, name, distance in zip(result, known_names, distances):
            if is_match:
                print("{},{},{}".format(image, distance, name))


if __name__ == "__main__":
    climatik.run()