5.28. QR 코드와 AprilTag

지금까지 다룬 검출기들(블롭, 선, 원, 사각형)은 기하학적 특징, 즉 위치와 윤곽을 찾아내며 이를 후속 단계에서 해석합니다. 남은 검출기들은 기호적 특징을 찾습니다. 즉 페이로드를 인코딩하기 위해 시각적 구조가 특별히 존재하는 인쇄된 패턴입니다. 카메라가 이를 찾아내고, 디코더가 비트를 읽으며, 그 결과로 돌아오는 것은 위치가 아니라 기호를 인쇄한 사람이 의도적으로 선택한 문자열(또는 ID)입니다.

이러한 두 부류가 소형 카메라 응용 분야를 지배합니다. QR 코드는 임의의 텍스트, URL, 연락처 카드 또는 바이너리 페이로드를 담습니다. 포스터, 포장재, 탑승권에 등장하는 소비자 대상 2D 코드입니다. AprilTag는 작은 고정 집합에서 하나의 숫자 ID를 담으며, 먼 거리에서도 빠르게 디코딩되고, (렌즈 내부 파라미터가 제공될 때) 카메라 프레임 내에서 6자유도(6-DoF) 자세(pose)를 보고합니다. 드론, 캘리브레이션 타깃, 기준 마커(fiducial)를 표시하는 로보틱스 대상 2D 코드입니다. 두 검출기 모두 블롭 및 사각형 검출기가 사용하는 것과 동일한 경계 상자 어휘를 가진 결과 객체를 반환하지만, 페이로드가 이들을 지금까지 다룬 그 어떤 것과도 근본적으로 다르게 만듭니다.

5.28.1. QR 코드

find_qrcodes() 는 프레임에서 QR 코드를 스캔하여 QRCode 결과 객체의 목록을 반환합니다:

codes = img.find_qrcodes()

for c in codes:
    img.draw_rectangle(c.rect, color=(0, 255, 0))
    for corner in c.corners:
        img.draw_circle((corner[0], corner[1], 4),
                        color=(0, 255, 0))
    print(c.payload)

이 검출기는 검색 범위를 제한하기 위한 단일 선택적 roi 를 받습니다. 그레이스케일 입력이 필요하며, 색상 프레임은 디코딩 전에 내부적으로 변환됩니다.

각 검출 결과는 경계 상자(x, y, w, h, rect), 검출된 네 개의 모서리(corners, QR 코드의 파인더 패턴이 그려내는 투영 사각형), 그리고 문자열로 디코딩된 페이로드를 담습니다. 검출 결과에 주석을 달 때 그릴 올바른 것은 모서리입니다. 비스듬한 각도에서 본 QR 코드는 축에 정렬되어 있지 않으며, 경계 상자는 느슨한 윤곽만 제공하기 때문입니다.

디코더 메타데이터는 QR 디코더가 처리 과정에서 알아낸 모든 것을 포함합니다. version 은 QR 코드 버전으로 1 – 40 범위이며, 모듈 그리드 크기를 결정합니다(버전 1 코드는 21 모듈 폭, 버전 40 코드는 177 모듈 폭). ecc_level 은 오류 정정 수준(L / M / Q / H에 대해 0 – 3)으로, 수준이 높을수록 오류 정정에 더 많은 코드워드를 할당하여 더 많은 손상을 견디지만 페이로드 공간은 줄어듭니다. mask 는 인코더가 디코더 혼동을 최소화하기 위해 선택한 마스크 패턴(0 – 7)입니다. data_type 은 디코더가 보고한 인코딩(숫자, 영숫자, 바이너리, 또는 한자)이며, is_numeric / is_alphanumeric / is_binary / is_kanji 플래그는 같은 값을 더 친숙한 불리언으로 노출합니다.

eci 는 확장 채널 해석(Extended Channel Interpretation) 값으로, 바이트가 어떤 텍스트 인코딩(UTF-8, ISO-8859-1 등)으로 되어 있는지를 식별합니다. 임의의 인쇄물에서 나온 QR 코드는 UTF-8임이 보장되지 않을 수 있습니다. 바이트를 올바르게 디코딩해야 하는 응용 프로그램은 eci 를 확인하고 그에 따라 디코딩합니다. 특히 한자의 경우, MicroPython은 한자 인코딩을 파싱하지 않으므로 is_kanji 페이로드는 바이트 배열로 취급하여 응용 프로그램이 디코딩해야 합니다.

전형적인 사용 예: 카메라가 컨베이어 위의 QR 코드를 읽어 디코딩된 페이로드를 호스트에 보고합니다. 카메라는 프레임당 한 번 find_qrcodes() 를 실행하고, 반환된 목록을 순회하며, data_type 이 응용 프로그램이 기대하는 것과 일치하는 코드를 선택하여 c.payload 를 UART 또는 USB로 전달합니다. 경계 상자와 모서리 데이터는 IDE 미리보기에는 유용하지만 호스트가 관심을 두는 것은 아닙니다.

5.28.2. AprilTag

find_apriltags() 는 프레임에서 AprilTag를 스캔하여 AprilTag 결과 객체의 목록을 반환합니다:

tags = img.find_apriltags(families=image.TAG36H11)

for t in tags:
    img.draw_rectangle(t.rect, color=(0, 255, 0))
    img.draw_cross(t.cx, t.cy, color=(0, 255, 0))
    print(t.id, t.decision_margin)

AprilTag는 설계 목표 면에서 QR 코드와 다릅니다. QR 코드는 사용자가 근거리에서 한 번 읽는 단일 밀집 기호에 임의의 데이터를 인코딩하도록 만들어졌습니다. AprilTag는 카메라가 멀리서 연속적으로 읽는 희소 기호에 작은 ID를, 해당 부류의 해밍 코드(Hamming code)가 허용하는 만큼의 오류 허용도로 인코딩하도록 만들어졌습니다. 이 절충은 양방향으로 나타납니다. QR 코드는 수백 바이트를 담을 수 있지만 가까이서 읽어야 하고, AprilTag는 고유 ID를 수백 개만 담지만 수 미터 떨어진 거리에서도 안정적으로 읽힙니다.

families 키워드는 디코딩할 태그 부류의 비트마스크를 받습니다. 사용 가능한 부류는 image.TAG16H5, image.TAG25H9, image.TAG36H10, image.TAG36H11, image.TAGCIRCLE21H7, image.TAGCIRCLE49H12, image.TAGCUSTOM48H12, image.TAGSTANDARD41H12, image.TAGSTANDARD52H13 입니다. 각 부류는 ID 개수와 견고성 사이에서 절충합니다. 이름의 H 숫자는 부류 내 임의의 두 코드 사이의 최소 해밍 거리(Hamming distance), 즉 하나의 유효한 코드가 다른 코드로 바뀌기까지 뒤집혀야 하는 비트 수입니다. TAG16H5 는 거리 5에서 30개의 ID를, TAG25H9 는 거리 9에서 35개의 ID를, TAG36H11 (기본값이자 가장 흔한 것)은 거리 11에서 587개의 ID를 가집니다. 검출기는 부류에 관계없이 최대 두 개의 비트 오류를 정정하므로, 거리가 그 정정이 얼마나 위험한지를 결정합니다. 노이즈가 있는 프레임의 무작위 패턴이 유효한 코드의 두 비트 이내에 들어오기만 하면 거짓 검출로 디코딩될 수 있는데, 거리가 큰 부류일수록 코드를 훨씬 더 희소하게 분산시켜 그러한 충돌이 드물어집니다. 이것이 TAG36H11 이 권장되는 이유입니다. 검출 시간은 활성화된 부류 수에 비례하여 증가하므로, 응용 프로그램은 실제로 인쇄하는 것만 활성화합니다. 한 번의 호출에서 여러 부류가 필요할 때 비트마스크는 부류 상수들의 비트 OR입니다.

각 검출 결과는 경계 상자 어휘(x, y, w, h, rect, area, 정수 및 서브픽셀 중심점 cx, cy, cxf, cyf)와 검출된 네 개의 모서리(corners)를 담습니다. 이어서 식별 필드가 나옵니다. id 는 부류 내의 숫자 ID(TAG36H11 의 경우 0 – 586), family 는 숫자 부류 상수, name 은 문자열로 된 부류 이름입니다.

매칭 품질 필드는 응용 프로그램이 검출 결과를 필터링하는 데 사용하는 것입니다. decision_margin 은 0.0 – 1.0 신뢰도 점수로 높을수록 좋으며, decision_margin > 0.1 미만의 검출을 필터링하면 대부분의 가짜 적중을 비용 없이 정리할 수 있습니다. hamming 은 이 태그에 대해 디코더가 수용한 비트 오류 수를 셉니다. 낮을수록 좋으며 0 은 완벽한 디코딩을 의미합니다. goodness 는 현재 디코더가 더 이상 계산하지 않는 과거의 이미지 품질 지표로, 항상 0.0이며 무시해도 됩니다.

5.28.3. 내부 파라미터로부터의 자세

find_apriltags() 의 획기적인 기능, 즉 AprilTag를 로보틱스 기준 마커로 선택할 만한 이유가 되는 기능은 이 메서드가 검출된 모서리와 소수의 캘리브레이션 내부 파라미터로부터 카메라 프레임 내 태그의 6자유도(6-DoF) 자세를 직접 복원할 수 있다는 점입니다. 내부 파라미터는 픽셀 단위의 카메라 X 및 Y 초점 거리(fx, fy)와 픽셀 단위의 광학 중심(cx, cy)이며, 이 네 가지 모두 응용 프로그램이 캘리브레이션 절차로 한 번 측정한 뒤 이후로는 하드코딩합니다.

내부 파라미터가 제공되면, 반환된 AprilTag 는 카메라에 대한 태그의 위치를 x_translation, y_translation, z_translation 필드에 채우고, 태그의 방향을 x_rotation, y_rotation, z_rotation (및 대칭을 위한 중복 rotation) 필드에 채웁니다. 내부 파라미터가 없으면 여섯 필드는 모두 0.0이며, 필요한 자세 추정은 응용 프로그램이 책임집니다.

이동(translation) 필드는 태그 폭 단위로 보고됩니다. 디코더는 태그를 폭 1 단위로 취급하므로, 응용 프로그램은 각 이동값에 인쇄된 태그의 물리적 폭을 곱하여 미터 단위 거리를 얻습니다. 폭 100 mm로 인쇄되어 z_translation = 8.3 을 보고하는 태그는 카메라에서 830 mm 떨어져 있습니다. 같은 태그를 폭 50 mm로 인쇄하여 같은 거리에 두면 z_translation = 16.6 을 보고합니다. 회전(rotation) 필드는 라디안 단위이며 스케일링이 필요 없습니다.

자세 추정은 광범위한 로보틱스 응용의 기초입니다. 태그로 표시된 충전 스테이션에 로봇을 도킹하기, 인쇄된 경유점 경로를 따라가기, 환경 내의 알려진 여러 태그로부터 카메라 자신의 자세를 복원하기 등입니다. 내부 파라미터를 알고, 태그를 보며, 태그의 실세계 위치를 가진 카메라는 같은 산술로 자기 자신의 실세계 위치도 갖게 됩니다.

5.28.4. 어느 것을 선택할지

QR 코드와 AprilTag는 서로 다른 문제를 해결합니다. 둘 사이의 선택은 결국 인쇄된 기호가 무엇을 담는가로 귀결됩니다.

애플리케이션이 인쇄된 심볼을 통해 임의의 데이터(URL, 시리얼 번호 문자열, 연락처 레코드 등)를 전달해야 한다면 QR 코드가 적절한 선택입니다. 적당한 크기의 코드에도 수백 바이트가 들어가고, 인코딩 방식이 공개되어 있어 모든 스마트폰에서 지원되며, 디코더가 회전, 어느 정도의 손상, 비스듬한 각도에 대처할 수 있습니다.

애플리케이션이 멀리서 연속적으로 읽어야 하는 작은 ID와 선택적인 자세 정보(움직이는 로봇의 기준 마커, 방 안의 보정 타깃, 충전 스테이션의 도킹 마커 등)를 필요로 한다면 AprilTag가 적절한 선택입니다. 이러한 용도에는 수백 개의 ID로 충분하며, Hamming 코드는 QR 코드라면 실패할 비트 오류로부터 복구할 수 있고, 내부 파라미터(intrinsics)를 보정해 두면 자세 추정을 추가 비용 없이 얻을 수 있습니다.

일부 응용은 둘 다 사용합니다. AprilTag가 알려진 위치를 표시하고, (나란히 인쇄된) 관련 QR 코드가 그 위치가 무엇을 의미하는지에 대한 메타데이터를 담습니다. 두 검출기는 같은 프레임에서 독립적으로 실행되며, 응용 프로그램은 각 태그를 그 짝이 되는 코드와 매칭하기 위해 경계 상자를 상관시킵니다.