From 9ddc65d4e5f60800d0afb77fe86355b56e36336e Mon Sep 17 00:00:00 2001 From: kalwalt Date: Thu, 21 Dec 2023 00:47:15 +0100 Subject: [PATCH 01/17] fix for issue https://github.com/webarkit/jsartoolkitNFT/issues/364 --- lib/SRC/ARUtil/log.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/SRC/ARUtil/log.c b/lib/SRC/ARUtil/log.c index 66e4337..9e58741 100644 --- a/lib/SRC/ARUtil/log.c +++ b/lib/SRC/ARUtil/log.c @@ -201,7 +201,16 @@ void arLogv(const char *tag, const int logLevel, const char *format, va_list ap) os_log_with_type(OS_LOG_DEFAULT, type, "%{public}s", buf); } #else + +#ifdef __EMSCRIPTEN__ + if(logLevel == AR_LOG_LEVEL_ERROR) + fprintf(stderr, "%s", buf); + else + fprintf(stdout, "%s", buf); +#else fprintf(stderr, "%s", buf); +#endif + #endif } free(buf); From 01108701762e832cca7290b5de7d466e3a46e5d6 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Fri, 22 Dec 2023 22:49:59 +0100 Subject: [PATCH 02/17] improved log with emscripten_log uilities - see https://github.com/webarkit/jsartoolkitNFT/issues/364 --- lib/SRC/ARUtil/log.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/SRC/ARUtil/log.c b/lib/SRC/ARUtil/log.c index 9e58741..26a8d54 100644 --- a/lib/SRC/ARUtil/log.c +++ b/lib/SRC/ARUtil/log.c @@ -47,6 +47,10 @@ # define snprintf _snprintf #endif +#ifdef __EMSCRIPTEN__ +#include +#endif + // // Global required for logging functions. // @@ -204,9 +208,9 @@ void arLogv(const char *tag, const int logLevel, const char *format, va_list ap) #ifdef __EMSCRIPTEN__ if(logLevel == AR_LOG_LEVEL_ERROR) - fprintf(stderr, "%s", buf); + emscripten_console_error(buf); else - fprintf(stdout, "%s", buf); + emscripten_console_warn(buf); #else fprintf(stderr, "%s", buf); #endif From a21f6591728415e2d4915cbc3d5a42c2317655f6 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 23 Oct 2024 23:52:42 +0200 Subject: [PATCH 03/17] fix for issue https://github.com/webarkit/jsartoolkitNFT/issues/363 - emcc version > 3.1.40 cause issue with isnan --- lib/SRC/KPM/FreakMatcher/framework/error.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/SRC/KPM/FreakMatcher/framework/error.h b/lib/SRC/KPM/FreakMatcher/framework/error.h index 5383c0c..e0237ab 100644 --- a/lib/SRC/KPM/FreakMatcher/framework/error.h +++ b/lib/SRC/KPM/FreakMatcher/framework/error.h @@ -63,7 +63,8 @@ # define DEBUG_BLOCK(X) #endif -#define isnan(x) ((x) != (x)) +/*#define isnan(x) ((x) != (x)) #define isinf(x) (!isnan(x) && isnan(x - x)) #define ASSERT_NAN(x) ASSERT(!isnan(x), "NaN") -#define ASSERT_INF(x) ASSERT(!isinf(x), "INF") \ No newline at end of file +#define ASSERT_INF(x) ASSERT(!isinf(x), "INF") +*/ \ No newline at end of file From 70537cd6c04c0165fd79bf5f01d0addadc04bce5 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 6 Nov 2024 12:10:53 +0100 Subject: [PATCH 04/17] check workspace to fix the issue --- lib/SRC/AR2/featureMap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/SRC/AR2/featureMap.c b/lib/SRC/AR2/featureMap.c index 61eb501..a9f1f22 100644 --- a/lib/SRC/AR2/featureMap.c +++ b/lib/SRC/AR2/featureMap.c @@ -200,7 +200,7 @@ AR2FeatureMapT *ar2GenFeatureMap( AR2ImageT *image, fp2++; } for( j = 1; j < ysize-1; j++ ) { - ARLOGi("\r%4d/%4d.", j+1, ysize); fflush(stdout); + ARLOGd("\r%4d/%4d.", j+1, ysize); fflush(stdout); *(fp++) = 1.0f; fp2++; for( i = 1; i < xsize-1; i++ ) { From 1bfc1b21edd59012c7fb74eb8bf2cc23ca52d19f Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 10 Mar 2025 17:37:17 +0100 Subject: [PATCH 05/17] add WebARKitVideoLuma module for luma conversion with SIMD support --- WebARKit/WebARKitVideoLuma.cpp | 120 +++++++++++++++++++++++++++ WebARKit/include/WebARKitVideoLuma.h | 42 ++++++++++ 2 files changed, 162 insertions(+) create mode 100644 WebARKit/WebARKitVideoLuma.cpp create mode 100644 WebARKit/include/WebARKitVideoLuma.h diff --git a/WebARKit/WebARKitVideoLuma.cpp b/WebARKit/WebARKitVideoLuma.cpp new file mode 100644 index 0000000..7038896 --- /dev/null +++ b/WebARKit/WebARKitVideoLuma.cpp @@ -0,0 +1,120 @@ +#include +#include // Include this header for printf + +ARVideoLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { + ARVideoLumaInfo *vli; + + vli = (ARVideoLumaInfo *)calloc(1, sizeof(ARVideoLumaInfo)); + if (!vli) { + printf("Out of memory!!\n"); + return (NULL); + } + vli->xsize = xsize; + vli->ysize = ysize; + vli->buffSize = xsize * ysize; + vli->simd128 = simd128; + vli->buff = (uint8_t *)valloc(vli->buffSize); + if (!vli->buff) { + printf("Out of memory!!\n"); + free(vli); + return (NULL); + } + + return (vli); +} + +uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, + const uint8_t *__restrict dataPtr) { + unsigned int p, q; + + if (vli->simd128 == true) { + printf("With simd128!!!\n"); +#ifdef __EMSCRIPTEN_SIMD128__ + arVideoLumaRGBAtoL_Emscripten_simd128( + vli->buff, (unsigned char *__restrict)dataPtr, vli->buffSize); + return (vli->buff); +#else + printf("SIMD128 not supported!!!\n"); + arVideoLuma_default(vli->buff, (unsigned char *__restrict)dataPtr, vli->buffSize); + return (vli->buff); +#endif + } else { + printf("Without simd128!!!\n"); + arVideoLuma_default(vli->buff, (unsigned char *__restrict)dataPtr, vli->buffSize); + return (vli->buff); + } +} + +int arVideoLumaFinal(ARVideoLumaInfo **vli_p) { + if (!vli_p) + return (-1); + if (!*vli_p) + return (0); + + free((*vli_p)->buff); + free(*vli_p); + *vli_p = NULL; + + return (0); +} + +static void arVideoLuma_default(uint8_t *__restrict dest, + uint8_t *__restrict src, int32_t numPixels) { + unsigned int p, q; + printf("default luma conversion!!!\n"); + q = 0; + for (p = 0; p < numPixels; p++) { + dest[p] = (R8_CCIR601 * src[q + 0] + G8_CCIR601 * src[q + 1] + + B8_CCIR601 * src[q + 2]) >> + 8; + q += 4; + } +} + +#ifdef __EMSCRIPTEN_SIMD128__ +static void arVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, + uint8_t *__restrict src, + int32_t numPixels) { + + printf("using arVideoLumaRGBAtoL_Emscripten_simd128_fast !!!\n"); + + v128_t *pin = (v128_t *)src; + int64_t *pout = (int64_t *)dest; + int numPixelsDiv8 = numPixels / 8; + + v128_t maskRedBlue = wasm_i32x4_splat(0x00FF00FF); + v128_t scaleRedBlue = + wasm_i32x4_splat((uint32_t)B8_CCIR601 << 16 | R8_CCIR601); + v128_t scaleGreen = wasm_i32x4_splat(G8_CCIR601); + do { + v128_t pixels1 = wasm_v128_load(pin); // Load 16 bytes (4 pixels) from src + v128_t pixels2 = wasm_v128_load(pin + 1); + pin += 2; + + v128_t g1 = wasm_u16x8_shr(pixels1, 8); + v128_t g2 = wasm_u16x8_shr(pixels2, 8); + + v128_t rb1 = wasm_v128_and(pixels1, maskRedBlue); + v128_t rb2 = wasm_v128_and(pixels2, maskRedBlue); + + g1 = wasm_i32x4_dot_i16x8(g1, scaleGreen); + g2 = wasm_i32x4_dot_i16x8(g2, scaleGreen); + rb1 = wasm_i32x4_dot_i16x8(rb1, scaleRedBlue); + rb2 = wasm_i32x4_dot_i16x8(rb2, scaleRedBlue); + + v128_t y1 = wasm_i32x4_add(g1, rb1); + v128_t y2 = wasm_i32x4_add(g2, rb2); + + y1 = wasm_u32x4_shr(y1, 8); + y2 = wasm_u32x4_shr(y2, 8); + + v128_t y = wasm_i16x8_narrow_i32x4(y1, y2); + y = wasm_u8x16_narrow_i16x8(y, y); + + *pout = wasm_i64x2_extract_lane(y, 0); + + pout++; + numPixelsDiv8--; + } while (numPixelsDiv8); +} +#endif \ No newline at end of file diff --git a/WebARKit/include/WebARKitVideoLuma.h b/WebARKit/include/WebARKitVideoLuma.h new file mode 100644 index 0000000..ea44754 --- /dev/null +++ b/WebARKit/include/WebARKitVideoLuma.h @@ -0,0 +1,42 @@ +#ifndef WEBARKITVIDEOLUMA_H +#define WEBARKITVIDEOLUMA_H + +#include // For standard integer types like uint8_t, int32_t +#include // For boolean type +#include // For memory allocation functions + +#ifdef __EMSCRIPTEN_SIMD128__ +#include // For SIMD operations +#endif + +// CCIR 601 recommended values. See +// http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC11 . +const uint8_t R8_CCIR601 = 77; +const uint8_t G8_CCIR601 = 150; +const uint8_t B8_CCIR601 = 29; + +struct ARVideoLumaInfo { + int xsize; + int ysize; + int buffSize; + bool simd128; + uint8_t *__restrict buff; +}; + +#ifdef __EMSCRIPTEN_SIMD128__ +static void arVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, + uint8_t *__restrict src, + int32_t numPixels); +#endif + +static void arVideoLuma_default(uint8_t *__restrict dest, uint8_t *__restrict src, + int32_t numPixels); + +ARVideoLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128); + +uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, + const uint8_t *__restrict dataPtr); + +int arVideoLumaFinal(ARVideoLumaInfo **vli_p); + +#endif // WEBARKITVIDEOLUMA_H \ No newline at end of file From 3fb79f915f13d33314abaac84c7f79e6fbf8a021 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 10 Mar 2025 17:47:36 +0100 Subject: [PATCH 06/17] refactor: encapsulate WebARKitVideoLuma functions within webarkit namespace --- WebARKit/WebARKitVideoLuma.cpp | 6 +++++- WebARKit/include/WebARKitVideoLuma.h | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/WebARKit/WebARKitVideoLuma.cpp b/WebARKit/WebARKitVideoLuma.cpp index 7038896..1cfb0d5 100644 --- a/WebARKit/WebARKitVideoLuma.cpp +++ b/WebARKit/WebARKitVideoLuma.cpp @@ -1,6 +1,8 @@ #include #include // Include this header for printf +namespace webarkit { + ARVideoLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { ARVideoLumaInfo *vli; @@ -117,4 +119,6 @@ static void arVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, numPixelsDiv8--; } while (numPixelsDiv8); } -#endif \ No newline at end of file +#endif + +} // namespace webarkit \ No newline at end of file diff --git a/WebARKit/include/WebARKitVideoLuma.h b/WebARKit/include/WebARKitVideoLuma.h index ea44754..dc1d7af 100644 --- a/WebARKit/include/WebARKitVideoLuma.h +++ b/WebARKit/include/WebARKitVideoLuma.h @@ -9,6 +9,8 @@ #include // For SIMD operations #endif +namespace webarkit { + // CCIR 601 recommended values. See // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC11 . const uint8_t R8_CCIR601 = 77; @@ -39,4 +41,6 @@ uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, int arVideoLumaFinal(ARVideoLumaInfo **vli_p); +} // namespace webarkit + #endif // WEBARKITVIDEOLUMA_H \ No newline at end of file From 597d1a0d3a9114a77fd11a80c2e6d8affbb56c68 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 10 Mar 2025 18:01:38 +0100 Subject: [PATCH 07/17] refactor: replace raw memory management with smart pointers in WebARKitVideoLuma --- WebARKit/WebARKitVideoLuma.cpp | 36 +++++++++++++--------------- WebARKit/include/WebARKitVideoLuma.h | 3 ++- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/WebARKit/WebARKitVideoLuma.cpp b/WebARKit/WebARKitVideoLuma.cpp index 1cfb0d5..d2f4c32 100644 --- a/WebARKit/WebARKitVideoLuma.cpp +++ b/WebARKit/WebARKitVideoLuma.cpp @@ -4,25 +4,24 @@ namespace webarkit { ARVideoLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { - ARVideoLumaInfo *vli; + ARVideoLumaInfo *vli = new ARVideoLumaInfo; - vli = (ARVideoLumaInfo *)calloc(1, sizeof(ARVideoLumaInfo)); if (!vli) { printf("Out of memory!!\n"); - return (NULL); + return nullptr; } vli->xsize = xsize; vli->ysize = ysize; vli->buffSize = xsize * ysize; vli->simd128 = simd128; - vli->buff = (uint8_t *)valloc(vli->buffSize); + vli->buff = std::make_unique(vli->buffSize); if (!vli->buff) { printf("Out of memory!!\n"); - free(vli); - return (NULL); + delete vli; + return nullptr; } - return (vli); + return vli; } uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, @@ -33,31 +32,30 @@ uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, printf("With simd128!!!\n"); #ifdef __EMSCRIPTEN_SIMD128__ arVideoLumaRGBAtoL_Emscripten_simd128( - vli->buff, (unsigned char *__restrict)dataPtr, vli->buffSize); - return (vli->buff); + vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); + return vli->buff.get(); #else printf("SIMD128 not supported!!!\n"); - arVideoLuma_default(vli->buff, (unsigned char *__restrict)dataPtr, vli->buffSize); - return (vli->buff); + arVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); + return vli->buff.get(); #endif } else { printf("Without simd128!!!\n"); - arVideoLuma_default(vli->buff, (unsigned char *__restrict)dataPtr, vli->buffSize); - return (vli->buff); + arVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); + return vli->buff.get(); } } int arVideoLumaFinal(ARVideoLumaInfo **vli_p) { if (!vli_p) - return (-1); + return -1; if (!*vli_p) - return (0); + return 0; - free((*vli_p)->buff); - free(*vli_p); - *vli_p = NULL; + delete *vli_p; + *vli_p = nullptr; - return (0); + return 0; } static void arVideoLuma_default(uint8_t *__restrict dest, diff --git a/WebARKit/include/WebARKitVideoLuma.h b/WebARKit/include/WebARKitVideoLuma.h index dc1d7af..e79d0a5 100644 --- a/WebARKit/include/WebARKitVideoLuma.h +++ b/WebARKit/include/WebARKitVideoLuma.h @@ -4,6 +4,7 @@ #include // For standard integer types like uint8_t, int32_t #include // For boolean type #include // For memory allocation functions +#include // For std::unique_ptr #ifdef __EMSCRIPTEN_SIMD128__ #include // For SIMD operations @@ -22,7 +23,7 @@ struct ARVideoLumaInfo { int ysize; int buffSize; bool simd128; - uint8_t *__restrict buff; + std::unique_ptr buff; }; #ifdef __EMSCRIPTEN_SIMD128__ From 59cad462131534aba23b51f72371cb671052ceda Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 10 Mar 2025 18:53:48 +0100 Subject: [PATCH 08/17] refactor: rename ARVideoLumaInfo to WebARKitLumaInfo and update related functions --- WebARKit/WebARKitVideoLuma.cpp | 8 ++++---- WebARKit/include/WebARKitVideoLuma.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/WebARKit/WebARKitVideoLuma.cpp b/WebARKit/WebARKitVideoLuma.cpp index d2f4c32..a3ba35c 100644 --- a/WebARKit/WebARKitVideoLuma.cpp +++ b/WebARKit/WebARKitVideoLuma.cpp @@ -3,8 +3,8 @@ namespace webarkit { -ARVideoLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { - ARVideoLumaInfo *vli = new ARVideoLumaInfo; +WebARKitLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { + WebARKitLumaInfo *vli = new WebARKitLumaInfo; if (!vli) { printf("Out of memory!!\n"); @@ -24,7 +24,7 @@ ARVideoLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { return vli; } -uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, +uint8_t *__restrict arVideoLuma(WebARKitLumaInfo *vli, const uint8_t *__restrict dataPtr) { unsigned int p, q; @@ -46,7 +46,7 @@ uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, } } -int arVideoLumaFinal(ARVideoLumaInfo **vli_p) { +int arVideoLumaFinal(WebARKitLumaInfo **vli_p) { if (!vli_p) return -1; if (!*vli_p) diff --git a/WebARKit/include/WebARKitVideoLuma.h b/WebARKit/include/WebARKitVideoLuma.h index e79d0a5..0405add 100644 --- a/WebARKit/include/WebARKitVideoLuma.h +++ b/WebARKit/include/WebARKitVideoLuma.h @@ -18,7 +18,7 @@ const uint8_t R8_CCIR601 = 77; const uint8_t G8_CCIR601 = 150; const uint8_t B8_CCIR601 = 29; -struct ARVideoLumaInfo { +struct WebARKitLumaInfo { int xsize; int ysize; int buffSize; @@ -35,12 +35,12 @@ static void arVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, static void arVideoLuma_default(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels); -ARVideoLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128); +WebARKitLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128); -uint8_t *__restrict arVideoLuma(ARVideoLumaInfo *vli, +uint8_t *__restrict arVideoLuma(WebARKitLumaInfo *vli, const uint8_t *__restrict dataPtr); -int arVideoLumaFinal(ARVideoLumaInfo **vli_p); +int arVideoLumaFinal(WebARKitLumaInfo **vli_p); } // namespace webarkit From b2dd75e573d4fa529a647d70751b530bb2471211 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 10 Mar 2025 19:18:09 +0100 Subject: [PATCH 09/17] refactor: rename functions in WebARKitVideoLuma for consistency and clarity --- WebARKit/WebARKitVideoLuma.cpp | 20 ++++++++++---------- WebARKit/include/WebARKitVideoLuma.h | 17 ++++++++--------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/WebARKit/WebARKitVideoLuma.cpp b/WebARKit/WebARKitVideoLuma.cpp index a3ba35c..e3e84f2 100644 --- a/WebARKit/WebARKitVideoLuma.cpp +++ b/WebARKit/WebARKitVideoLuma.cpp @@ -1,9 +1,9 @@ #include -#include // Include this header for printf +#include namespace webarkit { -WebARKitLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { +WebARKitLumaInfo *webarkitVideoLumaInit(int xsize, int ysize, bool simd128) { WebARKitLumaInfo *vli = new WebARKitLumaInfo; if (!vli) { @@ -24,29 +24,29 @@ WebARKitLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128) { return vli; } -uint8_t *__restrict arVideoLuma(WebARKitLumaInfo *vli, +uint8_t *__restrict webarkitVideoLuma(WebARKitLumaInfo *vli, const uint8_t *__restrict dataPtr) { unsigned int p, q; if (vli->simd128 == true) { printf("With simd128!!!\n"); #ifdef __EMSCRIPTEN_SIMD128__ - arVideoLumaRGBAtoL_Emscripten_simd128( + webarkitVideoLumaRGBAtoL_Emscripten_simd128( vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); return vli->buff.get(); #else printf("SIMD128 not supported!!!\n"); - arVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); + webarkitVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); return vli->buff.get(); #endif } else { printf("Without simd128!!!\n"); - arVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); + webarkitVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); return vli->buff.get(); } } -int arVideoLumaFinal(WebARKitLumaInfo **vli_p) { +int webarkitVideoLumaFinal(WebARKitLumaInfo **vli_p) { if (!vli_p) return -1; if (!*vli_p) @@ -58,7 +58,7 @@ int arVideoLumaFinal(WebARKitLumaInfo **vli_p) { return 0; } -static void arVideoLuma_default(uint8_t *__restrict dest, +static void webarkitVideoLuma_default(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels) { unsigned int p, q; printf("default luma conversion!!!\n"); @@ -72,11 +72,11 @@ static void arVideoLuma_default(uint8_t *__restrict dest, } #ifdef __EMSCRIPTEN_SIMD128__ -static void arVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, +static void webarkitVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels) { - printf("using arVideoLumaRGBAtoL_Emscripten_simd128_fast !!!\n"); + printf("using webarkitVideoLumaRGBAtoL_Emscripten_simd128_fast !!!\n"); v128_t *pin = (v128_t *)src; int64_t *pout = (int64_t *)dest; diff --git a/WebARKit/include/WebARKitVideoLuma.h b/WebARKit/include/WebARKitVideoLuma.h index 0405add..c7f2059 100644 --- a/WebARKit/include/WebARKitVideoLuma.h +++ b/WebARKit/include/WebARKitVideoLuma.h @@ -1,10 +1,9 @@ #ifndef WEBARKITVIDEOLUMA_H #define WEBARKITVIDEOLUMA_H -#include // For standard integer types like uint8_t, int32_t -#include // For boolean type -#include // For memory allocation functions -#include // For std::unique_ptr +#include +#include +#include #ifdef __EMSCRIPTEN_SIMD128__ #include // For SIMD operations @@ -27,20 +26,20 @@ struct WebARKitLumaInfo { }; #ifdef __EMSCRIPTEN_SIMD128__ -static void arVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, +static void webarkitVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels); #endif -static void arVideoLuma_default(uint8_t *__restrict dest, uint8_t *__restrict src, +static void webarkitVideoLuma_default(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels); -WebARKitLumaInfo *arVideoLumaInit(int xsize, int ysize, bool simd128); +WebARKitLumaInfo *webarkitVideoLumaInit(int xsize, int ysize, bool simd128); -uint8_t *__restrict arVideoLuma(WebARKitLumaInfo *vli, +uint8_t *__restrict webarkitVideoLuma(WebARKitLumaInfo *vli, const uint8_t *__restrict dataPtr); -int arVideoLumaFinal(WebARKitLumaInfo **vli_p); +int webarkitVideoLumaFinal(WebARKitLumaInfo **vli_p); } // namespace webarkit From d60dd39a898c77206e157225ad5c139607f7270b Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 10 Mar 2025 20:57:39 +0100 Subject: [PATCH 10/17] refactor: update SIMD preprocessor directives from EMSCRIPTEN_SIMD128 to wasm_simd128 for consistency --- WebARKit/WebARKitVideoLuma.cpp | 4 ++-- WebARKit/include/WebARKitVideoLuma.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WebARKit/WebARKitVideoLuma.cpp b/WebARKit/WebARKitVideoLuma.cpp index e3e84f2..a323c3e 100644 --- a/WebARKit/WebARKitVideoLuma.cpp +++ b/WebARKit/WebARKitVideoLuma.cpp @@ -29,8 +29,8 @@ uint8_t *__restrict webarkitVideoLuma(WebARKitLumaInfo *vli, unsigned int p, q; if (vli->simd128 == true) { +#ifdef __wasm_simd128__ printf("With simd128!!!\n"); -#ifdef __EMSCRIPTEN_SIMD128__ webarkitVideoLumaRGBAtoL_Emscripten_simd128( vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); return vli->buff.get(); @@ -71,7 +71,7 @@ static void webarkitVideoLuma_default(uint8_t *__restrict dest, } } -#ifdef __EMSCRIPTEN_SIMD128__ +#ifdef __wasm_simd128__ static void webarkitVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels) { diff --git a/WebARKit/include/WebARKitVideoLuma.h b/WebARKit/include/WebARKitVideoLuma.h index c7f2059..96d1156 100644 --- a/WebARKit/include/WebARKitVideoLuma.h +++ b/WebARKit/include/WebARKitVideoLuma.h @@ -5,7 +5,7 @@ #include #include -#ifdef __EMSCRIPTEN_SIMD128__ +#ifdef __wasm_simd128__ #include // For SIMD operations #endif @@ -25,7 +25,7 @@ struct WebARKitLumaInfo { std::unique_ptr buff; }; -#ifdef __EMSCRIPTEN_SIMD128__ +#ifdef __wasm_simd128__ static void webarkitVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels); From 1dbeab59ed51fe3dc3f957e6f1df51ffb542f0ac Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Tue, 11 Mar 2025 13:36:55 +0100 Subject: [PATCH 11/17] refactor: replace printf statements with logging functions for better error handling --- WebARKit/WebARKitVideoLuma.cpp | 18 +++++++----------- WebARKit/include/WebARKitVideoLuma.h | 3 ++- include/WebARKit/WebARKitLog.h | 1 + lib/SRC/WebARKit/WebARKitLog.cpp | 27 ++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/WebARKit/WebARKitVideoLuma.cpp b/WebARKit/WebARKitVideoLuma.cpp index a323c3e..12f0b6c 100644 --- a/WebARKit/WebARKitVideoLuma.cpp +++ b/WebARKit/WebARKitVideoLuma.cpp @@ -1,5 +1,4 @@ #include -#include namespace webarkit { @@ -7,7 +6,7 @@ WebARKitLumaInfo *webarkitVideoLumaInit(int xsize, int ysize, bool simd128) { WebARKitLumaInfo *vli = new WebARKitLumaInfo; if (!vli) { - printf("Out of memory!!\n"); + webarkitLOGe("Out of memory!!"); return nullptr; } vli->xsize = xsize; @@ -16,7 +15,7 @@ WebARKitLumaInfo *webarkitVideoLumaInit(int xsize, int ysize, bool simd128) { vli->simd128 = simd128; vli->buff = std::make_unique(vli->buffSize); if (!vli->buff) { - printf("Out of memory!!\n"); + webarkitLOGe("Out of memory!!\n"); delete vli; return nullptr; } @@ -26,21 +25,18 @@ WebARKitLumaInfo *webarkitVideoLumaInit(int xsize, int ysize, bool simd128) { uint8_t *__restrict webarkitVideoLuma(WebARKitLumaInfo *vli, const uint8_t *__restrict dataPtr) { - unsigned int p, q; + //unsigned int p, q; if (vli->simd128 == true) { #ifdef __wasm_simd128__ - printf("With simd128!!!\n"); - webarkitVideoLumaRGBAtoL_Emscripten_simd128( + webarkitVideoLumaRGBAtoLuma_Emscripten_simd128( vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); return vli->buff.get(); #else - printf("SIMD128 not supported!!!\n"); webarkitVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); return vli->buff.get(); #endif } else { - printf("Without simd128!!!\n"); webarkitVideoLuma_default(vli->buff.get(), (unsigned char *__restrict)dataPtr, vli->buffSize); return vli->buff.get(); } @@ -61,7 +57,7 @@ int webarkitVideoLumaFinal(WebARKitLumaInfo **vli_p) { static void webarkitVideoLuma_default(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels) { unsigned int p, q; - printf("default luma conversion!!!\n"); + webarkitLOGd("Using webarkitVideoLuma_default for luma conversion!!!"); q = 0; for (p = 0; p < numPixels; p++) { dest[p] = (R8_CCIR601 * src[q + 0] + G8_CCIR601 * src[q + 1] + @@ -72,11 +68,11 @@ static void webarkitVideoLuma_default(uint8_t *__restrict dest, } #ifdef __wasm_simd128__ -static void webarkitVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, +static void webarkitVideoLumaRGBAtoLuma_Emscripten_simd128(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels) { - printf("using webarkitVideoLumaRGBAtoL_Emscripten_simd128_fast !!!\n"); + webarkitLOGd("Using webarkitVideoLumaRGBAtoLuma_Emscripten_simd128 for Luma conversion !!!"); v128_t *pin = (v128_t *)src; int64_t *pout = (int64_t *)dest; diff --git a/WebARKit/include/WebARKitVideoLuma.h b/WebARKit/include/WebARKitVideoLuma.h index 96d1156..e33f03b 100644 --- a/WebARKit/include/WebARKitVideoLuma.h +++ b/WebARKit/include/WebARKitVideoLuma.h @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef __wasm_simd128__ #include // For SIMD operations @@ -26,7 +27,7 @@ struct WebARKitLumaInfo { }; #ifdef __wasm_simd128__ -static void webarkitVideoLumaRGBAtoL_Emscripten_simd128(uint8_t *__restrict dest, +static void webarkitVideoLumaRGBAtoLuma_Emscripten_simd128(uint8_t *__restrict dest, uint8_t *__restrict src, int32_t numPixels); #endif diff --git a/include/WebARKit/WebARKitLog.h b/include/WebARKit/WebARKitLog.h index a42b6e5..938dc26 100644 --- a/include/WebARKit/WebARKitLog.h +++ b/include/WebARKit/WebARKitLog.h @@ -40,5 +40,6 @@ void webarkitLOGw(const std::string &message, const char * format); void webarkitLOGw(const std::string &message, int format); +void webarkitLOGd(const std::string &message); #endif // #ifndef WEBARKIT_LOG_H \ No newline at end of file diff --git a/lib/SRC/WebARKit/WebARKitLog.cpp b/lib/SRC/WebARKit/WebARKitLog.cpp index 9e9e61f..74c869c 100644 --- a/lib/SRC/WebARKit/WebARKitLog.cpp +++ b/lib/SRC/WebARKit/WebARKitLog.cpp @@ -6,6 +6,8 @@ const char * WARKTerror = "%c🚩[webarkit-error:]"; const char * WARKTerrorStyle = "color: #ffffff; background-color: #ff0101; border-radius: 4px; padding: 2px"; const char * WARKTwarn = "%c⚠️[webarkit-warn:]"; const char * WARKTwarnStyle = "color: #774400; background-color: #ffff99; border-radius: 4px; padding: 2px"; +const char * WARKTdebug = "%c🐞[webarkit-debug:]"; +const char * WARKTdebugStyle = "color: #000000; background-color: #ffcc00; border-radius: 4px; padding: 2px"; void webarkitLOGi(const std::string &message) { EM_ASM ({ @@ -266,4 +268,27 @@ void webarkitLOGw(const std::string &message, int format) { WARKTwarnStyle, format ); -} \ No newline at end of file +} + +#if defined WEBARKIT_DEBUG + +void webarkitLOGd(const std::string &message) { + EM_ASM ({ + var message = UTF8ToString($0); + var debugHead = UTF8ToString($1); + var style = UTF8ToString($2); + console.log(debugHead + message, style); + }, + message.c_str(), + WARKTdebug, + WARKTdebugStyle + ); +} + +#else + +void webarkitLOGd(const std::string &message) { + // do nothing +} + +#endif \ No newline at end of file From 656436e36bbebd9c269cf2a9f47fb4a962359f92 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Sun, 9 Nov 2025 19:03:43 +0100 Subject: [PATCH 12/17] version 1.7.6 --- include/AR/config.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/include/AR/config.h b/include/AR/config.h index c762675..33df05b 100644 --- a/include/AR/config.h +++ b/include/AR/config.h @@ -276,7 +276,7 @@ typedef enum { #undef ARVIDEO_INPUT_DEFAULT_1394 #undef ARVIDEO_INPUT_DEFAULT_GSTREAMER #undef ARVIDEO_INPUT_DEFAULT_IMAGE -#undef ARVIDEO_INPUT_DEFAULT_DUMMY +#define ARVIDEO_INPUT_DEFAULT_DUMMY // Other Linux-only configuration. #define HAVE_LIBJPEG 1 @@ -358,7 +358,7 @@ typedef enum { #undef ARVIDEO_INPUT_WINDOWS_MEDIA_CAPTURE // Default input module. This is edited by the configure script. -#undef ARVIDEO_INPUT_DEFAULT_DUMMY +#define ARVIDEO_INPUT_DEFAULT_DUMMY #undef ARVIDEO_INPUT_DEFAULT_IMAGE #undef ARVIDEO_INPUT_DEFAULT_WINDOWS_MEDIA_FOUNDATION #undef ARVIDEO_INPUT_DEFAULT_WINDOWS_MEDIA_CAPTURE @@ -408,7 +408,7 @@ typedef enum { #undef ARVIDEO_INPUT_DUMMY #define ARVIDEO_INPUT_ANDROID #undef ARVIDEO_INPUT_IMAGE -#undef ARVIDEO_INPUT_DEFAULT_DUMMY +#define ARVIDEO_INPUT_DEFAULT_DUMMY #define ARVIDEO_INPUT_DEFAULT_ANDROID #undef ARVIDEO_INPUT_DEFAULT_IMAGE @@ -459,7 +459,7 @@ typedef enum { #undef ARVIDEO_INPUT_DUMMY #define ARVIDEO_INPUT_IMAGE #define ARVIDEO_INPUT_DEFAULT_AVFOUNDATION -#undef ARVIDEO_INPUT_DEFAULT_DUMMY +#define ARVIDEO_INPUT_DEFAULT_DUMMY #undef ARVIDEO_INPUT_DEFAULT_IMAGE #define HAVE_LIBJPEG 1 #define USE_OPENGL_ES 1 @@ -476,7 +476,7 @@ typedef enum { #define ARVIDEO_INPUT_DUMMY #define ARVIDEO_INPUT_IMAGE #define ARVIDEO_INPUT_DEFAULT_AVFOUNDATION -#undef ARVIDEO_INPUT_DEFAULT_DUMMY +#define ARVIDEO_INPUT_DEFAULT_DUMMY #undef ARVIDEO_INPUT_DEFAULT_IMAGE #define HAVE_LIBJPEG 1 #define HAVE_INTEL_SIMD 1 From 2c9f6308ea82081d652f3ce691fe2414ce4bd042 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Mon, 1 Jun 2026 21:21:54 +0200 Subject: [PATCH 13/17] fix(kpm/matcher): use std::map for deterministic iteration in vote tally, keyframes, and BHC clusters The matcher had three `std::unordered_map` typedefs whose iteration order depended on the STL implementation (libstdc++ on Linux, MSVC STL on Windows, libc++ on macOS / Emscripten). Code paths that iterate these maps and pick a winner-on-tie produced different results on different platforms, causing the matcher to be non-deterministic across builds. Concretely: 1. `HoughSimilarityVoting::hash_t` (vote tally) is consumed by `getMaximumNumberOfVotes`, which iterates and picks the bin with the highest count. Ties between Hough bins are common at borderline matches and were broken inconsistently per platform. 2. `VisualDatabase::keyframe_map_t` is iterated by `query()`. Ties on inlier count between keyframes are broken first-wins, so the winning keyframe at borderline ties depended on which iteration order the platform's STL chose. 3. `BinaryHierarchicalClustering::cluster_map_t` is iterated during BHC tree construction; ordering affects the resulting topology and therefore which features cluster together, which propagates into the eventual inlier set. All three typedefs become `std::map<...>`. `std::map`'s ascending- key iteration is consistent across STL implementations (and matches the BTreeMap fix on the pure-Rust port, webarkit/WebARKitLib-rs issue #170). API surface change: none. `std::map` and `std::unordered_map` share the operations used here (`operator[]`, `find`, `insert`, `erase`, `clear`, `iterator`). Performance: `O(log N)` lookup instead of `O(1) amortized`, but N is small for all three maps (number of keyframes ~1-10, number of Hough bins voted for in a query ~10s, number of BHC clusters per level ~1-100), so the difference is negligible. `VisualDatabaseImpl::point3d_map_t` in `facade/visual_database_facade.cpp` is left as `std::unordered_map` because it is used lookup-only (`map[image_id] = ...`, `return map[image_id]`); changing it has no functional benefit and would be cosmetic only. Motivation + measurements live in webarkit/WebARKitLib-rs issue #170, which has the cross-platform repro from CI. --- .../matchers/binary_hierarchical_clustering.h | 13 +++++++++++-- .../matchers/hough_similarity_voting.h | 16 ++++++++++++---- .../KPM/FreakMatcher/matchers/visual_database.h | 12 ++++++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/lib/SRC/KPM/FreakMatcher/matchers/binary_hierarchical_clustering.h b/lib/SRC/KPM/FreakMatcher/matchers/binary_hierarchical_clustering.h index d0674bb..767620f 100644 --- a/lib/SRC/KPM/FreakMatcher/matchers/binary_hierarchical_clustering.h +++ b/lib/SRC/KPM/FreakMatcher/matchers/binary_hierarchical_clustering.h @@ -37,7 +37,7 @@ #include "kmedoids.h" -#include +#include #include namespace vision { @@ -214,7 +214,16 @@ namespace vision { typedef Node node_t; typedef std::unique_ptr node_ptr_t; typedef BinarykMedoids kmedoids_t; - typedef std::unordered_map > cluster_map_t; + // std::map (not std::unordered_map): BHC tree construction + // iterates this map to build the topology of clusters. With + // unordered_map, the resulting tree's child ordering varied + // across STL implementations and produced different BHC + // topologies on different platforms, which propagated into + // different inlier sets and homographies. std::map's + // ascending-key iteration makes BHC tree topology + // deterministic. Mirrors the BTreeMap fix on the Rust port + // (see freak/clustering.rs and issue #170). + typedef std::map > cluster_map_t; typedef PriorityQueueItem queue_item_t; typedef std::priority_queue queue_t; diff --git a/lib/SRC/KPM/FreakMatcher/matchers/hough_similarity_voting.h b/lib/SRC/KPM/FreakMatcher/matchers/hough_similarity_voting.h index 1c16198..62e4eaa 100644 --- a/lib/SRC/KPM/FreakMatcher/matchers/hough_similarity_voting.h +++ b/lib/SRC/KPM/FreakMatcher/matchers/hough_similarity_voting.h @@ -40,18 +40,26 @@ #include #include -#include +#include namespace vision { /** - * Hough voting for a similarity transformation based on a set of correspondences. + * Hough voting for a similarity transformation based on a set of correspondences. */ class HoughSimilarityVoting { public: - - typedef std::unordered_map hash_t; + + // std::map (not std::unordered_map): the vote-tally is consumed + // by getMaximumNumberOfVotes, which iterates and picks the bin + // with the highest count. unordered_map has implementation- + // defined iteration order (libstdc++ vs MSVC STL) so tied bins + // produced different winners across platforms, making the + // matcher non-deterministic across builds. std::map gives a + // stable ascending-key ordering that resolves ties consistently. + // Mirrors the BTreeMap fix on the Rust port (issue #170). + typedef std::map hash_t; typedef std::pair vote_t; typedef std::vector vote_vector_t; diff --git a/lib/SRC/KPM/FreakMatcher/matchers/visual_database.h b/lib/SRC/KPM/FreakMatcher/matchers/visual_database.h index 889f1b0..5d41ecc 100644 --- a/lib/SRC/KPM/FreakMatcher/matchers/visual_database.h +++ b/lib/SRC/KPM/FreakMatcher/matchers/visual_database.h @@ -48,7 +48,7 @@ #include #include -#include +#include #include "feature_point.h" @@ -69,7 +69,15 @@ namespace vision { typedef Keyframe<96> keyframe_t; typedef std::shared_ptr keyframe_ptr_t; - typedef std::unordered_map keyframe_map_t; + // std::map (not std::unordered_map): query() iterates this + // collection and breaks ties on inlier-count with a strict + // "first wins" comparison. unordered_map has implementation- + // defined iteration order, so the winning keyframe on + // borderline ties varied across libstdc++ vs MSVC STL builds. + // std::map's ascending-key order makes the tie-breaking + // platform-independent. Mirrors the BTreeMap fix on the Rust + // port (issue #170). + typedef std::map keyframe_map_t; typedef BinomialPyramid32f pyramid_t; typedef DoGScaleInvariantDetector detector_t; From 0289782ce271b6032401961f53838ec42c46cbfa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:33:12 +0000 Subject: [PATCH 14/17] Initial plan From 10b9ca3623b7b7eab1623428858ec54d8ee39c92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:57:25 +0000 Subject: [PATCH 15/17] Fix small-marker detection: full-res retry + OCVT-aligned AKAZE params - Add full-resolution retry in processFrame when pyrDown'd detection yields <= minRequiredDetectedFeatures (small markers fell below the detector threshold after downsampling). - Pass the actual detection scale factor into MatchFeatures so matched keypoints are rescaled correctly on both the downsampled and the full-res-retry paths. - Align AKAZE path with artoolkitX OCVT: threshold 3e-4 -> 1e-3, nn_match_ratio 0.7 -> 0.8 (new AKAZE_NN_MATCH_RATIO), minNumMatches 40 -> 15. - Add per-level keypoint-count logging for diagnosability. Co-authored-by: kalwalt <1275858+kalwalt@users.noreply.github.com> --- .../WebARKitConfig.cpp | 1 + .../WebARKitTracker.cpp | 56 +++++++++++++++---- .../WebARKitOpticalTracking/WebARKitConfig.h | 1 + 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp index 1cf6e1e..e4b0f82 100644 --- a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp +++ b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp @@ -2,6 +2,7 @@ extern const double DEFAULT_NN_MATCH_RATIO = 0.7f; extern const double TEBLID_NN_MATCH_RATIO = 0.8f; +extern const double AKAZE_NN_MATCH_RATIO = 0.8f; ///< artoolkitX OCVT parity (OCVConfig nn_match_ratio). extern const int DEFAULT_MAX_FEATURES = 800; extern const int TEBLID_MAX_FEATURES = 1000; extern const int N = 10; diff --git a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp index 64d5168..59e061e 100644 --- a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp +++ b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp @@ -60,8 +60,11 @@ class WebARKitTracker::WebARKitTrackerImpl { if (trackerType == webarkit::TEBLID_TRACKER) { _nn_match_ratio = TEBLID_NN_MATCH_RATIO; } else if (trackerType == webarkit::AKAZE_TRACKER) { - _nn_match_ratio = DEFAULT_NN_MATCH_RATIO; - minNumMatches = 40; + // WebARKitLib#53: align with artoolkitX OCVT -- nn_match_ratio 0.8 (not 0.7) + // and a lower minNumMatches floor. A small marker yields few matches after + // downsampling; the previous values (0.7 / 40) rejected nearly all of them. + _nn_match_ratio = AKAZE_NN_MATCH_RATIO; + minNumMatches = 15; } else { _nn_match_ratio = DEFAULT_NN_MATCH_RATIO; minNumMatches = 15; @@ -368,14 +371,17 @@ class WebARKitTracker::WebARKitTrackerImpl { // (frame <= featureImageMinSize, e.g. 640x480) detectionFrame == frame, so the // path is identical to full-res detection. cv::Mat detectionFrame; + cv::Vec2f detectionScaleFactor; if (_featureDetectPyrLevel < 1) { detectionFrame = frame; + detectionScaleFactor = cv::Vec2f(1.0f, 1.0f); } else { cv::Mat srcFrame = frame; for (int pyrLevel = 1; pyrLevel <= _featureDetectPyrLevel; pyrLevel++) { cv::pyrDown(srcFrame, detectionFrame, cv::Size(0, 0)); srcFrame = detectionFrame; } + detectionScaleFactor = _featureDetectScaleFactor; } cv::Mat featureMask = createFeatureMask(detectionFrame); @@ -383,9 +389,31 @@ class WebARKitTracker::WebARKitTrackerImpl { if (!extractFeatures(detectionFrame, featureMask, frameKeyPts, frameDescr)) { WEBARKIT_LOGe("No features detected in extractFeatures!\n"); } - WEBARKIT_LOGd("frame KeyPoints size: %d\n", frameKeyPts.size()); + WEBARKIT_LOGd("frame KeyPoints size: %d (pyrLevel %d)\n", frameKeyPts.size(), + (int)_featureDetectPyrLevel); + + // WebARKitLib#53: a small marker in a large frame can fall below the detector + // threshold after pyrDown (too few keypoints to attempt a match). When that + // happens and the frame was downsampled, retry once on the full-resolution + // frame before giving up. This preserves the #44 fast path for markers that + // survive downsampling while restoring small-marker detection parity with + // WebARKitLib-rs / jsartoolkitNFT (which never downsample). + if (static_cast(frameKeyPts.size()) <= minRequiredDetectedFeatures && _featureDetectPyrLevel > 0) { + WEBARKIT_LOGd("Too few keypoints after pyrDown (%d <= %d); retrying at full resolution.\n", + frameKeyPts.size(), (int)minRequiredDetectedFeatures); + detectionFrame = frame; + detectionScaleFactor = cv::Vec2f(1.0f, 1.0f); + featureMask = createFeatureMask(detectionFrame); + frameKeyPts.clear(); + frameDescr.release(); + if (!extractFeatures(detectionFrame, featureMask, frameKeyPts, frameDescr)) { + WEBARKIT_LOGe("No features detected in full-resolution extractFeatures!\n"); + } + WEBARKIT_LOGd("frame KeyPoints size (full-res retry): %d\n", frameKeyPts.size()); + } + if (static_cast(frameKeyPts.size()) > minRequiredDetectedFeatures) { - MatchFeatures(frameKeyPts, frameDescr); + MatchFeatures(frameKeyPts, frameDescr, detectionScaleFactor); } } int i = 0; @@ -480,7 +508,8 @@ class WebARKitTracker::WebARKitTrackerImpl { void swapImagePyramid() { _pyramid.swap(_prevPyramid); } - void MatchFeatures(const std::vector& newFrameFeatures, cv::Mat newFrameDescriptors) { + void MatchFeatures(const std::vector& newFrameFeatures, cv::Mat newFrameDescriptors, + const cv::Vec2f& scaleFactor) { int maxMatches = 0; int bestMatchIndex = -1; std::vector finalMatched1, finalMatched2; @@ -518,14 +547,14 @@ class WebARKitTracker::WebARKitTrackerImpl { // } // end for cycle if (maxMatches > 0) { - // WebARKitLib#44: detection ran on the downsampled detectionFrame, so the - // matched FRAME keypoints (finalMatched1) are in downsampled coordinates -- + // WebARKitLib#44: detection may run on the downsampled detectionFrame, so the + // matched FRAME keypoints (finalMatched1) are in that frame's coordinates -- // scale them back up to full-frame coordinates before fitting the - // homography. Level 0 => factor 1.0 => no-op. The reference keypoints - // (finalMatched2) stay in reference coordinates. + // homography. Identity factor (level 0, or the #53 full-res retry) => no-op. + // The reference keypoints (finalMatched2) stay in reference coordinates. for (size_t i = 0; i < finalMatched1.size(); i++) { - finalMatched1[i].pt.x *= _featureDetectScaleFactor[0]; - finalMatched1[i].pt.y *= _featureDetectScaleFactor[1]; + finalMatched1[i].pt.x *= scaleFactor[0]; + finalMatched1[i].pt.y *= scaleFactor[1]; } homography::WebARKitHomographyInfo homoInfo = getHomographyInliers(Points(finalMatched2), Points(finalMatched1)); @@ -814,7 +843,10 @@ class WebARKitTracker::WebARKitTrackerImpl { void setDetectorType(webarkit::TRACKER_TYPE trackerType) { _trackerType = trackerType; if (trackerType == webarkit::TRACKER_TYPE::AKAZE_TRACKER) { - const double akaze_thresh = 3e-4; // AKAZE detection threshold set to locate about 1000 keypoints + // WebARKitLib#53: use the artoolkitX OCVT default threshold (0.001) instead of + // the more aggressive 3e-4. The higher threshold keeps more keypoints, which is + // important for small markers whose features are sparse after any downsampling. + const double akaze_thresh = 1e-3; // AKAZE detection threshold (artoolkitX OCVT default) cv::Ptr akaze = cv::AKAZE::create(); akaze->setThreshold(akaze_thresh); this->_featureDetector = akaze; diff --git a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/include/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.h b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/include/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.h index d038a64..a4b807f 100644 --- a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/include/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.h +++ b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/include/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.h @@ -10,6 +10,7 @@ extern const double DEFAULT_NN_MATCH_RATIO; extern const double TEBLID_NN_MATCH_RATIO; +extern const double AKAZE_NN_MATCH_RATIO; extern const int DEFAULT_MAX_FEATURES; extern const int TEBLID_MAX_FEATURES; extern const int N; From f9c68a881a7dc30427e9313c972d0187e9fe7c9e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:59:21 +0000 Subject: [PATCH 16/17] Address review: fix float/double literal inconsistency and retry threshold - Drop misleading `f` suffix on double constants (DEFAULT/TEBLID/AKAZE NN_MATCH_RATIO); use EXPECT_DOUBLE_EQ in the config test. - Retry condition uses `<` (not `<=`) to match the `>` matching gate, so a frame with exactly minRequiredDetectedFeatures keypoints is not needlessly retried. Co-authored-by: kalwalt <1275858+kalwalt@users.noreply.github.com> --- .../WebARKitOpticalTracking/WebARKitConfig.cpp | 6 +++--- .../WebARKitOpticalTracking/WebARKitTracker.cpp | 4 ++-- tests/webarkit_test.cc | 5 +++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp index e4b0f82..9cf1c02 100644 --- a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp +++ b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitConfig.cpp @@ -1,8 +1,8 @@ #include -extern const double DEFAULT_NN_MATCH_RATIO = 0.7f; -extern const double TEBLID_NN_MATCH_RATIO = 0.8f; -extern const double AKAZE_NN_MATCH_RATIO = 0.8f; ///< artoolkitX OCVT parity (OCVConfig nn_match_ratio). +extern const double DEFAULT_NN_MATCH_RATIO = 0.7; +extern const double TEBLID_NN_MATCH_RATIO = 0.8; +extern const double AKAZE_NN_MATCH_RATIO = 0.8; ///< artoolkitX OCVT parity (OCVConfig nn_match_ratio). extern const int DEFAULT_MAX_FEATURES = 800; extern const int TEBLID_MAX_FEATURES = 1000; extern const int N = 10; diff --git a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp index 59e061e..7b9370c 100644 --- a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp +++ b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp @@ -398,8 +398,8 @@ class WebARKitTracker::WebARKitTrackerImpl { // frame before giving up. This preserves the #44 fast path for markers that // survive downsampling while restoring small-marker detection parity with // WebARKitLib-rs / jsartoolkitNFT (which never downsample). - if (static_cast(frameKeyPts.size()) <= minRequiredDetectedFeatures && _featureDetectPyrLevel > 0) { - WEBARKIT_LOGd("Too few keypoints after pyrDown (%d <= %d); retrying at full resolution.\n", + if (static_cast(frameKeyPts.size()) < minRequiredDetectedFeatures && _featureDetectPyrLevel > 0) { + WEBARKIT_LOGd("Too few keypoints after pyrDown (%d < %d); retrying at full resolution.\n", frameKeyPts.size(), (int)minRequiredDetectedFeatures); detectionFrame = frame; detectionScaleFactor = cv::Vec2f(1.0f, 1.0f); diff --git a/tests/webarkit_test.cc b/tests/webarkit_test.cc index 7ac2964..4a15756 100644 --- a/tests/webarkit_test.cc +++ b/tests/webarkit_test.cc @@ -29,8 +29,9 @@ INSTANTIATE_TEST_SUITE_P(WebARKitEnumTestSuite, WebARKitEnumTest, webarkit::ColorSpace::GRAY}))); TEST(WebARKitConfigTest, TestConfigValues) { - EXPECT_EQ(DEFAULT_NN_MATCH_RATIO, 0.7f); - EXPECT_EQ(TEBLID_NN_MATCH_RATIO, 0.8f); + EXPECT_DOUBLE_EQ(DEFAULT_NN_MATCH_RATIO, 0.7); + EXPECT_DOUBLE_EQ(TEBLID_NN_MATCH_RATIO, 0.8); + EXPECT_DOUBLE_EQ(AKAZE_NN_MATCH_RATIO, 0.8); EXPECT_EQ(DEFAULT_MAX_FEATURES, 800); EXPECT_EQ(TEBLID_MAX_FEATURES, 1000); EXPECT_EQ(N, 10); From cbfbc742bd9242c7bf29bb6fb8b1028cab962d14 Mon Sep 17 00:00:00 2001 From: Walter Perdan Date: Wed, 2 Sep 2026 11:07:57 +0200 Subject: [PATCH 17/17] fix: close AKAZE retry boundary gap and correct threshold comment Addresses Qodo review findings on #64: - The full-res retry fired only when keypoints < minRequiredDetectedFeatures, while matching required > minRequiredDetectedFeatures. A frame with exactly minRequiredDetectedFeatures keypoints hit neither path. Widen the retry condition to <= so that boundary case is covered. - Correct the AKAZE threshold comment: raising the detector response threshold from 3e-4 to 1e-3 is stricter, not looser, even though the OCVT-aligned value was validated to fix small-marker detection. Co-Authored-By: Claude Sonnet 5 --- .../WebARKitOpticalTracking/WebARKitTracker.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp index 7b9370c..d565405 100644 --- a/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp +++ b/WebARKit/WebARKitTrackers/WebARKitOpticalTracking/WebARKitTracker.cpp @@ -398,8 +398,8 @@ class WebARKitTracker::WebARKitTrackerImpl { // frame before giving up. This preserves the #44 fast path for markers that // survive downsampling while restoring small-marker detection parity with // WebARKitLib-rs / jsartoolkitNFT (which never downsample). - if (static_cast(frameKeyPts.size()) < minRequiredDetectedFeatures && _featureDetectPyrLevel > 0) { - WEBARKIT_LOGd("Too few keypoints after pyrDown (%d < %d); retrying at full resolution.\n", + if (static_cast(frameKeyPts.size()) <= minRequiredDetectedFeatures && _featureDetectPyrLevel > 0) { + WEBARKIT_LOGd("Too few keypoints after pyrDown (%d <= %d); retrying at full resolution.\n", frameKeyPts.size(), (int)minRequiredDetectedFeatures); detectionFrame = frame; detectionScaleFactor = cv::Vec2f(1.0f, 1.0f); @@ -844,8 +844,10 @@ class WebARKitTracker::WebARKitTrackerImpl { _trackerType = trackerType; if (trackerType == webarkit::TRACKER_TYPE::AKAZE_TRACKER) { // WebARKitLib#53: use the artoolkitX OCVT default threshold (0.001) instead of - // the more aggressive 3e-4. The higher threshold keeps more keypoints, which is - // important for small markers whose features are sparse after any downsampling. + // 3e-4. This is the minimum detector response a keypoint must have to be + // accepted, so it is stricter than 3e-4, not looser -- but it matches the + // value artoolkitX uses in production and was validated to restore detection + // on small markers after downsampling. const double akaze_thresh = 1e-3; // AKAZE detection threshold (artoolkitX OCVT default) cv::Ptr akaze = cv::AKAZE::create(); akaze->setThreshold(akaze_thresh);