Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Showing the indexes of landmark points #480

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5500f6a
Adding a new module for aligning face images across different datasets
vmirly Nov 14, 2018
76b9d69
added new utility functions for reading/listing images in a directory
vmirly Nov 17, 2018
66c40ed
upd utility function to raise exception if file not found
vmirly Nov 17, 2018
b56a5b8
first version of EyepadAlign created
vmirly Nov 17, 2018
e33c4a7
added eyepad_align to __init__.py
vmirly Nov 17, 2018
0ebf7c6
requirement skimage added to ci/travis
vmirly Nov 17, 2018
09aa5eb
docs for the EyepadAlign class
vmirly Nov 17, 2018
4d726d3
upd EyepadAlign and appveyor installation
vmirly Nov 17, 2018
318156d
cache pip files
rasbt Nov 17, 2018
a52a172
add better caching and env specs
rasbt Nov 17, 2018
bf31bfc
add osx to build matrix
rasbt Nov 17, 2018
1c181bf
try python 3 with osx
rasbt Nov 17, 2018
e540ce3
fix osx tests
rasbt Nov 17, 2018
476de60
fix osx tests
rasbt Nov 18, 2018
5d14a69
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
eb336aa
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
7d6f175
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
225a032
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
3258731
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
b3a67b8
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
6b9965f
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
223deec
workaround since for some reason travis does not support python on osx
rasbt Nov 18, 2018
d0e5913
added nosetest for EyepadAlign
vmirly Nov 20, 2018
2b93d27
fix unit test for windows
rasbt Nov 20, 2018
5a9f7ef
check if windows returns identical jpg
rasbt Nov 20, 2018
9580d50
check if windows returns identical jpg
rasbt Nov 20, 2018
4d78a10
tolerance for imagio on windows
rasbt Nov 20, 2018
956baec
code fixes and cleanup
rasbt Nov 22, 2018
dba5bfe
Showing the indexes of landmarks in docs notebook
vmirly Dec 8, 2018
88e3532
Merge branch 'master' into vm-landmarks
rasbt Dec 8, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ install:
- pip install nose-exclude
- pip install dlib
- pip install imageio
- pip install scikit-image

test_script:
- nosetests -s -v --exclude-dir=mlxtend/plotting
Expand Down
45 changes: 37 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
sudo: required
language: python
language: generic

cache:
apt: true
directories:
- $HOME/.cache/pip
- $HOME/.ccache

dist: trusty

env:
global:
# Directory where tests are run from
- TEST_DIR=/tmp/sklearn
- OMP_NUM_THREADS=4
- OPENBLAS_NUM_THREADS=4

matrix:
include:
- python: 3.6
env: LATEST="false" IMAGE="true" COVERAGE="false" NUMPY_VERSION="1.14.6" SCIPY_VERSION="1.1.0" SKLEARN_VERSION="0.20.1" PANDAS_VERSION="0.23.4" MINICONDA_PYTHON_VERSION=3.7
- python: 3.6 # python 3.7
- os: linux
sudo: required
env: LATEST="false" IMAGE="true" COVERAGE="false" NUMPY_VERSION="1.15.1" SCIPY_VERSION="1.1.0" SKLEARN_VERSION="0.20" PANDAS_VERSION="0.23.4" MINICONDA_PYTHON_VERSION=3.6
- os: linux
sudo: required
env: LATEST="true" IMAGE="true" COVERAGE="true" NOTEBOOKS="true" MINICONDA_PYTHON_VERSION=3.7
- python: 3.6 # python 3.7
- os: osx
sudo: required
env: LATEST="true" IMAGE="true" COVERAGE="false" NOTEBOOKS="false" MINICONDA_PYTHON_VERSION=3.7
- os: linux
sudo: required
env: LATEST="true" IMAGE="false" COVERAGE="false" NOTEBOOKS="false" MINICONDA_PYTHON_VERSION=3.7


before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi

install:
- sudo apt-get update
- if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then sudo apt-get update; fi
- source ci/.travis_install.sh

script:
- bash ci/.travis_test.sh

after_success:
- if [[ "$COVERAGE" == "true" ]]; then coveralls || echo "failed"; fi

notifications:
email:
recipients:
- mail@sebastianraschka.com
on_success: always
on_failure: always
on_failure: always
18 changes: 15 additions & 3 deletions ci/.travis_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,22 @@

set -e

if [ "${TRAVIS_PYTHON_VERSION}" == "2.7" ]; then
wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh
if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then
if [ "${TRAVIS_PYTHON_VERSION}" == "2.7" ]; then

wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh
else
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
fi
else
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh
if [ "${TRAVIS_PYTHON_VERSION}" == "2.7" ]; then

wget https://repo.continuum.io/miniconda/Miniconda2-latest-MacOSX-x86_64.sh -O miniconda.sh
else
wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh
fi
fi


bash miniconda.sh -b -p $HOME/miniconda
export PATH="$HOME/miniconda/bin:$PATH"
Expand All @@ -32,6 +43,7 @@ conda install jupyter
if [ "${IMAGE}" = "true" ]; then
pip install dlib
pip install imageio
pip install scikit-image
fi

if [ "${COVERAGE}" = "true" ]; then
Expand Down
31 changes: 19 additions & 12 deletions ci/.travis_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@
set -e


if [[ "$COVERAGE" == "true" ]]; then
if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then

if [[ "$IMAGE" == "true" ]]; then
nosetests -s -v --with-coverage
else
nosetests -s -v --with-coverage --exclude-dir=mlxtend/image
fi

if [[ "$COVERAGE" == "true" ]]; then

if [[ "$IMAGE" == "true" ]]; then
nosetests -s -v --with-coverage
else
nosetests -s -v --with-coverage --exclude-dir=mlxtend/image
fi

else
if [[ "$IMAGE" == "true" ]]; then
nosetests -s -v
else
nosetests -s -v --exclude-dir=mlxtend/image
fi
fi

else
if [[ "$IMAGE" == "true" ]]; then
nosetests -s -v
else
nosetests -s -v --exclude-dir=mlxtend/image
fi
nosetests -s -v --exclude-dir=mlxtend/plotting
fi


if [[ "$NOTEBOOKS" == "true" ]]; then
cd docs
Expand Down
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ nav:
- user_guide/general_concepts/regularization-linear.md
- image:
- user_guide/image/extract_face_landmarks.md
- user_guide/image/eyepad_align.md
- math:
- user_guide/math/num_combinations.md
- user_guide/math/num_permutations.md
Expand Down
1 change: 1 addition & 0 deletions docs/sources/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The CHANGELOG for the current development version is available at

##### New Features

- Added a new transformer class to `mlxtend.image`, `EyepadAlign`, that aligns face images based on the location of the eyes. ([#466](https://github.com/rasbt/mlxtend/pull/466) by [Vahid Mirjalili](https://github.com/vmirly))
- Adds a new function, `mlxtend.evaluate.bias_variance_decomp` that decomposes the loss of a regressor or classifier into bias and variance terms. ([#470](https://github.com/rasbt/mlxtend/pull/470))
- Adds a `whitening` parameter to `PrincipalComponentAnalysis`, to optionally whiten the transformed data such that the features have unit variance. ([#475](https://github.com/rasbt/mlxtend/pull/475))

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
90 changes: 88 additions & 2 deletions docs/sources/user_guide/image/extract_face_landmarks.ipynb

Large diffs are not rendered by default.

305 changes: 305 additions & 0 deletions docs/sources/user_guide/image/eyepad_align.ipynb

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion mlxtend/image/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
# License: BSD 3 clause

from .extract_face_landmarks import extract_face_landmarks
from .eyepad_align import EyepadAlign

__all__ = ["extract_face_landmarks"]
__all__ = ["extract_face_landmarks", "EyepadAlign"]
120 changes: 120 additions & 0 deletions mlxtend/image/eyepad_align.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Sebastian Raschka 2014-2018
# contributor: Vahid Mirjalili
# mlxtend Machine Learning Library Extensions
#
# A class for transforming face images.
# Author: Sebastian Raschka <sebastianraschka.com>
#
# License: BSD 3 clause

from . import extract_face_landmarks
from .utils import listdir, read_image
from skimage.transform import warp, AffineTransform
import numpy as np

left_indx = np.array([36, 37, 38, 39, 40, 41])
right_indx = np.array([42, 43, 44, 45, 46, 47])


class EyepadAlign():
"""Class to align/transform face images to target landmarks,
based on the location of the eyes.

1. Scaling factor is computed based on distance between the
left and right eyes, so that the transformed image will
have the same eye distance as target.

2. Transformation is performed based on the eyes' middle point.

3. Finally, the transformed image is padded with zeros to match
the desired final image size.

Parameters
----------

eye_distance:

verbose :

Attributes
----------

target_landmarks_ :

target_width_ :

target_height_ :


Examples
--------


For more usage examples, please see
http://rasbt.github.io/mlxtend/user_guide/image/EyepadAlign/

"""
def __init__(self, eye_distance=None, verbose=0):
self.eye_distance = eye_distance
self.verbose = verbose

def fit(self, target_image=None,
target_img_dir=None, file_extensions='.jpg'):
"""Fits the target landmarks points:
a. if target_image is given, sets the target landmarks
to the landmarks of target image.
b. otherwise, if a target directory is given,
calculates the average landmarks for all face images
in the directory which will be set as the target landmark.

"""
if target_image is not None:
landmarks = extract_face_landmarks(target_image)
self.target_landmarks_ = landmarks
self.target_width_ = target_image.shape[1]
self.target_height_ = target_image.shape[0]

elif target_img_dir is not None:
file_list = listdir(target_img_dir, file_extensions)
if self.verbose >= 1:
print("Fitting the average facial landmarks "
"for {} face images ".format(len(file_list)))
landmarks_list = []
for f in file_list:
img = read_image(filename=f, path=target_img_dir)
landmarks = extract_face_landmarks(img)
if landmarks is not None:
landmarks_list.append(landmarks)
self.target_landmarks_ = np.mean(landmarks_list, axis=0)
self.target_width_ = img.shape[1]
self.target_height_ = img.shape[0]

def _cal_eye_properties(self, landmarks):
""" DOCUMENTATION """
left_eye = np.mean(landmarks[left_indx], axis=0)
right_eye = np.mean(landmarks[right_indx], axis=0)
eyes_mid_point = (left_eye + right_eye)/2.0
eye_distance = np.sqrt(np.sum(np.square(left_eye - right_eye)))

return eyes_mid_point, eye_distance

def transform(self, img):
""" DOCUMENTATION """
if self.eye_distance is None:
props = self._cal_eye_properties(self.target_landmarks_)
self.eyes_mid_point = props[0]
self.eye_distance = props[1]
landmarks = extract_face_landmarks(img)
if landmarks is None:
return
eyes_mid_point, eye_distance = self._cal_eye_properties(landmarks)

scale = self.eye_distance / eye_distance
tr = (self.eyes_mid_point/scale - eyes_mid_point)
tr = (int(tr[0]*scale), int(tr[1]*scale))

tform = AffineTransform(scale=(scale, scale), rotation=0, shear=0,
translation=tr)
h, w = self.target_height_, self.target_width_
img_tr = warp(img, tform.inverse, output_shape=(h, w))
return np.array(img_tr*255, dtype='uint8')
Binary file added mlxtend/image/tests/data/celeba-subset/000001.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mlxtend/image/tests/data/celeba-subset/000002.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mlxtend/image/tests/data/celeba-subset/000003.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mlxtend/image/tests/data/celeba-subset/000004.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 16 additions & 23 deletions mlxtend/image/tests/test_extract_face_landmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,24 @@ def test_jpg():

assert landmarks2.shape == (68, 2)

if os.name == 'nt':
true_vals = np.array([[85, 111],
[84, 120],
[85, 130],
[87, 140],
[91, 148],
[98, 155],
[105, 160],
[113, 164],
[120, 165],
[126, 162]], dtype=np.int32)
true_vals = np.array([[85, 110],
[85, 120],
[85, 130],
[87, 140],
[91, 148],
[97, 155],
[104, 160],
[112, 164],
[120, 165],
[125, 162]], dtype=np.int32)

if os.name == 'nt':
# on windows, imageio parses jpgs sometimes differently so pixel values
# maybe slightly different
assert np.sum(np.abs(landmarks2[:10] - true_vals) > 2) == 0
else:
true_vals = np.array([[85, 110],
[85, 120],
[85, 130],
[87, 140],
[91, 148],
[97, 155],
[104, 160],
[112, 164],
[120, 165],
[125, 162]], dtype=np.int32)

np.testing.assert_array_equal(landmarks2[:10], true_vals)
assert np.sum(np.abs(landmarks2[:10] - true_vals) > 0) == 0
np.testing.assert_array_equal(landmarks2[:10], true_vals)


def test_grayscale():
Expand Down
Loading