From a1555167b813e426ff7bdc5782bb98c1d667c507 Mon Sep 17 00:00:00 2001 From: Bouke van der Bijl Date: Thu, 26 Mar 2026 11:13:49 +0100 Subject: [PATCH] Cache unionfind structure across detect calls and lazy-initialize size array This saves a potentially big allocation if multiple detections are performed after each other. For 6MP images this gives me a 1.33x increase in detection throughput. --- apriltag.c | 2 ++ apriltag.h | 4 +++ apriltag_quad_thresh.c | 14 ++++++++-- common/unionfind.h | 21 +++++++++++--- test/CMakeLists.txt | 8 ++++++ test/test_multiple_sizes.c | 56 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 test/test_multiple_sizes.c diff --git a/apriltag.c b/apriltag.c index 6a90358f..ea11e828 100644 --- a/apriltag.c +++ b/apriltag.c @@ -411,6 +411,8 @@ void apriltag_detector_destroy(apriltag_detector_t *td) apriltag_detector_clear_families(td); zarray_destroy(td->tag_families); + if (td->cached_uf) + unionfind_destroy(td->cached_uf); free(td); } diff --git a/apriltag.h b/apriltag.h index 39ef282f..cfb11f25 100644 --- a/apriltag.h +++ b/apriltag.h @@ -39,6 +39,7 @@ extern "C" { #include "common/workerpool.h" #include "common/timeprofile.h" #include "common/pthreads_cross.h" +#include "common/unionfind.h" #define APRILTAG_TASKS_PER_THREAD_TARGET 10 @@ -188,6 +189,9 @@ struct apriltag_detector // Used for thread safety. pthread_mutex_t mutex; + + // Cached unionfind structure (reused across detect calls) + unionfind_t *cached_uf; }; // Represents the detection of a tag. These are returned to the user diff --git a/apriltag_quad_thresh.c b/apriltag_quad_thresh.c index 3ad0443e..17d5cfb0 100644 --- a/apriltag_quad_thresh.c +++ b/apriltag_quad_thresh.c @@ -1511,7 +1511,17 @@ image_u8_t *threshold_bayer(apriltag_detector_t *td, image_u8_t *im) } unionfind_t* connected_components(apriltag_detector_t *td, image_u8_t* threshim, int w, int h, int ts) { - unionfind_t *uf = unionfind_create(w * h); + uint32_t maxid = w * h; + if (td->cached_uf) { + if (td->cached_uf->maxid >= maxid) { + unionfind_reset(td->cached_uf); + } else { + unionfind_resize(td->cached_uf, maxid); + } + } else { + td->cached_uf = unionfind_create(maxid); + } + unionfind_t *uf = td->cached_uf; if (td->nthreads <= 1) { do_unionfind_first_line(uf, threshim, w, ts); @@ -2004,8 +2014,6 @@ zarray_t *apriltag_quad_thresh(apriltag_detector_t *td, image_u8_t *im) timeprofile_stamp(td->tp, "fit quads to clusters"); - unionfind_destroy(uf); - for (int i = 0; i < zarray_size(clusters); i++) { zarray_t *cluster; zarray_get(clusters, i, &cluster); diff --git a/common/unionfind.h b/common/unionfind.h index 2b298eea..b91d8839 100644 --- a/common/unionfind.h +++ b/common/unionfind.h @@ -67,15 +67,27 @@ static inline void uf_store_parent(unionfind_t *uf, uint32_t id, uint32_t val) #endif } +static inline void unionfind_reset(unionfind_t *uf) +{ + // Sets parent to 0xffffffff; size is lazily initialized in + // unionfind_get_representative when a node is first touched. + memset(uf->data, 0xff, (uf->maxid+1) * sizeof(struct unionfind_node)); +} + static inline unionfind_t *unionfind_create(uint32_t maxid) { unionfind_t *uf = (unionfind_t*) calloc(1, sizeof(unionfind_t)); uf->maxid = maxid; uf->data = (struct unionfind_node *) malloc((maxid+1) * sizeof(struct unionfind_node)); - for (uint32_t i = 0; i <= maxid; i++) { - uf->data[i].parent = 0xffffffff; - uf->data[i].size = 0; - } + unionfind_reset(uf); + return uf; +} + +static inline unionfind_t *unionfind_resize(unionfind_t *uf, uint32_t maxid) +{ + uf->maxid = maxid; + uf->data = (struct unionfind_node *) realloc(uf->data, (maxid+1) * sizeof(struct unionfind_node)); + unionfind_reset(uf); return uf; } @@ -109,6 +121,7 @@ static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id // unititialized node, so set to self if (uf_load_parent(uf, id) == 0xffffffff) { uf_store_parent(uf, id, id); + uf->data[id].size = 0; return id; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ef9e3d7b..5ca8ae63 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -49,3 +49,11 @@ endif() add_test(NAME test_quick_decode COMMAND test_quick_decode) +add_executable(test_multiple_sizes test_multiple_sizes.c) +target_link_libraries(test_multiple_sizes ${PROJECT_NAME}) + +add_test(NAME test_multiple_sizes + COMMAND $ data/33369213973_9d9bb4cc96_c.jpg + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + diff --git a/test/test_multiple_sizes.c b/test/test_multiple_sizes.c new file mode 100644 index 00000000..a3cd8313 --- /dev/null +++ b/test/test_multiple_sizes.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include + +// This test verifies that the cached data inside the detector handle size changes + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + pjpeg_t *pjpeg = pjpeg_create_from_file(argv[1], 0, NULL); + if (pjpeg == NULL) { + fprintf(stderr, "Failed to load %s\n", argv[1]); + return EXIT_FAILURE; + } + image_u8_t *im = pjpeg_to_u8_baseline(pjpeg); + + apriltag_family_t *tf = tag36h11_create(); + apriltag_detector_t *td = apriltag_detector_create(); + apriltag_detector_add_family(td, tf); + td->refine_edges = false; + + // First detect with quad_decimate=2 + td->quad_decimate = 2; + zarray_t *dets1 = apriltag_detector_detect(td, im); + int n1 = zarray_size(dets1); + printf("decimate=2: %d detections\n", n1); + apriltag_detections_destroy(dets1); + + // Now detect with quad_decimate=1 + td->quad_decimate = 1; + zarray_t *dets2 = apriltag_detector_detect(td, im); + int n2 = zarray_size(dets2); + printf("decimate=1: %d detections\n", n2); + apriltag_detections_destroy(dets2); + + // Then detect with quad_decimate=2 again + td->quad_decimate = 2; + zarray_t *dets3 = apriltag_detector_detect(td, im); + int n3 = zarray_size(dets3); + printf("decimate=2: %d detections\n", n3); + apriltag_detections_destroy(dets3); + + image_u8_destroy(im); + pjpeg_destroy(pjpeg); + apriltag_detector_destroy(td); + tag36h11_destroy(tf); + + printf("PASS\n"); + return EXIT_SUCCESS; +}