PCA(고유 성분 분석)를 통한 Eigenface recognizer
PCA를 활용하여 Eigenface vector 계산 그리고 얼굴 인식
영상처리에서 얼굴을 인식하는 방법은 다양하다. 그 기본으로 Eigenface방법이 존재하는데, 영상에서 고유성분을 분석(PCA) 및 추출하여 각각의 고유벡터를 Eigenface(고유 얼굴)로 사용한다. Eigenface는 PCA의 결과로 가장 큰 고유값의 고유 벡터가 첫번째 데이터 분포축이고 그 다음으로 큰 고유값의 고유벡터는 두 번째 데이터 분포축… 이 된다.
재밌는 점은 이 Eigenface vector들은 훈련(학습) 이미지들로 만들어진 벡터이므로 벡터들을 이용해 다시 훈련 이미지를 재현(reconstruction)할 수 있다. 뿐만 아니라 새로운 테스트 이미지나 인식이 필요한 이미지에서 얼굴을 재현할 수 있고, 그 근사도에 따라 인식할 수 있게된다.
이 이론을 이용해 eigenface recognizer를 개발해보려고 한다.
1. 서 론
사물 및 얼굴 인식 기술은 개인을 인증하는 수단부터 디지털 포렌식까지 다양한 분야에서 활용될 수 있는 영상처리에서 중요하게 여기어지는 분야중 하나이다. 그 중 얼굴인식은 사람의 얼굴특성을 활용하여 얼굴을 감지한 뒤, 훈련된 이미지 정보들과 비교해 어느정도 일치하는지에 따라 얼굴을 인식한다. 얼굴 인식기는 그 방법에 따라 크게 eigen face, fisher face, lbph face로 나눌 수 있으며 본 프로젝트에서는 가장 기초가 되는 eigen face를 활용한 얼굴 인식기를 설계 및 구현한다.
Eigen face를 활용해 얼굴인식을 하기 위해서는 성능적 측면을 위해 고려되는 몇 가지 사항이 있다. 먼저 Eigen face 생성을 위해 공분산행렬, 고유값 계산이 필요하다. 이 과정에서 이미지 크기에 의존되는 차원의 연산이 필요한데, 공분산 행렬 계산 순서를 뒤집음으로 이미지 크기 대신 이미지 수에 의존되는 차원으로 사상하도록 바꾸어 연산 속도를 월등히 빠르게 높일 수 있다. 또한 고유벡터는 데이터를 특정 축에 사상하였을 때 분산 정도에 따라 해당 축의 중요성을 파악할 수 있는 특성을 가지는데 PCA(Principle Component Analyser)기법을 이용하여 에러를 최소화함과 동시에 필요없는 데이터를 절삭하도록 한다. 따라서 성능개선이 가능한 알고리즘들을 이용해 얼굴인식기를 구현한다.
구현 결과물을 통해 Eigen face를 통한 인식기 자체가 고성능의 인식기는 아니지만 PCA 기법을 이해하기 쉽고 유용한 예제로서 쓰일 수 있음을 알 수 있다. 따라서 본 프로젝트에서는 OpenCV Contribute 라이브러리로 제공되는 Face 모듈을 사용하지 않고 Numpy를 주로 이용해 PCA기법을 적용한 Eigen face 얼굴 인식기 구현을 목표로한다.
2. 배 경
Principle Component Analysis (이하 PCA)는 고차원의 데이터를 저차원의 데이터로 환원 시키는 기법이다. PCA는 데이터를 한 개의 축으로 사상시켰을 때 그 분산이 가장 커지는 축을 첫 번째 주성분, 두 번째로 커지는 축을 두 번째 주성분으로 놓이도록 좌표계로 데이터를 선형 변환한다. 이와 같이 표본의 차이를 가장 잘 나타내는 성분들로 분해함으로 의미있는 상위 K개의 차원만을 사용해 데이터를 눈에 띄게 빠르게 처리할 수 있다.
영상처리에서 모든 영상은 N*M 크기의 한 장의 이미지 단위로 통용된다. 물체 인식은 이 낱장의 이미지들이 사전에 훈련한 값과 얼마나 일치하는지에 따라 물체를 판별하게 된다. 판별과정에 있어 전체 데이터를 사용하지 않고, PCA기법을 이용해 전체 N*M차원 데이터와 오류(차이)가 적은 유의미한 일부 차원만을 추출하여 빠르고 효율적인 계산을 목표로 한다.
이를 위해서는 가장 처음으로 주어진 이미지 데이터에서의 공분산 행렬을 구해야한다.
공분산 행렬을 구하기 위해서는 우선 M개의 이미지 영상들을 N*N 형태의 정사각형으로 리사이즈 해주고, (N*N * 1)과 같이 띠 형태의 벡터로 변형 해주어야 한다. 그 후에 각 벡터들의 평균 벡터를 구하고 각 벡터들에 평균 벡터를 빼서 정규화 된 형태의 벡터들로 만든다. 이때 중요한 것은 predict를 위해 각 이미지 영상들의 label를 매핑해두어야 한다.
정규화된 벡터들을 A라는 행렬로 묶는다. 따라서 A행렬은 (N^2 * M)크기를 갖는다.
이제 공분산행렬을 구하기 위해 정규화된 벡터행렬을 대칭하여 곱한다.
(N^2 * M) * (M * N^2) 행렬연산을 하게 되면 N^2 * N^2크기의 공분산 행렬을 구할 수 있고 N^2개의 고유값 행렬과 N^2 * N^2 크기의 고유값벡터 행렬을 구할 수 있다.
문제는 (N^2 * N^2) 크기의 이미지에서 고유성분을 추출하기 위해 공분산 행렬을 계산하려한다면 모든 픽셀의 수인 매우 큰 차원의 데이터 핸들링으로 이어지게 된다. 이는 많은 양의 연산을 필요로 하며 이미지 크기가 일정 수준을 넘어가게 되면 N^2차원은 일반적인 컴퓨터에서 처리하기 힘들 수준의 연산이 된다. 따라서 효율적이지 못하므로 A*transpose(A) 대신, transpose(A)*A 연산을 하여 M * M크기의 공분산 행렬을 구한다.
다음으로 구해진 공분산 행렬로부터 고유값과 고유벡터 vi, Mi를 구한다.
각각의 고유벡터를 얼굴인식에서는 Eigenface vector라고 부른다.
이 Eigenface vector들은 가중치 w를 곱해 훈련 이미지로 들어온 얼굴들을 재현(reconstruct)할 수 있게 된다.
Eigenface를 사용하여 얼굴을 재현하기 전에, 고유값을 구한 뒤 고유값의 내림차순으로 재정렬이 필요하다. 재정렬 된 고유값과 고유벡터 행렬을 PCA 기법으로 K개의 값만을 추출하여 사용한다.
모든 고유값을 더한 값과 K개의 고유값을 더한 값의 비율이 Threshold이상(일반적으로 90%나 95%를 사용한다)이라면 K개의 고유벡터로 reconstruct시 M개의 고유벡터로 reconstruct한 값에 근사하다고 할 수 있다.
추출된 K개의 Eigenface를 가지고 각각의 훈련용 이미지들 reconstruct할 수 있는 w행렬을 구한다.
처리속도를 위해 고유벡터 K개를 사용하므로 고유벡터 M개를 사용한 값에 근사한다는 것을 잊어서는 안 된다. 여기까지 과정을 주어진 훈련용 이미지들을 학습했다고 할 수 있다.
다음으로 실제 모르는 얼굴이 들어왔을 때 학습을 통해 알고 있는 얼굴인지, 아니면 모르는 얼굴로 남을지를 계산해야한다. 새로 들어온 얼굴역시 그림 6과 같이 N^2 * 1형태의 벡터로 변형해주어야한다. 그 다음 이전에 계산해두었던 mean face를 벡터로부터 빼 정규화 한다. 정규화된 벡터가 기존 eigenface vector들로부터 reconstruct할 수 있는 w행렬을 구한다.
마지막으로 w행렬을 기존 w행렬과 유클리드 거리 혹은 Norm 거리를 계산해 가장 거리가 짧은 (즉, 근사도가 가장 높은) eigenface vector의 label을 출력한다.
3. 구 현
18.09.16 업데이트
github링크 삭제
4. 실 험
실험을 위해 훈련용 데이터 셋을 수집하기 위한 OpenCV C++ 응용 프로그램은 별지 x에 추가하였다.
사전에 준비된 훈련셋을 이용해 Eigenface 방식의 얼굴 인식 개발을 목표로 하였다.
개발환경은 windows 플랫폼, python3.6, opencv 3.4, numpy를 이용하였다. 또한 얼굴 감지는 별도로 구현하지 않고 opencv에서 제공하는 face detect 알고리즘을 이용하였다.
실험 1) opencv face detector를 활용한 얼굴감지
cv2모듈 내에 CascadeClassifier 클래스가 있다. 이 클래스는 사전에 정의된 얼굴특성정보를 가지고 영상 내에서 얼굴을 감지해준다. 얼굴특성정보는 CascadeClassifier 클래스 폴더 내에 haarcascade_xml폴더가 존재하는데, 이 중에서 haarcascade_frontalface_alt.xml를 사용해서 얼굴을 감지한다.
얼굴 특성 정보를 사용하기 때문에 과도하게 기울어졌거나, 밝음, 어둠정도가 심해 얼굴형태를 알아보기 힘들 경우 제대로 감지가 되지 않음을 알 수 있었다.
다음으로 다수의 인원이 포함된 테스트 이미지를 이용해 검출을 시도해보았다.
CascadeClassifier로 다수의 얼굴을 검출하는 것 또한 간단하였다.
마지막으로, 최적화 고려사항으로 동영상의 매 프레임마다 얼굴 검출 시 어느 정도의 처리시간이 걸릴지를 테스트하였다. 기존에 사용된 테스트 이미지인 그림17을 100장으로 복사해 소요시간을 측정하였다. 추가로 컬러와 흑백이미지의 차이를 알아보고자 컬러 100장, 흑백 100장에 소요되는 시간을 측정하였다.
CascadeClassifier의 detectMultiScale 함수 정의를 보면 CV_8U 타입(그레이스케일)의 이미지를 요구하고 있다. 때문에 내부적으로 들어온 파라미터 값이 CV_8U인지 체크하고 아니라면 cvtColor를 통해 그레이스케일 이미지로 변환하는 것으로 추측된다. 따라서 컬러 100장과 흑백 100장의 처리시간 차이는 추가적인 cvtColor 연산이 필요한 정도라고 볼 수 있다.
실험 2) opencv contrib face를 활용한 얼굴인식
본 eigenface face recognition을 구현하기 전에, 예상할 수 있는 결과물을 미리 접할 수 있도록 완성돼있는 opencv face모듈을 사용하여 얼굴인식을 구현하였다.
opencv에서 제공하는 contribute 모듈 중 face를 설치하게 되면 cv2모듈 내 face라는 디렉터리가 생기고 이 안에 LBPHFaceRecognizer, FisherFaceRecognizer, EigenFaceRecognizer 3가지 클래스가 있음을 볼 수 있다. 이 중에서 추출한 고유성분을 통해 Eigenface로 얼굴을 인식하는 EigenFaceRecognizer 클래스를 사용한다.
얼굴 인식 자체는 face모듈을 사용했을 때 3줄의 코드로 가능했다. 맨 처음으로는 얼굴인식기 객체를 생성하고 다음으로 주어진 학습 데이터와 정답(label) 데이터를 담아 훈련(train)한다. 마지막으로 훈련된 객체내부 변수값들을 이용해 predict가 가능해진다.
본 과제에서 해결해야 할 궁극적인 목표는 그림22와 같으며 cv2 모듈의 face를 사용하지않고 numpy를 활용하여 eigenface를 생성하고, 얼굴인식 하는 과정을 실험3에서 진행한다.
실험 3) numpy를 활용한 eigenface 생성 및 얼굴인식
배경에 서술한 내용대로 eigenface를 생성하고, eigenface를 통해 기존의 훈련 이미지들을 reconstruction하고, 마지막으로 테스트 이미지를 reconstruction하여 예측하는 순으로 진행하였다.
Eigenface 생성을 위해 전체 이미지를 불러오고 그림 3~5와 같이 N차원의 M개 벡터로 만들고 mean face를 구한 뒤, 각각의 N차원 벡터에 mean face를 빼 정규화 하였다. 이렇게 계산된 M개의 N차원 벡터들을 N * M형태의 배열로 묶고, 이 배열을 A라고 칭한다. 공분산행렬을 구하기 위해 A*transpose(A)를 계산 하지만 배경의 그림8과 같이 N * N은 너무 큰 차원의 데이터이므로 효율적이지 못하다. 따라서 transpose(A)*A로 M * M형태로 공분산행렬을 계산한다.
M * M 역시 사람이 계산하기에는 큰 차원의 수 이다. 또한 numpy에서는 sympy처럼 미지수를 사용할 수 없기 때문에 고유성분을 계산하기 까다롭다. 따라서, numpy 모듈에서 지원하는 선형대수 함수중 eig를 사용해 공분산 행렬로부터 고유값과 고유벡터를 계산한다.
다음으로 고유값 크기에 따라 재정렬한다. numpy의 eig함수에서 반환해주는 고유값은 거진 내림차순으로 정렬되어있지만 약간의 차이로 섞여있는 경우를 발견하였다. 따라서 정확한 eigenvalue의 threshold 절삭을 위해 내림차순으로 다시 내림차순으로 정렬한다. 뿐만 아니라 eigvector와 label역시 내림차순으로 정렬이 필요하기 때문에 indices를 구해 재정렬을 진행한다.
정렬된 eigenvalue와 eigenvector들에서 상위 n%의 값만을 사용하도록 절삭하고, n%의 해당하는 eigenvector들이 eigenface가 된다. 이 eigenface들을 임의의 가중치와 곱하여 시그마 합을 계산하면 기존 훈련셋에 포함된 얼굴들을 reconstruction할 수 있다.
정상적으로 eigenface 생성과 reconstruction이 수행되었기 때문에 이 단계 까지를 train단계로 부른다. 중요한 것은, reconstruction과정에서 계산된 w는 변수에 따로 보관해야한다. 인식결과를 계산하기 위해 유클리드 거리나 norm 거리를 계산해야하기 때문이다.
이제 알 수 없는 새로운 사진이 들어왔을 때 지금과 같은 연산으로 얼굴을 인식할 수 있다. 훈련에 사용되지 않은 새로운 이미지를 테스트 이미지로 사용하고, 이를 reconstruction하여 기존 eigenvector에 가장 근접한 얼굴의 label로 인식결과를 출력한다.
다음으로, 정적 이미지 영상내에서 성공적으로 얼굴인식을 수행하였으므로 코드 리팩토링을 통한 클래스화와 비디오영상에 적용을 하였다.
최종적으로, 웹캠을 통해 다수의 얼굴을 실시간으로 인식하여 화면에 표시할 수 있었다.
개발시 느꼈던 한계점이나 개선사항으로 3가지를 꼽았다.
- a) 유클리드 거리 등으로 검증 이미지와 eigenface 근사도 계산시 Threshold 값을 정해 일정 치 미만으로 근사할 경우 알 수 없다는 Unknown을 출력해야한다.
- b) 유클리드 거리는 두 스칼라의 차이의 제곱이기 때문에 연산 속도가 오래걸린다. 따라서, norm 거리를 사용하여 거리의 근사치를 구하여 연산 시간을 단축시키는 방안이 효과적으로 추정된다.
- c) Eigenface는 각 훈련 이미지들의 고유성분을 추출하는 것이기 때문에 조명변화나 안경 유무, 배경색, 촬영시의 틸팅이나 좌우기울기등 영향을 받는 요소가 많다. 그러므로 훈련이미지 선정이 중요하고, 인식시에도 검증 이미지가 훈련 이미지와 유사해야한다는 단점을 찾을 수 있었다.
5. 결 론
Eigenface를 활용한 얼굴인식에는 훈련이미지(with Label)와 훈련이미지로부터 추출한 유의미한 수준의 고유성분이 필요하다. 하지만 이미지를 N차원의 벡터로 펼쳤을 때 선형대수적 관점에서 매우 고차원의 데이터 처리로 이어지게된다. 따라서 PCA는 데이터를 사상했을 때 분산이 최대화 되는 축을 성분으로 많은 데이터로부터 의미있는 데이터를 사상한 축을 줄여내므로 이런 문제를 해결하는데에 매우 좋은 수단이 된다. 뿐만 아니라 PCA를 통해 비중요 데이터를 절삭하기 위해 고유성분이 필요한데 고유성분을 구하기 위해서는 공분산행렬 계산이 불가피하다. 하지만 N차원의 벡터의 공분산행렬을 구하기 위해서는 N^2크기의 행렬을 계산해야 하는데, 이 또한 행렬의 연산 순서를 뒤바꾸어 차원을 입력 데이터 수인 M의 제곱 수준으로 대폭 줄일 수 있었다. 이를 통해 numpy모듈 사용만으로 효율이 좋은 Eigenface Recognizer를 구현할 수 있었다.
본 프로젝트는 여러사항을 고려했음에도 불구하고 산출해낸 결과물이 opencv contrib 에서 제공하는 face모듈의 eigenface recognizer와의 성능과 에러차이가 존재하며, 다음과 같은 몇가지 문제점을 보이고 있다. 단순히 reconstruction에서 근사되는 eigenface의 label을 찾으면 되는데, 이를 위해 유클리드 거리를 사용하여 큰 수의 제곱을 연산해 수행속도에 지장을 주고있다. 다음으로, eigenface와의 근사한 거리를 찾더라도 일정 threshold이하의 값일 경우 없는 label로 표기해야하지만 적절한 수치를 찾지못해 추가적인 처리를 하지 못하였다. 마지막으로 이는 Eigenface를 활용한 얼굴인식기의 공통적인 문제점으로 각 훈련 이미지들의 고유성분이 곧 Eigenface가 되기때문에 조명변화나 안경, 촬영각도등에 큰 영향을 받아 face detection은 되지만 face recognition이 불가능한 경우가 존재했다.
따라서 현재에는 eigenface recognizer가 고성능의 얼굴 인식기는 아니며, 이 문제를 극복하기 위해 fisher face recognizer나 LBPH face recognizer등의 알고리즘이 개발되어 조명변화등 다양한 변인에도 높은 인식률을 보여주는 인식기가 개발되고 있음을 배경조사 과정에서 알 수 있었다.
19개의 댓글
다미 · 2018-08-20 14:03:41
eigenface 구현에 대해 공부하고 있었는데 정리해주셔서 감사합니다~! 나중에 구현 github 링크 공유 가능하시면 부탁드릴게요^^
myoatm · 2018-08-23 20:12:40
최근 다른 작업을 하느라 이제야 확인합니다 죄송합니다.
댓글 확인 후 바로 repo작업을 진행하고 있습니다.
마크다운문서 완성 후 푸쉬하게 되면 올려주신 메일 주소로 회신 드리겠습니다
mome · 2018-08-27 15:07:12
eigenface 구현에 관해 잘 정리해주셔서, 도움이 많이 되었습니다. 저도 github 링크 공유 가능하시면 부탁드립니다.
myoatm · 2018-09-04 13:27:23
응답이 늦어져 죄송합니다.
메일 드렸습니다
몽몽이 · 2019-01-31 14:19:54
안녕하세요. 글을 보고 많이 배워 나가게 되었습니다.
현재 이쪽으로 처음 공부하는데 도움을 받을 수 있을까요? ㅠ
rbalsdl12@naver.com 저도 자료 받아보고싶습니다. ㅠ
mome · 2018-08-27 15:08:41
정리하신거 보고 도움이 많이 되었습니다. 혹시 저도 github 링크 공유 가능할까요?
임규민 · 2019-01-30 21:20:27
안녕하세요. 글을 읽어보고 많은 도움이 되었습니다.
저도 이쪽 분야로 처음 시작하게 되었는데 많이 어려워서 그런데..
도움을 받을수 있을까요 ㅠ
도와주세요ㅣ · 2019-01-30 22:05:30
안녕하세요. 석사 과정으로 이쪽으로 처음으로 연구하게 된 학생입니다.
eigen 에 대해서 좀 막막한 감이 있었는데 본 글을 읽고서 많은 도움이 되었습니다.
감사합니다.
공부에 많은 어려움이 있어 도움을 받고 싶습니다. 혹시 저도 깃허브를 통해서 자료를 통해 공부할 수 있을까요.
몽몽이 · 2019-01-31 17:24:03
안녕하세요. 글이 안써지는건지 자세히 몰라서 여러번 글을 남겨봅니다.
저도 자료를 통해서 학습 가능할까요? ㅠㅠ
콩닭 · 2020-05-20 11:56:51
안녕하세요 현제 PCA를 공부중인 대학생인데요 한글로 되어 있는 글이 이글 밖에 없어서 정말 유용하게 잘보았습니다.
혹시 소스코드는 얻을 수 없을까요??
Engineer myoa · 2020-05-23 20:05:19
메일주소로 보내드리면 될까요?
더랑 · 2020-05-24 18:21:29
안녕하세요. PCA를 공부하는 학생입니다. 혹시 저도 소스코드 메일로 공유가능할까요?? 유용하게 공부하고 갑니다~
더랑 · 2020-05-24 18:14:51
안녕하세요 PCA공부 하면서 정말 유용하게 공부할 수 있었습니다. 혹시 아직 가능하시다면 소스코드 얻을 수 없을까요?
더랑 · 2020-05-24 18:16:17
안녕하세요. PCA에 대해 공부하던 도중 정말 유용하게 잘 알고 갔습니다. 혹시 소스코드 공유 가능하신가요?
더랑 · 2020-05-24 18:18:11
e댓글이 잘 안남겨져서 혹시 댓글이 여러번 달리게 되었다면 정말 죄송합니다.
PCA를 공부하던 도중 정말 유용하게 공부하고 갈 수 있었습니다. 혹시 괜찮으시다면 메일로 소스코드 공유 가능할까요??
더랑 · 2020-05-24 18:18:54
댓글이 잘 안남겨져서 혹시 댓글이 여러번 달리게 되었다면 정말 죄송합니다.
PCA를 공부하던 도중 정말 유용하게 공부하고 갈 수 있었습니다. 혹시 괜찮으시다면 메일로 소스코드 공유 가능할까요??
더랑 · 2020-05-24 18:22:17
안녕하세요. PCA를 공부하는 학생입니다. 혹시 저도 소스코드 메일로 공유가능할까요?? 유용하게 공부하고 갑니다~
Engineer myoa · 2020-05-24 22:38:20
스팸 차단때문에 승인과정이 있어서 그렇습니다. 메일로 발송해드릴게요.
고33 · 2020-08-10 16:20:17
안녕하세요 컴공을 지망하는 고등학교 3학년 학생입니다. 실시간 얼굴인식 출석 프로그램 개발을 학술활동으로 진행하고 싶어 Google teachable machine을 통해 해봤지만 인식률도 떨어지고 최종적으로 원하는 모양새도 안 나와서 다른 방법을 찾다보니 오픈CV를 발견했습니다. 죄송하지만 정말 급해서 그런데 소스코드 메일로 저도 공유 해주실 수 있으실까요? 게시글 너무너무 잘 봤습니다 🙂