From a4e556e7e385c434fa97e2f8fcac0923b949904c Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Thu, 13 Jul 2017 01:24:23 -0400 Subject: [PATCH] Add find_rects() using AprilTag's quad detector. It's awesome. --- src/omv/boards/OPENMV3/omv_boardconfig.h | 3 + src/omv/img/apriltag.c | 234 +++++++++++++++++- src/omv/img/hough.c | 4 +- src/omv/img/imlib.h | 13 +- src/omv/py/py_image.c | 132 ++++++++++ src/omv/py/qstrdefsomv.h | 16 +- .../09-Feature-Detection/find_rects.py | 31 +++ 7 files changed, 423 insertions(+), 10 deletions(-) create mode 100644 usr/examples/09-Feature-Detection/find_rects.py diff --git a/src/omv/boards/OPENMV3/omv_boardconfig.h b/src/omv/boards/OPENMV3/omv_boardconfig.h index 1bfa1e2e6..3720d7ee9 100644 --- a/src/omv/boards/OPENMV3/omv_boardconfig.h +++ b/src/omv/boards/OPENMV3/omv_boardconfig.h @@ -29,6 +29,9 @@ // Enable Find_Circles #define OMV_ENABLE_FIND_CIRCLES +// Enable Find_Rects +#define OMV_ENABLE_FIND_RECTS + // Enable AprilTags (64 KB). #define OMV_ENABLE_APRILTAGS diff --git a/src/omv/img/apriltag.c b/src/omv/img/apriltag.c index 1e59d44e5..ce6d53ade 100644 --- a/src/omv/img/apriltag.c +++ b/src/omv/img/apriltag.c @@ -9882,7 +9882,7 @@ int quad_segment_maxima(apriltag_detector_t *td, zarray_t *cluster, struct line_ } // return 1 if the quad looks okay, 0 if it should be discarded -int fit_quad(apriltag_detector_t *td, image_u8_t *im, zarray_t *cluster, struct quad *quad) +int fit_quad(apriltag_detector_t *td, image_u8_t *im, zarray_t *cluster, struct quad *quad, bool overrideMode) { int res = 0; @@ -9934,7 +9934,7 @@ int fit_quad(apriltag_detector_t *td, image_u8_t *im, zarray_t *cluster, struct } // Ensure that the black border is inside the white border. - if (dot < 0) + if ((!overrideMode) && (dot < 0)) return 0; // we now sort the points according to theta. This is a prepatory @@ -10552,7 +10552,7 @@ image_u8_t *threshold(apriltag_detector_t *td, image_u8_t *im) return threshim; } -zarray_t *apriltag_quad_thresh(apriltag_detector_t *td, image_u8_t *im) +zarray_t *apriltag_quad_thresh(apriltag_detector_t *td, image_u8_t *im, bool overrideMode) { //////////////////////////////////////////////////////// // step 1. threshold the image, creating the edge image. @@ -10720,7 +10720,7 @@ zarray_t *apriltag_quad_thresh(apriltag_detector_t *td, image_u8_t *im) struct quad quad; memset(&quad, 0, sizeof(struct quad)); - if (fit_quad(td, im, cluster, &quad)) { + if (fit_quad(td, im, cluster, &quad, overrideMode)) { zarray_add_fail_ok(quads, &quad); } @@ -11581,7 +11581,7 @@ zarray_t *apriltag_detector_detect(apriltag_detector_t *td, image_u8_t *im_orig) // and blurring parameters. // zarray_t *quads = apriltag_quad_gradient(td, im_orig); - zarray_t *quads = apriltag_quad_thresh(td, im_orig); + zarray_t *quads = apriltag_quad_thresh(td, im_orig, false); zarray_t *detections = zarray_create(sizeof(apriltag_detection_t*)); @@ -11956,4 +11956,228 @@ void imlib_find_apriltags(list_t *out, image_t *ptr, rectangle_t *roi, apriltag_ fb_free(); // umm_init_x(); } +void imlib_find_rects(list_t *out, image_t *ptr, rectangle_t *roi, + uint32_t threshold) +{ + // Frame Buffer Memory Usage... + // -> GRAYSCALE Input Image = w*h*1 + // -> GRAYSCALE Threhsolded Image = w*h*1 + // -> UnionFind = w*h*4 (+w*h*2 for hash table) + size_t resolution = roi->w * roi->h; + size_t fb_alloc_need = resolution * (1 + 1 + 4 + 2); // read above... + umm_init_x(((fb_avail() - fb_alloc_need) / resolution) * resolution); + apriltag_detector_t *td = apriltag_detector_create(); + + uint8_t *grayscale_image = fb_alloc(roi->w * roi->h); + + image_u8_t im; + im.width = roi->w; + im.height = roi->h; + im.stride = roi->w; + im.buf = grayscale_image; + + switch(ptr->bpp) { + case IMAGE_BPP_BINARY: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + *(grayscale_image++) = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + } + } + break; + } + case IMAGE_BPP_GRAYSCALE: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + *(grayscale_image++) = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + } + } + break; + } + case IMAGE_BPP_RGB565: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + *(grayscale_image++) = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + } + } + break; + } + default: { + memset(grayscale_image, 0, roi->w * roi->h); + break; + } + } + + /////////////////////////////////////////////////////////// + // Detect quads according to requested image decimation + // and blurring parameters. + +// zarray_t *detections = apriltag_quad_gradient(td, &im, true); + zarray_t *detections = apriltag_quad_thresh(td, &im, true); + + td->nquads = zarray_size(detections); + + //////////////////////////////////////////////////////////////// + // Decode tags from each quad. + if (1) { + for (int i = 0; i < zarray_size(detections); i++) { + struct quad *quad_original; + zarray_get_volatile(detections, i, &quad_original); + + // refine edges is not dependent upon the tag family, thus + // apply this optimization BEFORE the other work. + //if (td->quad_decimate > 1 && td->refine_edges) { + if (td->refine_edges) { + refine_edges(td, &im, quad_original); + } + + // make sure the homographies are computed... + if (quad_update_homographies(quad_original)) + continue; + } + } + + //////////////////////////////////////////////////////////////// + // Reconcile detections--- don't report the same tag more + // than once. (Allow non-overlapping duplicate detections.) + if (1) { + zarray_t *poly0 = g2d_polygon_create_zeros(4); + zarray_t *poly1 = g2d_polygon_create_zeros(4); + + for (int i0 = 0; i0 < zarray_size(detections); i0++) { + + struct quad *det0; + zarray_get_volatile(detections, i0, &det0); + + for (int k = 0; k < 4; k++) + zarray_set(poly0, k, det0->p[k], NULL); + + for (int i1 = i0+1; i1 < zarray_size(detections); i1++) { + + struct quad *det1; + zarray_get_volatile(detections, i1, &det1); + + for (int k = 0; k < 4; k++) + zarray_set(poly1, k, det1->p[k], NULL); + + if (g2d_polygon_overlaps_polygon(poly0, poly1)) { + // the tags overlap. Delete one, keep the other. + + int pref = 0; // 0 means undecided which one we'll keep. + + // if we STILL don't prefer one detection over the other, then pick + // any deterministic criterion. + for (int i = 0; i < 4; i++) { + pref = prefer_smaller(pref, det0->p[i][0], det1->p[i][0]); + pref = prefer_smaller(pref, det0->p[i][1], det1->p[i][1]); + } + + if (pref == 0) { + // at this point, we should only be undecided if the tag detections + // are *exactly* the same. How would that happen? + printf("uh oh, no preference for overlappingdetection\n"); + } + + if (pref < 0) { + // keep det0, destroy det1 + matd_destroy(det1->H); + matd_destroy(det1->Hinv); + zarray_remove_index(detections, i1, 1); + i1--; // retry the same index + goto retry1; + } else { + // keep det1, destroy det0 + matd_destroy(det0->H); + matd_destroy(det0->Hinv); + zarray_remove_index(detections, i0, 1); + i0--; // retry the same index. + goto retry0; + } + } + + retry1: ; + } + + retry0: ; + } + + zarray_destroy(poly0); + zarray_destroy(poly1); + } + + list_init(out, sizeof(find_rects_list_lnk_data_t)); + + const int r_diag_len = fast_roundf(fast_sqrtf((roi->w * roi->w) + (roi->h * roi->h))) * 2; + int *theta_buffer = fb_alloc(sizeof(int) * r_diag_len); + uint32_t *mag_buffer = fb_alloc(sizeof(uint32_t) * r_diag_len); + point_t *point_buffer = fb_alloc(sizeof(point_t) * r_diag_len); + + for (int i = 0, j = zarray_size(detections); i < j; i++) { + struct quad *det; + zarray_get_volatile(detections, i, &det); + + line_t lines[4]; + lines[0].x1 = fast_roundf(det->p[0][0]) + roi->x; lines[0].y1 = fast_roundf(det->p[0][1]) + roi->y; + lines[0].x2 = fast_roundf(det->p[1][0]) + roi->x; lines[0].y2 = fast_roundf(det->p[1][1]) + roi->y; + lines[1].x1 = fast_roundf(det->p[1][0]) + roi->x; lines[1].y1 = fast_roundf(det->p[1][1]) + roi->y; + lines[1].x2 = fast_roundf(det->p[2][0]) + roi->x; lines[1].y2 = fast_roundf(det->p[2][1]) + roi->y; + lines[2].x1 = fast_roundf(det->p[2][0]) + roi->x; lines[2].y1 = fast_roundf(det->p[2][1]) + roi->y; + lines[2].x2 = fast_roundf(det->p[3][0]) + roi->x; lines[2].y2 = fast_roundf(det->p[3][1]) + roi->y; + lines[3].x1 = fast_roundf(det->p[3][0]) + roi->x; lines[3].y1 = fast_roundf(det->p[3][1]) + roi->y; + lines[3].x2 = fast_roundf(det->p[0][0]) + roi->x; lines[3].y2 = fast_roundf(det->p[0][1]) + roi->y; + + uint32_t magnitude = 0; + + for (int i = 0; i < 4; i++) { + if(!lb_clip_line(&lines[i], 0, 0, ptr->w, ptr->h)) { + continue; + } + + size_t index = trace_line(ptr, &lines[i], theta_buffer, mag_buffer, point_buffer); + + for (int j = 0; j < index; j++) { + magnitude += mag_buffer[j]; + } + } + + if (magnitude < threshold) { + continue; + } + + find_rects_list_lnk_data_t lnk_data; + rectangle_init(&(lnk_data.rect), fast_roundf(det->p[0][0]) + roi->x, fast_roundf(det->p[0][1]) + roi->y, 0, 0); + + for (size_t k = 1, l = (sizeof(det->p) / sizeof(det->p[0])); k < l; k++) { + rectangle_t temp; + rectangle_init(&temp, fast_roundf(det->p[k][0]) + roi->x, fast_roundf(det->p[k][1]) + roi->y, 0, 0); + rectangle_united(&(lnk_data.rect), &temp); + } + + // Add corners... + lnk_data.corners[0].x = fast_roundf(det->p[3][0]) + roi->x; // top-left + lnk_data.corners[0].y = fast_roundf(det->p[3][1]) + roi->y; // top-left + lnk_data.corners[1].x = fast_roundf(det->p[2][0]) + roi->x; // top-right + lnk_data.corners[1].y = fast_roundf(det->p[2][1]) + roi->y; // top-right + lnk_data.corners[2].x = fast_roundf(det->p[1][0]) + roi->x; // bottom-right + lnk_data.corners[2].y = fast_roundf(det->p[1][1]) + roi->y; // bottom-right + lnk_data.corners[3].x = fast_roundf(det->p[0][0]) + roi->x; // bottom-left + lnk_data.corners[3].y = fast_roundf(det->p[0][1]) + roi->y; // bottom-left + + lnk_data.magnitude = magnitude; + + list_push_back(out, &lnk_data); + } + + fb_free(); // point_buffer + fb_free(); // mag_buffer + fb_free(); // theta_buffer + + zarray_destroy(detections); + fb_free(); // grayscale_image; + apriltag_detector_destroy(td); + fb_free(); // umm_init_x(); +} + #pragma GCC diagnostic pop diff --git a/src/omv/img/hough.c b/src/omv/img/hough.c index 7d941ebd6..d559f4437 100644 --- a/src/omv/img/hough.c +++ b/src/omv/img/hough.c @@ -342,7 +342,7 @@ void imlib_find_lines(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int } } -static void pixel_magnitude(image_t *ptr, int x, int y, int *theta, uint32_t *mag) +void pixel_magnitude(image_t *ptr, int x, int y, int *theta, uint32_t *mag) { switch (ptr->bpp) { case IMAGE_BPP_BINARY: { @@ -518,7 +518,7 @@ static void pixel_magnitude(image_t *ptr, int x, int y, int *theta, uint32_t *ma // http://www.brackeen.com/vga/source/djgpp20/lines.c.html // http://www.brackeen.com/vga/source/bc31/lines.c.html -static size_t trace_line(image_t *ptr, line_t *l, int *theta_buffer, uint32_t *mag_buffer, point_t *point_buffer) +size_t trace_line(image_t *ptr, line_t *l, int *theta_buffer, uint32_t *mag_buffer, point_t *point_buffer) { int dx = l->x2 - l->x1; // the horizontal distance of the line int dy = l->y2 - l->y1; // the vertical distance of the line diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index cb8b22b69..40d5ac20d 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -898,10 +898,15 @@ typedef struct find_lines_list_lnk_data { typedef struct find_circles_list_lnk_data { point_t p; - int r; - uint32_t magnitude; + uint32_t r, magnitude; } find_circles_list_lnk_data_t; +typedef struct find_rects_list_lnk_data { + point_t corners[4]; + rectangle_t rect; + uint32_t magnitude; +} find_rects_list_lnk_data_t; + typedef struct find_qrcodes_list_lnk_data { point_t corners[4]; rectangle_t rect; @@ -1149,6 +1154,8 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int bool (*threshold_cb)(void*,find_blobs_list_lnk_data_t*), void *threshold_cb_arg, bool (*merge_cb)(void*,find_blobs_list_lnk_data_t*,find_blobs_list_lnk_data_t*), void *merge_cb_arg); // Shape Detection +void pixel_magnitude(image_t *ptr, int x, int y, int *theta, uint32_t *mag); // helper/internal +size_t trace_line(image_t *ptr, line_t *l, int *theta_buffer, uint32_t *mag_buffer, point_t *point_buffer); // helper/internal void imlib_find_lines(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); void imlib_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, @@ -1156,6 +1163,8 @@ void imlib_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsig 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); +void imlib_find_rects(list_t *out, image_t *ptr, rectangle_t *roi, + uint32_t threshold); // 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 9e06a01a4..5f49a893e 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -2602,6 +2602,132 @@ static mp_obj_t py_image_find_circles(uint n_args, const mp_obj_t *args, mp_map_ } #endif // OMV_ENABLE_FIND_CIRCLES +#ifdef OMV_ENABLE_FIND_RECTS +// Rect Object // +#define py_rect_obj_size 5 +typedef struct py_rect_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t x, y, w, h, magnitude; +} py_rect_obj_t; + +static void py_rect_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_rect_obj_t *self = self_in; + mp_printf(print, + "{x:%d, y:%d, w:%d, h:%d, magnitude:%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_get_int(self->magnitude)); +} + +static mp_obj_t py_rect_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_rect_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_rect_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_rect_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->magnitude; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_rect_corners(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->corners; } +mp_obj_t py_rect_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_rect_obj_t *) self_in)->x, + ((py_rect_obj_t *) self_in)->y, + ((py_rect_obj_t *) self_in)->w, + ((py_rect_obj_t *) self_in)->h}); +} + +mp_obj_t py_rect_x(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->x; } +mp_obj_t py_rect_y(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->y; } +mp_obj_t py_rect_w(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->w; } +mp_obj_t py_rect_h(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->h; } +mp_obj_t py_rect_magnitude(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->magnitude; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_corners_obj, py_rect_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_rect_obj, py_rect_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_x_obj, py_rect_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_y_obj, py_rect_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_w_obj, py_rect_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_h_obj, py_rect_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_magnitude_obj, py_rect_magnitude); + +STATIC const mp_rom_map_elem_t py_rect_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_rect_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_rect_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_rect_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_rect_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_rect_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_rect_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_magnitude), MP_ROM_PTR(&py_rect_magnitude_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(py_rect_locals_dict, py_rect_locals_dict_table); + +static const mp_obj_type_t py_rect_type = { + { &mp_type_type }, + .name = MP_QSTR_rect, + .print = py_rect_print, + .subscr = py_rect_subscr, + .locals_dict = (mp_obj_t) &py_rect_locals_dict, +}; + +static mp_obj_t py_image_find_rects(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); + + list_t out; + fb_alloc_mark(); + imlib_find_rects(&out, arg_img, &roi, py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 1000)); + 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_rects_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_rect_obj_t *o = m_new_obj(py_rect_obj_t); + o->base.type = &py_rect_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[0].x), mp_obj_new_int(lnk_data.corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[1].x), mp_obj_new_int(lnk_data.corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[2].x), mp_obj_new_int(lnk_data.corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[3].x), mp_obj_new_int(lnk_data.corners[3].y)})}); + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->magnitude = mp_obj_new_int(lnk_data.magnitude); + + objects_list->items[i] = o; + } + + return objects_list; +} +#endif + // QRCode Object // #define py_qrcode_obj_size 10 typedef struct py_qrcode_obj { @@ -3708,6 +3834,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_line_segments_obj, 1, py_image_f #ifdef OMV_ENABLE_FIND_CIRCLES STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_circles_obj, 1, py_image_find_circles); #endif +#ifdef OMV_ENABLE_FIND_RECTS +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_rects_obj, 1, py_image_find_rects); +#endif /* Code Detection */ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_qrcodes_obj, 1, py_image_find_qrcodes); #ifdef OMV_ENABLE_APRILTAGS @@ -3808,6 +3937,9 @@ static const mp_map_elem_t locals_dict_table[] = { {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 +#ifdef OMV_ENABLE_FIND_RECTS + {MP_OBJ_NEW_QSTR(MP_QSTR_find_rects), (mp_obj_t)&py_image_find_rects_obj}, #endif /* Code Detection */ {MP_OBJ_NEW_QSTR(MP_QSTR_find_qrcodes), (mp_obj_t)&py_image_find_qrcodes_obj}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 042c2a37f..3886977c1 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -499,12 +499,26 @@ Q(circle) Q(r) // duplicate Q(magnitude) +// Find Rects +Q(find_rects) +// duplicate Q(roi) +// duplicate Q(threshold) +// Rect Object +// duplicate Q(rect) +Q(corners) +// duplicate Q(rect) +// duplicate Q(x) +// duplicate Q(y) +// duplicate Q(w) +// duplicate Q(h) +// duplicate Q(magnitude) + // Find QRCodes Q(find_qrcodes) // duplicate Q(roi) // QRCode Object Q(qrcode) -Q(corners) +// duplicate Q(corners) // duplicate Q(rect) // duplicate Q(x) // duplicate Q(y) diff --git a/usr/examples/09-Feature-Detection/find_rects.py b/usr/examples/09-Feature-Detection/find_rects.py new file mode 100644 index 000000000..5fafba626 --- /dev/null +++ b/usr/examples/09-Feature-Detection/find_rects.py @@ -0,0 +1,31 @@ +# Find Rects Example +# +# This example shows off how to find rectangles in the image using the quad threshold +# detection code from our April Tags code. The quad threshold detection algorithm +# detects rectangles in an extremely robust way and is much better than Hough +# Transform based methods. For example, it can still detect rectangles even when lens +# distortion causes those rectangles to look bent. Rounded rectangles are no problem! +# (But, given this the code will also detect small radius circles too)... + +import sensor, image, time + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) # grayscale is faster (160x120 max on OpenMV-M7) +sensor.set_framesize(sensor.QQVGA) +sensor.skip_frames(time = 2000) +clock = time.clock() + +while(True): + clock.tick() + img = sensor.snapshot() + + # `threshold` below should be set to a high enough value to filter out noise + # rectangles detected in the image which have low edge magnitudes. Rectangles + # have larger edge magnitudes the larger and more contrasty they are... + + for r in img.find_rects(threshold = 10000): + img.draw_rectangle(r.rect(), color = (255, 0, 0)) + for p in r.corners(): img.draw_circle(p[0], p[1], 5, color = (0, 255, 0)) + print(r) + + print("FPS %f" % clock.fps())