diff --git a/README.md b/README.md index fbddb78..ac392e1 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,16 @@ pip install -e . * To test on live video: `python face_alignment_test.py [-i webcam_index]` * To test on a video file: `python face_alignment_test.py [-i input_file] [-o output_file]` +### Docker +You can run a containerized demo using Docker. It will install the needed dependencies and allow you to test the FANPredictor on a sample image. + +Either run `bash demo/run.sh` or `docker build -t ibug-face_alignment -f ./demo/Dockerfile . && docker run -it --rm -p 8888:8888 ibug-face_alignment` +You will automatically enter a tmux session and open jupter-lab, you can then open the notebook.ipynb. If you don't want this, you can either kill it immediately or provide "--entrypoint /bin/bash" to the docker run command. + +Please install Docker using [the official instructions](https://docs.docker.com/get-docker/) + +**NOTE** If you're running on Windows, running the container might fail, and you would need to replace `docker run` with `winpty docker run`. I also recommend running the commands from a Unix-shell, for example Git Bash, or Docker Quickstart Terminal. This is taken care of if you use the shell script. + ## How to Use ```python # Import the libraries diff --git a/demo/Dockerfile b/demo/Dockerfile new file mode 100644 index 0000000..f668c55 --- /dev/null +++ b/demo/Dockerfile @@ -0,0 +1,34 @@ +FROM continuumio/miniconda3:4.12.0 + +RUN apt update && apt upgrade -y && \ + apt install -y \ + libgl1 \ + lsof \ + tmux \ + vim + +WORKDIR /face_alignment + +COPY ../requirements.txt . +RUN pip install -r requirements.txt && \ + pip install scikit-image + +COPY ../setup.py . +COPY ../ibug ibug +RUN pip install -e . + +RUN git clone https://github.com/ibug-group/face_detection.git && \ + pip install -e face_detection && \ + cp -r face_detection ibug/ + +COPY demo/main.py . + +RUN conda install jupyterlab matplotlib ipywidgets + +COPY demo/notebook.ipynb . + +CMD [ \ + "tmux", \ + "new-session", "/bin/bash", ";", \ + "new-window", "jupyter-lab --ip=0.0.0.0 --no-browser --allow-root" \ +] diff --git a/demo/main.py b/demo/main.py new file mode 100644 index 0000000..b85f4b4 --- /dev/null +++ b/demo/main.py @@ -0,0 +1,27 @@ +import os +import cv2 +import torch +import numpy as np +from skimage.data import astronaut + +from ibug.face_alignment import FANPredictor +from ibug.face_detection import RetinaFacePredictor + +image = astronaut() + +device = "cpu" +if torch.cuda.is_available(): + device = torch.cuda.current_device() + +face_detector = RetinaFacePredictor( + device=device, + model=RetinaFacePredictor.get_model("mobilenet0.25"), +) +detections = face_detector(image, rgb=False) +assert len(detections) == 1, "Please submit an image with exactly one clear frontal face" +detection = detections[0, :4] + +config = FANPredictor.create_config(gamma = 1.0, radius = 0.1, use_jit = False) +fan = FANPredictor(device=device, model=FANPredictor.get_model('2dfan2_alt'), config=config) + +landmarks, scores = fan(image, detection) diff --git a/demo/notebook.ipynb b/demo/notebook.ipynb new file mode 100644 index 0000000..9ec0c97 --- /dev/null +++ b/demo/notebook.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "b3e3e628-e16a-474e-ad32-3edb91a82ba1", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "import io\n", + "from typing import Callable\n", + "from contextlib import contextmanager\n", + "\n", + "import cv2\n", + "import torch\n", + "from skimage.data import astronaut\n", + "import numpy as np\n", + "from matplotlib import pyplot as plt\n", + "from PIL import Image\n", + "import ipywidgets as widgets\n", + "\n", + "from ibug.face_alignment import FANPredictor\n", + "from ibug.face_alignment.utils import plot_landmarks\n", + "from ibug.face_detection import RetinaFacePredictor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eea2256f-2d81-406f-8d30-6753b026e8a4", + "metadata": {}, + "outputs": [], + "source": [ + "def detect_face(face_detector, image):\n", + " detections = face_detector(image, rgb=False)\n", + " assert len(detections) == 1, \"Please submit an image with exactly one clear frontal face\"\n", + " return detections[0, :4]\n", + "\n", + "def resize(image: np.ndarray, longer_side: int) -> np.ndarray:\n", + " width, height = image.shape[:2]\n", + " largest = max(width, height)\n", + " ratio = longer_side / largest\n", + " return cv2.resize(image, (int(height * ratio), int(width * ratio)))\n", + "\n", + "@contextmanager\n", + "def time_tracking(callback: Callable[[float], None]):\n", + " start = time.perf_counter()\n", + " try:\n", + " yield\n", + " finally:\n", + " end = time.perf_counter()\n", + " callback(end - start)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f065b832-3d29-473a-aa56-de236f5040e1", + "metadata": {}, + "outputs": [], + "source": [ + "file_upload = widgets.FileUpload()\n", + "\n", + "display(file_upload)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d18fc123-fcce-40a2-83be-0bf7fd430e99", + "metadata": {}, + "outputs": [], + "source": [ + "image = astronaut()\n", + "\n", + "for filename, file_info in file_upload.value.items():\n", + " image = np.array(Image.open(io.BytesIO(file_info['content'])))\n", + " \n", + "image = resize(image, 400)\n", + "Image.fromarray(image)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c15e1d3-2dd5-400d-85f6-46f7a1d88af5", + "metadata": {}, + "outputs": [], + "source": [ + "device = \"cpu\"\n", + "if torch.cuda.is_available():\n", + " device = torch.cuda.current_device()\n", + "\n", + "face_detector = RetinaFacePredictor(\n", + " device=device, \n", + " model=RetinaFacePredictor.get_model(\"mobilenet0.25\"),\n", + ")\n", + "\n", + "with time_tracking(print):\n", + " detection = detect_face(face_detector, image)\n", + "print(f\"Detected face at {detection=}\")\n", + "\n", + "config = FANPredictor.create_config(\n", + " gamma = 1.0, \n", + " radius = 0.1, \n", + " use_jit = False,\n", + ")\n", + "\n", + "fan = FANPredictor(\n", + " device=device, \n", + " model=FANPredictor.get_model('2dfan2_alt'), \n", + " config=config,\n", + ")\n", + "with time_tracking(print):\n", + " landmarks, scores = fan(image, detection)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "afef425a-84d1-4ee2-b14b-8ee8bbbd21f6", + "metadata": {}, + "outputs": [], + "source": [ + "vis = image.copy()\n", + "cv2.rectangle(\n", + " vis, \n", + " detection[:2].astype(np.int32), \n", + " detection[2:].astype(np.int32), \n", + " color=(255, 0, 0),\n", + " thickness=2,\n", + ")\n", + "plot_landmarks(vis, landmarks[0])\n", + "\n", + "\n", + "Image.fromarray(vis)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/demo/run.sh b/demo/run.sh new file mode 100644 index 0000000..8f6ceaa --- /dev/null +++ b/demo/run.sh @@ -0,0 +1,16 @@ +if ! [ "$(basename $PWD)" = "face_alignment" ] +then + echo "Please run from the base directory of this repo." + exit 1 +fi + +docker build -t ibug-face_alignment -f ./demo/Dockerfile . + +prefix="" +if [ $(uname | grep -iE "(mingw|cygwin)") ] +then + prefix="winpty" +fi + +echo $prefix +$prefix docker run -it --rm -p 8888:8888 ibug-face_alignment