659.119 InvalidArgumentException 처리 (Handling InvalidArgumentException)
1. InvalidArgumentException의 정의와 발생 조건
tf2::InvalidArgumentException은 TF2 라이브러리의 함수에 잘못된 인자(argument)가 전달되었을 때 발생하는 예외이다. 이 예외는 tf2::TransformException으로부터 파생되며, 변환 트리의 상태나 시간 범위와는 무관하게 입력 값 자체의 유효성 위반을 나타낸다.
InvalidArgumentException은 TF2 예외 계층에서 가장 기본적인 입력 검증 오류에 해당하며, 대부분의 경우 개발자의 프로그래밍 오류(programming error)를 반영한다. 따라서 다른 TF2 예외와 달리, 이 예외는 일시적 상태 변화에 의해 해소되지 않으며 코드 수정을 통해서만 근본적으로 해결할 수 있다.
2. 구체적 발생 원인
2.1 빈 문자열 프레임 ID
lookupTransform(), canTransform(), setTransform() 등의 메서드에 빈 문자열("")이 프레임 ID로 전달되면 InvalidArgumentException이 발생한다. TF2는 프레임 ID가 비어 있는 변환을 저장하거나 조회하는 것을 허용하지 않는다.
// 다음 호출은 InvalidArgumentException을 발생시킨다
auto transform = tf_buffer->lookupTransform("", "base_link",
tf2::TimePointZero);
빈 프레임 ID가 전달되는 전형적 상황은 다음과 같다.
- 초기화되지 않은 변수:
std::string변수를 선언한 후 값을 할당하지 않은 채 프레임 ID로 전달하는 경우 - 파라미터 기본값 누락: ROS2 파라미터에서 프레임 이름을 읽어오되, 파라미터가 설정되지 않았을 때 기본값이 빈 문자열인 경우
- 메시지 필드 미설정: 수신된 메시지의
header.frame_id가 발행자 측에서 설정되지 않아 빈 문자열인 경우
2.2 선행 슬래시가 포함된 프레임 ID
TF2(ROS2)에서는 프레임 ID에 선행 슬래시(/)를 포함하는 것을 허용하지 않는다. ROS1의 TF에서는 /base_link와 같이 선행 슬래시를 포함하는 프레임 ID가 허용되었으나, ROS2의 TF2에서는 이를 InvalidArgumentException으로 거부한다.
// ROS2 TF2에서 다음은 InvalidArgumentException을 발생시킬 수 있다
geometry_msgs::msg::TransformStamped t;
t.header.frame_id = "/map"; // 선행 슬래시 포함
t.child_frame_id = "/base_link"; // 선행 슬래시 포함
tf_broadcaster->sendTransform(t);
2.3 동일한 프레임 ID의 부모-자식 관계
setTransform() 호출 시 header.frame_id(부모 프레임)와 child_frame_id(자식 프레임)가 동일한 문자열이면, 자기 자신으로의 변환을 정의하는 것이 되어 논리적으로 무의미하다. TF2는 이러한 자기 참조 변환을 거부하며 InvalidArgumentException을 발생시킨다.
geometry_msgs::msg::TransformStamped t;
t.header.frame_id = "base_link";
t.child_frame_id = "base_link"; // 부모와 자식이 동일
// tf_broadcaster->sendTransform(t); // InvalidArgumentException 발생
2.4 비정규화된 쿼터니언
변환의 회전 성분을 나타내는 쿼터니언 \mathbf{q} = (x, y, z, w)의 노름(norm)이 1에서 크게 벗어나는 경우, setTransform() 또는 doTransform() 과정에서 InvalidArgumentException이 발생할 수 있다. 쿼터니언이 단위 쿼터니언(unit quaternion)이 아니면 회전 변환의 수학적 의미가 정의되지 않기 때문이다.
\|\mathbf{q}\| = \sqrt{x^2 + y^2 + z^2 + w^2} \neq 1
특히, 쿼터니언의 모든 성분이 0인 경우(x = y = z = w = 0)는 가장 빈번한 오류 사례이며, 이는 TransformStamped 메시지의 회전 필드를 명시적으로 설정하지 않았을 때 발생한다. geometry_msgs::msg::Quaternion의 기본 초기화 값은 모든 필드가 0이므로, 회전 성분을 설정하지 않으면 노름이 0인 비정규화 쿼터니언이 전달된다.
geometry_msgs::msg::TransformStamped t;
t.header.frame_id = "map";
t.child_frame_id = "base_link";
t.transform.translation.x = 1.0;
// 회전 미설정: rotation.x = 0, y = 0, z = 0, w = 0 (노름 = 0)
// tf_broadcaster->sendTransform(t); // 잠재적 InvalidArgumentException
올바른 항등 회전(identity rotation)을 나타내려면 w = 1로 설정하여야 한다.
t.transform.rotation.w = 1.0; // 항등 회전 (노름 = 1)
3. 예외 처리 패턴
3.1 방어적 입력 검증
InvalidArgumentException은 대부분 프로그래밍 오류에 기인하므로, 예외 처리보다는 사전 검증을 통한 방지가 더 적절하다. lookupTransform() 호출 전에 프레임 ID의 유효성을 확인하는 유틸리티 함수를 구현하여 적용한다.
bool isValidFrameId(const std::string& frame_id)
{
if (frame_id.empty()) {
return false;
}
if (frame_id[0] == '/') {
return false;
}
return true;
}
void processData(const sensor_msgs::msg::PointCloud2::SharedPtr msg)
{
if (!isValidFrameId(msg->header.frame_id)) {
RCLCPP_ERROR(this->get_logger(),
"수신 메시지의 frame_id가 유효하지 않음: '%s'",
msg->header.frame_id.c_str());
return;
}
try {
auto transform = tf_buffer_->lookupTransform(
target_frame_, msg->header.frame_id,
tf2_ros::fromMsg(msg->header.stamp));
// 변환 적용
} catch (const tf2::TransformException& ex) {
RCLCPP_WARN(this->get_logger(), "%s", ex.what());
}
}
3.2 쿼터니언 정규화 검증
변환을 발행하기 전에 쿼터니언의 노름을 검증하고, 필요시 정규화를 수행하는 방어적 패턴을 적용한다.
void validateAndNormalizeQuaternion(
geometry_msgs::msg::Quaternion& q)
{
double norm = std::sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w);
if (norm < 1e-10) {
// 영 쿼터니언: 항등 회전으로 대체
RCLCPP_WARN(rclcpp::get_logger("tf_validator"),
"영 쿼터니언 감지, 항등 회전으로 설정");
q.x = 0.0;
q.y = 0.0;
q.z = 0.0;
q.w = 1.0;
return;
}
double tolerance = 1e-3;
if (std::abs(norm - 1.0) > tolerance) {
RCLCPP_WARN(rclcpp::get_logger("tf_validator"),
"비정규화 쿼터니언 감지 (노름=%.6f), 정규화 수행", norm);
q.x /= norm;
q.y /= norm;
q.z /= norm;
q.w /= norm;
}
}
3.3 Python에서의 InvalidArgumentException 처리
from tf2_ros import InvalidArgumentException
def transform_point(self, point_stamped):
if not point_stamped.header.frame_id:
self.get_logger().error('빈 frame_id가 감지됨')
return None
try:
transformed = self.tf_buffer.transform(
point_stamped, 'map')
return transformed
except InvalidArgumentException as ex:
self.get_logger().error(
f'잘못된 인자: {ex}')
return None
except Exception as ex:
self.get_logger().warn(f'변환 실패: {ex}')
return None
4. 선행 슬래시 자동 제거 유틸리티
ROS1 코드를 ROS2로 마이그레이션하는 과정에서 선행 슬래시가 포함된 프레임 ID가 잔존하는 경우가 빈번하다. 이를 자동으로 제거하는 유틸리티를 구현하여 마이그레이션 단계에서 활용할 수 있다.
std::string stripLeadingSlash(const std::string& frame_id)
{
if (!frame_id.empty() && frame_id[0] == '/') {
RCLCPP_WARN_ONCE(rclcpp::get_logger("tf_migration"),
"선행 슬래시가 포함된 프레임 ID '%s' 감지. "
"ROS2에서는 선행 슬래시를 제거하여야 합니다.",
frame_id.c_str());
return frame_id.substr(1);
}
return frame_id;
}
이 유틸리티는 마이그레이션 과정의 임시 조치이며, 최종적으로는 모든 소스 코드와 설정 파일에서 선행 슬래시를 완전히 제거하여야 한다.
5. 진단 및 디버깅
5.1 오류 메시지 분석
InvalidArgumentException의 what() 메서드가 반환하는 메시지는 잘못된 인자의 구체적 내용을 포함한다.
| 오류 메시지 패턴 | 원인 |
|---|---|
"" passed to lookupTransform argument target_frame | 목표 프레임이 빈 문자열 |
"" passed to lookupTransform argument source_frame | 원본 프레임이 빈 문자열 |
Invalid frame ID "..." passed to ... | 선행 슬래시 등 형식 오류 |
Transform ... has invalid quaternion | 쿼터니언 정규화 위반 |
5.2 정적 분석을 통한 사전 탐지
코드 리뷰 및 정적 분석 도구를 활용하여 프레임 ID가 빈 문자열로 전달될 가능성이 있는 코드 경로를 사전에 식별한다. 특히 파라미터로부터 프레임 이름을 읽어오는 코드에서 기본값이 빈 문자열로 설정되어 있는지 점검한다.
// 잠재적 오류: 파라미터 미설정 시 빈 문자열 반환
this->declare_parameter<std::string>("target_frame", "");
// 개선: 기본값을 명시적으로 설정하거나 필수 파라미터로 선언
this->declare_parameter<std::string>("target_frame", "map");
6. LookupException과의 구별 기준
InvalidArgumentException과 LookupException은 모두 프레임 ID와 관련된 오류이나, 발생 원인과 의미론이 명확히 구별된다.
| 구분 항목 | InvalidArgumentException | LookupException |
|---|---|---|
| 원인 | 입력 인자 자체의 형식적 결함 | 변환 트리에 프레임이 미등록 |
| 프레임 ID 상태 | 빈 문자열, 슬래시 포함 등 | 형식적으로 유효하나 미등록 |
| 복구 가능성 | 코드 수정 필요 | 발행 노드 기동으로 해소 가능 |
| 재시도 유효성 | 재시도로 해결 불가 | 재시도로 해결 가능 |
| 심각도 | 높음 (프로그래밍 오류) | 중간 (구성 또는 타이밍 문제) |
이 구분에 기반하여, InvalidArgumentException은 RCLCPP_ERROR 또는 RCLCPP_FATAL 수준으로 기록하고, 해당 오류 경로를 개발 단계에서 즉각적으로 수정하는 것이 바람직하다.
참고 문헌 및 출처
- ROS2 geometry2 리포지터리,
tf2패키지, https://github.com/ros2/geometry2 - Foote, T. (2013). “tf: The transform library.” IEEE International Conference on Technologies for Practical Robot Applications (TePRA), pp. 1–6.
- ROS2 공식 문서, TF2 튜토리얼, https://docs.ros.org/en/humble/Tutorials/Intermediate/Tf2/Tf2-Main.html
- REP 105 – Coordinate Frames for Mobile Platforms, https://www.ros.org/reps/rep-0105.html
버전: 1.0