https://github.com/artificialzeng/torchlm

๐Ÿ’ŽA high level pipeline for face landmarks detection, it supports training, evaluating, exporting, inference(Python/C++) and 100+ data augmentations, can easily install via pip.

https://github.com/artificialzeng/torchlm

Science Score: 10.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • โ—‹
    CITATION.cff file
  • โ—‹
    codemeta.json file
  • โ—‹
    .zenodo.json file
  • โ—‹
    DOI references
  • โœ“
    Academic publication links
    Links to: arxiv.org
  • โ—‹
    Academic email domains
  • โ—‹
    Institutional organization owner
  • โ—‹
    JOSS paper metadata
  • โ—‹
    Scientific vocabulary similarity
    Low similarity (11.5%) to scientific vocabulary
Last synced: 10 months ago · JSON representation

Repository

๐Ÿ’ŽA high level pipeline for face landmarks detection, it supports training, evaluating, exporting, inference(Python/C++) and 100+ data augmentations, can easily install via pip.

Basic Info
Statistics
  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • Open Issues: 0
  • Releases: 0
Fork of DefTruth/torchlm
Created almost 3 years ago · Last pushed almost 3 years ago
Metadata Files
Readme License

README.md

torchlm-logo

English | Data Augmentations API Docs | ZhiHu Page | Pypi Downloads

๐Ÿค— Introduction

torchlm is aims to build a high level pipeline for face landmarks detection, it supports training, evaluating, exporting, inference(Python/C++) and 100+ data augmentations, can easily install via pip.

โค๏ธ Star ๐ŸŒŸ๐Ÿ‘†๐Ÿป this repo to support me if it does any helps to you, thanks ~

๐Ÿ‘‹ Core Features

  • High level pipeline for training and inference.
  • Provides 30+ native landmarks data augmentations.
  • Can bind 80+ transforms from torchvision and albumentations with one-line-code.
  • Support PIPNet, YOLOX, ResNet, MobileNet and ShuffleNet for face landmarks detection.

๐Ÿ†• What's New

๐Ÿ”ฅ๐Ÿ”ฅPerformance(@NME)

|Model|Backbone|Head|300W|COFW|AFLW|WFLW|Download| |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |PIPNet|MobileNetV2|Heatmap+Regression+NRM|3.40|3.43|1.52|4.79| link| |PIPNet|ResNet18|Heatmap+Regression+NRM|3.36|3.31|1.48|4.47| link| |PIPNet|ResNet50|Heatmap+Regression+NRM|3.34|3.18|1.44|4.48| link| |PIPNet|ResNet101|Heatmap+Regression+NRM|3.19|3.08|1.42|4.31| link|

๐Ÿ› ๏ธInstallation

you can install torchlm directly from pypi. shell pip install torchlm>=0.1.6.10 # or install the latest pypi version `pip install torchlm` pip install torchlm>=0.1.6.10 -i https://pypi.org/simple/ # or install from specific pypi mirrors use '-i' or install from source if you want the latest torchlm and install it in editable mode with -e. shell git clone --depth=1 https://github.com/DefTruth/torchlm.git cd torchlm && pip install -e .

๐ŸŒŸ๐ŸŒŸData Augmentation

torchlm provides 30+ native data augmentations for landmarks and can bind with 80+ transforms from torchvision and albumentations. The layout format of landmarks is xy with shape (N, 2).

Use almost 30+ native transforms from torchlm directly python import torchlm transform = torchlm.LandmarksCompose([ torchlm.LandmarksRandomScale(prob=0.5), torchlm.LandmarksRandomMask(prob=0.5), torchlm.LandmarksRandomBlur(kernel_range=(5, 25), prob=0.5), torchlm.LandmarksRandomBrightness(prob=0.), torchlm.LandmarksRandomRotate(40, prob=0.5, bins=8), torchlm.LandmarksRandomCenterCrop((0.5, 1.0), (0.5, 1.0), prob=0.5) ])

Also, a user-friendly API build_default_transform is available to build a default transform pipeline. python transform = torchlm.build_default_transform( input_size=(input_size, input_size), mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], force_norm_before_mean_std=True, # img/=255. first rotate=30, keep_aspect=False, to_tensor=True # array -> Tensor & HWC -> CHW ) See transforms.md for supported transforms sets and more example can be found at test/transforms.py.

๐Ÿ’ก more details about transform in torchlm **torchlm** provides **30+** native data augmentations for landmarks and can **bind** with **80+** transforms from torchvision and albumentations through **torchlm.bind** method. The layout format of landmarks is `xy` with shape `(N, 2)`, `N` denotes the number of the input landmarks. Further, **torchlm.bind** provide a `prob` param at bind-level to force any transform or callable be a random-style augmentation. The data augmentations in **torchlm** are `safe` and `simplest`. Any transform operations at runtime cause landmarks outside will be auto dropped to keep the number of landmarks unchanged. Yes, is ok if you pass a Tensor to a np.ndarray-like transform, **torchlm** will automatically be compatible with different data types and then wrap it back to the original type through a **autodtype** wrapper.
bind 80+ torchvision and albumentations's transforms **NOTE**: Please install albumentations first if you want to bind albumentations's transforms. If you have the conflict problem between different installed version of opencv (opencv-python and opencv-python-headless, `ablumentations` need opencv-python-headless). Please uninstall the opencv-python and opencv-python-headless first, and then reinstall albumentations. See [albumentations#1140](https://github.com/albumentations-team/albumentations/issues/1140) for more details. ```shell # first uninstall conflict opencvs pip uninstall opencv-python pip uninstall opencv-python-headless pip uninstall albumentations # if you have installed albumentations pip install albumentations # then reinstall albumentations, will also install deps, e.g opencv ``` Then, check if albumentations is available. ```python torchlm.albumentations_is_available() # True or False ``` ```python transform = torchlm.LandmarksCompose([ torchlm.bind(torchvision.transforms.GaussianBlur(kernel_size=(5, 25)), prob=0.5), torchlm.bind(albumentations.ColorJitter(p=0.5)) ]) ```
bind custom callable array or Tensor transform functions ```python # First, defined your custom functions def callable_array_noop(img: np.ndarray, landmarks: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: # do some transform here ... return img.astype(np.uint32), landmarks.astype(np.float32) def callable_tensor_noop(img: Tensor, landmarks: Tensor) -> Tuple[Tensor, Tensor]: # do some transform here ... return img, landmarks ``` ```python # Then, bind your functions and put it into the transforms pipeline. transform = torchlm.LandmarksCompose([ torchlm.bind(callable_array_noop, bind_type=torchlm.BindEnum.Callable_Array), torchlm.bind(callable_tensor_noop, bind_type=torchlm.BindEnum.Callable_Tensor, prob=0.5) ]) ```
some global debug setting for torchlm's transform * setup logging mode as `True` globally might help you figure out the runtime details ```python # some global setting torchlm.set_transforms_debug(True) torchlm.set_transforms_logging(True) torchlm.set_autodtype_logging(True) ``` some detail information will show you at each runtime, the infos might look like ```shell LandmarksRandomScale() AutoDtype Info: AutoDtypeEnum.Array_InOut LandmarksRandomScale() Execution Flag: False BindTorchVisionTransform(GaussianBlur())() AutoDtype Info: AutoDtypeEnum.Tensor_InOut BindTorchVisionTransform(GaussianBlur())() Execution Flag: True BindAlbumentationsTransform(ColorJitter())() AutoDtype Info: AutoDtypeEnum.Array_InOut BindAlbumentationsTransform(ColorJitter())() Execution Flag: True BindTensorCallable(callable_tensor_noop())() AutoDtype Info: AutoDtypeEnum.Tensor_InOut BindTensorCallable(callable_tensor_noop())() Execution Flag: False Error at LandmarksRandomTranslate() Skip, Flag: False Error Info: LandmarksRandomTranslate() have 98 input landmarks, but got 96 output landmarks! LandmarksRandomTranslate() Execution Flag: False ``` * Execution Flag: True means current transform was executed successful, False means it was not executed because of the random probability or some Runtime Exceptions(torchlm will should the error infos if debug mode is True). * AutoDtype Info: * Array_InOut means current transform need a np.ndnarray as input and then output a np.ndarray. * Tensor_InOut means current transform need a torch Tensor as input and then output a torch Tensor. * Array_In means current transform needs a np.ndarray input and then output a torch Tensor. * Tensor_In means current transform needs a torch Tensor input and then output a np.ndarray. Yes, is ok if you pass a Tensor to a np.ndarray-like transform, **torchlm** will automatically be compatible with different data types and then wrap it back to the original type through a **autodtype** wrapper.

๐ŸŽ‰๐ŸŽ‰Training

In torchlm, each model have two high level and user-friendly APIs named apply_training and apply_freezing for training. apply_training handle the training process and apply_freezing decide whether to freeze the backbone for fune-tuning.

Quick Start๐Ÿ‘‡

Here is an example of PIPNet. You can freeze backbone before fine-tuning through apply_freezing.

```python from torchlm.models import pipnet

will auto download pretrained weights from latest release if pretrained=True

model = pipnet(backbone="resnet18", pretrained=True, numnb=10, numlms=98, netstride=32, inputsize=256, meanfacetype="wflw", backbonepretrained=True) model.applyfreezing(backbone=True) model.applytraining( annotationpath="../data/WFLW/converted/train.txt", # or fine-tuning your custom data numepochs=10, learningrate=0.0001, savedir="./save/pipnet", saveprefix="pipnet-wflw-resnet18", saveinterval=1, logginginterval=1, device="cuda", coordinatesalreadynormalized=True, batchsize=16, numworkers=4, shuffle=True ) ```
Please jump to the entry point of the function for the detail documentations of **apply
training** API for each defined models in torchlm, e.g pipnet/_impls.py#L166. You might see some logs if the training process is running:

```shell Parameters for DataLoader: {'batchsize': 16, 'numworkers': 4, 'shuffle': True} Built _PIPTrainDataset: train count is 7500 !

Epoch 0/9

[Epoch 0/9, Batch 1/468] [Epoch 0/9, Batch 2/468] [Epoch 0/9, Batch 3/468] [Epoch 0/9, Batch 4/468] [Epoch 0/9, Batch 5/468] [Epoch 0/9, Batch 6/468] ... [Epoch 0/9, Batch 33/468] ```

Dataset Format๐Ÿ‘‡

The annotation_path parameter is denotes the path to a custom annotation file, the format must be: shell "img0_path x0 y0 x1 y1 ... xn-1,yn-1" "img1_path x0 y0 x1 y1 ... xn-1,yn-1" "img2_path x0 y0 x1 y1 ... xn-1,yn-1" "img3_path x0 y0 x1 y1 ... xn-1,yn-1" ... If the label in annotationpath is already normalized by image size, please set `coordinatesalreadynormalizedasTrueinapplytrainingAPI. shell "img0_path x0/w y0/h x1/w y1/h ... xn-1/w,yn-1/h" "img1_path x0/w y0/h x1/w y1/h ... xn-1/w,yn-1/h" "img2_path x0/w y0/h x1/w y1/h ... xn-1/w,yn-1/h" "img3_path x0/w y0/h x1/w y1/h ... xn-1/w,yn-1/h" ... ` Here is an example of WFLW to show you how to prepare the dataset, also see test/data.py.

Additional Custom Settings๐Ÿ‘‹

Some models in torchlm support additional custom settings beyond the num_lms of your custom dataset. For example, PIPNet also need to set a custom meanface generated by your custom dataset. Please jump the source code of each defined model in torchlm for the details about additional custom settings to get more flexibilities of training or fine-tuning processes. Here is an example of How to train PIPNet in your own dataset with custom meanface setting?

Set up your custom meanface and nearest-neighbor landmarks through pipnet.set_custom_meanface method, this method will calculate the Euclidean Distance between different landmarks in meanface and will auto set up the nearest-neighbors for each landmark. NOTE: The PIPNet will reshape the detection headers if the number of landmarks in custom dataset is not equal with the num_lms you initialized.

python def set_custom_meanface(custom_meanface_file_or_string: str) -> bool: """ :param custom_meanface_file_or_string: a long string or a file contains normalized or un-normalized meanface coords, the format is "x0,y0,x1,y1,x2,y2,...,xn-1,yn-1". :return: status, True if successful. """ Also, a generate_meanface API is available in torchlm to help you get meanface in custom dataset. ```python

generate your custom meanface.

custommeanface, custommeanfacestring = torchlm.data.annotools.generatemeanface( annotationpath="../data/WFLW/converted/train.txt", coordinatesalready_normalized=True)

check your generated meanface.

renderedmeanface = torchlm.data.annotools.drawmeanface( meanface=custommeanface, coordinatesalreadynormalized=True) cv2.imwrite("./logs/wflwmeanface.jpg", rendered_meanface)

setting up your custom meanface

model.setcustommeanface(custommeanfacefileorstring=custommeanfacestring) ```

Benchmarks Dataset Converters๐Ÿ‘‡

In torchlm, some pre-defined dataset converters for common use benchmark datasets are available, such as 300W, COFW, WFLW and AFLW. These converters will help you to convert the common use dataset to the standard annotation format that torchlm need. Here is an example of WFLW.
```python from torchlm.data import LandmarksWFLWConverter

setup your path to the original downloaded dataset from official

converter = LandmarksWFLWConverter( datadir="../data/WFLW", savedir="../data/WFLW/converted", extend=0.2, rebuild=True, targetsize=256, keepaspect=False, forcenormalize=True, forceabsolutepath=True ) converter.convert() converter.show(count=30) # show you some converted images with landmarks for debugging Then, the output's layout in `../data/WFLW/converted` would be look like: shell โ”œโ”€โ”€ image โ”‚ย ย  โ”œโ”€โ”€ test โ”‚ย ย  โ””โ”€โ”€ train โ”œโ”€โ”€ show โ”‚ย ย  โ”œโ”€โ”€ 16--AwardCeremony16AwardCeremonyAwardsCeremony16589x456y91.jpg โ”‚ย ย  โ”œโ”€โ”€ 20--FamilyGroup20FamilyGroupFamilyGroup20_118x458y58.jpg ... โ”œโ”€โ”€ test.txt โ””โ”€โ”€ train.txt ```

๐Ÿ›ธ๐Ÿšตโ€๏ธ Inference

C++ APIs๐Ÿ‘€

The ONNXRuntime(CPU/GPU), MNN, NCNN and TNN C++ inference of torchlm will be release in lite.ai.toolkit. Here is an example of 1000 Facial Landmarks Detection using FaceLandmarks1000. Download model from Model-Zoo2.

```C++

include "lite/lite.h"

static void testdefault() { std::string onnxpath = "../../../hub/onnx/cv/FaceLandmark1000.onnx"; std::string testimgpath = "../../../examples/lite/resources/testlitefacelandmarks0.png"; std::string saveimgpath = "../../../logs/testlitefacelandmarks1000.jpg";

auto *facelandmarks1000 = new lite::cv::face::align::FaceLandmark1000(onnx_path);

lite::types::Landmarks landmarks; cv::Mat imgbgr = cv::imread(testimgpath); facelandmarks1000->detect(imgbgr, landmarks); lite::utils::drawlandmarksinplace(imgbgr, landmarks); cv::imwrite(saveimgpath, imgbgr);

delete facelandmarks1000; } ``` The output is:

More classes for face alignment (68 points, 98 points, 106 points, 1000 points) c++ auto *align = new lite::cv::face::align::PFLD(onnx_path); // 106 landmarks, 1.0Mb only! auto *align = new lite::cv::face::align::PFLD98(onnx_path); // 98 landmarks, 4.8Mb only! auto *align = new lite::cv::face::align::PFLD68(onnx_path); // 68 landmarks, 2.8Mb only! auto *align = new lite::cv::face::align::MobileNetV268(onnx_path); // 68 landmarks, 9.4Mb only! auto *align = new lite::cv::face::align::MobileNetV2SE68(onnx_path); // 68 landmarks, 11Mb only! auto *align = new lite::cv::face::align::FaceLandmark1000(onnx_path); // 1000 landmarks, 2.0Mb only! auto *align = new lite::cv::face::align::PIPNet98(onnx_path); // 98 landmarks, CVPR2021! auto *align = new lite::cv::face::align::PIPNet68(onnx_path); // 68 landmarks, CVPR2021! auto *align = new lite::cv::face::align::PIPNet29(onnx_path); // 29 landmarks, CVPR2021! auto *align = new lite::cv::face::align::PIPNet19(onnx_path); // 19 landmarks, CVPR2021!
More details of C++ APIs, please check lite.ai.toolkit.

Python APIs๐Ÿ‘‡

In torchlm, we provide pipelines for deploying models with PyTorch and ONNXRuntime. A high level API named runtime.bind can bind face detection and landmarks models together, then you can run the runtime.forward API to get the output landmarks and bboxes. Here is an example of PIPNet. Pretrained weights of PIPNet, Download.

Inference on PyTorch Backend

```python import torchlm from torchlm.tools import faceboxesv2 from torchlm.models import pipnet

torchlm.runtime.bind(faceboxesv2(device="cpu")) # set device="cuda" if you want to run with CUDA

set map_location="cuda" if you want to run with CUDA

torchlm.runtime.bind( pipnet(backbone="resnet18", pretrained=True,
numnb=10, numlms=98, netstride=32, inputsize=256, meanfacetype="wflw", maplocation="cpu", checkpoint=None) ) # will auto download pretrained weights from latest release if pretrained=True landmarks, bboxes = torchlm.runtime.forward(image) image = torchlm.utils.drawbboxes(image, bboxes=bboxes) image = torchlm.utils.drawlandmarks(image, landmarks=landmarks) ```

Inference on ONNXRuntime Backend

```python import torchlm from torchlm.runtime import faceboxesv2ort, pipnetort

torchlm.runtime.bind(faceboxesv2ort()) torchlm.runtime.bind( pipnetort(onnxpath="pipnetresnet18.onnx",numnb=10, numlms=98, netstride=32,inputsize=256, meanfacetype="wflw") ) landmarks, bboxes = torchlm.runtime.forward(image) image = torchlm.utils.drawbboxes(image, bboxes=bboxes) image = torchlm.utils.drawlandmarks(image, landmarks=landmarks) ```

<img src='docs/assets/pipnet300WCELEBAmodel.gif' height="200px" width="400px">

๐Ÿค ๐ŸŽฏ Evaluating

In torchlm, each model have a high level and user-friendly API named apply_evaluating for evaluation. This method will calculate the NME, FR and AUC for eval dataset. Here is an example of PIPNet.

```python from torchlm.models import pipnet

will auto download pretrained weights from latest release if pretrained=True

model = pipnet(backbone="resnet18", pretrained=True, numnb=10, numlms=98, netstride=32, inputsize=256, meanfacetype="wflw", backbonepretrained=True) NME, FR, AUC = model.applyevaluating( annotationpath="../data/WFLW/convertd/test.txt", normindices=[60, 72], # the indexes of two eyeballs. coordinatesalreadynormalized=True, evalnormalized_coordinates=False ) print(f"NME: {NME}, FR: {FR}, AUC: {AUC}") Then, you will get the **Performance(@NME@FR@AUC)** results. shell Built _PIPEvalDataset: eval count is 2500 ! Evaluating PIPNet: 100%|โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ| 2500/2500 [02:53<00:00, 14.45it/s] NME: 0.04453323229181989, FR: 0.04200000000000004, AUC: 0.5732673333333334 ```

โš™๏ธโš”๏ธ Exporting

In torchlm, each model have a high level and user-friendly API named apply_exporting for ONNX export. Here is an example of PIPNet.

```python from torchlm.models import pipnet

will auto download pretrained weights from latest release if pretrained=True

model = pipnet(backbone="resnet18", pretrained=True, numnb=10, numlms=98, netstride=32, inputsize=256, meanfacetype="wflw", backbonepretrained=True) model.applyexporting( onnxpath="./save/pipnet/pipnetresnet18.onnx", opset=12, simplify=True, outputnames=None # use default output names. ) Then, you will get a Static ONNX model file if the exporting process was done. shell ... %195 = Add(%259, %189) %196 = Relu(%195) %outputscls = Conv[dilations = [1, 1], group = 1, kernelshape = [1, 1], pads = [0, 0, 0, 0], strides = [1, 1]](%196, %clslayer.weight, %clslayer.bias) %outputsx = Conv[dilations = [1, 1], group = 1, kernelshape = [1, 1], pads = [0, 0, 0, 0], strides = [1, 1]](%196, %xlayer.weight, %xlayer.bias) %outputsy = Conv[dilations = [1, 1], group = 1, kernelshape = [1, 1], pads = [0, 0, 0, 0], strides = [1, 1]](%196, %ylayer.weight, %ylayer.bias) %outputsnbx = Convdilations = [1, 1], group = 1, kernel_shape = [1, 1], pads = [0, 0, 0, 0], strides = [1, 1] %outputsnby = Convdilations = [1, 1], group = 1, kernel_shape = [1, 1], pads = [0, 0, 0, 0], strides = [1, 1] return %outputscls, %outputsx, %outputsy, %outputsnbx, %outputsnb_y } Checking 0/3... Checking 1/3... Checking 2/3... ```

๐Ÿ“– Documentations

๐ŸŽ“ License

The code of torchlm is released under the MIT License.

โค๏ธ Contribution

Please consider โญ this repo if you like it, as it is the simplest way to support me.

๐Ÿ‘‹ Acknowledgement

Owner

  • Name: Dr. Artificialๆ›พๅฐๅฅ
  • Login: ArtificialZeng
  • Kind: user
  • Location: Beijing

LLM practitioner/engineer, AI/ML/DL Quant

GitHub Events

Total
Last Year