Skip to content

Commit 01931af

Browse files
committed
Move global initializers to new boot function
1 parent 52a290e commit 01931af

File tree

2 files changed

+32
-50
lines changed

2 files changed

+32
-50
lines changed

src/core.cpp

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// C++ headers
22
#include "opencv2/core.hpp"
3+
#include "opencv2/imgcodecs.hpp"
34
#include "convert.h"
45
#include "numpy.h"
56

@@ -11,46 +12,46 @@ extern "C" {
1112

1213
using namespace cv;
1314

14-
// Fix for https://github.com/sparkfun/micropython-opencv/issues/13
15+
// The function below is a workaround for memory management issues between
16+
// OpenCV and the MicroPython GC. OpenCV allocates some objects on the heap,
17+
// whenever the first function that needs the objects happen to be called. That
18+
// only happens from the user's code after the GC has been initialized, meaning
19+
// they get allocated on the GC heap (see `__wrap_malloc()`). If a soft reset
20+
// occurs, the GC gets reset and the memory locations get overwritten, but the
21+
// same memory locations are still referenced for the objects, resulting in bad
22+
// values and problems (crashes and freezes, `CV_Assert()` calls fail, etc.).
1523
//
16-
// TLDR; The CoreTLSData object gets allocated once, whenever the first OpenCV
17-
// function that needs it happens to be called. That will only happen from the
18-
// user's code, after the GC has been initialized, meaning it gets allocated on
19-
// the GC heap (see `__wrap_malloc()`). If a soft reset occurs, the GC gets
20-
// reset and overwrites the memory location, but the same memory location is
21-
// still referenced for the CoreTLSData object, resulting in bogus values and
22-
// subsequent `CV_Assert()` calls fail
24+
// The solution here is to ensure those objects are allocated in the C heap
25+
// instead of the GC heap. The function below calls various OpenCV functions
26+
// that subsequently allocate the problematic objects. To ensure they are
27+
// allocated on the C heap, this needs to happen before the GC is initialized
28+
// (before `main()` is called), so __wrap_malloc() will use __real_malloc()
29+
// instead of the GC.
2330
//
24-
// The solution here is to create a global variable that subsequently calls
25-
// `getCoreTlsData()` to allocate the CoreTLSData object before the GC has
26-
// been initialized, so it gets allocated on the C heap and persists through
27-
// soft resets. `getCoreTlsData()` is not publicly exposed, but `theRNG()` is
28-
// exposed, which just runs `return getCoreTlsData().rng`
29-
volatile RNG rng = theRNG();
30-
31-
// Fix for https://github.com/sparkfun/micropython-opencv/issues/17
32-
//
33-
// TLDR; The `StdMatAllocator` gets allocated once, whenever the first time a
34-
// Mat object is created without the NumpyAllocator being set (OpenCV creates
35-
// internal Mat objects for various operations that use whatever the default
36-
// allocator is). Similar to above, the `StdMatAllocator` gets allocated on the
37-
// GC heap, so if a soft reset occurs, the GC gets reset and overwrites the
38-
// memory location, causing problems
39-
//
40-
// Instead of ensuring the `StdMatAllocator` is allocated on the C heap, we just
41-
// set the NumpyAllocator as the default allocator. `Mat::setDefaultAllocator()`
42-
// does not return anything, so this wrapper function returns a dummy value so
43-
// we can use it to initialize a global variable, ensuring it gets run before
44-
// `main()` gets called
45-
bool setNumpyAllocator() {
31+
// The function below returns a dummy value that we use to initialize a global
32+
// variable, ensuring it gets run before `main()` gets called. This also means
33+
// it can be used as a general boot function for anything else that needs to
34+
// happen before `main()` is called, such as setting the default Mat allocator.
35+
bool upyOpenCVBoot() {
4636
try {
37+
// Initializes `CoreTLSData` on the C heap, see:
38+
// https://github.com/sparkfun/micropython-opencv/issues/13
39+
theRNG();
40+
41+
// Initializes all image codecs on the C heap, see:
42+
// https://github.com/sparkfun/micropython-opencv/issues/17
43+
haveImageWriter(".bmp");
44+
45+
// Sets the NumpyAllocator as the default Mat object allocator, see
46+
// https://github.com/sparkfun/micropython-opencv/issues/17
4747
Mat::setDefaultAllocator(&GetNumpyAllocator());
48+
4849
return true;
4950
} catch (const Exception& e) {
5051
return false;
5152
}
5253
}
53-
volatile bool defaultAllocatorSet = setNumpyAllocator();
54+
volatile bool bootSuccess = upyOpenCVBoot();
5455

5556
mp_obj_t cv2_core_convertScaleAbs(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
5657
// Define the arguments

src/imgcodecs.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,6 @@ extern "C" {
1313

1414
using namespace cv;
1515

16-
// Fix for https://github.com/sparkfun/micropython-opencv/issues/17
17-
//
18-
// TLDR; The `g_codecs` object (which stores all image encoders and decoders) is
19-
// allocated once, whenever the first OpenCV function that needs it happens to
20-
// be called. That will only happen from the user's code, after the GC has been
21-
// initialized, meaning it gets allocated on the GC heap (see `__wrap_malloc()`)
22-
// If a soft reset occurs, the GC gets reset and overwrites the memory location,
23-
// but the same memory location is still referenced for the the `g_codecs`
24-
// object, resulting in bogus values and subsequent `imread()` and `imwrite()`
25-
// calls fail
26-
//
27-
// The solution here is to create a global variable that subsequently creates
28-
// the `g_codecs` object before the GC has been initialized, so it's allocated
29-
// on the C heap and persists through soft resets. `g_codecs` is initialized
30-
// when calling `getCodecs()`, which is not publicly exposed. The next best
31-
// option is to call `haveImageWriter()`, which calls `findEncoder()`, which
32-
// calls `getCodecs()`
33-
volatile bool haveImageWriterPNG = haveImageWriter(".png");
34-
3516
// Helper macro to create an empty mp_map_t, derived from MP_DEFINE_CONST_MAP.
3617
// Primarily used for function calls with no keyword arguments, since we can't
3718
// just pass `NULL` or mp_const_none (crash occurs otherwise)

0 commit comments

Comments
 (0)