1. 들어가며
- 지난 시간에 이어서 눈동자를 검출 할 수 있는 방법에 대해 다루겠다.
- 이런 태스크를 위해 꼭 필요한 데이터셋이 충분히 갖추어져 있지 않은 상황을 우리는 헤쳐나가야 한다.
- 오늘의 목표는 아래와 같다.
1) 공개 데이터 사용해서 라벨 직접 모아보기
2) 색상 값을 이용한 검출 방법
3) 라벨링 툴 만들기 - point selection
4) 째려보는 효과 구현하기
- 오늘의 노드는 에디터와 터미널을 이용하는 것 절반, 주피터 노트북을 사용하는 것 절반이였다.
2. 위치 측정을 위한 라벨링 툴 만들기 (1) OpenCV 사용
- 이전 노드에서 True / False를 라벨링(labeling) 할 수 있는 도구를 만들어 봤다.
- 품질이 좋지 못한 데이터에 위치를 직접 입력하려면 어떻게 해야 할까?
- 이번에는 눈동자 위치를 선택할 수 있는 도구를 만들어 보겠다.
- 예를 들어, 아래와 같은 눈 이미지가 있다고 하면
- 기존 예측 결과는 아래의 이미지처럼 이상한 곳을 측정했다.
- 이런 경우 눈동자 위치를 새로 지정해서 fine 라벨로 만들어야 한다.
- 정확한 곳을 지정하기 위해서 아래의 이미지처럼 마우스를 이용해서 이벤트를 만드는 방법이 있을까?
- OpenCV에는 마우스 이벤트를 callback 함수 형태로 지원한다.
- 그럼 callback이 무엇일까?
1) callback 함수란?
(1) 두 가지 뜻이 있다.
(2) 어떤 이벤트에 의해 호출되어지는 함수
(3) 다른 함수의 인자로 인용되는 함수
2) OpenCV에서 마우스 이벤트를 확인하고 callback을 호출하는 함수는 무엇인가?
(1) cv2.setMouseCallback
3) OpenCV에서 마우스 왼쪽 버튼이 눌러졌을 때 on되는 flag는 무엇인가?
(1) cv2.EVENT_LBUTTONDOWN
3. 위치 측정을 위한 라벨링 툴 만들기 (2) 툴 만들기
- 이제 OpenCV의 마우스 이벤트를 이용해서 라벨링 툴(labeling tool)을 만들어보겠다.
- 노드를 시작할 때 다운받은 파일에서 keypoint_using_mouse 파일의 코드를 살펴본다.
1) 먼저 아래 코드는 필요한 패키지를 불러온다. 주로 cv2를 사용한다.
2) 그리고 이번에는 기존 라벨을 읽지 않고 새로 위치를 정하기 때문에 img_path만 불러온다.
3) flg_button은 마우스 이벤트가 발생할 때 사용할 불리언(boolean)타입 전역변수로 default는 False로 지정했다.
4) 다음으로 사용할 함수를 만들었다.
(1) 해당 함수에서는 먼저 img_path가 유효한지 체크한다.
(2) img_path로 디렉토리가 입력될 경우 해당 디렉토리 내의 첫 번째 이미지를 img_path에 입력하고 경로를 반환한다.
5) 그리고 이미지 간 이동할 move() 함수도 선언했다.
6) 다음으로 mouse callback 함수를 정의했다.
(1) 전역변수인 qparam에는 img와 point 정보를 저장한다.
(2) 마우스 왼쪽 버튼이 눌러졌다 떨어질 때 qparam의 point에 x, y를 리스트로 저장한다.
7) main 함수인 blend_view()를 구현한다.
8) 해당 함수는 이론 노드에서 만든 라벨링 툴의 구조와 비슷하게 구현했다.
9) 달라진 점은 아래와 같다.
(1) 마우스 이벤트를 사용하기 위해 무한루프를 사용해서 qparam을 입력 받을 수 있게 했다.
(2) 이미지 변경이 없다면 qparam["point"]를 초기화 하지 않는다.
(3) 이미지 변경이 없더라도 callback 함수에서 qparam 변경이 얼어나는 경우는 수정한다.
- 그리고 만든 프로그램을 실행시켜서 마우스에 점을 찍어봤다.
- 눈동자 지점을 클린한 후 s를 눌러 저장하면 esc를 눌러 프로그램이 종료할 때 json파일에 레이블이 저장되는 것을 확인할 수 있다.
- 이제 레이블을 모아서 학습시킬 준비가 되었다.
4. 데이터를 모아보자
- 이제 라벨링할 초기 데이터를 수집해야 한다.
- 직접 촬영한 데이터를 쓸 수도 있지만 들이는 노력에 비해 데이터가 많이 모이지 않을 것이다.
- 때문에 공개된 데이터를 적극적으로 활용할 필요가 있다.
- 우리가 모아야 할 라벨은 눈동자 위치이기 때문에 아래 순서로 데이터셋을 찾으면 좋다.
1) 눈이 crop 되어 있고 눈동자 위치를 라벨로 가지고 있는 데이터
2) 얼굴 랜드마크(face landmark)를 가지고 있는 데이터
3) 얼굴 이미지를 가지고 있는 데이터
- 위 3개 중 1번에 해당하는 데이터셋은 BioID가 있다.
1) BioID는 몇 장의 이미지를 가지고 있나? -> 1521장의 gray image를 가지고 있다.
2) BioID의 이미지 해상도는 얼마인가? -> 384x286의 해상도를 가지고 있다.
3) BioID는 몇 명의 사람으로 구성되어 있나? -> 23명으로 구성되어 있다.
- BioID의 경우 우리가 해결해야 할 문제를 위한 데이터셋이지만 규모가 너무 작다는 단점이 있다.
- 충분한 양의 이미지를 수집하기 위해서 다른 데이터셋도 조사해야 한다.
- 얼굴 랜드마크가 제공되는 데이터를 생각해보자.
- 랜드마크가 제공되는 경우는 눈 부분의 사진을 쉽게 crop해서 사용할 수 있기 때문에 라벨을 쉽게 만들 수 있다.
- 랜드마크를 제공하는 데이터셋을 찾아보자. 데이터를 어떻게 찾아가는지 고민하는 것에 집중해보자.
- 우리는 dlib 패키지의 얼굴 랜드마크를 사용해오고 있었다.
- 그렇다면 dlib 패키지를 구현하기 위해 사용된 랜드마크는 어떤 데이터셋을 이용했을까 를 생각해보면 데이터를 찾을 수 있을 것이다.
- 구글에 dlib face landmark dataset을 검색해보면 아마 첫 페이지에서 데이터셋을 찾을 수 있을 것이다.
- 이 데이터셋의 주석을 몇 줄 읽어보면 IBUG 300-W라는 데이터셋을 학습했다는 것을 알 수 있다. 이 데이터셋을 이용하는 방법도 유용할 것이다.
- 이런 경우에는 운이 좋은 편이다. 하지만 실무에서 대부분은 내가 풀고자 하는 문제의 관련 도메인 데이터셋이 없기 때문에 위에서 말한 방법 중 3번째 방법(3개 중 가장 최악)도 고려해야 한다.
- 3번 방법을 연습해 보기 위해, 여기서는 LFW 데이터셋을 사용했다.
- 이 데이터셋은 안면 인식(face recognition)과 관련된 데이터셋이다. 해당 사진이 누구의 얼굴인지 판단하는 데이터셋이다.
- 얼굴이 포함된 이미지만 있고 얼굴의 랜드마크에 대한 정보는 없는 데이터셋이다.
- 이 이미지에 dlib을 적용해서 얼굴 위치와 랜드마크 위치를 찾고 눈을 잘라낸 뒤 라벨링을 할 수 있다.
5. Mean-shift를 이용한 눈동자 검출 방법 (1) 이론
- 눈동자를 어떻게 검출할까?
- 저번에도 생각했던 방법인 눈동자는 주변 부분에 비해 어두운 색을 지니고 있다라는 가정으로 반전된 1D이미지에서 최댓값을 찾는 방법이 있다.
- 물론 이와 같은 가우시안 블러가 모든 상황을 해결할 수 있는 방법은 아니다.
- 아래 이미지와 같이 눈 근처에 머리카락이 나타나서 눈 가장자리에 검정색이 큰 비중으로 등장하는 경우는 눈동자보다 가장자리에 수렴할 확률이 높다.
- 특히 머리가 긴 사람에게 자주 나타날 수 있는 현상이다.
- 2차원 블러 특성 이미지(feature image)에서는 눈동자가 2차원 정규분포로 나타나는 영역이 있는 것으로 보인다.
- 하지만 1차원 누적 그래프를 보면 x축으로 2개의 봉우리를 가지는 것을 관찰할 수 있다.
- 최대값을 찾는 알고리즘을 왼쪽부터 시작했다면 가장 왼쪽에서 만나는 255에 수렴할 것이다.
- 따라서 우리는 1D 누적 그래프와 2D 특성 이미지를 모두 사용한다.
- 그럼 2D에서는 어떻게 최고점을 찾아갈 수 있을까?
1) 이미지 중심점을 초기값으로 설정한다. -> 눈의 중심에는 눈동자가 있을 확률이 높기 때문에 초기값으로 설정하기 좋다.
2) 중심점을 기준으로 작은 box를 설정한다. -> box의 크기는 문제에 따라 적절한 값으로 설정한다. 위 그림에서는 회색박스를 생각하면 된다.
3) box 내부의 pixel 값을 이용해서 무게중심을 찾는다 -> 이 때 무게중심은 pixel intensity를 weight로 사용할 수 있다.
4) 찾은 무게중심을 새로운 box의 중심으로 설정한다. -> 이 단계에서 박스가 이동하게 된다. 그림에서는 회색박스에서 초록색 박스로 관심영역이 이동했다고 보면 된다.
5) 다시 초록색 박스를 기준으로 2) ~ 4)를 반복한다.
6) 중심점이 수렴할 때 까지 2) ~ 5)를 반복하면 수렴한 점의 위치로 눈동자를 찾을 수 있다.
- 머신러닝에는 이미 비슷한 알고리즘이 존재한다.
- 현재 위치와 탐색반경을 가질 때 평균의 위치를 이용해서 반복적으로 움직이는 알고리즘인 Mean Shift라는 알고리즘이다.
- mean shift는 탐색반경 내 데이터 포인트의 평균을 구하고 평균 위치로 이동을 반복해 가면서 데이터 분포의 중심으로 이동한다.
1) mean shift를 이용해서 global optima를 찾을 수 있을까? -> 찾을 수 없다. mean shift는 local optima에만 수렴하기 때문이다.
2) mean shift의 단점은 어떤 것이 있을까? -> 초깃값에 따라 수렴 위치가 달라진다. 때문에 항상 일정한 성능을 보장하기 힘들다.
3) mean shift는 컴퓨터 비전의 어떤 분야에 응용할 수 있을까?
(1) 물체 추적 (object tracking)
(2) 영상 세그멘테이션 (segmentation)
(3) 데이터 클러스터링 (clustering)
(4) 경계를 보존하는 영상 스무딩 (smoothing)
(5) 그 외 다양하게 응용할 수 있다.
4) 1차원 데이터 분포에 mean shift를 적용하면 어떤 형태를 나타낼까? -> 가우시안 분포에서 등산하듯 위로 올라가는 형태를 나타낸다.
6. Mean-shift를 이용한 눈동자 검출 방법 (2) 실습
- 눈동자를 검출하는 mean shift 기법을 코드로 구현해 본다.
- 시작포인트는 저번 노드에서 구현해 본 눈동자 찾기 실습코드이다.
- 저번 시간에 눈동자 찾기 코드를 하나의 파일로 정리하여 eye_center_basic 파일에 저장했다.
- 이 코드에서 show_substep argment의 옵션을 True로 주게 되면 매 스텝의 작동을 차례로 확인할 수 있다. default는 False이고, 이 경우 최종 결과만 확인하게 된다.
- 이번 스텝은 이 파일의 기존 함수 중 findCenterPoint를 수정했다. 파일 이름은 eye_center_meanshift로 저장했다.
- 그리고 눈 이미지를 low pass filter를 이용해서 smoothing을 한다. 여기서 bilateral filter를 이용했다.
- 다음으로 1차원 값으로 누적시킨 후 y축 기준으로 최대값을 찾아서 y축의 중심점 좌표를 먼저 얻어낸다.
- x축은 1차원 최대값 지점을 기준으로 mean shift를 수행한다.
- 양 끝단에 수렴하는 예외를 처리한 루 결과를 출력한다.
- 프로그램을 실행해보면 아직 눈동자 중심은 아니지만 근처에 찍힌다.
- 기존 머신러닝 기반 알고리즘으로는 성능을 큰 폭으로 향상시키기 어렵다.
- 하지만 예외 상황에 대해 더 강건한 모델을 만들 수 있다는 장점이 있다.
- 일반화에 한 걸음 더 가까워지고 있다.
7. 키포인트 검출 딥러닝 모델 만들기 (1) 데이터 확인
- 더 나은 성능을 위해 딥러닝 모델을 만들어보자. 지난번에 배웠던 딥러닝 모델링 기법을 실제로 사용해 보자.
- 이번에는 대량의 눈동자 위치 라벨이 필요하다. 앞에서 만든 coarse dataset 또는 직접 어노테이션 한 라벨이 10,000개 이상 있어야 성능을 확인할 수 있다.
- 이전 스텝에서 다룬 눈동자 검출 방법을 LFW 데이터셋에 적용하여 필요한 만큼의 데이터셋을 생성해보자.
- 데이터셋을 생성하는 코드 prepare_eye_dataset를 실행하면 사용할 데이터셋이 LFW 데이터셋으로부터 가공 생성된다.
- (주의) 총 13,000개의 LFW 데이터셋으로부터 데이터셋을 가공 / 생성하는 과정은 20분 이상 소요될 수 있다.
- 이번 노드부터는 주피터 노트북을 이용한 코드 실습으로 진행되었다.
8. 키포인트 검출 딥러닝 모델 만들기 (2) 모델 설계
- 이전 스텝에서 데이터를 준비했다.
- 이제는 네트워크를 디자인한다.
- 우리는 데이터가 없는 상황이기 때문에 미리 학습된 모델을 적극적으로 활용해야 한다.
- Tensorflow Hub에서 ResNet의 특성추출기 부분을 백본(backbone)으로 사용한다.
- 코드 실습을 통해 ResNet을 불러오고, mse / mae를 각각 loss와 metrics로 지정하고, 학습률은 지수적으로 감소하게 만들어서 학습시켰다.
9. 키포인트 검출 딥러닝 모델 만들기 (3) 평가
- 코드 실습으로 평가를 진행했다.
- 실습 결과 노드에서는 눈동자의 왼쪽, 중앙, 오른쪽 지점을 잘 찾는다. 하지만 내가 했을 땐 하나도 잘 찾지 못했다.
- 사실 7번 스텝에서 라벨링 과정에서부터 노드대로 재현되지 않았다.
- AIFFEL측에서는 저자의 원본콘텐츠와 비교하면서 문제를 찾아보니 라벨링 과정에서 0~1 값으로 normalize 되야하는데 우리는 0, 0같은 꼭지점에 좌표가 찍히는 형태로 나왔다.
- 이로 인해 모델 학습을 왜곡시켜 결과가 이상하게 나오는 것으로 판단했다.
- 때문에 오늘 노드는 프로젝트 수행과정에 초점을 맞춰서 학습을 진행했고, 문제의 원인이 밝혀지면 공유하겠다고 했다.
10. 프로젝트 : 카메라 앱에 당황한 표정 효과를 적용해보기
- 저번 시간에 첫 번째 그림 캐릭터를 기억해보자.
- 그 그림처럼 놀라서 눈이 튀어나오는 듯 한 효과를 내보는 것이 이번 프로젝트의 목표이다.
- 어려워 보여도 차근차근 해보면 충분히 할 수 있는 프로젝트라고 한다.
- 나는 Exploration 3의 코드를 생각하며 했었다.
- 프로젝트 과정은 아래 순서로 진행한다.
1) 먼저 이론 시간에 다룬 모델을 참고하여 딥러닝 모델을 설계해보자.
(1) 7 ~ 9번 스텝에서 키포인트 검출을 위한 딥러닝 모델을 만들어 봤다.
(2) 이를 활용해서 눈 이미지에서 적합한 키포인트를 찾는 딥러닝 모델을 구현해보자.
(3) 이 모델의 학습을 위해서는 위에서 해본 것처럼 데이터를 모아 데이터셋을 구축하는 과정이 함께 진행되어야 한다.
2) 그리고 눈동자 효과를 추가해보자.
(1) 추출된 눈 위치에 당황한 표정의 눈 이미지를 합성해보자.
(2) 눈 이미지는 위에서 말한 캐릭터의 이미지를 사용한다.
(3) 이렇게 합성된 이미지를 제출하는 것 까지가 프로젝트 과제의 목표이다.
'공부 > AIFFEL' 카테고리의 다른 글
Going Deeper(CV)_DJ 19 : 사람의 몸짓을 읽어보자. (0) | 2021.05.06 |
---|---|
Going Deeper(CV)_DJ 18 : 멀리 있는 사람도 스티커를 붙여주자 (0) | 2021.05.05 |
Going Deeper(CV)_DJ 15 : 내 거친 생각과 불안한 눈빛과 그걸 지켜보는 카메라앱 (0) | 2021.04.29 |
Going Deeper(CV)_DJ 14 : 카메라 스티커앱을 개선하자! (0) | 2021.04.27 |
Going Deeper(CV)_DJ 13 : 내가 만든 카메라앱, 무엇이 문제일까? (0) | 2021.04.26 |