From a8694b73f0128cac29c1382721fc60a1ab28e711 Mon Sep 17 00:00:00 2001 From: Plucky Date: Tue, 1 Jan 2019 22:08:21 +0800 Subject: [PATCH] DockerFile for Mac users to run DeepfaceLab with CPU Mode (#95) * fix localization nullpointer exception * fix devicelib error line:61,remove e * support create docker from cpu dockerfile * support preview or not when train(resolve cannot connect to X server) --- .gitignore | 4 +- DockerCPU.md | 132 +++++++++++++++++++++++++++++++++++ Dockerfile.cpu | 17 +++++ README.md | 3 + cpu.sh | 71 +++++++++++++++++++ doc/merged-face.jpg | Bin 0 -> 14771 bytes localization/localization.py | 3 +- main.py | 6 +- mainscripts/Trainer.py | 9 +-- nnlib/devicelib.py | 2 +- requirements-cpu-docker.txt | 9 +++ 11 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 DockerCPU.md create mode 100644 Dockerfile.cpu create mode 100755 cpu.sh create mode 100644 doc/merged-face.jpg create mode 100644 requirements-cpu-docker.txt diff --git a/.gitignore b/.gitignore index 2a76c1e..442e0ee 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ !mathlib !models !nnlib -!utils \ No newline at end of file +!utils +!Dockerfile* +!*.sh \ No newline at end of file diff --git a/DockerCPU.md b/DockerCPU.md new file mode 100644 index 0000000..1ffbd8d --- /dev/null +++ b/DockerCPU.md @@ -0,0 +1,132 @@ +# For Mac Users +If you just have a **MacBook**.DeepFaceLab **GPU** mode does not works. However,it can also works with **CPU** mode.Follow the Steps below will help you build the **DRE** (DeepFaceLab Runtime Environment) Easier. + +### 1. Open a new terminal and Clone DeepFaceLab with git +``` +$ git git@github.com:iperov/DeepFaceLab.git +``` + +### 2. Change the directory to DeepFaceLab +``` +$ cd DeepFaceLab +``` + +### 3. Install Docker + +[Docker Desktop for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac) + +### 4. Build Docker Image For DeepFaceLab + +``` +$ docker build -t deepfacelab-cpu -f Dockerfile.cpu . +``` + +### 5. Mount DeepFaceLab volume and Run it + +``` +$ docker run -p 8888:8888 --hostname deepfacelab-cpu --name deepfacelab-cpu -v $PWD:/notebooks deepfacelab-cpu +``` + +PS: Because your current directory is `DeepFaceLab`,so `-v $PWD:/notebooks` means Mount `DeepFaceLab` volume to `notebooks` in **Docker** + +And then you will see the log below: + +``` +The Jupyter Notebook is running at: +http://(deepfacelab-cpu or 127.0.0.1):8888/?token=your token +``` + +### 6. Open a new terminal to run DeepFaceLab in /notebooks + +``` +$ docker exec -it deepfacelab-cpu bash +$ ls -A +``` + +### 7. Use jupyter in deepfacelab-cpu bash + +``` +$ jupyter notebook list +``` +or just open it on your browser `http://127.0.0.1:8888/?token=your_token` + +PS: You can run python with jupyter.However,we just run our code in bash.It's simpler and clearer.Now the **DRE** (DeepFaceLab Runtime Environment) almost builded. + +### 8. Stop or Kill Docker Container + +``` +$ docker stop deepfacelab-cpu +$ docker kill deepfacelab-cpu +``` + +### 9. Start Docker Container + +``` +# start docker container +$ docker start deepfacelab-cpu +# open bash to run deepfacelab +$ docker exec -it deepfacelab-cpu bash +``` + +PS: `STEP 8` or `STEP 9` just show you the way to stop and start **DRE**. + +### 10. enjoy it + +``` +# make sure you current directory is `/notebooks` +$ pwd +# make sure all `DeepFaceLab` code is in current path `/notebooks` +$ ls -a +# read and write permission +$ chmod +x cpu.sh +# run `DeepFaceLab` +$ ./cpu.sh +``` + +### Details with `DeepFaceLab` + +#### 1. Concepts + +![SRC](doc/DF_Cage_0.jpg) + +In our Case,**Cage**'s Face is **SRC Face**,and **Trump**'s Face is **DST Face**.and finally we get the **Result** below. + +![Result](doc/merged-face.jpg) + +So,before you run `./cpu.sh`.You should be aware of this. + +#### 2. Use MTCNN(mt) to extract faces +Do not use DLIB extractor in CPU mode + +#### 3. Best practice for SORT +1) delete first unsorted aligned groups of images what you can to delete. + +2) use `hist` + +#### 4. Use `H64 model` to train and convert +Only H64 model reasonable to train on home CPU.You can choice other model like **H128 (3GB+)** | **DF (5GB+)** and so on ,it depends entirely on your CPU performance. + +#### 5. execute the script below one by one + +``` +root@deepfacelab-cpu:/notebooks# ./cpu.sh +1) clear workspace 7) data_dst sort by hist +2) extract PNG from video data_src 8) train +3) data_src extract faces 9) convert +4) data_src sort 10) converted to mp4 +5) extract PNG from video data_dst 11) quit +6) data_dst extract faces +Please enter your choice: +``` + +#### 6. Put all videos in `workspace` directory +``` +. +├── data_dst +├── data_src +├── dst.mp4 +├── model +└── src.mp4 + +3 directories, 2 files +``` diff --git a/Dockerfile.cpu b/Dockerfile.cpu new file mode 100644 index 0000000..dc1b8d1 --- /dev/null +++ b/Dockerfile.cpu @@ -0,0 +1,17 @@ +FROM tensorflow/tensorflow:latest-py3 + +RUN apt-get update -qq -y \ + && apt-get install -y libsm6 libxrender1 libxext-dev python3-tk\ + && apt-get install -y ffmpeg \ + && apt-get install -y wget \ + && apt-get install -y vim \ + && apt-get install -y git \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements-cpu-docker.txt /opt/ +RUN pip3 install cmake +RUN pip3 --no-cache-dir install -r /opt/requirements-cpu-docker.txt && rm /opt/requirements-cpu-docker.txt + +WORKDIR "/notebooks" +CMD ["/run_jupyter.sh", "--allow-root"] diff --git a/README.md b/README.md index 1794088..bf29df7 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,9 @@ Video tutorial: https://www.youtube.com/watch?v=K98nTNjXkq8 Windows 10 consumes % of VRAM even if card unused for video output. +### For Mac Users +Check out [DockerCPU.md](DockerCPU.md) for more detailed instructions. + ### **Problem of the year**: algorithm of overlaying neural face onto video face located in ConverterMasked.py. diff --git a/cpu.sh b/cpu.sh new file mode 100755 index 0000000..80c88e9 --- /dev/null +++ b/cpu.sh @@ -0,0 +1,71 @@ +#!/bin/bash +INTERNAL_DIR=`pwd` +WORKSPACE=$INTERNAL_DIR/workspace +PYTHON=`which python` + +PS3="Please enter your choice: " +options=("clear workspace" "extract PNG from video data_src" "data_src extract faces" "data_src sort" "extract PNG from video data_dst" "data_dst extract faces" "data_dst sort by hist" "train" "convert" "converted to mp4" "quit") +select opt in "${options[@]}" +do + case $opt in + "clear workspace" ) + echo -n "Clean up workspace? [Y/n] "; read workspace_ans + if [ "$workspace_ans" == "Y" ] || [ "$workspace_ans" == "y" ]; then + rm -rf $WORKSPACE + mkdir -p $WORKSPACE/data_src/aligned + mkdir -p $WORKSPACE/data_dst/aligned + mkdir -p $WORKSPACE/model + echo "Workspace has been successfully cleaned!" + fi + ;; + "extract PNG from video data_src" ) + echo -n "File name: "; read filename + echo -n "FPS: "; read fps + if [ -z "$fps" ]; then fps="25"; fi + ffmpeg -i $WORKSPACE/$filename -r $fps $WORKSPACE/data_src/%04d.png -loglevel error + ;; + "data_src extract faces" ) + echo -n "Detector? [mt | manual] "; read detector + $PYTHON $INTERNAL_DIR/main.py extract --input-dir $WORKSPACE/data_src --output-dir $WORKSPACE/data_src/aligned --detector $detector --debug --cpu-only + ;; + "data_src sort" ) + echo -n "Sort by? [blur | brightness | face-yaw | hue | hist | hist-blur | hist-dissim] "; read sort_method + $PYTHON $INTERNAL_DIR/main.py sort --input-dir $WORKSPACE/data_src/aligned --by $sort_method + ;; + "extract PNG from video data_dst" ) + echo -n "File name: "; read filename + echo -n "FPS: "; read fps + if [ -z "$fps" ]; then fps="25"; fi + ffmpeg -i $WORKSPACE/$filename -r $fps $WORKSPACE/data_dst/%04d.png -loglevel error + ;; + "data_dst extract faces" ) + echo -n "Detector? [mt | manual] "; read detector + $PYTHON $INTERNAL_DIR/main.py extract --input-dir $WORKSPACE/data_dst --output-dir $WORKSPACE/data_dst/aligned --detector $detector --debug --cpu-only + ;; + "data_dst sort by hist" ) + $PYTHON $INTERNAL_DIR/main.py sort --input-dir $WORKSPACE/data_dst/aligned --by hist + ;; + "train" ) + echo -n "Model? [ H64 (2GB+) | H128 (3GB+) | DF (5GB+) | LIAEF128 (5GB+) | LIAEF128YAW (5GB+) | MIAEF128 (5GB+) | AVATAR (4GB+) ] "; read model + echo -n "Show Preview? [Y/n] "; read preview + if [ "$preview" == "Y" ] || [ "$preview" == "y" ]; then preview="--preview"; else preview=""; fi + $PYTHON $INTERNAL_DIR/main.py train --training-data-src-dir $WORKSPACE/data_src/aligned --training-data-dst-dir $WORKSPACE/data_dst/aligned --model-dir $WORKSPACE/model --model $model --cpu-only $preview + ;; + "convert" ) + echo -n "Model? [ H64 (2GB+) | H128 (3GB+) | DF (5GB+) | LIAEF128 (5GB+) | LIAEF128YAW (5GB+) | MIAEF128 (5GB+) | AVATAR(4GB+) ] "; read model + $PYTHON $INTERNAL_DIR/main.py convert --input-dir $WORKSPACE/data_dst --output-dir $WORKSPACE/data_dst/merged --aligned-dir $WORKSPACE/data_dst/aligned --model-dir $WORKSPACE/model --model $model --ask-for-params --cpu-only + ;; + "converted to mp4" ) + echo -n "File name of destination video: "; read filename + echo -n "FPS: "; read fps + if [ -z "$fps" ]; then fps="25"; fi + ffmpeg -y -i $WORKSPACE/$filename -r $fps -i "$WORKSPACE/data_dst/merged/%04d.png" -map 0:a? -map 1:v -r $fps -c:v libx264 -b:v 8M -pix_fmt yuv420p -c:a aac -strict -2 -b:a 192k -ar 48000 "$WORKSPACE/result.mp4" -loglevel error + ;; + "quit" ) + break + ;; + *) + echo "Invalid choice!" + ;; + esac +done diff --git a/doc/merged-face.jpg b/doc/merged-face.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27ed67fa686b68bead7cfb146e368d374461a64e GIT binary patch literal 14771 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