MetFaces Dataset은 StyleGAN2-ADA 논문에서 사용한 예술작품 얼굴 데이터 세트이다.
https://aigong.tistory.com/439
본 데이터 세트는 1336개의 고품질 PNG 파일로 구성되어 있으며 각 얼굴 이미지는 1024x1024 해상도를 가지고 있다.
관련 GitHub
https://github.com/NVlabs/metfaces-dataset
위 링크를 타고 들어가시면 Overview 부분에서 이미지를 다운받으실 수 있다.
1024x1024 해상도의 얼굴 이미지만을 원하신다면 images를 클릭하셔서 다운받으면 된다. 대략 1.6GB
그러나 만약 데이터 preprocessing을 직접 진행하고 싶다면 본 github의 코드와 더불어 metfaces.json 파일과 unprocessed 데이터 세트 파일을 다운받으면 된다.
참고로 이미지들은 Metropolitan Museum of Art Collection API를 통해서도 다운받을 수 있다.
https://github.com/metmuseum/openaccess
이때 unprocessed 파일은 13GB로 굉장히 큰 데이터 세트임을 감안해야 한다.
예를 들어 'image-828517.png' 파일의 경우 2205x2864 정도로 굉장히 큰 데이터임을 확인할 수 있다.(7.68MB)
'image-822756.png' 파일 역시 2631x3599로 굉장히 큰 이미지 파일임을 확인할 수 있다. (19.2MB)
이처럼 각 데이터의 해상도가 높다보니 이에 대한 데이터의 크기 또한 큰 것이 사실이다.
그럼 이것을 어떻게 얼굴부분만 crop할 것이냐가 관건이다.
이를 위해서 metfaces.json 파일이 사용된다.
해당 json 파일에는 각 이미지에 대한 정보들이 담겨있는데 list 안에 여러 개의 dict가 존재한다.
[
{
"obj_id": "828517",
"meta_url": "https://collectionapi.metmuseum.org/public/collection/v1/objects/828517",
"source_url": "https://images.metmuseum.org/CRDImages/dp/original/DP886840.jpg",
"source_path": "unprocessed/image-828517.png",
"source_md5": "47dda02f6334e2fd47dbcd6541211dd6",
"image_path": "images/828517-00.png",
"image_md5": "df1604bc49951984ea854d7e8c86448d",
"face_spec": {
"rect": [400, 424, 615, 639],
"shrink": 2,
"landmarks": [[443, 496], [442, 519], [448, 545], [455, 570], [461, 596], [469, 620], [480, 644], [496, 662], [519, 667], [547, 664], [578, 652], [604, 634], [624, 610], [636, 582], [640, 551], [643, 521], [643, 492], [442, 465], [451, 457], [464, 456], [478, 461], [489, 469], [512, 471], [531, 461], [552, 458], [574, 461], [593, 470], [496, 486], [493, 506], [489, 525], [485, 544], [482, 558], [488, 561], [497, 563], [506, 561], [516, 560], [455, 483], [462, 477], [474, 478], [484, 488], [473, 491], [461, 490], [536, 489], [547, 481], [561, 480], [573, 487], [562, 492], [548, 492], [479, 597], [485, 589], [492, 585], [502, 589], [514, 586], [532, 592], [553, 600], [536, 610], [518, 614], [506, 614], [495, 612], [487, 606], [483, 597], [493, 596], [503, 599], [515, 597], [547, 599], [516, 598], [504, 599], [494, 596]]
},
"face_idx": 0,
"title": "Portrait of Jacob van Veen, after Maarten van Heemskerck",
"artist_display_name": "Jules-Ferdinand Jacquemart"
}
{다른 이미지 content},
{다른 이미지 content},
{다른 이미지 content},
]
위에 보이는 dict는 'image-828517.png' 파일에 대한 dict 내용인 것을 확인할 수 있다.
본 json 파일에는 id, source_url, source_path, image_path, face_spec, title 등이 담겨져 있다.
본 json 파일을 사용함에 있어 수동을 설정해줘야하는 부분 혹은 반드시 맞춰져야하는 부분은 source_path로 원본 이미지 저장된 path에 대한 부분이다. 만약 부분이 아니라 앞에서 언급한 전체 unprocessed 파일을 통체로 다운받았다면 크게 걱정하지 않으셔도 되는 부분이다.
여기서 중요한 것은 face_spec에서 landmark 부분으로 face 위치에 대한 정보가 담겨져 있다.
다만 신기한 것은 코드상에서는 landmark를 그대로 사용하는 것이 아니라 다음과 같이 사용한다.
face_spec = face['face_spec']
landmarks = (np.float32(face_spec['landmarks']) + 0.5) * face_spec['shrink']
shrink로 줄였기 때문에 곱셈한다는 것까지는 이해하는데 0.5까지 신경써야하는지는 잘 모르겠다.
결과적으로 모든 데이터 이미지에 대해서 landmark는 (68,2)를 가지게 된다.
dot landmark에서 고정된 숫자에 대해 고정된 위치가 나타낼 수 있다.
왼쪽 눈에 해당하는 landmark는 36~41번째(노랑),
오른쪽 눈에 해당하는 landmark는 42~47번째(분홍),
입 외곽에 해당하는 landmark는 48~59번째(청록)
기타(빨강)
그리고 해당 landmark를 기반으로 여러 위치에 대한 평균값, 중간값들을 계산해낸다.
lm_eye_left = landmarks[36 : 42] # left-clockwise
lm_eye_right = landmarks[42 : 48] # left-clockwise
lm_mouth_outer = landmarks[48 : 60] # left-clockwise
# Calculate auxiliary vectors.
eye_left = np.mean(lm_eye_left, axis=0)
eye_right = np.mean(lm_eye_right, axis=0)
eye_avg = (eye_left + eye_right) * 0.5
eye_to_eye = eye_right - eye_left
mouth_left = lm_mouth_outer[0]
mouth_right = lm_mouth_outer[6]
mouth_avg = (mouth_left + mouth_right) * 0.5
eye_to_mouth = mouth_avg - eye_avg
눈과 입을 기반으로 eye_left, eye_right, eye_avg, mouth_left, mouth_right, mouth_avg를 나타내면 흰색 점으로 표현할 수 있다.
eye_to_eye, eye_to_mouth 두 값을 활용하여 기울어짐이 있는지에 따라 사각형을 선택한다. (quad)
quad = np.stack([c0 - x - y, c0 - x + y, c0 + x + y, c0 + x - y])
"""
[[ 613.3181 593.1433 ]
[ 622.84326 1406.1819 ]
[1435.8818 1396.6567 ]
[1426.3567 583.61816]]
"""
np.round(quad).astype(np.int64)
"""
[[ 613 593]
[ 623 1406]
[1436 1397]
[1426 584]]
"""
quad는 자세히보면 직사각형이 아니다. 실제 quad 값은 사각형이다. (기울어짐)
이 quad를 기반으로 shrink, crop, pad, transform 그리고 마지막으로 저장의 기능을 수행하여 얼굴 사진을 뽑아낸다.
cf)
transform의 경우 일종의 upsampling을 진행하고 다시 resize를 진행하는데 이는 혹시나 모를 super size가 target size보다 작을 때를 대비한 일종의 경우의 수가 아니었나 생각해본다.
그리고 결과적으로 QUAD를 통해 사각형 형태로 맞춰야하는 경우도 필요했다.
img = img.transform((super_size, super_size), PIL.Image.QUAD, (quad + 0.5).flatten(), PIL.Image.BILINEAR)
중간중간 코드적으로 해석하는데 어려움이 있었지만 (아직도... 있다는 것은 함정) 본 코드들을 보면서 데이터에 대한 preprocessing에 대한 이해가 조금은 더 생겼다.
아무래도 NVIDIA에서 제공하는 공식 데이터 세트이다보니 배울 점이 확실히 있어보인다.