Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Ovyerus committed Sep 2, 2017
0 parents commit b6531b4
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.vscode
token.json
__pycache__

gradient-fried.jpg
gradient.jpg
human-fried.jpg
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Ovyerus

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# deeppyer
![banner image](./banner.jpg)

deeppyer is an image deepfryer written in Python using [Pillow](https://python-pillow.org/
) and using the [Microsoft Facial Recognition API](https://azure.microsoft.com/services/cognitive-services/face/).

NOTE: This *requires* at least Python v3.6 in order to run.

## How to use
You can either use deeppyer as a module, or straight from the command line.

### Command line usage
```
$ python deeppyer.py -h
usage: deeppyer.py [-h] [-v] [-t TOKEN] [-o OUTPUT] FILE
Deepfry an image, optionally adding lens flares for eyes.
positional arguments:
FILE File to deepfry.
optional arguments:
-h, --help show this help message and exit
-v, --version Display program version.
-t TOKEN, --token TOKEN
Token to use for facial recognition API.
-o OUTPUT, --output OUTPUT
Filename to output to.
```

When a token is supplied, the script will automatically try to add lens flares for the eyes, otherwise it won't.

### Program usage
```py
from PIL import Image
import deeppyer, asyncio

async def main():
img = Image.open('./foo.jpg')
img = await deeppyer.deepfry(img, token='optional token')
img.save('./bar.jpg')

loop = asynio.get_event_loop()
loop.run_until_complete(main())
```

## API Documentation
#### `async deeppyer.deepfry(img: PIL.Image, *, token: str=None, url_base: str='westcentralus', session: aiohttp.ClientSession=None)`
Deepfry a given image.

**Arguments**
- *img* (PIL.Image) - Image to apply the deepfry effect on.
- *[token]* (str) - Token to use for the facial recognition API. Defining this will add lens flares to the eyes of a face in the image.
- *[url_base]* (str='westcentralus') - URL base to use for the facial recognition API. Can either be `westus`, `eastus2`, `westcentralus`, `westeurope` or `southeastasia`.
- *[session]* (aiohttp.ClientSession) - Optional session to use when making the request to the API. May make it a tad faster if you already have a created session, and allows you to give it your own options.

Returns:
`PIL.Image` - Deepfried image.

## Why?
¯\\\_(ツ)_/¯ Why not

## Contributing
If you wish to contribute something to this, go ahead! Just please try to keep your code similar-ish to mine, and make sure that it works with the tests.

## Testing
Create a file in [tests](./tests) called `token.json` with the following format:
```json
{
"token": "",
"url_base": ""
}
```
`token` is your token for the facial recognition API.
`url_base` is optional, and is for if your token is from a different region.

After that, simply run `test.py` and make sure that all the images output as you want.
Binary file added banner.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
108 changes: 108 additions & 0 deletions deeppyer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from PIL import Image, ImageOps, ImageEnhance
from io import BytesIO
import aiohttp, asyncio, math, argparse

async def deepfry(img: Image, *, token: str=None, url_base: str='westcentralus', session: aiohttp.ClientSession=None) -> Image:
"""
Deepfry an image.
img: PIL.Image - Image to deepfry.
[token]: str - Token to use for Microsoft facial recognition API. If this is not supplied, lens flares will not be added.
[url_base]: str = 'westcentralus' - API base to use. Only needed if your key's region is not `westcentralus`.
[session]: aiohttp.ClientSession - Optional session to use with API requests. If provided, may provide a bit more speed.
Returns: PIL.Image - Deepfried image.
"""
if token:
req_url = f'https://{url_base}.api.cognitive.microsoft.com/face/v1.0/detect?returnFaceId=false&returnFaceLandmarks=true' # WHY THE FUCK IS THIS SO LONG
headers = {
'Content-Type': 'application/octet-stream',
'Ocp-Apim-Subscription-Key': token,
'User-Agent': 'DeepPyer/1.0'
}
b = BytesIO()

img.save(b, 'jpeg')
b.seek(0)

if session:
async with session.post(req_url, headers=headers, data=b.read()) as r:
face_data = await r.json()
else:
async with aiohttp.ClientSession() as s, s.post(req_url, headers=headers, data=b.read()) as r:
face_data = await r.json()

if 'error' in face_data:
err = face_data['error']
code = err.get('code', err.get('statusCode'))
msg = err['message']

raise Exception(f'Error with Microsoft Face Recognition API\n{code}: {msg}')
elif not face_data:
raise Exception('No face detected. Note that only real pictures of faces should work.')

landmarks = face_data[0]['faceLandmarks']

# Get size and positions of eyes, and generate sizes for the flares
eye_left_width = math.ceil(landmarks['eyeLeftInner']['x'] - landmarks['eyeLeftOuter']['x'])
eye_left_height = math.ceil(landmarks['eyeLeftBottom']['y'] - landmarks['eyeLeftTop']['y'])
eye_left_corner = (landmarks['eyeLeftOuter']['x'], landmarks['eyeLeftTop']['y'])
flare_left_size = eye_left_height if eye_left_height > eye_left_width else eye_left_width
flare_left_size *= 4
eye_left_corner = tuple(math.floor(x - flare_left_size / 2.5 + 5) for x in eye_left_corner)

eye_right_width = math.ceil(landmarks['eyeRightOuter']['x'] - landmarks['eyeRightInner']['x'])
eye_right_height = math.ceil(landmarks['eyeRightBottom']['y'] - landmarks['eyeRightTop']['y'])
eye_right_corner = (landmarks['eyeRightInner']['x'], landmarks['eyeRightTop']['y'])
flare_right_size = eye_right_height if eye_right_height > eye_right_width else eye_right_width
flare_right_size *= 4
eye_right_corner = tuple(math.floor(x - flare_right_size / 2.5 + 5) for x in eye_right_corner)

# Crush image to hell and back
img = img.convert('RGB')
width, height = img.width, img.height
img = img.resize((int(width ** .75), int(height ** .75)), resample=Image.LANCZOS)
img = img.resize((int(width ** .88), int(height ** .88)), resample=Image.BILINEAR)
img = img.resize((int(width ** .9), int(height ** .9)), resample=Image.BICUBIC)
img = img.resize((width, height), resample=Image.BICUBIC)
img = ImageOps.posterize(img, 4)

# Generate red and yellow overlay for classic deepfry effect
r = img.split()[0]
r = ImageEnhance.Contrast(r).enhance(2.0)
r = ImageEnhance.Brightness(r).enhance(1.5)
r = ImageOps.colorize(r, (254, 0, 2), (255, 255, 15))

# Overlay red and yellow onto main image and sharpen the hell out of it
img = Image.blend(img, r, 0.75)
img = ImageEnhance.Sharpness(img).enhance(100.0)

if token:
# Copy and resize flares
flare = Image.open('./flare.png')
flare_left = flare.copy().resize((flare_left_size,) * 2, resample=Image.BILINEAR)
flare_right = flare.copy().resize((flare_right_size,) * 2, resample=Image.BILINEAR)

del flare

img.paste(flare_left, eye_left_corner, flare_left)
img.paste(flare_right, eye_right_corner, flare_right)

return img

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Deepfry an image, optionally adding lens flares for eyes.')
parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0', help='Display program version.')
parser.add_argument('-t', '--token', help='Token to use for facial recognition API.')
parser.add_argument('-o', '--output', help='Filename to output to.')
parser.add_argument('file', metavar='FILE', help='File to deepfry.')
args = parser.parse_args()

token = args.token
img = Image.open(args.file)
out = args.output or './deepfried.jpg'

loop = asyncio.get_event_loop()
img = loop.run_until_complete(deepfry(img, token=token))

img.save(out, 'jpeg')
2 changes: 2 additions & 0 deletions dependencies.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
aiohttp>=2.2.5
Pillow>=4.2.0
Binary file added flare.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from PIL import Image
import asyncio, math, json, deeppyer

async def main():
print('[tests] Generating gradient image...')
img = Image.new('RGB', (100, 100))

for y in range(100):
for x in range(100):
distanceToCenter = math.sqrt((x - 100 / 2) ** 2 + (y - 100 / 2) ** 2)
distanceToCenter = float(distanceToCenter) / (math.sqrt(2) * 100 / 2)

r = 0 * distanceToCenter + 255 * (1 - distanceToCenter)
g = 0 * distanceToCenter + 255 * (1 - distanceToCenter)
b = 0 * distanceToCenter + 255 * (1 - distanceToCenter)

img.putpixel((x, y), (int(r), int(g), int(b)))

img.save('./tests/gradient.jpg')

print('[tests] Deepfrying gradient...')
img = await deeppyer.deepfry(img)
img.save('./tests/gradient-fried.jpg')
print('[tests] Image successfully deepfried. Saved at `./test/gradient-fried.jpg`.')

with open('./tests/token.json') as t:
data = json.load(t)

print('[tests] Deepfrying `./test/test.jpg` with flares.')

img = Image.open('./tests/human-test.jpg')
img = await deeppyer.deepfry(img, token=data['token'], url_base=data.get('url_base', 'westcentralus'))

img.save('./tests/human-fried.jpg')
print('[tests] Human image successfully deepfried. Saved at `./test/human-fried`.')

print('[tests] All tests successfully completed.')

loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Binary file added tests/human-test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b6531b4

Please sign in to comment.