forked from leblancfg/autocrop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
56 changed files
with
34,592 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
include README.md LICENSE | ||
include autocrop/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
Metadata-Version: 1.1 | ||
Name: autocrop | ||
Version: 0.1 | ||
Summary: Automatically crops faces from batches of pictures | ||
Home-page: https://github.com/leblancfg/autocrop | ||
Author: Francois Leblanc | ||
Author-email: [email protected] | ||
License: MIT | ||
Description: | ||
# autocrop | ||
|
||
![obama_crop](https://cloud.githubusercontent.com/assets/15659410/10975709/3e38de48-83b6-11e5-8885-d95da758ca17.png) | ||
|
||
Basic script using openCV, that automatically detects and crops faces from batches of photos. | ||
|
||
Perfect for batch work for ID cards or profile pictures, will output 500px wide square images, centered around the biggest face detected. It can also add a touch of auto gamma correction. | ||
|
||
## Installation | ||
Simple! | ||
|
||
The script will process all `.jpg` files in the `/photos` directory. The cropped files are placed in `photos/crop`, and originals are moved to `photos/bkp`. | ||
|
||
If it can't find a face in the picture, it'll simply leave it in `/photos`. | ||
|
||
### conda | ||
The easiest way to run *autocrop* is to use the [Anaconda Python distribution](https://www.anaconda.com/download/) and run the following: | ||
|
||
git clone https://github.com/leblancfg/autocrop | ||
conda install --channel conda-forge --file requirements.txt | ||
|
||
Move your pictures to be cropped in the *photos* directory, then run the script with: | ||
|
||
cd autocrop | ||
python autocrop.py | ||
|
||
If running on Windows, this is by far the sanest way to approach this problem. Also, installing Anaconda doesn't require admin privileges on Windows. | ||
|
||
### pip | ||
Otherwise, binaries for the Python-only bindings for OpenCV have recently been made available through pip, which makes installation a breeze. | ||
|
||
git clone https://github.com/leblancfg/autocrop | ||
pip install numpy opencv-python | ||
|
||
Move your pictures to be cropped in the *photos* directory, then run the script with: | ||
|
||
cd autocrop | ||
python autocrop.py | ||
|
||
## Versions | ||
The script works on openCV 2.4.9 and python 2.7+ and 3+. It has not been tested otherwise. For now, it also artificially restricts filetype as jpg and output size as 500px. These values can easily be tweaked in the header in `autocrop.py`. | ||
|
||
## More Info | ||
Check out: | ||
* http://docs.opencv.org/master/d7/d8b/tutorial_py_face_detection.html#gsc.tab=0 | ||
* http://docs.opencv.org/master/d5/daf/tutorial_py_histogram_equalization.html#gsc.tab=0 | ||
|
||
Adapted from: | ||
* http://photo.stackexchange.com/questions/60411/how-can-i-batch-crop-based-on-face-location | ||
|
||
## TODO | ||
Pull requests welcome! I don't see major feature additions in the future, but proper | ||
* [ ] Create PyPI and conda-forge packages so that it can be directly pip- or conda-installable. | ||
* [ ] Split off into smaller functions, and write unit tests. | ||
* [ ] Handle input filetypes for `*.bmp`, `*.dib`, `*.jp2`, `*.png`, `*.webp`, `*.pbm`, `*.pgm`, `*.ppm`, `*.sr`, `*.ras`, `*.tiff`, `*.tif`. | ||
* [ ] Handle output image size. | ||
* [ ] Handle CLI input: `$ autocrop [-w width] [-h height] [-i input-folder] [-o output-folder] [--passport=<country>]` | ||
|
||
Platform: UNKNOWN | ||
Classifier: License :: OSI Approved :: MIT License | ||
Classifier: Programming Language :: Python | ||
Classifier: Programming Language :: Python :: 2.6 | ||
Classifier: Programming Language :: Python :: 2.7 | ||
Classifier: Programming Language :: Python :: 3 | ||
Classifier: Programming Language :: Python :: 3.3 | ||
Classifier: Programming Language :: Python :: 3.4 | ||
Classifier: Programming Language :: Python :: 3.5 | ||
Classifier: Programming Language :: Python :: 3.6 | ||
Classifier: Programming Language :: Python :: Implementation :: CPython | ||
Classifier: Programming Language :: Python :: Implementation :: PyPy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
LICENSE | ||
MANIFEST.in | ||
README.md | ||
setup.cfg | ||
setup.py | ||
autocrop/.__init__.py.swp | ||
autocrop/.autocrop.py.swp | ||
autocrop/__init__.py | ||
autocrop/__version__.py | ||
autocrop/autocrop.py | ||
autocrop/haarcascade_frontalface_default.xml | ||
autocrop.egg-info/PKG-INFO | ||
autocrop.egg-info/SOURCES.txt | ||
autocrop.egg-info/dependency_links.txt | ||
autocrop.egg-info/entry_points.txt | ||
autocrop.egg-info/requires.txt | ||
autocrop.egg-info/top_level.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[console_scripts] | ||
autocrop = autocrop:cli | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
numpy | ||
opencv-python |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
autocrop |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import argparse | ||
import os | ||
import sys | ||
|
||
|
@@ -13,3 +14,4 @@ | |
|
||
if __name__ == '__main__': | ||
cli() | ||
|
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
__title__ = 'autocrop' | ||
__description__ = 'Automatically crops faces from batches of pictures' | ||
__author__ = 'François Leblanc' | ||
__version__ = '0.1' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:', path) | ||
|
||
main(args.path, args.height, args.width) | ||
|
Oops, something went wrong.