From 5f4e690fa16148abdd806ace22efceb9bd5ad60a Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Tue, 4 Jul 2017 14:09:00 -0400 Subject: [PATCH] Add find circles. Now you can find circles with your OpenMV Cam! The alrogithm can eek out about 7 FPS on a 160x120 image which is quite impressive given how computationally expensive circle finding is... --- src/omv/boards/OPENMV3/omv_boardconfig.h | 3 + src/omv/img/hough.c | 328 ++++++++++++++++++ src/omv/img/imlib.h | 8 + src/omv/py/py_image.c | 132 ++++++- src/omv/py/qstrdefsomv.h | 33 +- .../09-Feature-Detection/find_circles.py | 35 ++ 6 files changed, 532 insertions(+), 7 deletions(-) create mode 100644 usr/examples/09-Feature-Detection/find_circles.py diff --git a/src/omv/boards/OPENMV3/omv_boardconfig.h b/src/omv/boards/OPENMV3/omv_boardconfig.h index 01422c8b7..52a1548f5 100644 --- a/src/omv/boards/OPENMV3/omv_boardconfig.h +++ b/src/omv/boards/OPENMV3/omv_boardconfig.h @@ -26,6 +26,9 @@ // Have built-in RGB->LAB table. #define OMV_HAVE_LAB_TABLE +// Enable Find_Circles +#define OMV_ENABLE_FIND_CIRCLES + // Enable AprilTags (64 KB). #define OMV_ENABLE_APRILTAGS diff --git a/src/omv/img/hough.c b/src/omv/img/hough.c index 0a78878de..3fb90656c 100644 --- a/src/omv/img/hough.c +++ b/src/omv/img/hough.c @@ -851,3 +851,331 @@ void imlib_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsig fb_free(); // mag_buffer fb_free(); // theta_buffer } + +void imlib_find_circles(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int x_margin, unsigned int y_margin, unsigned int r_margin) +{ + uint16_t *theta_acc = fb_alloc0(sizeof(uint16_t) * roi->w * roi->h); + uint16_t *magnitude_acc = fb_alloc0(sizeof(uint16_t) * roi->w * roi->h); + + switch (ptr->bpp) { + case IMAGE_BPP_BINARY: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + int theta = fast_roundf(fast_atan2f(y_acc, x_acc) * 57.295780) % 360; // * (180 / PI) + if (theta < 0) theta += 360; + int magnitude = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + int index = (roi->w * (y - roi->y)) + (x - roi->x); + + theta_acc[index] = theta; + magnitude_acc[index] = magnitude; + } + } + break; + } + case IMAGE_BPP_GRAYSCALE: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int theta = fast_roundf(fast_atan2f(y_acc, x_acc) * 57.295780) % 360; // * (180 / PI) + if (theta < 0) theta += 360; + int magnitude = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + int index = (roi->w * (y - roi->y)) + (x - roi->x); + + theta_acc[index] = theta; + magnitude_acc[index] = magnitude; + } + } + break; + } + case IMAGE_BPP_RGB565: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int theta = fast_roundf(fast_atan2f(y_acc, x_acc) * 57.295780) % 360; // * (180 / PI) + if (theta < 0) theta += 360; + int magnitude = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + int index = (roi->w * (y - roi->y)) + (x - roi->x); + + theta_acc[index] = theta; + magnitude_acc[index] = magnitude; + } + } + break; + } + default: { + break; + } + } + + // Theta Direction (% 180) + // + // 0,0 X_MAX + // + // 090 + // 000 000 + // 090 + // + // Y_MAX + + // Theta Direction (% 360) + // + // 0,0 X_MAX + // + // 090 + // 000 180 + // 270 + // + // Y_MAX + + list_init(out, sizeof(find_circles_list_lnk_data_t)); + + for (int r = 2, rr = IM_MIN((roi->w / 2), (roi->h / 2)); r < rr; r += 2) { // ignore r = 0/1 + int a_size, b_size, hough_divide = 1; // divides a and b accumulators + int w_size = roi->w - (2 * r); + int h_size = roi->h - (2 * r); + + for (;;) { // shrink to fit... + a_size = 1 + ((w_size + hough_divide - 1) / hough_divide) + 1; // left & right padding + b_size = 1 + ((h_size + hough_divide - 1) / hough_divide) + 1; // top & bottom padding + if ((sizeof(uint32_t) * a_size * b_size) <= fb_avail()) break; + hough_divide = hough_divide << 1; // powers of 2... + if (hough_divide > 4) fb_alloc_fail(); // support 1, 2, 4 + } + + uint32_t *acc = fb_alloc0(sizeof(uint32_t) * a_size * b_size); + + for (int y = 0, yy = roi->h; y < yy; y++) { + for (int x = 0, xx = roi->w; x < xx; x++) { + int index = (roi->w * y) + x; + int theta = theta_acc[index]; + int magnitude = magnitude_acc[index]; + if (!magnitude) continue; + + int a = fast_roundf(x + (r * cos_table[theta])) - r; + if ((a < 0) || (w_size <= a)) continue; // circle doesn't fit in the window + int b = fast_roundf(y + (r * sin_table[theta])) - r; + if ((b < 0) || (h_size <= b)) continue; // circle doesn't fit in the window + int acc_index = (((b / hough_divide) + 1) * a_size) + ((a / hough_divide) + 1); // add offset + + int acc_value = acc[acc_index] += magnitude; + acc[acc_index] = acc_value; + } + } + + for (int y = 1, yy = b_size - 1; y < yy; y++) { + uint32_t *row_ptr = acc + (a_size * y); + + for (int x = 1, xx = a_size - 1; x < xx; x++) { + if ((row_ptr[x] >= threshold) + && (row_ptr[x] >= row_ptr[x-a_size-1]) + && (row_ptr[x] >= row_ptr[x-a_size]) + && (row_ptr[x] >= row_ptr[x-a_size+1]) + && (row_ptr[x] >= row_ptr[x-1]) + && (row_ptr[x] >= row_ptr[x+1]) + && (row_ptr[x] >= row_ptr[x+a_size-1]) + && (row_ptr[x] >= row_ptr[x+a_size]) + && (row_ptr[x] >= row_ptr[x+a_size+1])) { + + find_circles_list_lnk_data_t lnk_data; + lnk_data.magnitude = row_ptr[x]; + lnk_data.p.x = ((x - 1) * hough_divide) + r + roi->x; // remove offset + lnk_data.p.y = ((y - 1) * hough_divide) + r + roi->y; // remove offset + lnk_data.r = r; + + list_push_back(out, &lnk_data); + } + } + } + + fb_free(); // acc + } + + fb_free(); // magnitude_acc + fb_free(); // theta_acc + + for (;;) { // Merge overlapping. + bool merge_occured = false; + + list_t out_temp; + list_init(&out_temp, sizeof(find_circles_list_lnk_data_t)); + + while (list_size(out)) { + find_circles_list_lnk_data_t lnk_data; + list_pop_front(out, &lnk_data); + + for (size_t k = 0, l = list_size(out); k < l; k++) { + find_circles_list_lnk_data_t tmp_data; + list_pop_front(out, &tmp_data); + + bool x_diff_ok = abs(lnk_data.p.x - tmp_data.p.x) < x_margin; + bool y_diff_ok = abs(lnk_data.p.y - tmp_data.p.y) < y_margin; + bool r_diff_ok = abs(lnk_data.r - tmp_data.r) < r_margin; + + if (x_diff_ok && y_diff_ok && r_diff_ok) { + uint32_t magnitude = lnk_data.magnitude + tmp_data.magnitude; + lnk_data.p.x = ((lnk_data.p.x * lnk_data.magnitude) + (tmp_data.p.x * tmp_data.magnitude)) / magnitude; + lnk_data.p.y = ((lnk_data.p.y * lnk_data.magnitude) + (tmp_data.p.y * tmp_data.magnitude)) / magnitude; + lnk_data.r = ((lnk_data.r * lnk_data.magnitude) + (tmp_data.r * tmp_data.magnitude)) / magnitude; + lnk_data.magnitude = magnitude / 2; + merge_occured = true; + } else { + list_push_back(out, &tmp_data); + } + } + + list_push_back(&out_temp, &lnk_data); + } + + list_copy(out, &out_temp); + + if (!merge_occured) { + break; + } + } +} diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 55df5272b..4b9381a09 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -896,6 +896,12 @@ typedef struct find_lines_list_lnk_data { int16_t theta, rho; } find_lines_list_lnk_data_t; +typedef struct find_circles_list_lnk_data { + point_t p; + int r; + uint32_t magnitude; +} find_circles_list_lnk_data_t; + typedef struct find_qrcodes_list_lnk_data { point_t corners[4]; rectangle_t rect; @@ -1148,6 +1154,8 @@ void imlib_find_lines(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int void imlib_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, uint32_t threshold, unsigned int theta_margin, unsigned int rho_margin, uint32_t segment_threshold); +void imlib_find_circles(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int x_margin, unsigned int y_margin, unsigned int r_margin); // 1/2D Bar Codes void imlib_find_qrcodes(list_t *out, image_t *ptr, rectangle_t *roi); void imlib_find_apriltags(list_t *out, image_t *ptr, rectangle_t *roi, apriltag_families_t families, diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 2a01adcc1..8ec680044 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -201,19 +201,19 @@ static void py_image_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k switch(self->_cobj.bpp) { case IMAGE_BPP_BINARY: { mp_printf(print, "{w:%d, h:%d, type=\"binary\", size:%d}", - self->_cobj.w, self->_cobj.h, + self->_cobj.w, self->_cobj.h, ((self->_cobj.w + UINT32_T_MASK) >> UINT32_T_SHIFT) * self->_cobj.h); break; } case IMAGE_BPP_GRAYSCALE: { mp_printf(print, "{w:%d, h:%d, type=\"grayscale\", size:%d}", - self->_cobj.w, self->_cobj.h, + self->_cobj.w, self->_cobj.h, (self->_cobj.w * self->_cobj.h) * sizeof(uint8_t)); break; } case IMAGE_BPP_RGB565: { mp_printf(print, "{w:%d, h:%d, type=\"rgb565\", size:%d}", - self->_cobj.w, self->_cobj.h, + self->_cobj.w, self->_cobj.h, (self->_cobj.w * self->_cobj.h) * sizeof(uint16_t)); break; } @@ -222,7 +222,7 @@ static void py_image_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k print->print_strn(print->data, (const char *) self->_cobj.data, self->_cobj.bpp); } else { // not for ide mp_printf(print, "{w:%d, h:%d, type=\"jpeg\", size:%d}", - self->_cobj.w, self->_cobj.h, + self->_cobj.w, self->_cobj.h, self->_cobj.bpp); } break; @@ -2481,6 +2481,124 @@ static mp_obj_t py_image_find_line_segments(uint n_args, const mp_obj_t *args, m return objects_list; } +#ifdef OMV_ENABLE_FIND_CIRCLES +// Circle Object // +#define py_circle_obj_size 4 +typedef struct py_circle_obj { + mp_obj_base_t base; + mp_obj_t x, y, r, magnitude; +} py_circle_obj_t; + +static void py_circle_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_circle_obj_t *self = self_in; + mp_printf(print, + "{x:%d, y:%d, r:%d, magnitude:%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->r), + mp_obj_get_int(self->magnitude)); +} + +static mp_obj_t py_circle_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_circle_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_circle_obj_size, index, &slice)) { + mp_not_implemented("only slices with step=1 (aka None) are supported"); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_circle_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->r; + case 3: return self->magnitude; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_circle_circle(mp_obj_t self_in) +{ + return mp_obj_new_tuple(3, (mp_obj_t []) {((py_circle_obj_t *) self_in)->x, + ((py_circle_obj_t *) self_in)->y, + ((py_circle_obj_t *) self_in)->r}); +} + +mp_obj_t py_circle_x(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->x; } +mp_obj_t py_circle_y(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->y; } +mp_obj_t py_circle_r(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->r; } +mp_obj_t py_circle_magnitude(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->magnitude; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_circle_obj, py_circle_circle); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_x_obj, py_circle_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_y_obj, py_circle_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_r_obj, py_circle_r); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_magnitude_obj, py_circle_magnitude); + +STATIC const mp_rom_map_elem_t py_circle_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_circle), MP_ROM_PTR(&py_circle_circle_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_circle_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_circle_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_r), MP_ROM_PTR(&py_circle_r_obj) }, + { MP_ROM_QSTR(MP_QSTR_magnitude), MP_ROM_PTR(&py_circle_magnitude_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(py_circle_locals_dict, py_circle_locals_dict_table); + +static const mp_obj_type_t py_circle_type = { + { &mp_type_type }, + .name = MP_QSTR_circle, + .print = py_circle_print, + .subscr = py_circle_subscr, + .locals_dict = (mp_obj_t) &py_circle_locals_dict, +}; + +static mp_obj_t py_image_find_circles(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), "Operation not supported on JPEG or RAW frames."); + + rectangle_t roi; + py_helper_lookup_rectangle(kw_args, arg_img, &roi); + + unsigned int x_stride = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_stride), 2); + PY_ASSERT_TRUE_MSG(x_stride > 0, "x_stride must not be zero."); + unsigned int y_stride = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_stride), 1); + PY_ASSERT_TRUE_MSG(y_stride > 0, "y_stride must not be zero."); + + list_t out; + fb_alloc_mark(); + imlib_find_circles(&out, arg_img, &roi, x_stride, y_stride, py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 1600), + py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_margin), 10), + py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_margin), 10), + py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_r_margin), 10)); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_circles_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_circle_obj_t *o = m_new_obj(py_circle_obj_t); + o->base.type = &py_circle_type; + o->x = mp_obj_new_int(lnk_data.p.x); + o->y = mp_obj_new_int(lnk_data.p.y); + o->r = mp_obj_new_int(lnk_data.r); + o->magnitude = mp_obj_new_int(lnk_data.magnitude); + + objects_list->items[i] = o; + } + + return objects_list; +} +#endif // OMV_ENABLE_FIND_CIRCLES + // QRCode Object // #define py_qrcode_obj_size 10 typedef struct py_qrcode_obj { @@ -3562,6 +3680,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_blobs_obj, 2, py_image_find_blob /* Shape Detection */ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_lines_obj, 1, py_image_find_lines); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_line_segments_obj, 1, py_image_find_line_segments); +#ifdef OMV_ENABLE_FIND_CIRCLES +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_circles_obj, 1, py_image_find_circles); +#endif /* Code Detection */ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_qrcodes_obj, 1, py_image_find_qrcodes); #ifdef OMV_ENABLE_APRILTAGS @@ -3657,6 +3778,9 @@ static const mp_map_elem_t locals_dict_table[] = { /* Shape Detection */ {MP_OBJ_NEW_QSTR(MP_QSTR_find_lines), (mp_obj_t)&py_image_find_lines_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_find_line_segments), (mp_obj_t)&py_image_find_line_segments_obj}, +#ifdef OMV_ENABLE_FIND_CIRCLES + {MP_OBJ_NEW_QSTR(MP_QSTR_find_circles), (mp_obj_t)&py_image_find_circles_obj}, +#endif /* Code Detection */ {MP_OBJ_NEW_QSTR(MP_QSTR_find_qrcodes), (mp_obj_t)&py_image_find_qrcodes_obj}, #ifdef OMV_ENABLE_APRILTAGS diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 6cdfd579e..7302c5dc6 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -352,12 +352,17 @@ Q(zoom) // Get Histogram Q(get_hist) Q(get_histogram) -// Histogram Object -Q(histogram) +// duplicate Q(roi) Q(bins) Q(l_bins) Q(a_bins) Q(b_bins) +// Histogram Object +Q(histogram) +// duplicate Q(bins) +// duplicate Q(l_bins) +// duplicate Q(a_bins) +// duplicate Q(b_bins) Q(get_percentile) Q(get_stats) Q(get_statistics) @@ -372,6 +377,11 @@ Q(b_value) // Get Statistics // duplicate Q(get_stats) // duplicate Q(get_statistics) +// duplicate Q(roi) +// duplicate Q(bins) +// duplicate Q(l_bins) +// duplicate Q(a_bins) +// duplicate Q(b_bins) // Statistics Object // duplicate Q(statistics) // duplicate Q(mean) @@ -428,6 +438,7 @@ Q(rho) // Find Blobs Q(find_blobs) +// duplicate Q(roi) // duplicate Q(x_stride) // duplicate Q(y_stride) Q(area_threshold) @@ -436,7 +447,6 @@ Q(merge) Q(margin) Q(threshold_cb) Q(merge_cb) -// duplicate Q(roi) // Blob Object Q(blob) Q(rect) @@ -472,6 +482,23 @@ Q(find_line_segments) // duplicate Q(rho_margin) Q(segment_threshold) +// Find Circles +Q(find_circles) +// duplicate Q(roi) +// duplicate Q(x_stride) +// duplicate Q(y_stride) +// duplicate Q(threshold) +Q(x_margin) +Q(y_margin) +Q(r_margin) +// Circle Object +Q(circle) +// duplicate Q(circle) +// duplicate Q(x) +// duplicate Q(y) +Q(r) +// duplicate Q(magnitude) + // Find QRCodes Q(find_qrcodes) // duplicate Q(roi) diff --git a/usr/examples/09-Feature-Detection/find_circles.py b/usr/examples/09-Feature-Detection/find_circles.py new file mode 100644 index 000000000..3334b25aa --- /dev/null +++ b/usr/examples/09-Feature-Detection/find_circles.py @@ -0,0 +1,35 @@ +# Find Circles Example +# +# This example shows off how to find circles in the image using the Hough +# Transform. https://en.wikipedia.org/wiki/Circle_Hough_Transform +# +# Note that the find_circles() method will only find circles which are completely +# inside of the image. Circles which go outside of the image/roi are ignored... + +import sensor, image, time + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) # grayscale is faster +sensor.set_framesize(sensor.QQVGA) +sensor.skip_frames(time = 2000) +clock = time.clock() + +while(True): + clock.tick() + img = sensor.snapshot().lens_corr(1.8) + + # Circle objects have four values: x, y, r (radius), and magnitude. The + # magnitude is the strength of the detection of the circle. Higher is + # better... + + # `threshold` controls how many circles are found. Increase its value + # to decrease the number of circles detected... + + # `x_margin`, `y_margin`, and `r_margin` control the merging of similar + # circles in the x, y, and r (radius) directions. + + for c in img.find_circles(threshold = 2000, x_margin = 10, y_margin = 10, r_margin = 10): + img.draw_circle(c.x(), c.y(), c.r(), color = (255, 0, 0)) + print(c) + + print("FPS %f" % clock.fps())