카메라 캘리브레이션
고친 사람 github-actions[bot]
카메라 캘리브레이션은 카메라 한 대가 장면을 사진에 어떻게 옮겨 담는지 그 값을 알아내는 일입니다. 크기를 아는 무늬를 여러 각도에서 찍고, 무늬가 찍힌 모습에서 거꾸로 계산합니다. 이 값을 알아야 사진으로 실제 길이와 방향을 잴 수 있습니다.
쉽고 빠른 이해
카메라 캘리브레이션은 사진 여러 장을 받아 그 카메라의 성질을 숫자 몇 개로 내놓습니다. 바둑판 무늬를 인쇄해 웹캠 앞에서 이리저리 기울이며 찍으면, 그 웹캠이 얼마나 넓게 담는지와 렌즈가 곧은 선을 얼마나 휘는지가 숫자로 나옵니다.
이게 없으면 사진 속 상자가 얼마나 크게 찍혔는지는 알아도 몇 미터 앞에 있는지는 알 수 없습니다. 렌즈가 휘게 찍은 곧은 선도 펼 수 없습니다.
어떻게 도나:
- 칸 크기를 아는 바둑판 무늬를 여러 각도에서 찍습니다
- 사진마다 칸 모서리가 사진의 어디에 찍혔는지 찾습니다
- 모서리의 실제 위치와 찍힌 위치가 가장 잘 맞도록 카메라 값을 구합니다
대가도 있습니다. 카메라마다 따로 해야 하고, 줌이나 초점 맞춤을 바꾸면 다시 해야 합니다. 무늬를 사진 한가운데에서만 찍으면 가장자리의 휨은 잘 못 맞춥니다.
사진 속에 무엇이 있는지만 가리는 일에는 필요 없습니다. 길이나 거리를 재거나, 가상 물체를 사진에 겹칠 때 필요합니다.
상세
처음 산 주방 저울은 눈금이 맞는지 알 수 없습니다. 무게를 아는 추를 올려 보고 눈금이 얼마나 어긋나는지 적어 둡니다. 그다음부터는 그 어긋남을 빼고 읽습니다.
이 절은 카메라에 같은 일을 하는 방법을 봅니다. 먼저 카메라가 장면을 사진으로 옮기는 식과 그 식에 든 값 세 묶음을 봅니다. 다음은 그 값을 무늬 사진으로 구하는 순서와 계산이 잘됐는지 재는 법입니다. 끝으로 이 값을 어디에 쓰는지와 백엔드 개발자가 이 일을 만나는 때를 봅니다.
예로는 책상 위 웹캠 하나를 계속 씁니다. 목표는 이 웹캠으로 찍은 사진만 보고 책상 위 상자가 몇 미터 떨어져 있는지 재는 것입니다.
카메라의 색을 맞추는 일도 캘리브레이션이라고 부릅니다. 같은 빨간색이 카메라마다 다르게 찍히는 것을 맞추는 일입니다. 이쪽은 색 보정이 다룹니다. 이 항목의 캘리브레이션은 모양과 위치에 관한 값입니다.
장면의 점이 픽셀이 되는 식
카메라는 입체인 장면을 평평한 사진에 옮겨 담습니다. 3차원의 점을 2차원 평면에 떨어뜨리는 이 일이 원근 투영입니다. 가까운 것이 크게, 먼 것이 작게 찍히는 것이 이 투영 때문입니다.
투영을 계산하는 기본 모형은 핀홀 카메라입니다. 장면에서 나온 빛이 바늘구멍 하나를 지나 뒤쪽 평면에 맺힌다고 봅니다. 이 모형을 쓰면 장면의 점 하나가 사진의 어디에 찍히는지 곱셈과 나눗셈으로 구할 수 있습니다.
사진은 픽셀이라는 작은 점들이 가로세로로 늘어서서 이루어집니다. 그래서 「사진의 어디」는 결국 가로 몇 번째, 세로 몇 번째 픽셀이라는 두 숫자입니다.
이 계산에는 세 묶음의 값이 들어갑니다. 아래 그림이 장면의 점 하나가 픽셀이 되기까지 거치는 순서입니다.
flowchart TD
A["장면의 점 · 미터 단위"] -->|"외부 파라미터 · 카메라 위치와 방향"| B["카메라에서 본 점"]
B -->|"원근 투영 · 거리로 나눔"| C["바늘구멍 뒤 평면의 점"]
C -->|"렌즈 왜곡 · 휨을 더함"| D["휘어 찍힌 점"]
D -->|"내부 파라미터 · 렌즈와 센서가 정하는 값"| E["픽셀"]
카메라 캘리브레이션이 주로 노리는 것은 이 가운데 카메라에 딸린 둘, 내부 파라미터와 렌즈 왜곡입니다. 외부 파라미터도 함께 구해지지만 사진을 찍을 때마다 다른 값이 나옵니다. 아래 소절에서 하나씩 봅니다.
내부 파라미터 — 초점 거리와 주점
내부 파라미터는 렌즈와 센서가 정하는 값입니다. 카메라를 어디로 옮겨도 바뀌지 않으므로 한 번 재 두면 계속 씁니다. 그래서 캘리브레이션이 가장 먼저 노리는 값입니다.
첫째는 초점 거리입니다. 핀홀 모형에서 바늘구멍과 뒤쪽 평면 사이의 거리입니다. 이 값이 카메라가 얼마나 좁게 또는 넓게 담는지를 정합니다. 캘리브레이션에서는 이 값을 밀리미터가 아니라 픽셀 수로 적습니다. 그래야 장면의 방향을 바로 픽셀 위치로 바꿀 수 있습니다.
초점 거리는 가로와 세로로 두 값을 둡니다. 센서의 픽셀이 완전한 정사각형이 아닐 수 있기 때문입니다. 대부분의 카메라에서는 두 값이 거의 같게 나옵니다.
둘째는 사진의 중심입니다. 렌즈의 축이 센서와 만나는 점이 주점입니다. 주점은 사진 한가운데 근처에 있지만, 조립할 때 생기는 작은 어긋남 때문에 한가운데에서 조금 비켜난 일이 흔합니다.
초점 거리 둘과 주점 좌표 둘, 이 네 숫자가 기본 내부 파라미터입니다. 이 넷은 3×3 행렬 하나로 묶어 적기도 합니다. 나머지 칸은 0과 1로 채웁니다. 이 행렬이 내부 행렬입니다.
아래는 예로 든 웹캠의 내부 파라미터로 점 하나를 픽셀로 옮겨 본 것입니다. 카메라에서 오른쪽으로 0.4미터, 앞으로 2미터 떨어진 점입니다.
const fx = 700; // 초점 거리
const cx = 640; // 주점 가로 좌표
const X = 0.4, Z = 2; // 점 위치(m)
const u = fx * X / Z + cx; // 780 (픽셀)
점은 가로 780번째 픽셀에 찍힙니다. 앞 거리 Z 로 나누는 부분이 원근 투영입니다. 초점 거리를 곱하고
주점을 더하는 부분이 내부 파라미터의 몫입니다. 세로 위치도 같은 식으로 계산합니다.
렌즈 왜곡 — 곧은 선이 휘는 정도
실제 렌즈는 바늘구멍과 달리 곧은 선을 조금 휘게 찍습니다. 이 휨이 렌즈 왜곡입니다. 휨을 모르면 핀홀 모형의 식이 사진 가장자리로 갈수록 어긋납니다.
가장 흔한 휨은 방사 왜곡입니다. 사진 중심에서 멀어질수록 휨이 커집니다. 광각 렌즈일수록 이 휨이 큽니다.
방사 왜곡은 두 모양으로 나타납니다. 가장자리가 술통처럼 바깥으로 불룩해지는 모양이 배럴 왜곡입니다. 반대로 네 변이 가운데 쪽으로 움푹 들어간 모양이 핀쿠션 왜곡입니다.
렌즈와 센서가 완전히 나란하지 않아서 생기는 휨도 있습니다. 이 휨이 접선 왜곡입니다. 대개 방사 왜곡보다 작습니다.
캘리브레이션은 이 휨을 계수 몇 개로 적습니다. 계수를 알면 휜 사진을 곧게 펴서 다시 그릴 수 있습니다. 이 일이 왜곡 보정입니다. 캘리브레이션은 값을 알아내는 일이고, 왜곡 보정은 그 값으로 사진을 고치는 일입니다.
외부 파라미터 — 사진마다 달라지는 위치
외부 파라미터는 카메라가 장면 안에서 선 곳과 향한 쪽입니다. 얼마나 돌았는지를 적는 회전과 얼마나 옮겨 갔는지를 적는 이동, 두 가지로 이루어집니다. 카메라를 들고 움직이면 사진마다 다른 값이 나옵니다.
캘리브레이션 중에도 외부 파라미터는 함께 구해집니다. 무늬를 찍은 사진마다 무늬가 카메라 앞 어디에 어떻게 놓였는지가 나옵니다. 이 값은 다른 사진에 쓸 수 없으므로 내부 파라미터를 구하는 길에 거쳐 가는 값으로 봅니다.
카메라를 한곳에 고정해 두면 사정이 다릅니다. 매장 천장의 카메라처럼 움직이지 않으면 외부 파라미터도 한 번 재 두고 계속 씁니다. 이렇게 고정된 카메라의 위치와 방향을 재는 일을 따로 외부 캘리브레이션이라고 부르기도 합니다.
크기를 아는 무늬
캘리브레이션은 실제 위치를 아는 점과 그 점이 찍힌 픽셀을 짝지어 계산합니다. 그래서 점의 실제 위치를 미리 알 수 있는 물건이 필요합니다. 이 물건이 캘리브레이션 패턴입니다.
가장 흔한 패턴은 흑백 칸을 번갈아 칠한 체커보드입니다. 칸과 칸이 만나는 모서리는 사진에서 또렷하게 잡힙니다.
모서리의 실제 좌표는 판의 한 귀퉁이를 원점으로 놓고 매깁니다. 판 위에 자를 대듯 가로로 몇 칸, 세로로 몇 칸인지에 칸 한 변의 길이를 곱하면 모서리마다 좌표가 나옵니다.
판이 평평하므로 판 위의 모든 점은 높이를 0으로 둡니다. 그러면 점마다 숫자 셋 대신 가로세로 둘만 다루면 됩니다. 이 덕분에 뒤에 나올 첫 어림값 계산이 쉬워집니다.
체커보드 말고 둥근 점을 격자로 늘어놓은 패턴도 씁니다. 칸마다 서로 다른 표식을 넣은 패턴은 판의 일부만 찍혀도 어느 모서리인지 알아봅니다.
예로 든 웹캠에서는 한 칸이 3센티미터인 체커보드를 종이에 인쇄해 단단한 판에 붙입니다. 인쇄한 뒤에는 칸 길이를 자로 재 둡니다. 프린터가 크기를 조금 바꿔 찍는 일이 있어서, 인쇄 설정의 숫자를 믿으면 결과가 그만큼 틀립니다.
계산하는 순서
이 소절은 무늬 사진에서 값이 나오기까지의 순서를 봅니다. 아래 그림이 그 순서입니다.
flowchart TD
A["체커보드를 여러 각도에서 찍기"] --> B["사진마다 모서리 픽셀 찾기"]
B --> C["모서리마다 실제 좌표와 짝짓기"]
C --> D["왜곡 없이 식을 풀어 첫 어림값 얻기"]
D --> E["어긋남이 가장 작아지도록 다듬기"]
E --> F["내부 파라미터와 왜곡 계수"]
먼저 판을 기울이고 돌리고 옮겨 가며 여러 장 찍습니다. 사진마다 모서리를 찾아 픽셀 위치를 적습니다. 모서리 위치는 픽셀 하나보다 잘게, 소수점 아래까지 찾습니다.
다음으로 첫 어림값을 얻습니다. 판 위의 점은 높이가 0이라 가로세로 숫자 둘로 나타납니다. 그래서 판 위의 점과 사진 위의 점 사이 대응은 3×3 행렬 하나로 적힙니다. 이 행렬이 호모그래피입니다.
행렬 하나로는 곧은 선을 곧은 선으로 옮기는 대응만 적을 수 있습니다. 렌즈의 휨은 담지 못합니다. 그래서 이 단계에서는 렌즈 왜곡이 없다고 치고 계산합니다.
호모그래피는 카메라의 내부 파라미터와 그 사진에서 판이 놓인 위치가 곱해져 나온 결과입니다. 판의 위치는 사진마다 다르지만 내부 파라미터는 모든 사진에서 같습니다. 그래서 사진마다 호모그래피를 하나씩 구해 한데 모아 풀면, 공통으로 든 내부 파라미터의 어림값이 나옵니다.
마지막으로 이 어림값을 출발점으로 삼아 값을 조금씩 고쳐 갑니다. 왜곡 계수도 이때 함께 넣습니다. 고칠 때마다 모서리가 사진에 어디로 찍혀야 하는지 다시 계산해 보고, 찾은 위치와의 어긋남이 줄어드는 쪽으로 옮깁니다.
어긋남을 제곱해 모두 더한 값을 가장 작게 만드는 이런 계산이 비선형 최소제곱입니다. 한 번에 답이 나오지 않아 되풀이해 다가갑니다. 출발점이 답에서 너무 멀면 엉뚱한 값에 머물 수 있어서 첫 어림값을 먼저 구합니다.
재투영 오차 — 계산이 잘됐는지 재는 법
구한 값으로 체커보드 모서리를 사진 위에 다시 그려 봅니다. 그린 위치와 사진에서 찾은 위치 사이 거리를 재투영 오차라고 부릅니다. 모든 모서리의 오차를 모아 픽셀 단위의 수 하나로 적습니다.
오차가 작을수록 값이 사진과 잘 맞는다는 뜻입니다. 오차가 크면 모서리를 잘못 찾은 사진이 섞였거나, 흔들려 흐린 사진이 있거나, 판이 휘었을 수 있습니다. 오차가 유난히 큰 사진을 빼고 다시 계산하는 일이 흔합니다.
오차가 작다고 다 된 것은 아닙니다. 재투영 오차는 찍은 사진 안에서만 맞춰 본 값입니다. 무늬가 사진 한가운데에만 찍혔다면 가장자리의 휨은 계산에 들어가지 않았습니다. 그래도 오차는 작게 나옵니다.
잘 찍는 법
그래서 무늬가 사진의 네 귀퉁이와 가장자리까지 고루 덮도록 찍습니다. 웹캠 앞에서 판을 가까이, 멀리, 왼쪽 위, 오른쪽 아래로 옮겨 가며 찍습니다.
판을 기울이는 것도 중요합니다. 판을 늘 카메라와 나란하게 들고 찍으면, 무늬가 크게 찍힌 것이 판이 가까워서인지 초점 거리가 길어서인지 가를 수 없습니다.
판을 기울이면 가까운 쪽 칸은 크게, 먼 쪽 칸은 작게 찍힙니다. 한 장 안에 거리가 여럿 섞이므로 크기 차이를 거리 탓으로만 돌릴 수 없게 됩니다. 이 차이가 초점 거리를 가르는 단서가 됩니다.
여기서 초점은 피사체가 선명하게 맺히도록 렌즈를 앞뒤로 옮겨 맞춘 위치입니다. 초점 거리와는 다른 것입니다. 다만 렌즈가 움직이면 초점 거리와 주점도 조금씩 바뀝니다.
그래서 초점과 줌은 고정한 채 찍습니다. 자동 초점이 켜져 있으면 사진마다 렌즈가 움직여 내부 파라미터가 바뀝니다. 그렇게 찍은 사진들은 서로 다른 카메라로 찍은 것과 같아서 한데 모아 계산할 수 없습니다.
알아낸 값을 쓰는 곳
값을 알면 사진의 픽셀 하나가 장면의 어느 방향인지 알 수 있습니다. 여기에 크기를 아는 물체가 있으면 거리까지 나옵니다. 아래는 예로 든 웹캠이 폭 0.5미터인 상자를 찍은 경우입니다.
const fx = 700; // 초점 거리(픽셀)
const W = 0.5; // 상자 실제 폭(m)
const w = 175; // 사진 속 폭(픽셀)
const Z = fx * W / w; // 2 (m)
상자는 웹캠에서 2미터 떨어져 있습니다. 같은 상자가 350픽셀로 찍히면 거리는 1미터입니다. 초점 거리를 모르면 이 계산의 첫 줄을 채울 수 없습니다. 상자가 사진 가장자리에 찍혔다면 왜곡부터 펴야 폭을 바로 잽니다.
나란히 놓은 카메라 두 대로 거리를 재는 방식이 스테레오 비전입니다. 이때는 카메라마다 내부 파라미터를 재고, 두 카메라가 서로 어떻게 놓였는지도 구합니다. 이 일을 스테레오 캘리브레이션이라고 부릅니다.
그래픽스 카메라와 반대 방향
그래픽스도 같은 핀홀 모형으로 카메라를 적습니다. 방향만 반대입니다.
그래픽스는 시야각 같은 값을 먼저 정해 두고 그림을 만듭니다. 시야각은 카메라가 담는 각의 넓이이고, 초점 거리가 이 각을 정합니다. 초점 거리가 길수록 좁게 담습니다. 캘리브레이션은 찍힌 사진에서 이 초점 거리를 거꾸로 알아냅니다.
두 방향은 증강 현실에서 만납니다. 휴대폰 화면에 비친 바닥 위에 가상의 의자를 그리려면 그래픽스 카메라가 실제 카메라와 같은 값을 가져야 합니다. 캘리브레이션으로 얻은 초점 거리와 주점으로 그래픽스의 투영 행렬을 만들면 의자가 바닥에 맞게 놓입니다.
그래픽스 카메라에는 렌즈 왜곡이 없습니다. 그래서 실제 사진에 가상 물체를 겹칠 때는 사진의 왜곡을 먼저 펴거나, 그린 그림에 같은 휨을 입힙니다.
백엔드 개발자가 이 일을 만나는 때
캘리브레이션 결과는 카메라 한 대에 숫자 열 개 안팎입니다. 초점 거리 둘, 주점 좌표 둘, 왜곡 계수 몇 개입니다. 카메라가 여럿인 서비스는 이 값을 카메라 식별자마다 저장해 둡니다. 사진이 올라오면 그 카메라의 값을 꺼내 씁니다.
같은 모델의 카메라라도 값은 한 대 한 대, 개체마다 조금씩 다릅니다. 조립할 때 생기는 어긋남이 개체마다 다르기 때문입니다. 정밀하게 재야 하는 일이면 모델별 값 하나를 돌려 쓰지 않고 개체마다 잽니다.
값과 카메라의 짝이 어긋나도 에러는 나지 않습니다. 카메라를 바꿔 달았는데 옛 값을 쓰면 계산은 끝까지 돌고 거리만 조용히 틀립니다. 그래서 값에 어느 카메라 개체로 언제 쟀는지를 함께 적습니다. 카메라를 바꾸거나 렌즈에 손을 대면 다시 잽니다.
캘리브레이션이 필요 없는 일도 많습니다. 사진에 고양이가 있는지 가리는 일처럼 사진 안에서 끝나는 일은 실제 길이와 방향을 묻지 않습니다. 길이를 재거나, 여러 카메라의 사진을 맞대거나, 가상 물체를 겹칠 때 캘리브레이션이 필요해집니다.
관련 항목
카메라 캘리브레이션이 알아내는 값
내부 파라미터 · 외부 파라미터 · 초점 거리 · 주점 · 렌즈 왜곡 · 방사 왜곡 · 접선 왜곡 · 내부 행렬
카메라 캘리브레이션이 기대는 카메라 모형
카메라 · 핀홀 카메라 · 원근 투영 · 투영 행렬 · 동차 좌표 · 좌표 변환 · 호모그래피
카메라 캘리브레이션이 거치는 처리 단계
코너 검출 · 서브픽셀 · 비선형 최소제곱 · 레벤버그-마쿼트 알고리즘 · 재투영 오차 · 왜곡 보정
카메라 캘리브레이션에 쓰는 패턴
체커보드 · 원형 격자 패턴 · ArUco 마커 · ChArUco
카메라 캘리브레이션의 하위 종류
스테레오 캘리브레이션 · 외부 캘리브레이션 · 핸드-아이 캘리브레이션 · 자기 캘리브레이션 · 라이다-카메라 캘리브레이션
카메라 캘리브레이션 값을 받아 쓰는 기술
컴퓨터 비전 · 스테레오 비전 · 3D 재현 · 증강 현실 · SLAM · 시각 오도메트리 · 에피폴라 기하 · 그래픽스
카메라 캘리브레이션을 구현한 도구
OpenCV · Kalibr · MATLAB
카메라 캘리브레이션과 헷갈리는 이웃
색 보정 · 화이트 밸런스 · 이미지 정합 · 확률 보정
다른 이름: camera calibration · 카메라 보정 · 카메라 교정 · 사진기 캘리브레이션