AEGLOSLAB ROS 2 Jazzy 대전

제7부 센서·인지 파이프라인

7.2 이미지 파이프라인 — image_transport·cv_bridge

7.2.1 이 장에서 배우는 것 / 선수 지식

이미지는 ROS 2에서 다루는 가장 대용량 데이터다. 1.3.4에서 계산했듯 1920×1080 RGB 한 장이 약 6.2 MB이고, 30 Hz면 초당 186 MB — 기가비트 이더넷을 넘는다. 무선에서는 논외다.

그래서 이미지 파이프라인 설계는 "어떻게 처리할까"보다 "어떻게 옮기지 않을까" 의 문제가 된다. 압축·프로세스 배치·구독 지연·처리 위치가 전부 이 질문에 대한 답이다.

배우는 것:

  • sensor_msgs/Image 구조 — 인코딩과 메모리 레이아웃.
  • image_transport — 플러그인 기반 전송과 토픽 이름 규약.
  • lazy 구독 — 관측이 시스템을 바꾸는 실제 사례.
  • cv_bridge — 복사와 공유의 차이.
  • image_proc 파이프라인 — 디베이어·정류.
  • 성능 설계 — 압축 선택·컴포지션·대역 예산.
  • 함정 — 인코딩 불일치·스탬프·CameraInfo 짝.

선수 지식: 7.1(센서 규약·광학 프레임), 1.3.4(CDR 크기 계산), 1.4.2(lazy publisher), 1.13·2.7(컴포지션·intra-process), 7.8(대용량 설계 — 함께 읽으면 좋다).

정직: image_transport 플러그인의 설치 여부는 어떤 메타패키지를 설치했는지에 달렸다 — Jazzy 컨테이너에서 실행 확인한 내용을 7.2.12에 따로 정리했다. 요약하면 image_transport 단독으로는 raw밖에 없다. cv_bridge의 API 세부는 배포판마다 다를 수 있다(목차 3).

7.2.2 sensor_msgs/Image 구조

header:
  stamp: {sec: ..., nanosec: ...}   # 노출 시각(1.10.6)
  frame_id: "camera_optical_frame"  # ★ 광학 프레임(7.1.4)
height: 1080
width: 1920
encoding: "rgb8"
is_bigendian: 0
step: 5760                          # 한 행의 바이트 수 = width * 채널 * 바이트/채널
data: [...]                         # height * step 바이트

크기 계산:

$$S_{\text{data}} = \text{height} \times \text{step}, \qquad \text{step} = \text{width} \times C \times B$$

여기서 $C$는 채널 수, $B$는 채널당 바이트 수.

인코딩채널바이트/채널1920×1080 크기
mono8112.1 MB
bgr8·rgb8316.2 MB
bgra8·rgba8418.3 MB
mono16124.1 MB
16UC1(깊이)124.1 MB
32FC1(깊이·부동)148.3 MB
bayer_rggb8112.1 MB

베이어 인코딩이 중요하다. 카메라 센서는 원래 베이어 패턴(픽셀당 1채널)이며, RGB로 변환(디베이어)하면 3배로 커진다. 압축하지 않을 거라면 베이어 상태로 전송하고 소비 지점에서 디베이어하는 것이 대역을 3분의 1로 줄인다.

함정: step을 잘못 계산하면 이미지가 비스듬히 어긋난다. 정렬(alignment) 때문에 step > width * C * B인 경우가 있으므로, 데이터를 직접 다룰 때는 step을 써야지 width로 계산하면 안 된다.

깊이 이미지 인코딩:

인코딩단위비고
16UC1밀리미터흔함. 0은 무효
32FC1미터REP 103 준수. NaN이 무효

함정: 깊이 단위가 mm인지 m인지 확인하라. 16UC1은 관례상 mm이며, 이것을 m로 착각하면 1000배 오차가 난다. 드라이버 문서와 실제 값(1 m 앞 물체가 1000인가 1.0인가)으로 확인한다.

7.2.3 image_transport — 플러그인 전송

같은 이미지를 여러 형식으로 전송할 수 있게 하는 층이다.

카메라 드라이버 image_ transport::Pu blisher /camera/ image_raw (sensor_ msgs/Image) /camera/ image_raw/ compressed (CompressedI mage) /camera/ image_raw/ theora (패킷) 로컬 소비자 raw 구독 원격 뷰어 compressed 구독

토픽 이름 규약:

전송토픽
raw<base>
compressed<base>/compressed
theora<base>/theora
<plugin><base>/<plugin>

위 도해와 표는 플러그인이 설치된 환경을 전제한다. image_transport만 설치하면 raw 줄 하나뿐이며, compressed·theora는 별도 패키지가 있어야 생긴다(실행 확인 — 7.2.12). ros-base만 설치한 로봇에서 /compressed가 없는 것이 이 때문이다.

발행 측:

#include "image_transport/image_transport.hpp"

// 노드에서
image_transport::ImageTransport it(node);
image_transport::CameraPublisher pub = it.advertiseCamera("image_raw", 1);

// 발행 — 모든 활성 전송으로 나간다
pub.publish(image_msg, camera_info_msg);

구독 측:

image_transport::Subscriber sub = it.subscribe(
    "image_raw", 1, callback,
    nullptr, image_transport::TransportHints(node.get(), "compressed"));
// → 실제로는 /camera/image_raw/compressed 를 구독하고 자동 복호

콜백은 항상 sensor_msgs/Image를 받는다 — 압축 여부와 무관하게. image_transport가 복호를 담당한다.

# CLI에서 전송 지정
ros2 run rqt_image_view rqt_image_view /camera/image_raw/compressed

# 사용 가능한 전송 확인
ros2 topic list | grep image_raw

7.2.4 lazy 구독 — 관측이 시스템을 바꾼다

image_transport의 핵심 최적화다.

구독자 0 압축·발행 안 함 CPU 절약 compressed 구독자 등장 압축 시작 구독자 사라짐

1.4.2의 lazy publisher 패턴이 여기서 구현되어 있다. 아무도 압축 이미지를 구독하지 않으면 압축하지 않는다 — JPEG 인코딩은 CPU를 상당히 쓰므로 이 절약이 크다.

부작용 — 0.7.4에서 예고한 것:

행동결과
rqt_image_view로 compressed 열기압축이 시작되어 CPU 상승
ros2 topic echo /image_rawraw 전송 시작 → 대역 폭증
RViz2에서 이미지 표시동일
뷰어를 닫음다시 절약 모드

함정: "성능 측정 중에 뷰어를 열어 두면 결과가 왜곡된다"(2.7.6의 반복). 이미지 파이프라인 벤치마크를 할 때는 모든 뷰어를 닫고 측정해야 한다.

함정: 무선으로 연결된 원격 PC에서 raw 이미지를 열면 링크가 즉시 포화된다. 3.9.3에서 본 무선 문제가 그대로 발생하며, 로봇의 제어 통신까지 영향을 받는다. 원격에서는 반드시 compressed를 명시하라.

# ❌ 무선에서 위험
ros2 run rqt_image_view rqt_image_view /camera/image_raw

# ✅ 압축 전송 지정
ros2 run rqt_image_view rqt_image_view /camera/image_raw/compressed

7.2.5 cv_bridge — 복사와 공유

ROS 이미지와 OpenCV cv::Mat 사이를 변환한다.

#include "cv_bridge/cv_bridge.hpp"

// ① 복사 — 수정 가능
cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, "bgr8");
cv::GaussianBlur(cv_ptr->image, cv_ptr->image, cv::Size(5,5), 0);

// ② 공유 — 복사 없음, 읽기 전용
cv_bridge::CvImageConstPtr cv_const = cv_bridge::toCvShare(msg, "bgr8");
double mean = cv::mean(cv_const->image)[0];        // 읽기만

// ③ 역방향
sensor_msgs::msg::Image::SharedPtr out =
    cv_bridge::CvImage(header, "bgr8", processed).toImageMsg();
함수복사수정조건
toCvCopy있음가능항상
toCvShare없음금지인코딩이 일치할 때만

함정: toCvShare에서 인코딩 변환이 필요하면 결국 복사가 일어난다. rgb8 메시지를 bgr8로 요청하면 변환이 필요하므로 공유가 불가능하다. 인코딩을 맞춰야 실제 무복사가 된다.

// ✅ 인코딩 일치 — 실제 공유
auto cv_const = cv_bridge::toCvShare(msg, msg->encoding);
// 또는 빈 문자열로 "그대로"
auto cv_const = cv_bridge::toCvShare(msg, "");

함정: toCvShare로 얻은 cv::Mat은 원본 메시지의 수명에 의존한다(2.8.7의 numpy 뷰와 같은 성질). 콜백 밖으로 넘기려면 shared_ptr를 함께 보유하거나 복사해야 한다.

// 멤버에 저장할 때 — 메시지도 함께 보유해야 한다
cv_bridge::CvImageConstPtr held_;   // 내부적으로 원본을 참조
held_ = cv_bridge::toCvShare(msg, "");   // msg의 수명이 held_에 묶인다

Python:

from cv_bridge import CvBridge
bridge = CvBridge()

# 변환 없이 (가능하면)
cv_img = bridge.imgmsg_to_cv2(msg, desired_encoding='passthrough')

# 변환 지정
cv_img = bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')

# 역방향
out_msg = bridge.cv2_to_imgmsg(cv_img, encoding='bgr8')
out_msg.header = msg.header      # ★ 헤더 전파 필수

함정: 역변환 시 헤더를 전파하지 않으면 스탬프와 프레임이 사라진다. 그러면 하류에서 tf 변환이 불가능해지고(5.2), 시간 정합이 깨진다. 처리 노드는 입력 헤더를 출력에 반드시 전달해야 한다 — 이것이 파이프라인 전체의 시간 정합을 유지한다.

7.2.6 image_proc 파이프라인

원시 이미지를 사용 가능한 형태로 만드는 표준 단계들이다.

image_raw (베이어 또는 mono) debayer → image_color rectify → image_rect_color rectify(mono) → image_rect camera_info
단계입력 → 출력필요
디베이어image_raw(bayer) → image_color컬러 카메라
정류(rectify)image_color → image_rect_color왜곡 보정(7.3)
크롭·리사이즈선택대역 절약

표준 토픽 이름:

토픽내용
image_raw센서 원시(베이어 가능)
image_mono흑백
image_color컬러(디베이어 후)
image_rect흑백 + 정류
image_rect_color컬러 + 정류 — 대부분의 비전 알고리즘이 기대

함정: 정류하지 않은 이미지로 3D 계산을 하면 왜곡 때문에 오차가 난다. 렌즈 왜곡이 큰 광각 카메라에서 특히 심하다. CameraInfo의 K(원시)와 P(정류 후)를 구분해야 하며(7.3), 정류된 이미지에는 P를 써야 한다.

컴포지션으로 실행(1.13):

ComposableNodeContainer(
    name='camera_container',
    package='rclcpp_components',
    executable='component_container_isolated',
    composable_node_descriptions=[
        ComposableNode(
            package='my_camera_driver', plugin='my_camera::CameraDriver',
            name='camera',
            extra_arguments=[{'use_intra_process_comms': True}],   # ★ 필수
        ),
        ComposableNode(
            package='image_proc', plugin='image_proc::DebayerNode',
            name='debayer',
            extra_arguments=[{'use_intra_process_comms': True}],
        ),
        ComposableNode(
            package='image_proc', plugin='image_proc::RectifyNode',
            name='rectify',
            extra_arguments=[{'use_intra_process_comms': True}],
        ),
    ],
)

견해: 이미지 파이프라인은 컴포지션의 대표적 수혜 사례다(1.13.8). 6 MB 이미지가 노드 사이를 포인터로 오가면 직렬화·복사·네트워크가 전부 사라진다. 카메라 드라이버부터 최종 소비자까지 한 컨테이너에 넣을 수 있다면 넣으라.

7.2.7 압축 선택

전송방식비율(대략)손실CPU용도
raw없음1×무손실없음로컬·intra-process
compressed(JPEG)프레임 내10~20×손실중간컬러 관측
compressed(PNG)프레임 내2~3×무손실높음깊이·정밀
compressedDepth깊이 전용다양설정중간깊이 이미지
theora프레임 간높음손실높음스트리밍

정직: 압축비는 영상 내용에 크게 의존한다(확인 필요). 위 값은 일반적 범위이며 자신의 데이터로 측정해야 한다(3.6의 방침).

설정:

# JPEG 품질 (파라미터 경로는 구성에 따라 다름 — 확인 필요)
ros2 param set /camera image_raw.compressed.jpeg_quality 80

# PNG 압축 레벨
ros2 param set /camera image_raw.compressed.png_level 6

# 포맷 선택
ros2 param set /camera image_raw.compressed.format jpeg

선택 기준:

요구선택
같은 프로세스raw + intra-process(2.7)
같은 호스트, 다른 프로세스raw + 공유메모리 또는 compressed
유선 LANcompressed(JPEG)
무선compressed + 낮은 품질 + 낮은 주기
깊이 이미지손실 압축 금지 — PNG·compressedDepth
기록(bag)압축 검토(11.2)

함정: 깊이 이미지에 JPEG를 쓰면 안 된다. 깊이 값은 픽셀 강도가 아니라 거리이며, 손실 압축이 만드는 아티팩트가 가짜 장애물이나 구멍이 된다. 무손실이나 전용 압축을 쓴다.

견해: 무선 원격 관측에는 "품질을 낮추고 주기를 낮추는" 조합이 효과적이다. 사람이 보는 용도라면 5 Hz·품질 50으로도 충분한 경우가 많고, 대역이 자릿수로 줄어든다. 전체 해상도·전체 주기를 원격으로 보내려는 시도가 무선 문제의 주원인이다(3.9.3).

7.2.8 대역·CPU 예산

1.3.9의 식을 이미지에 적용한다.

$$B = f \cdot S_{\text{img}} \cdot \frac{1}{r_{\text{압축}}} \cdot N_{\text{프로세스 밖 구독자}}$$

예시 계산:

구성계산대역
1920×1080 RGB, 30 Hz, raw, 구독자 1$30 \times 6.2\,\text{MB}$186 MB/s
위 + JPEG 15×$186 / 15$12.4 MB/s
1280×720 RGB, 10 Hz, JPEG 15×$10 \times 2.7 / 15$1.8 MB/s
640×480 mono, 5 Hz, JPEG$5 \times 0.3 / 10$0.15 MB/s

해상도·주기·압축 세 손잡이가 곱으로 작용한다. 해상도를 절반(가로세로 각각)으로 줄이면 4배 감소하므로 가장 효과가 크다.

CPU 예산도 고려하라:

작업비용
디베이어중간
정류중간(맵 사전 계산 시 낮음)
JPEG 인코딩높음 — 해상도·주기에 비례
JPEG 디코딩중간
직렬화(프로세스 간)크기에 비례(1.3)

견해: 임베디드 보드에서 고해상도 JPEG 인코딩이 CPU를 모두 점유하는 경우가 흔하다. 하드웨어 인코더(V4L2·GPU)가 있으면 쓰고, 없으면 해상도를 줄이는 것이 가장 확실하다. GPU 가속 파이프라인은 자매서 nvidia가 다룬다(BK-06).

7.2.9 CameraInfo 짝 맞추기

이미지와 CameraInfo는 항상 함께 다닌다(7.3).

// CameraPublisher가 둘을 함께 발행 — 스탬프가 자동으로 일치
image_transport::CameraPublisher pub = it.advertiseCamera("image_raw", 1);
pub.publish(image_msg, info_msg);
// 구독도 함께
image_transport::CameraSubscriber sub = it.subscribeCamera(
    "image_raw", 1,
    [](const sensor_msgs::msg::Image::ConstSharedPtr & img,
       const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info) {
      // 둘이 짝지어 온다
    });

규약:

항목규약
토픽<base> 와 camera_info가 같은 네임스페이스에
스탬프동일해야 한다
frame_id동일

함정: 스탬프가 다르면 CameraSubscriber나 message_filters(7.7)가 짝을 못 짓는다. 드라이버가 두 메시지를 따로 만들며 각각 now()를 호출하면 미세하게 달라진다. 한 번 읽은 시각을 두 메시지에 모두 넣으라.

const auto stamp = this->now();      // 한 번만
img_msg.header.stamp = stamp;
info_msg.header.stamp = stamp;       // 같은 값

7.2.12 플러그인은 기본으로 포함되지 않는다 — 실측

이 장이 내내 compressed를 전제로 서술했지만, 그 플러그인이 설치되어 있다는 보장이 없다. Jazzy 컨테이너에서 확인한 결과다(실행 확인).

7.2.12.1 image_transport만 설치하면 raw뿐이다

ros-jazzy-image-transport를 설치하고 share/image_transport/default_plugins.xml을 열면 등록된 플러그인이 네 개뿐이다 — raw_pub·raw_sub, 그리고 그 베이스 클래스 선언. compressed는 없다.

압축 전송은 별도 패키지에 들어 있다:

패키지제공하는 전송 이름
compressed_image_transportcompressed
compressed_depth_image_transportcompressedDepth
theora_image_transporttheora
zstd_image_transportzstd

넷 다 메타패키지 ros-jazzy-image-transport-plugins가 한꺼번에 끌어온다(실행 확인, apt-cache depends).

7.2.12.2 어느 설치 구성에 포함되나

의존 사슬을 실제로 따라가면 이렇다(실행 확인, apt-cache depends):

의존 없음 ros-jazzy-desktop ros-jazzy-perception image-transport-plugins compressed compressedDepth theora zstd ros-jazzy-ros-base raw 만
설치 구성compressed 사용 가능?
ros-jazzy-desktop / desktop-full가능 (perception 경유)
ros-jazzy-perception가능
ros-jazzy-ros-base불가 — 따로 설치 필요
ros-jazzy-ros-core불가

이것이 "개발 PC에선 되는데 로봇에선 안 되는" 전형적 이유다. 개발 PC는 desktop을, 로봇은 용량을 아끼려 ros-base를 깔기 때문이다(0.4). 로봇 위에서 /camera/image_raw/compressed가 전혀 생기지 않고, 원격 뷰어는 raw를 받아 무선을 포화시킨다(7.2.4의 함정이 여기서 시작된다).

함정: 증상이 "에러"가 아니라 "토픽이 없음"이다. 플러그인이 없으면 image_transport는 조용히 raw만 발행한다. ros2 topic list에 /compressed가 없는 것을 보고 "카메라 드라이버가 이상하다"로 오진하기 쉽다. 확인 명령은 이것이다:

ros2 run image_transport list_transports   # 사용 가능한 전송 목록

플러그인이 다 설치된 환경의 출력은 이렇다(실행 확인):

Declared transports:
image_transport/compressed
image_transport/compressedDepth
image_transport/raw
image_transport/theora
image_transport/zstd

ros-base만 설치된 로봇에서는 image_transport/raw 한 줄만 나온다. 이 한 줄 차이가 진단의 출발점이다.

7.2.12.3 플러그인 lookup 이름의 형태

default_plugins.xml에 등록된 이름은 image_transport/compressed_pub·image_transport/compressed_sub 형태다 — 발행용·구독용이 따로 등록되고, 이름에 슬래시를 쓴다.

반면 사용자가 TransportHints나 파라미터에 적는 이름은 접미사 없는 compressed다. 등록 이름과 사용 이름이 다르다는 점을 알아 두면, 플러그인 로드 실패 로그에 image_transport/compressed_pub이 출력돼도 당황하지 않는다.

참고: 8.5.5에서 Nav2 플러그인은 pkg::Class(콜론 두 개)를 쓴다고 했는데, 여기 pluginlib 이름은 슬래시다. 플러그인 이름 표기는 프레임워크마다 다르다 — 한쪽 규칙을 다른 쪽에 옮겨 쓰면 안 된다.

7.2.12.4 republish — 없는 전송을 나중에 만들어 추가하기

같은 패키지에 실행 파일이 하나 더 있다(실행 확인).

# raw 로만 나오는 토픽을 compressed 로 다시 발행한다 (실행 확인, Jazzy)
ros2 run image_transport republish --ros-args \
  -p in_transport:=raw -p out_transport:=compressed \
  -r in:=/camera/image_raw -r out:=/camera/repub
# → /camera/repub/compressed 로 나온다

인자 형식이 바뀌었다. 옛 자료의 republish raw compressed <in> <out> 같은 위치 인자 형태는 Jazzy에서 통하지 않는다 — 전송 종류는 in_transport·out_transport 파라미터로, 토픽은 in·out 리맵으로 준다. 리맵을 생략하면 노드는 /in·/out을 쓴다(실행 확인 — out 리맵 없이 실행하면 발행 토픽이 /out/compressed로 설정된다).

언제 쓰나. 카메라 드라이버가 image_transport를 쓰지 않고 평범한 sensor_msgs/Image 퍼블리셔로 짜여 있으면, 아무리 플러그인을 설치해도 /compressed가 생기지 않는다 — 압축은 발행자 쪽 image_transport::Publisher가 하는 일이기 때문이다. 이때 드라이버를 고치는 대신 republish 노드를 하나 실행해 중계한다.

republish도 lazy다(실행 확인). 출력에 구독자가 없으면 입력조차 구독하지 않는다 — ros2 node info로 보면 Subscribers에 입력 토픽이 전혀 없다가, /camera/repub/compressed에 구독자가 연결되는 순간 나타난다. 7.2.4의 절약이 중계 노드에도 그대로 적용된다.

함정: 이 lazy 성질 때문에 "republish를 실행했는데 토픽이 안 보인다"가 정상 동작이다. ros2 topic list에 출력이 없다고 설정이 틀렸다고 판단하지 마라 — 뷰어를 하나 열면 그때 생긴다. 실제 확인은 ros2 node info /image_republisher로 노드가 실행 중인지 보는 것이다.

7.2.10 함정 카탈로그

#함정증상대처
1무선에서 raw 이미지 뷰어링크 포화·제어 영향compressed 명시
2성능 측정 중 뷰어 열림결과 왜곡측정 시 닫기
3깊이 이미지에 JPEG가짜 장애물·구멍무손실·전용 압축
4깊이 단위 혼동(mm/m)1000배 오차인코딩·실측 확인
5toCvShare 후 수정원본 오염toCvCopy
6toCvShare + 인코딩 변환무복사가 안 됨인코딩 일치
7공유 cv::Mat을 콜백 밖으로무효 메모리복사 또는 참조 유지
8역변환 시 헤더 미전파시간·프레임 상실헤더 복사
9frame_id가 광학 프레임 아님90도 회전(7.1.4)_optical_frame
10정류 안 한 이미지로 3D 계산왜곡 오차image_rect_color
11이미지·CameraInfo 스탬프 불일치짝 못 지음같은 시각 사용
12step 대신 width 사용이미지가 밀림step 사용
13베이어를 RGB로 변환 후 전송대역 3배베이어 전송 후 변환
14컴포지션 없이 대용량 파이프라인직렬화·복사 비용한 컨테이너에(1.13)
15ros-base 로봇에서 /compressed 기대토픽이 전혀 없음(에러 아님)image_transport_plugins 설치(7.2.12)
16republish 를 실행했는데 출력이 없다정상 — lazy라 구독자가 있어야 생김ros2 node info로 확인(7.2.12.4)

7.2.11 체크포인트

  • 이미지 크기 계산식과 step의 의미를 말할 수 있는가. width로 계산하면 안 되는 이유는?
  • 베이어 인코딩으로 전송하면 대역이 왜 3분의 1이 되는가.
  • 깊이 이미지의 두 인코딩과 각각의 단위·무효 값 표현은?
  • image_transport의 토픽 이름 규약과, 콜백이 항상 Image를 받는 이유는?
  • lazy 구독이 무엇이며, 0.7.4의 어떤 원칙을 구현하는가. 성능 측정에서 왜 주의해야 하는가.
  • toCvCopy와 toCvShare의 차이와, 실제 무복사가 되는 조건은?
  • cv_bridge 역변환에서 헤더를 전파해야 하는 이유는?
  • image_proc 파이프라인의 세 단계와 표준 토픽 이름을 말할 수 있는가. 대부분의 비전 알고리즘이 기대하는 토픽은?
  • 압축 방식 다섯 가지의 특성을 비교하고, 깊이 이미지에 손실 압축을 쓰면 안 되는 이유를 말할 수 있는가.
  • 대역 계산식의 세 손잡이 중 가장 효과가 큰 것은 무엇이며 왜인가.
  • 이미지와 CameraInfo의 스탬프가 달라지는 흔한 원인과 대처는?
  • image_transport만 설치하면 어떤 전송이 있는가. ros-base 로봇에서 /compressed가 없는 이유는(7.2.12).

다음 장 7.3는 그 CameraInfo의 내용 — 카메라 모델과 캘리브레이션을 다룬다. K·D·R·P가 각각 무엇이고, 캘리브레이션을 어떻게 하며 어떻게 검증하는가.