Skip to content

Commit aae8824

Browse files
committed
Add get_second_solution() to handle tag pose ambiguity
Extract second pose candidate resolution into a dedicated function. Uses fix_pose_ambiguities() to find the alternative solution and refines it via orthogonal iteration. Returns HUGE_VAL in err2 if no second solution exists.
1 parent 3057666 commit aae8824

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,21 @@ Note: The tag size should not be measured from the outside of the tag. The tag s
193193
### Coordinate System
194194
The coordinate system has the origin at the camera center. The z-axis points from the camera center out the camera lens. The x-axis is to the right in the image taken by the camera, and y is down. The tag's coordinate frame is centered at the center of the tag. From the viewer's perspective, the x-axis is to the right, y-axis down, and z-axis is into the tag.
195195

196+
### Handling Pose Ambiguity
197+
Planar targets often result in two possible pose solutions with similar errors (the "ambiguity" problem). To retrieve the alternative solution, you can use:
198+
199+
```c
200+
apriltag_pose_t pose1, pose2;
201+
double err1 = estimate_tag_pose(&info, &pose1);
202+
double err2;
203+
int nIters = 50;
204+
205+
// v and p are the object and image points used during the iteration
206+
get_second_solution(v, p, &pose1, &pose2, nIters, &err2);
207+
```
208+
209+
This is particularly useful when temporal filtering or additional constraints are used to disambiguate the tag's orientation.
210+
196211
Utility Functions
197212
=================
198213
AprilTag 3 now includes helper functions for deep-copying structures, which is essential for multi-threaded applications or when you need to store detections beyond the detector's lifecycle.

apriltag_pose.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,15 @@ double estimate_tag_pose(apriltag_detection_info_t* info, apriltag_pose_t* pose)
545545
return err2;
546546
}
547547
}
548+
549+
void get_second_solution(matd_t* v[4], matd_t* p[4], apriltag_pose_t* solution1, apriltag_pose_t* solution2, int nIters, double* err2)
550+
{
551+
solution2->R = fix_pose_ambiguities(v, p, solution1->t, solution1->R, 4);
552+
if (solution2->R) {
553+
solution2->t = matd_create(3, 1);
554+
*err2 = orthogonal_iteration(v, p, &solution2->t, &solution2->R, 4, nIters);
555+
}
556+
else {
557+
*err2 = HUGE_VAL;
558+
}
559+
}

apriltag_pose.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,40 @@ void estimate_tag_pose_orthogonal_iteration(
7474
*/
7575
double estimate_tag_pose(apriltag_detection_info_t* info, apriltag_pose_t* pose);
7676

77+
/**
78+
* Computes the second pose solution from a known first solution.
79+
*
80+
* Pose estimation from a planar target (such as an AprilTag) suffers from an
81+
* inherent ambiguity: two geometrically distinct poses can produce the same
82+
* image projection. This function attempts to recover this second candidate
83+
* solution using fix_pose_ambiguities(), then refines it via orthogonal iteration.
84+
*
85+
* If no second solution exists (unambiguous pose), solution2->R is left as NULL
86+
* and err2 is set to HUGE_VAL.
87+
*
88+
* @param[in] v Array of 4 normalized image points (3x1 column vectors),
89+
* expressed in normalized camera coordinates:
90+
* [(u - cx)/fx, (v - cy)/fy, 1]'.
91+
* @param[in] p Array of 4 object points in the tag frame (3x1 column vectors).
92+
* @param[in] solution1 First pose solution (R and t already estimated and refined).
93+
* Used as a starting point to search for the second solution.
94+
* @param[out] solution2 Second candidate pose solution. If a second solution is found,
95+
* solution2->R and solution2->t are allocated by this function
96+
* and must be freed by the caller via matd_destroy().
97+
* Otherwise, solution2->R is NULL and solution2->t is undefined.
98+
* @param[in] nIters Number of iterations for refinement via orthogonal_iteration().
99+
* @param[out] err2 Object-space error associated with solution2 after refinement.
100+
* Set to HUGE_VAL if no second solution was found.
101+
*
102+
* @note The caller is responsible for freeing solution2->R and solution2->t
103+
* (only if solution2->R != NULL) via matd_destroy().
104+
*
105+
* @see fix_pose_ambiguities()
106+
* @see orthogonal_iteration()
107+
* @see estimate_tag_pose_orthogonal_iteration()
108+
*/
109+
void get_second_solution(matd_t* v[4], matd_t* p[4], apriltag_pose_t* solution1, apriltag_pose_t* solution2, int nIters, double* err2);
110+
77111
#ifdef __cplusplus
78112
}
79113
#endif

0 commit comments

Comments
 (0)