Skip to content

Commit

Permalink
Issue 008 (#10)
Browse files Browse the repository at this point in the history
* Created package folder

* PEP8 style fixes

* Basic CLI argument handling with argparse

* OpenCV now >3

* Fixed license info related to third party: OpenCV

* Added gitignore

* Ready for PyPI:test
  • Loading branch information
leblancfg authored Sep 28, 2017
1 parent 57c94fd commit c472156
Show file tree
Hide file tree
Showing 9 changed files with 880 additions and 126 deletions.
60 changes: 60 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Created by .gitignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
.idea/
.ropeproject/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Vim nonsense
*.swp
552 changes: 552 additions & 0 deletions LICENSE-3RD-PARTY.txt

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include README.md LICENSE
include autocrop/*
115 changes: 0 additions & 115 deletions autocrop.py

This file was deleted.

17 changes: 17 additions & 0 deletions autocrop/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import argparse
import os
import sys

# Inject vendored directory into system path.
v_path = os.path.abspath(os.path.sep.join([os.path.dirname(os.path.realpath(__file__)), 'vendor']))
sys.path.insert(0, v_path)

# Inject patched directory into system path.
v_path = os.path.abspath(os.path.sep.join([os.path.dirname(os.path.realpath(__file__)), 'patched']))
sys.path.insert(0, v_path)

from .autocrop import cli

if __name__ == '__main__':
cli()

4 changes: 4 additions & 0 deletions autocrop/__version__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__title__ = 'autocrop'
__description__ = 'Automatically crops faces from batches of pictures'
__author__ = 'François Leblanc'
__version__ = '0.1.1'
134 changes: 134 additions & 0 deletions autocrop/autocrop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from __future__ import print_function

import argparse
from contextlib import contextmanager
import cv2
import glob
import numpy as np
import os
import shutil
import sys

from .__version__ import __title__, __description__, __author__, __version__


# Internal variables
fixexp = True # Flag to fix underexposition
marker = False # Flag for gamma correct
INPUT_FILETYPES = ['*.jpg', '*.jpeg']
INCREMENT = 0.06
GAMMA_THRES = 0.001
GAMMA = 0.90
FACE_RATIO = 6

# Load XML Resource
cascFile= 'haarcascade_frontalface_default.xml'
d = os.path.dirname(sys.modules['autocrop'].__file__)
cascPath = os.path.join(d, cascFile)

# Define directory change within context
@contextmanager
def cd(newdir):
prevdir = os.getcwd()
os.chdir(os.path.expanduser(newdir))
try:
yield
finally:
os.chdir(prevdir)

# Define simple gamma correction fn
def gamma(img, correction):
img = cv2.pow(img/255.0, correction)
return np.uint8(img*255)

def main(path, fheight, fwidth):
"""Given path containing image files to process, will
1) copy them to `path/bkp`, and
2) create face-cropped versions and place them in `path/crop`
"""
errors = 0
# Create the haar cascade
faceCascade = cv2.CascadeClassifier(cascPath)

with cd(path):
files_grabbed = []
for files in INPUT_FILETYPES:
files_grabbed.extend(glob.glob(files))

for file in files_grabbed:
image = cv2.imread(file)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Scale the image
height, width = (image.shape[:2])
minface = int(np.sqrt(height*height + width*width) / 8)

# ====== Detect faces in the image ======
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(minface, minface),
flags = cv2.CASCADE_FIND_BIGGEST_OBJECT | cv2.CASCADE_DO_ROUGH_SEARCH
)

# Handle no faces
if len(faces) == 0:
print(' No faces can be detected in file {0}.'.format(str(file)))
errors += 1
break

# Copy to /bkp
shutil.copy(file, 'bkp')

# Make padding from probable biggest face
x, y, w, h = faces[-1]
pad = h / FACE_RATIO

# Make sure padding is contained within picture
while True: # decreases pad by 6% increments to fit crop into image. Can lead to very small faces.
if y-2*pad < 0 or y+h+pad > height or int(x-1.5*pad) < 0 or x+w+int(1.5*pad) > width:
pad = (1 - INCREMENT) * pad
else:
break

# Crop the image from the original
h1 = int(x-1.5*pad)
h2 = int(x+w+1.5*pad)
v1 = int(y-2*pad)
v2 = int(y+h+pad)
image = image[v1:v2, h1:h2]

# Resize the damn thing
image = cv2.resize(image, (fheight, fwidth), interpolation = cv2.INTER_AREA)

# ====== Dealing with underexposition ======
if fixexp == True:
# Check if under-exposed
uexp = cv2.calcHist([gray], [0], None, [256], [0,256])

if sum(uexp[-26:]) < GAMMA_THRES * sum(uexp) :
marker = True
image = gamma(image, GAMMA)

# Write cropfile
cropfilename = '{0}'.format(str(file))
cv2.imwrite(cropfilename, image)

# Move files to /crop
shutil.move(cropfilename, 'crop')

# Stop and print timer
print(' {0} files have been cropped'.format(len(files_grabbed) - errors))

def cli():
parser = argparse.ArgumentParser(description='Automatically crops faces from batches of pictures')
parser.add_argument('-p', '--path', default='photos', help='Path where images to crop are located')
parser.add_argument('-w', '--width', type=int, default=500, help='Width of the cropped files in pixels')
parser.add_argument('-H', '--height', type=int, default=500, help='Height of the cropped files in pixels')

args = parser.parse_args()
print('Processing images in folder:', args.path)

main(args.path, args.height, args.width)

File renamed without changes.
Loading

0 comments on commit c472156

Please sign in to comment.