Skip to content

Commit c5e2600

Browse files
committed
Initial implementation
1 parent 9c630d1 commit c5e2600

16 files changed

+663
-0
lines changed

.gitmodules

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[submodule "src/opencv"]
2+
path = src/opencv
3+
url = https://github.com/sfe-SparkFro/opencv.git
4+
[submodule "src/ulab"]
5+
path = src/ulab
6+
url = https://github.com/v923z/micropython-ulab.git
7+
[submodule "micropython"]
8+
path = micropython
9+
url = https://github.com/sparkfun/micropython.git

Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Set Pico SDK flags to create our own malloc wrapper and enable exceptions
2+
CMAKE_ARGS += -DSKIP_PICO_MALLOC=1 -DPICO_CXX_ENABLE_EXCEPTIONS=1
3+
4+
# Get current directory
5+
CURRENT_DIR = $(shell pwd)
6+
7+
# Set the MicroPython user C module path to the OpenCV module
8+
MAKE_ARGS = USER_C_MODULES="$(CURRENT_DIR)/src/opencv_upy.cmake"
9+
10+
# Build MicroPython with the OpenCV module
11+
all:
12+
@cd micropython/ports/rp2 && export CMAKE_ARGS="$(CMAKE_ARGS)" && make -f Makefile $(MAKEFLAGS) $(MAKE_ARGS)
13+
14+
# Clean the MicroPython build
15+
clean:
16+
@cd micropython/ports/rp2 && make -f Makefile $(MAKEFLAGS) clean
17+
18+
# Load the MicroPython submodules
19+
submodules:
20+
@cd micropython/ports/rp2 && make -f Makefile $(MAKEFLAGS) submodules

micropython

Submodule micropython added at 186caf9

src/alloc.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// C headers
2+
#include "py/runtime.h"
3+
4+
// Hacky solution to see if the GC is initialized. If so, we can use gc_alloc
5+
// and gc_free. Otherwise, we need to use __real_malloc and __real_free.
6+
// TODO: Determine if this is acceptable or if we should use a different method.
7+
// Would be nice to be able to #define MICROPY_BOARD_EARLY_INIT to set a flag,
8+
// however that needs to be #defined in the board's mpconfigboard.h file.
9+
bool gc_inited() {
10+
return MP_STATE_MEM(area).gc_pool_start != NULL;
11+
}
12+
13+
// Since the linker flag `-Wl,--wrap=malloc` (and calloc, realloc, and free) is
14+
// set, calls to `malloc()` get replaced with `__wrap_malloc()` by the linker.
15+
// To use the original `malloc()`, we can instead use `__real_malloc()`, which
16+
// the linker replaces with the original `malloc()`. However because that's done
17+
// by the linker, we need to declare it here so the compiler knows about it.
18+
extern void *__real_malloc(size_t size);
19+
extern void *__real_calloc(size_t count, size_t size);
20+
extern void *__real_realloc(void *mem, size_t size);
21+
extern void *__real_free(void *mem);
22+
23+
// Implementations of the malloc, calloc, realloc, and free functions. If the
24+
// GC is initialized, we use the MicroPython functions to use the GC heap.
25+
// Otherwise, we use the "real" functions to use the C heap.
26+
void *__wrap_malloc(size_t size) {
27+
if(gc_inited()) {
28+
return m_tracked_calloc(1, size);
29+
}
30+
else {
31+
return __real_malloc(size);
32+
}
33+
}
34+
void __wrap_free(void *ptr) {
35+
if(gc_inited()) {
36+
m_tracked_free(ptr);
37+
}
38+
else {
39+
__real_free(ptr);
40+
}
41+
}
42+
void *__wrap_calloc(size_t count, size_t size)
43+
{
44+
if(gc_inited()) {
45+
return m_tracked_calloc(count, size);
46+
}
47+
else {
48+
return __real_calloc(count, size);
49+
}
50+
}
51+
void *__wrap_realloc(void *ptr, size_t size)
52+
{
53+
if(gc_inited()) {
54+
void *new_ptr = m_tracked_calloc(1, size);
55+
if (new_ptr == NULL) {
56+
return NULL;
57+
}
58+
memcpy(new_ptr, ptr, size);
59+
m_tracked_free(ptr);
60+
return new_ptr;
61+
}
62+
else {
63+
return __real_realloc(ptr, size);
64+
}
65+
}

src/convert.cpp

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// C++ headers
2+
#include "convert.h"
3+
#include "numpy.h"
4+
5+
// C headers
6+
extern "C" {
7+
#include "ulab_tools.h"
8+
#include "py/obj.h"
9+
} // extern "C"
10+
11+
uint8_t mat_depth_to_ndarray_type(int depth)
12+
{
13+
switch (depth) {
14+
case CV_8U: return NDARRAY_UINT8;
15+
case CV_8S: return NDARRAY_INT8;
16+
case CV_16U: return NDARRAY_UINT16;
17+
case CV_16S: return NDARRAY_INT16;
18+
case CV_32F: return NDARRAY_FLOAT;
19+
// case CV_Bool: return NDARRAY_BOOL;
20+
default: mp_raise_ValueError(MP_ERROR_TEXT("Unsupported Mat depth"));
21+
}
22+
}
23+
24+
int ndarray_type_to_mat_depth(uint8_t type)
25+
{
26+
switch (type) {
27+
case NDARRAY_UINT8: return CV_8U;
28+
case NDARRAY_INT8: return CV_8S;
29+
case NDARRAY_UINT16: return CV_16U;
30+
case NDARRAY_INT16: return CV_16S;
31+
case NDARRAY_FLOAT: return CV_32F;
32+
// case NDARRAY_BOOL: return CV_Bool;
33+
default: mp_raise_ValueError(MP_ERROR_TEXT("Unsupported ndarray type"));
34+
}
35+
}
36+
37+
ndarray_obj_t *mat_to_ndarray(Mat& mat)
38+
{
39+
// Derived from:
40+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L313-L328
41+
if(mat.data == NULL)
42+
mp_const_none;
43+
Mat temp, *ptr = (Mat*)&mat;
44+
if(!ptr->u || ptr->allocator != &GetNumpyAllocator())
45+
{
46+
temp.allocator = &GetNumpyAllocator();
47+
mat.copyTo(temp);
48+
ptr = &temp;
49+
}
50+
ndarray_obj_t* ndarray = (ndarray_obj_t*) ptr->u->userdata;
51+
return ndarray;
52+
}
53+
54+
Mat ndarray_to_mat(ndarray_obj_t *ndarray)
55+
{
56+
// Derived from:
57+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L54-L311
58+
// Some steps are skipped, as they are not needed or handle extreme edge
59+
// cases. Skipping these steps helps keep the code smaller and run faster.
60+
61+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L56-L119
62+
// We have an ndarray_obj_t, so these checks have already been done.
63+
64+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L130-L172
65+
int type = ndarray_type_to_mat_depth(ndarray->dtype);
66+
67+
int ndims = ndarray->ndim;
68+
size_t elemsize = ndarray->itemsize;
69+
int _sizes[ndarray->ndim];
70+
size_t _strides[ndarray->ndim];
71+
for (int i = 0; i < ndarray->ndim; i++) {
72+
_sizes[i] = ndarray->shape[ULAB_MAX_DIMS - ndarray->ndim + i];
73+
_strides[i] = ndarray->strides[ULAB_MAX_DIMS - ndarray->ndim + i];
74+
}
75+
76+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L176-L221
77+
bool ismultichannel = ndims == 3;
78+
79+
if (ismultichannel)
80+
{
81+
int channels = ndims >= 1 ? (int)_sizes[ndims - 1] : 1;
82+
ndims--;
83+
type |= CV_MAKETYPE(0, channels);
84+
elemsize = CV_ELEM_SIZE(type);
85+
}
86+
87+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L243-L261
88+
int size[CV_MAX_DIM+1] = {};
89+
size_t step[CV_MAX_DIM+1] = {};
90+
91+
size_t default_step = elemsize;
92+
for ( int i = ndims - 1; i >= 0; --i )
93+
{
94+
size[i] = (int)_sizes[i];
95+
if ( size[i] > 1 )
96+
{
97+
step[i] = (size_t)_strides[i];
98+
default_step = step[i] * size[i];
99+
}
100+
else
101+
{
102+
step[i] = default_step;
103+
default_step *= size[i];
104+
}
105+
}
106+
107+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L290-L294
108+
if( ndims == 0) {
109+
size[ndims] = 1;
110+
step[ndims] = elemsize;
111+
ndims++;
112+
}
113+
114+
// https://github.com/opencv/opencv/blob/aee828ac6ed3e45d7ca359d125349a570ca4e098/modules/python/src2/cv2_convert.cpp#L300-L10
115+
Mat mat = Mat(ndims, size, type, (void*) ndarray->array, step);
116+
mat.u = GetNumpyAllocator().allocate(ndarray, ndims, size, type, step);
117+
mat.addref();
118+
119+
mat.allocator = &GetNumpyAllocator();
120+
121+
return mat;
122+
}
123+
124+
mp_obj_t mat_to_mp_obj(Mat &mat)
125+
{
126+
return MP_OBJ_FROM_PTR(mat_to_ndarray(mat));
127+
}
128+
129+
Mat mp_obj_to_mat(mp_obj_t obj)
130+
{
131+
// Check for None object
132+
if(obj == mp_const_none)
133+
{
134+
// Create an empty Mat object. Set the NumpyAllocator in case this
135+
// matrix gets automatically allocated later
136+
Mat mat;
137+
mat.allocator = &GetNumpyAllocator();
138+
return mat;
139+
}
140+
141+
// Assume the object is a ndarray, or can be converted to one. Will raise an
142+
// exception if not
143+
ndarray_obj_t *ndarray = ndarray_from_mp_obj(obj, 0);
144+
145+
// Convert ndarray to Mat
146+
Mat mat = ndarray_to_mat(ndarray);
147+
148+
return mat;
149+
}

src/convert.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// C++ headers
2+
#include "opencv2/core.hpp"
3+
4+
// C headers
5+
extern "C" {
6+
#include "py/runtime.h"
7+
#include "ulab/code/ndarray.h"
8+
} // extern "C"
9+
10+
using namespace cv;
11+
12+
// Conversion functions between Mat depth and ndarray_obj_t type
13+
uint8_t mat_depth_to_ndarray_type(int depth);
14+
int ndarray_type_to_mat_depth(uint8_t type);
15+
16+
// Converstion functions between Mat and ndarray_obj_t
17+
ndarray_obj_t *mat_to_ndarray(Mat &mat);
18+
Mat ndarray_to_mat(ndarray_obj_t *ndarray);
19+
20+
// Conversion functions between Mat and mp_obj_t. Abstracts away intermediate
21+
// conversions to ndarray_obj_t
22+
mp_obj_t mat_to_mp_obj(Mat &mat);
23+
Mat mp_obj_to_mat(mp_obj_t obj);

src/core.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// C++ headers
2+
#include "opencv2/core.hpp"
3+
#include "convert.h"
4+
#include "numpy.h"
5+
6+
// C headers
7+
extern "C" {
8+
#include "core.h"
9+
#include "ulab/code/ndarray.h"
10+
} // extern "C"
11+
12+
using namespace cv;
13+
14+
mp_obj_t cv2_core_inRange(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
15+
// Define the arguments
16+
enum { ARG_src, ARG_lower, ARG_upper, ARG_dst };
17+
static const mp_arg_t allowed_args[] = {
18+
{ MP_QSTR_src, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
19+
{ MP_QSTR_lower, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
20+
{ MP_QSTR_upper, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
21+
{ MP_QSTR_dst, MP_ARG_OBJ, { .u_obj = mp_const_none } },
22+
};
23+
24+
// Parse the arguments
25+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
26+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
27+
28+
// Convert arguments to required types
29+
Mat src = mp_obj_to_mat(args[ARG_src].u_obj);
30+
Mat lower = mp_obj_to_mat(args[ARG_lower].u_obj);
31+
Mat upper = mp_obj_to_mat(args[ARG_upper].u_obj);
32+
Mat dst = mp_obj_to_mat(args[ARG_dst].u_obj);
33+
34+
// Call the corresponding OpenCV function
35+
try {
36+
inRange(src, lower, upper, dst);
37+
} catch(Exception& e) {
38+
mp_raise_msg(&mp_type_Exception, MP_ERROR_TEXT(e.what()));
39+
}
40+
41+
// Return the result
42+
return mat_to_mp_obj(dst);
43+
}
44+
45+
mp_obj_t cv2_core_max(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
46+
// Define the arguments
47+
enum { ARG_src1, ARG_src2, ARG_dst };
48+
static const mp_arg_t allowed_args[] = {
49+
{ MP_QSTR_src1, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
50+
{ MP_QSTR_src2, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
51+
{ MP_QSTR_dst, MP_ARG_OBJ, { .u_obj = mp_const_none } },
52+
};
53+
54+
// Parse the arguments
55+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
56+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
57+
58+
// Convert arguments to required types
59+
Mat src1 = mp_obj_to_mat(args[ARG_src1].u_obj);
60+
Mat src2 = mp_obj_to_mat(args[ARG_src2].u_obj);
61+
Mat dst = mp_obj_to_mat(args[ARG_dst].u_obj);
62+
63+
// Call the corresponding OpenCV function
64+
try {
65+
max(src1, src2, dst);
66+
} catch(Exception& e) {
67+
mp_raise_msg(&mp_type_Exception, MP_ERROR_TEXT(e.what()));
68+
}
69+
70+
// Return the result
71+
return mat_to_mp_obj(dst);
72+
}
73+
74+
mp_obj_t cv2_core_min(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
75+
// Define the arguments
76+
enum { ARG_src1, ARG_src2, ARG_dst };
77+
static const mp_arg_t allowed_args[] = {
78+
{ MP_QSTR_src1, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
79+
{ MP_QSTR_src2, MP_ARG_REQUIRED | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
80+
{ MP_QSTR_dst, MP_ARG_OBJ, { .u_obj = mp_const_none } },
81+
};
82+
83+
// Parse the arguments
84+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
85+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
86+
87+
// Convert arguments to required types
88+
Mat src1 = mp_obj_to_mat(args[ARG_src1].u_obj);
89+
Mat src2 = mp_obj_to_mat(args[ARG_src2].u_obj);
90+
Mat dst = mp_obj_to_mat(args[ARG_dst].u_obj);
91+
92+
// Call the corresponding OpenCV function
93+
try {
94+
min(src1, src2, dst);
95+
} catch(Exception& e) {
96+
mp_raise_msg(&mp_type_Exception, MP_ERROR_TEXT(e.what()));
97+
}
98+
99+
// Return the result
100+
return mat_to_mp_obj(dst);
101+
}

src/core.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// C headers
2+
#include "py/runtime.h"
3+
4+
extern mp_obj_t cv2_core_inRange(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
5+
extern mp_obj_t cv2_core_max(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
6+
extern mp_obj_t cv2_core_min(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);

0 commit comments

Comments
 (0)