
MCATrack의 Magno-Motion Module의 근간이 되는 핵심 두 가지를 알아보자.
카메라가 움직이면 (Ego-motion), 화면 속의 모든 배경 픽셀들이 한꺼번에 이동한다.
변환 행렬은 '이전 프레임의 픽셀들이 현재 프레임에서 어디로 이동했는가?'를 설명해주는 수학적인 '지도' 역할을 해주는 것이다.
- Affine 변환 : 회전(Rotation), 크기 조절(Scale), 이동(Translation)을 포함한다. 행렬의 크기는 이다.
에? 아닌가요?
수학적으로는 이 맞다. 하지만 OpenCV의 효율성을 위한 '생략'때문에 코드에서는 으로 보인다.
어떤 점 를 Affine 변환하여 새로운 점 를 만드는 수식을 전개해보면 다음과 같다.
이것을 행렬 곱셈으로 표현한다면, OpenCV가 반환하는 행렬은 행렬이 된다.
그런데, 아까 다중 프레임 연산을 위해 Chain Rule이 등장한다고 했는데, 행렬은 자기들끼지 곱할 수 없다. 떄문에 항상 맨 아래에[0, 0, 1]을 붙여서 정사각형 행렬로 만든다.
- Affine의 본질 : 맨 아랫줄이 무조건
[0, 0, 1]로 고정되어 있다. 그래서 OpenCV는 메모리와 연산 속도를 아끼기 위해 굳이 쓸모없는 아랫줄을 계산하거나 반환하지 않고 만 준다.
(cv2.warpAffine())도 이 만 받아서 처리하도록 최적화되어 있다.- Homography의 본질 : 반면 Homography는 맨 아랫줄이
[h31, h32, 1]처럼 원근감을 계산하는 변수들로 채워져있기 때문에, 생략할 수 없이 반드시 이어야한다.
(cv2.warpPerspective)함수를 따로 써야한다.- Homography 변환 : 원근감(Perspective) 변화까지 포함한다. 행렬의 크기는 이다.
시점의 프레임을 시점으로 맞추기 위해 구한 행렬을 라고 하자. 이 행렬을 이전 이미지에 곱해주면 (Warping), 카메라가 흔들리지 않은것 처럼 배경이 고정된다.변환 행렬은 자유도 (Degrees of Freedom, DoF)에 따라서 여러 단계로 나뉜다.
- Translation (이동, 2 DoF) : 상하좌우 이동만 한다. (단순 Panning)
- Euclidean / Rigid (강체 변환, 3 DoF) : 이동 + 회전이 추가된다. (모양과 크기 유지)
- Similarity (유사 변환, 4 DoF) : 이동 + 회전 + 크기조절 + 기울임(Shear)이 추가된다. (평행선은 유지됨)
- Projective / Homography (호모그래피, 8 DoF) : 이 모든 것에 원근감(Perspective)가 추가된다. (평행선이 만날 수 있다.)
2개의 프레임 ()는 그냥 매칭해서 구하면 되지만, 과거의 프레임들()을 모두 현재 시점 ()로 가져오려면 어떻게 해야할까?
가장 직관적인 방법은 매번 프레임과 과거 프레임을 직접 비교하는 것이다. 하지만 이 방법은 시간이 지날수록 카메라가 너무 많이 이동해서 공통된 배경이 사라지거나 원근이 바뀌어 매칭 자체가 실패해버린다.
때문에 인접한 프레임끼리만 행렬을 구한 뒤, 그것들을 곱해서(Matrix Mutiplication) 한 번에 건너뛰는 행렬을 만드는 것이다.
이런 식으로 과거의 어떤 프레임이든 인접 프레임 간의 행렬들을 차례로 곱해준다면, 현재 시점으로 완벽하게 겹치도록 변환할 수 있다. 이것이 다중 프레임 정렬의 핵심이다.
현실 세계 (3D)의 어떤 점 를 이동시키고 회전할 때에는, 동차 좌표계를 사용하면 벡터는 이 되고, 변환 행렬은 가 된다.
이것이 바로 3D 비전에서 다루는 절대적인 진리인데..
현재 내가 겪고 있는 Anti-UAV 프레임 정렬 문제는 3D 공간이 아니다. 카메라는 3D 세상을 촬영하여 깊이(축)을 짓눌러버리고 2D 평면 이미지 (픽셀 좌표계)로 만든다.
MCATrack에서 인접 프레임 간 정렬 (Inter-frame Registration)을 수행할 때에는 3D 깊이를 알 수 없으니, 오직 2D 이미지 평면의 픽셀 들이 어떻게 이동했는지만을 계산한다.
2D 공간의 점를 동차 좌표계로 나타내면 depth-축 이 날아가고 라는 벡터가 된다.
이를 변환하려면 행렬도 당연히 으로 줄어든다.
Affine 변환에서 하단이 왜 [0, 0, 1]인가?
호모그래피의 행렬에서 맨 아랫줄 은 2D 평면이 3D 공간상에서 기울어질 때 발생하는 원근 왜곡 (Perspective Distortion)을 계산하는 파라미터이다.
하지만 하늘 배경의 드론 추적에서는 원근 변화를 무시하고 평행선을 유지하는 Affine 변환을 사용하는데, 수학적으로 원근 왜곡이 없다는 것은 과 가 정확히 0이라는 것을 의미한다. 또한 스케일 정규화를 위해 은 1이 된다.
따라서 2D Affine 변환의 동차 행렬은 반드시 아래와 같은 형태가 된다.
이제 이 행렬 곱셈을 픽셀 좌표를 구하는 대수 방정식으로 풀어보자.
1.
2.
3.
세 번째 수식을 보면 이다.
이 수식은 새로운 픽셀 좌표 을 계산하는데에 아무런 쓸모가 없는 건데..
한 프레임에 수백만 개의 픽셀이 있는 상황에서 이 무의미한 연산을 수백만 번 반복하는 것은 자원 낭비이다.
그래서 OpenCV의 연구원들은 메모리와 연산량을 아끼기 위해 Affine 변환에서 쓸모가 없는 맨 아랫줄을 아예 메모리에서 잘라내버렸다.
이론은 행렬을 곱하면 된다고 하지만, 막상 파이썬 (OpenCV)에서 Affine 변환 행렬을 2개 np.dot()으로 곱하려고 하면 에러가 발생한다.
이유는 OpenCV가 반환하는 Affine 행렬의 크기가 이기 때문인데, 수학적으로 끼리의 행렬곱은 불가능하다.
그래서 곱셈을 하기 전에 이 행렬을 정사각형 행렬 (Homogeneous Matrix)로 만들어줘야 한다.
- 행렬의 맨 아래에
[0, 0, 1]이라는 한 줄을 추가해서 으로 만든다.- 이제 행렬끼리 곱한다. (
np.matmul)- 계산이 끝나면 맨 아랫줄을 다시 잘라내어 으로 만든 뒤,
cv2.warpAffine()에 집어넣는다.
카메라가 오른쪽으로 Panning 했다고 가정해보자.
이전 프레임을 현재 프레임에 맞추기 위해 왼쪽으로 당겨오면, 원래 화면에 없던 오른쪽 가장자리 부분에는 빈 공간이 생긴다.
cv2.absdiff(warped_prev, curr)를 실행하면 현재 프레임의 오른쪽 가장자리 배경(예를 들어 하늘의 색 150) 과 검은색(0)이 빼기가 되면서, 가장자리에 거대한 하얀색 노이즈가 생겨버린다. Magno-Motion 모듈은 이 거대한 노이즈를 엄청나게 빠르게 움직이는 거대한 타겟으로 생각하고 시야를 완전히 빼앗긴다.borderMode=cv2.BORDER_REPLICATE카메라가 이동했을 때 두 프레임 간의 변환 행렬을 구하려면 "이전 프레임의 A 픽셀이 현재 프레임의 B 픽셀로 이동했다" 라는 확신이 필요하다.
이 때 아무런 픽셀이나 잡아버리면 Aperture Problem이라는 오류에 빠지게 된다.
결국 좋은 특징점은 코너 (Corner)를 의미한다.
구현에서 특징점 검출을 위해 ORB (Oriented FAST and Rotated BRIEF) 를 사용했다. 이는 두 가지 알고리즘의 결합인데, 빠른 속도와 회전 불변성 때문에 실무에서 많이 쓰인다.
import cv2
orb = cv2.ORB_create(
nfeatures=40000, # 최대 추출 특징점 수
scaleFactor=1.2, # 이미지 피라미드에서 레벨 간 축소 비율
nlevels=8, # 피라미드 레벨 수
edgeThreshold=31, # 이미지 경계에서 Keypoint 검출하지 않는 Margin
firstLevel=0, # 피라미드 시작 레벨
WTA_K = 2, # BRIEF descriptor에서 사용하는 비교 픽셀 수
scoreType=cv2.ORB_HARRIS_SCORE, # Key Point Ranking 방식
patchSize=31, # descriptor 계산에 사용하는 Patch 크기
fastThreshold=20 # FAST corner detection threshold
)
STEP 1 : 코너 찾기 (FAST 알고리즘)
어떤 픽셀 가 코너인지 확인하기 위해 를 중심으로 원을 그리듯 주변 16개의 픽셀 밝기를 검사한다.
만약 보다 '확실히 더 밝거나 확실히 더 어두운' 픽셀들이 연속으로 9개 이상 존재한다면, 그 점을 코너로 합격시킨다.
STEP 2: BRIEF descriptor
찾은 특징점을 다음 프레임에서 찾을 수 있도록 Descriptor를 만들어야 한다.
- 특징점 주변의 픽셀들을 무작위로 두 개씩(A, B) 짝지어 밝기를 비교한다.
- A가 B보다 밝으면 1, 어두우면 0 이런 식으로 256번을 비교하여 256비트 이진수 문자열을 생성한다.
- 이진수로 되어 있어, 두 특징점이 같은지 비교할 때 컴퓨터의 XOR 연산(해밍거리)로 일치여부를 판별한다.
주의할 점
- 적외선 카메라는 열을 감지하기 때문에 텍스쳐가 가시광선 카메라처럼 뚜렷하지 않고 뭉개져 보인다.
- 하늘에는 Corner가 없어서 ORB의 FAST 알고리즘이 측징점을 찾지 못하는 경우가 有
- 코너가 없으니, 자글자글한 열 노이즈나 압축 Artifact를 코너로 착각하고 특징점으로 잡아버린다.