-
Notifications
You must be signed in to change notification settings - Fork 5
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
0 parents
commit b6531b4
Showing
9 changed files
with
256 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.vscode | ||
token.json | ||
__pycache__ | ||
|
||
gradient-fried.jpg | ||
gradient.jpg | ||
human-fried.jpg |
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,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. |
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,78 @@ | ||
# deeppyer | ||
 | ||
|
||
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. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,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') |
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 @@ | ||
aiohttp>=2.2.5 | ||
Pillow>=4.2.0 |
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,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()) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.