Skip to content

Commit 7eb7adf

Browse files
committed
Initial commit
0 parents  commit 7eb7adf

File tree

4 files changed

+207
-0
lines changed

4 files changed

+207
-0
lines changed

LICENSE.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
MetFaces is an image dataset of human faces extracted from works of art.
2+
3+
The source images are made available under the Creative Commons Zero (CC0)
4+
(https://creativecommons.org/publicdomain/zero/1.0/) license by the
5+
Metropolitan Museum of Art (https://www.metmuseum.org/).
6+
For more information about their Open Access policy, please refer to
7+
https://www.metmuseum.org/about-the-met/policies-and-documents/image-resources.
8+
9+
The dataset itself (including JSON metadata, processed images, and documentation) is
10+
made available under Creative Commons BY-NC 2.0 (https://creativecommons.org/licenses/by-nc/2.0/)
11+
license by NVIDIA Corporation. You can use, redistribute, and adapt it
12+
for non-commercial purposes, as long as you (a) give appropriate credit by
13+
citing our paper, and (b) indicate any changes that you've made.

README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
## MetFaces Dataset
2+
3+
![Teaser image](./img/metfaces-teaser.png)
4+
5+
MetFaces is an image dataset of human faces extracted from works of art.
6+
7+
The dataset consists of 1336 high-quality PNG images at 1024×1024 resolution. The images were downloaded via the [Metropolitan Museum of Art Collection API](https://metmuseum.github.io/), and automatically aligned and cropped using [dlib](http://dlib.net/). Various automatic filters were used to prune the set.
8+
9+
For business inquiries, please contact [researchinquiries@nvidia.com](mailto:researchinquiries@nvidia.com)
10+
11+
For press and other inquiries, please contact Hector Marinez at [hmarinez@nvidia.com](mailto:hmarinez@nvidia.com)
12+
13+
## Licenses
14+
15+
The source images are made available under the [Creative Commons Zero (CC0)](https://creativecommons.org/publicdomain/zero/1.0/) license by the [Metropolitan Museum of Art](https://www.metmuseum.org/). Please [read here](https://www.metmuseum.org/about-the-met/policies-and-documents/image-resources) for more information about their Open Access policy.
16+
17+
The dataset itself (including JSON metadata, processed images, and documentation) is made available under [Creative Commons BY-NC 2.0](https://creativecommons.org/licenses/by-nc/2.0/) license by NVIDIA Corporation. You can **use, redistribute, and adapt it for non-commercial purposes**, as long as you (a) give appropriate credit by **citing our paper**, and (b) **indicate any changes** that you've made.
18+
19+
20+
## Overview
21+
22+
All data is hosted on Google Drive:
23+
24+
| Path | Size | Files | Format | Description
25+
| :--- | :--: | ----: | :----: | :----------
26+
| [metfaces-dataset](https://drive.google.com/open?id=1w-Os4uERBmXwCm7Oo_kW6X3Sd2YHpJMC) | 14.5 GB | 2621 | | Main folder
27+
| ├ [metfaces.json](https://drive.google.com/open?id=1o11-JkkwBbZW61w03O7qGrhkydNALDSH) | 1.8 MB | 1 | JSON | Image metadata including original download URL.
28+
| ├ [images](https://drive.google.com/open?id=1iChdwdW7mZFUyivKtDwL8ehCNhYKQz6D) | 1.6 GB | 1336 | PNG | Aligned and cropped images at 1024×1024
29+
| └ [unprocessed](https://drive.google.com/open?id=1lut1g1oASGsipQQB67EFqVhjt4UgC5JW) | 13 GB | 1284 | PNG | Original images
30+
31+
## Reproducing the dataset
32+
33+
MetFaces 1024x1024 images can be reproduced with the `metfaces.py` script as follows:
34+
35+
1. Download the contents of the metfaces-dataset Google Drive folder. Retain the original folder structure (e.g., you should have `local/path/metfaces.json`, `local/path/unprocessed`.)
36+
2. Run `metfaces.py --json data/metfaces.json --source-images data --output-dir out`
37+
38+
## Metadata
39+
40+
The `metfaces.json` file contains the following information for each image:
41+
42+
```
43+
[
44+
{
45+
"obj_id": "11713", # Metmuseum object ID
46+
"meta_url": "https://collectionapi.metmuseum.org/public/collection/v1/objects/11713",
47+
"source_url": "https://images.metmuseum.org/CRDImages/ad/original/ap26.129.1.jpg",
48+
"source_path": "unprocessed/image-11713.png", # Original raw image file under local dataset copy
49+
"source_md5": "c1e4c5a42de6a4d6909d3820c16f9eb5", # MD5 checksum of the raw image file
50+
"image_path": "images/11713-00.png", # Processed 1024x1024 image
51+
"image_md5": "605a90ab744bdbc9737da5620f2777ab", # MD5 checksum of the processed image
52+
"title": "Portrait of a Gentleman", # Metmuseum object's title
53+
"artist_display_name": "Charles Willson Peale", # Metmuseum object's artist's display name
54+
"face_spec": { # Info about the raw image:
55+
"rect": [404, 238, 775, 610], # - Axis-aligned rectangle of the face region
56+
"landmarks": [...], # - 68 face landmarks reported by dlib
57+
"shrink": 2
58+
},
59+
"face_idx": 0
60+
},
61+
...
62+
]
63+
```
64+
65+
For full Metmuseum metadata, you can access the `meta_url` contents by e.g., `curl https://collectionapi.metmuseum.org/public/collection/v1/objects/11713`.

img/metfaces-teaser.png

616 KB
Loading

metfaces.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Copyright 2020 NVIDIA Corporation
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import argparse
16+
import json
17+
import numpy as np
18+
import os
19+
import PIL.Image
20+
import scipy.ndimage
21+
from tqdm import tqdm
22+
23+
_examples = '''examples:
24+
25+
# Run x, y, z
26+
python %(prog)s --output=tmp
27+
'''
28+
29+
def extract_face(face, source_images, output_dir, target_size=1024, supersampling=4, enable_padding=True):
30+
def rot90(v) -> np.ndarray:
31+
return np.array([-v[1], v[0]])
32+
33+
# Sanitize facial landmarks.
34+
face_spec = face['face_spec']
35+
landmarks = (np.float32(face_spec['landmarks']) + 0.5) * face_spec['shrink']
36+
assert landmarks.shape == (68, 2)
37+
lm_eye_left = landmarks[36 : 42] # left-clockwise
38+
lm_eye_right = landmarks[42 : 48] # left-clockwise
39+
lm_mouth_outer = landmarks[48 : 60] # left-clockwise
40+
41+
# Calculate auxiliary vectors.
42+
eye_left = np.mean(lm_eye_left, axis=0)
43+
eye_right = np.mean(lm_eye_right, axis=0)
44+
eye_avg = (eye_left + eye_right) * 0.5
45+
eye_to_eye = eye_right - eye_left
46+
mouth_left = lm_mouth_outer[0]
47+
mouth_right = lm_mouth_outer[6]
48+
mouth_avg = (mouth_left + mouth_right) * 0.5
49+
eye_to_mouth = mouth_avg - eye_avg
50+
51+
# Choose oriented crop rectangle.
52+
x = eye_to_eye - rot90(eye_to_mouth)
53+
x /= np.hypot(*x)
54+
x *= max(np.hypot(*eye_to_eye) * 2.0, np.hypot(*eye_to_mouth) * 1.8)
55+
y = rot90(x)
56+
c = eye_avg + eye_to_mouth * 0.1
57+
58+
# Calculate auxiliary data.
59+
qsize = np.hypot(*x) * 2
60+
quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y])
61+
lo = np.min(quad, axis=0)
62+
hi = np.max(quad, axis=0)
63+
lm_rel = np.dot(landmarks - c, np.transpose([x, y])) / qsize**2 * 2 + 0.5
64+
rp = np.dot(np.random.RandomState(123).uniform(-1, 1, size=(1024, 2)), [x, y]) + c
65+
66+
# Load.
67+
img = PIL.Image.open(os.path.join(source_images, face['source_path'])).convert('RGB')
68+
69+
# Shrink.
70+
shrink = int(np.floor(qsize / target_size * 0.5))
71+
if shrink > 1:
72+
rsize = (int(np.rint(float(img.size[0]) / shrink)), int(np.rint(float(img.size[1]) / shrink)))
73+
img = img.resize(rsize, PIL.Image.ANTIALIAS)
74+
quad /= shrink
75+
qsize /= shrink
76+
77+
# Crop.
78+
border = max(int(np.rint(qsize * 0.1)), 3)
79+
crop = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1]))))
80+
crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, img.size[0]), min(crop[3] + border, img.size[1]))
81+
if crop[2] - crop[0] < img.size[0] or crop[3] - crop[1] < img.size[1]:
82+
img = img.crop(crop)
83+
quad -= crop[0:2]
84+
85+
# Pad.
86+
pad = (int(np.floor(min(quad[:,0]))), int(np.floor(min(quad[:,1]))), int(np.ceil(max(quad[:,0]))), int(np.ceil(max(quad[:,1]))))
87+
pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - img.size[0] + border, 0), max(pad[3] - img.size[1] + border, 0))
88+
if enable_padding and max(pad) > border - 4:
89+
pad = np.maximum(pad, int(np.rint(qsize * 0.3)))
90+
img = np.pad(np.float32(img), ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect')
91+
h, w, _ = img.shape
92+
y, x, _ = np.ogrid[:h, :w, :1]
93+
mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0], np.float32(w-1-x) / pad[2]), 1.0 - np.minimum(np.float32(y) / pad[1], np.float32(h-1-y) / pad[3]))
94+
blur = qsize * 0.02
95+
img += (scipy.ndimage.gaussian_filter(img, [blur, blur, 0]) - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0)
96+
img += (np.median(img, axis=(0,1)) - img) * np.clip(mask, 0.0, 1.0)
97+
img = PIL.Image.fromarray(np.uint8(np.clip(np.rint(img), 0, 255)), 'RGB')
98+
quad += pad[:2]
99+
100+
# Transform.
101+
super_size = target_size * supersampling
102+
img = img.transform((super_size, super_size), PIL.Image.QUAD, (quad + 0.5).flatten(), PIL.Image.BILINEAR)
103+
if target_size < super_size:
104+
img = img.resize((target_size, target_size), PIL.Image.ANTIALIAS)
105+
106+
# Save face image.
107+
img.save(os.path.join(output_dir, f"{face['obj_id']}-{face['face_idx']:02d}.png"))
108+
109+
110+
def main():
111+
parser = argparse.ArgumentParser(
112+
description='MetFaces dataset processing tool',
113+
epilog=_examples,
114+
formatter_class=argparse.RawDescriptionHelpFormatter
115+
)
116+
parser.add_argument('--json', help='MetFaces metadata json file path', required=True)
117+
parser.add_argument('--source-images', help='Location of MetFaces raw image data', required=True)
118+
parser.add_argument('--output-dir', help='Where to save output files')
119+
args = parser.parse_args()
120+
121+
os.makedirs(args.output_dir, exist_ok=True)
122+
123+
with open(args.json) as fin:
124+
faces = json.load(fin)
125+
for f in tqdm(faces):
126+
extract_face(f, source_images=args.source_images, output_dir=args.output_dir)
127+
128+
if __name__ == "__main__":
129+
main()

0 commit comments

Comments
 (0)