diff --git a/src/omv/img/blob.c b/src/omv/img/blob.c index 602c80453..a059e3293 100644 --- a/src/omv/img/blob.c +++ b/src/omv/img/blob.c @@ -3,136 +3,355 @@ * Copyright (c) 2013/2014 Ibrahim Abdelkader * This work is licensed under the MIT license, see the file LICENSE for details. * - * Blob count. + * Blob and color code/marker detection code... * */ -#include "xalloc.h" +#include +#include "mdefs.h" +#include "fb_alloc.h" #include "imlib.h" -#include -blob_t *blob_alloc(int x, int y, int w, int h, int id, int c) +ALWAYS_INLINE static uint8_t *init_mask(rectangle_t *roi) { - blob_t *blob = xalloc(sizeof(*blob)); - blob->x = x; - blob->y = y; - blob->w = w; - blob->h = h; - blob->id = id; - blob->c = c; - return blob; + return fb_alloc0(((roi->w+7)/8)*roi->h); } -void blob_add_point(blob_t *blob, int px, int py) +ALWAYS_INLINE static void deinit_mask() { - /* expand blob */ - if (px < blob->x) { - blob->x = px; - } + fb_free(); +} - if (py < blob->y) { - blob->y = py; - } +ALWAYS_INLINE static void set_mask_pixel(rectangle_t *roi, uint8_t *mask, int x, int y) +{ + mask[(((roi->w+7)/8)*y)+(x/8)] |= (1 << (x%8)); +} - if (px > blob->w) { - blob->w = px; - } +ALWAYS_INLINE static bool get_not_mask_pixel(rectangle_t *roi, uint8_t *mask, int x, int y) +{ + return !((mask[(((roi->w+7)/8)*y)+(x/8)] >> (x%8)) & 1); +} - if (py > blob->h) { - blob->h = py; +typedef struct stack_queue { + int head_p, tail_p, size; + point_t *data_p; +} stack_queue_t; + +ALWAYS_INLINE static stack_queue_t *init_stack_queue(rectangle_t *roi) +{ + stack_queue_t *sq = fb_alloc(sizeof(stack_queue_t)); + sq->head_p = 0; + sq->tail_p = 0; + // The size here is the perimeter in pixels around the roi. It's the perimeter + // around the roi vs the roi perimeter so that we can't run out of space while + // executing the wildfire algorithm for new points. Additionally, this also + // takes care of the pointer comparison issue since it will never get full. + sq->size = (((roi->w+2)*2)-2)+(((roi->h+2)*2)-2); + sq->data_p = fb_alloc(sq->size*sizeof(point_t)); + return sq; +} + +ALWAYS_INLINE static void deinit_stack_queue() +{ + fb_free(); + fb_free(); +} + +ALWAYS_INLINE static void stack_queue_push(stack_queue_t *sq, int x, int y) +{ + sq->data_p[sq->head_p] = (point_t) {.x = x, .y = y}; + sq->head_p = (sq->head_p + 1) % sq->size; +} + +ALWAYS_INLINE static point_t stack_queue_pop(stack_queue_t *sq) +{ + point_t p = sq->data_p[sq->tail_p]; + sq->tail_p = (sq->tail_p + 1) % sq->size; + return p; +} + +ALWAYS_INLINE static bool stack_queue_not_empty(stack_queue_t *sq) +{ + return sq->head_p != sq->tail_p; +} + +ALWAYS_INLINE static bool threshold_gs(image_t *img, int x, int y, simple_color_t l_thresholds, simple_color_t h_thresholds, bool invert) +{ + int pixel = IM_GET_GS_PIXEL(img, x, y); + return invert ^ + ((l_thresholds.G <= pixel) && + (pixel <= h_thresholds.G)); +} + +ALWAYS_INLINE static bool threshold_rgb565(image_t *img, int x, int y, simple_color_t l_thresholds, simple_color_t h_thresholds, bool invert) +{ + int pixel = IM_GET_RGB565_PIXEL(img, x, y); + const int lab_l = IM_RGB5652L(pixel); + const int lab_a = IM_RGB5652A(pixel); + const int lab_b = IM_RGB5652B(pixel); + return invert ^ + ((l_thresholds.L <= lab_l) && + (lab_l <= h_thresholds.L) && + (l_thresholds.A <= lab_a) && + (lab_a <= h_thresholds.A) && + (l_thresholds.B <= lab_b) && + (lab_b <= h_thresholds.B)); +} + +ALWAYS_INLINE static bool threshold(image_t *img, int x, int y, simple_color_t l_thresholds, simple_color_t h_thresholds, bool invert) +{ + if (IM_IS_GS(img)) { + return threshold_gs(img, x, y, l_thresholds, h_thresholds, invert); + } else { + return threshold_rgb565(img, x, y, l_thresholds, h_thresholds, invert); } } -array_t *imlib_count_blobs(struct image *image) +mp_obj_t imlib_find_blobs(mp_obj_t img_obj, image_t *img, int num_thresholds, simple_color_t *l_thresholds, simple_color_t *h_thresholds, bool invert, rectangle_t *r, mp_obj_t filtering_fn) { - array_t *blobs; - blob_t *blob; + // We're using a modified wildfire algorithm below where instead of using a + // the stack we use a queue along with a burn mask to filter out already + // visited pixels. For each color blob in the image, where a color blob is + // an area of connected pixels that all are within a threshold, the algorithm + // computes the bounding box around all those pixels, number of pixels in the + // blob, centroid, and blob orientation. The algorithm then returns a list + // of blobs for each set of thresholds passed in. That is, this function + // returns a tuple of lists of blobs. - array_alloc(&blobs, xfree); - uint8_t *pixels = (uint8_t*) image->pixels; + rectangle_t rect; + if (!rectangle_subimg(img, r, &rect)) { + return mp_const_none; + } - // points array - int p_size = 100; - point_t *points = xalloc(p_size*sizeof*points); + mp_obj_t blob_lists[num_thresholds]; - for (int y=0; yh; y++) { - for (int x=0; xw; x++) { - blob = NULL; - uint8_t label = pixels[y*image->w+x]; + uint8_t *mask = init_mask(&rect); + stack_queue_t *sq = init_stack_queue(&rect); - if (label) { - int w,e; - int p_idx =0; - int p_max = 1; - - // set initial point - points[0].x=x; - points[0].y=y; - - // alloc new blob - blob = blob_alloc(image->w, image->h, 0, 0, label, 0); - while(p_idxx, p->y); - // scan west - for (w=p->x-1; w>=0 && pixels[p->y*image->w+w]==label; w--) { - blob_add_point(blob, w, p->y); - } - - // scan east - for (e=p->x+1; ew && pixels[p->y*image->w+e]==label; e++) { - blob_add_point(blob, e, p->y); - } - - // scan north and south rows, add a point only if it's the last - // point or the last connected point in a row, this saves some memory - // and other points on this segment will still be reachable from this one. - - // add points on north row - for (int i=w+1; iy*image->w+i]=0; - if ((p->y-1) > 0 && pixels[(p->y-1)*image->w+i]==label) { - if (i==(e-1) || (pixels[(p->y-1)*image->w+i+1]!=label)) { - points[p_max].x = i; - points[p_max].y = p->y-1; - - if (++p_max == p_size) { - p_size +=100; - points = xrealloc(points, p_size*sizeof*points); + for (int n = 0; n < num_thresholds; n++) { + blob_lists[n] = mp_obj_new_list(4, NULL); // 4 is just the intial list size guess + mp_obj_list_set_len(blob_lists[n], 0); + for (int i = 0; i < rect.h; i++) { + for (int j = 0; j < rect.w; j++) { + int x = (rect.x + j); // in img + int y = (rect.y + i); // in img + if (get_not_mask_pixel(&rect, mask, j, i) // in roi + && threshold(img, x, y, l_thresholds[n], h_thresholds[n], invert)) { // in img + int blob_x1 = x; + int blob_y1 = y; + int blob_x2 = x; + int blob_y2 = y; + int blob_pixels = 1; + int blob_cx = x; + int blob_cy = y; + int blob_a = x*x; // equal to (x-mx)^2 + int blob_b = x*y; // equal to (x-mx)*(y-my) + int blob_c = y*y; // equal to (y-my)^2 + set_mask_pixel(&rect, mask, j, i); // in roi + stack_queue_push(sq, x, y); // in img + do { + point_t p = stack_queue_pop(sq); + for (int a = -1; a <= 1; a++) { + for (int b = -1; b <= 1; b++) { + int c = (p.x + b); // in img + int d = (p.y + a); // in img + int e = (c - rect.x); // in roi + int f = (d - rect.y); // in roi + if (IM_X_INSIDE(&rect, e) // in roi + && IM_Y_INSIDE(&rect, f) // in roi + && get_not_mask_pixel(&rect, mask, e, f) // in roi + && threshold(img, c, d, l_thresholds[n], h_thresholds[n], invert)) { // in img + blob_x1 = IM_MIN(blob_x1, c); + blob_y1 = IM_MIN(blob_y1, d); + blob_x2 = IM_MAX(blob_x2, c); + blob_y2 = IM_MAX(blob_y2, d); + blob_pixels += 1; + blob_cx += c; + blob_cy += d; + blob_a += c*c; + blob_b += c*d; + blob_c += d*d; + set_mask_pixel(&rect, mask, e, f); // in roi + stack_queue_push(sq, c, d); // in img } } } - } - - // add points on south row - for (int i=w+1; iy+1) < image->h && pixels[(p->y+1)*image->w+i]==label) { - if (i==(e-1) || (pixels[(p->y+1)*image->w+i+1]!=label)) { - points[p_max].x = i; - points[p_max].y = p->y+1; - - if (++p_max == p_size) { - p_size +=100; - points = xrealloc(points, p_size*sizeof*points); - } - } + } while(stack_queue_not_empty(sq)); + int mx = (blob_cx/blob_pixels); // x centroid + int my = (blob_cy/blob_pixels); // y centroid + // The below equations were derived by translating the orientation + // calculation from a double pass algorithm to single pass. + blob_a -= (mx*blob_cx)+(mx*blob_cx); + blob_a += blob_pixels*mx*mx; + blob_b -= (mx*blob_cy)+(my*blob_cx); + blob_b += blob_pixels*mx*my; + blob_c -= (my*blob_cy)+(my*blob_cy); + blob_c += blob_pixels*my*my; + // Compute the final blob orientation from a, b, and c sums. + float o = ((blob_a!=blob_c)?fast_atan2f(blob_b,blob_a-blob_c):0.0)/2.0; + mp_obj_t blob_tuple[10]; + blob_tuple[0] = mp_obj_new_int(blob_x1); + blob_tuple[1] = mp_obj_new_int(blob_y1); + blob_tuple[2] = mp_obj_new_int(blob_x2-blob_x1+1); + blob_tuple[3] = mp_obj_new_int(blob_y2-blob_y1+1); + blob_tuple[4] = mp_obj_new_int(blob_pixels); + blob_tuple[5] = mp_obj_new_int(mx); + blob_tuple[6] = mp_obj_new_int(my); + blob_tuple[7] = mp_obj_new_float(o); + blob_tuple[8] = mp_obj_new_int(1<w = blob->w - blob->x; - blob->h = blob->h - blob->y; - // discard small blobs - if (blob->w > 10 && blob->h > 10) { - array_push_back(blobs, blob); } else { - xfree(blob); + if (blob_pixels >= ((img->w*img->h)/1000)) { + mp_obj_list_append(blob_lists[n], blob_tuple_obj); + } else { + mp_obj_tuple_del(blob_tuple_obj); + } } } } } } - xfree(points); - return blobs; + + deinit_stack_queue(); + deinit_mask(); + + return mp_obj_new_tuple(num_thresholds, blob_lists); +} + +mp_obj_t imlib_find_markers(mp_obj_t img_obj, mp_obj_t blob_lists_obj, int margin, mp_obj_t filtering_fn) +{ + // After you have a list of blobs this function will merge blobs from the + // different colors lists that intersect into one blob. The new merged big + // blob will have a bounding box that surronds all the merged blobs, pixels will + // include all the blobs, and centroids/orientations are averaged. Additionally, + // the new blob will have an extra code value with a bit set for each color + // that was merged into the blob along with the number of blobs merged. The + // color code provides a nice and easy user controllable way to get an idea + // of what colors are in a merged blob. + + mp_uint_t blob_l_len; + mp_obj_t *blob_l; + mp_obj_get_array(blob_lists_obj, &blob_l_len, &blob_l); + if (!blob_l_len) return mp_const_none; + + mp_uint_t blob_lists_len[blob_l_len]; + mp_obj_t *blob_lists[blob_l_len]; + + rectangle_t rect; // reusing mask from above - so we need a fake rect obj. + rect.x = 0; + rect.y = 0; + rect.w = 0; + rect.h = blob_l_len; + + for (mp_uint_t i = 0; i < blob_l_len; i++) { + mp_obj_get_array(blob_l[i], &blob_lists_len[i], &blob_lists[i]); + rect.w = IM_MAX(rect.w, blob_lists_len[i]); // find longest list + } + if (!rect.w) return mp_const_none; + + uint8_t *mask = init_mask(&rect); + + mp_obj_t out = mp_obj_new_list(4, NULL); // 4 is just the intial list size guess + mp_obj_list_set_len(out, 0); + for (mp_uint_t i = 0; i < blob_l_len; i++) { + for (mp_uint_t j = 0; j < blob_lists_len[i]; j++) { + if (get_not_mask_pixel(&rect, mask, j, i)) { + set_mask_pixel(&rect, mask, j, i); + + mp_obj_t *temp0; + mp_obj_get_array_fixed_n(blob_lists[i][j], 10, &temp0); + + int blob_x = mp_obj_get_int(temp0[0]); // rect x + int blob_y = mp_obj_get_int(temp0[1]); // rect y + int blob_w = mp_obj_get_int(temp0[2]); // rect w + int blob_h = mp_obj_get_int(temp0[3]); // rect h + int blob_pixels = mp_obj_get_int(temp0[4]); // pixels + int blob_cx = mp_obj_get_int(temp0[5]); // centroid x + int blob_cy = mp_obj_get_int(temp0[6]); // centroid y + float blob_rotation = mp_obj_get_float(temp0[7]); // rotation + int blob_code = mp_obj_get_int(temp0[8]); // code bit + int blob_count = mp_obj_get_int(temp0[9]); // blob count + + for (mp_uint_t a = 0; a < blob_l_len; a++) { + for (mp_uint_t b = 0; b < blob_lists_len[a]; b++) { + if (get_not_mask_pixel(&rect, mask, b, a)) { + + mp_obj_t *temp1; + mp_obj_get_array_fixed_n(blob_lists[a][b], 10, &temp1); + + rectangle_t t0; + t0.x = blob_x - margin; + t0.y = blob_y - margin; + t0.w = blob_w + (2*margin); + t0.h = blob_h + (2*margin); + + rectangle_t t1; + t1.x = mp_obj_get_int(temp1[0]) - margin; + t1.y = mp_obj_get_int(temp1[1]) - margin; + t1.w = mp_obj_get_int(temp1[2]) + (2*margin); + t1.h = mp_obj_get_int(temp1[3]) + (2*margin); + + if (rectangle_intersects(&t0, &t1)) { + set_mask_pixel(&rect, mask, b, a); + + // Compute bounding rect... + blob_x = IM_MIN(blob_x, t1.x); + blob_y = IM_MIN(blob_y, t1.y); + int x2_0 = t0.x+t0.w-1; + int x2_1 = t1.x+t1.w-1; + int x2 = IM_MAX(x2_0, x2_1); + blob_w = x2-blob_x+1; + int y2_0 = t0.y+t0.h-1; + int y2_1 = t1.y+t1.h-1; + int y2 = IM_MAX(y2_0, y2_1); + blob_h = y2-blob_y+1; + // Update tracking info... + blob_pixels += mp_obj_get_int(temp1[4]); + blob_cx += mp_obj_get_int(temp1[5]); + blob_cy += mp_obj_get_int(temp1[6]); + blob_rotation += mp_obj_get_float(temp1[7]); + blob_code |= mp_obj_get_int(temp1[8]); + blob_count += mp_obj_get_int(temp1[9]); + } + } + } + } + blob_cx /= blob_count; + blob_cy /= blob_count; + blob_rotation /= blob_count; + // Build output object. + mp_obj_t blob_tuple[10]; + blob_tuple[0] = mp_obj_new_int(blob_x); + blob_tuple[1] = mp_obj_new_int(blob_y); + blob_tuple[2] = mp_obj_new_int(blob_w); + blob_tuple[3] = mp_obj_new_int(blob_h); + blob_tuple[4] = mp_obj_new_int(blob_pixels); + blob_tuple[5] = mp_obj_new_int(blob_cx); + blob_tuple[6] = mp_obj_new_int(blob_cy); + blob_tuple[7] = mp_obj_new_float(blob_rotation); + blob_tuple[8] = mp_obj_new_int(blob_code); + blob_tuple[9] = mp_obj_new_int(blob_count); + mp_obj_t blob_tuple_obj = mp_obj_new_tuple(10, blob_tuple); + if (filtering_fn != MP_OBJ_NULL) { + if (mp_obj_is_true(mp_call_function_2(filtering_fn, img_obj, blob_tuple_obj))) { + mp_obj_list_append(out, blob_tuple_obj); + } else { + mp_obj_tuple_del(blob_tuple_obj); + } + } else { + mp_obj_list_append(out, blob_tuple_obj); + } + } + } + } + + deinit_mask(); + + return out; } diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index e33d3fa7c..3cc192177 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -13,6 +13,7 @@ #include #include "array.h" #include "fmath.h" +#include "obj.h" #define IM_SWAP16(x) __REV16(x) // Swap bottom two chars in short. #define IM_SWAP32(x) __REV32(x) // Swap bottom two shorts in long. @@ -231,15 +232,6 @@ typedef struct statistics { int8_t l_upper_q, a_upper_q, b_upper_q; } statistics_t; -typedef struct blob { - int x; - int y; - int w; - int h; - int c; - int id; -} blob_t; - typedef struct color { union { uint8_t vec[3]; @@ -466,6 +458,10 @@ void imlib_mean_filter(image_t *img, const int ksize); void imlib_mode_filter(image_t *img, const int ksize); void imlib_median_filter(image_t *img, const int ksize, const int percentile); +/* Color Tracking */ +mp_obj_t imlib_find_blobs(mp_obj_t img_obj, image_t *img, int num_thresholds, simple_color_t *l_thresholds, simple_color_t *h_thresholds, bool invert, rectangle_t *r, mp_obj_t filtering_fn); +mp_obj_t imlib_find_markers(mp_obj_t img_obj, mp_obj_t blob_lists_obj, int margin, mp_obj_t filtering_fn); + /* Clustering functions */ array_t *cluster_kmeans(array_t *points, int k); @@ -474,7 +470,6 @@ int imlib_image_mean(struct image *src); void imlib_histeq(struct image *src); void imlib_threshold(image_t *src, image_t *dst, color_t *color, int color_size, int threshold); void imlib_rainbow(image_t *src, struct image *dst); -array_t *imlib_count_blobs(struct image *image); /* Integral image functions */ void imlib_integral_image_alloc(struct integral_image *sum, int w, int h); diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index eb210c407..a20dbbffd 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -752,6 +752,72 @@ static mp_obj_t py_image_median(uint n_args, const mp_obj_t *args, mp_map_t *kw_ return mp_const_none; } +static mp_obj_t py_image_find_blobs(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"); + + mp_uint_t arg_t_len; + mp_obj_t *arg_t; + mp_obj_get_array(args[1], &arg_t_len, &arg_t); + if (!arg_t_len) return mp_const_none; + + simple_color_t l_t[arg_t_len], u_t[arg_t_len]; + if (IM_IS_GS(arg_img)) { + for (int i=0; ivalue : MP_OBJ_NULL; + + int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), 0); + return imlib_find_blobs(args[0], arg_img, arg_t_len, l_t, u_t, arg_invert ? 1 : 0, &arg_r, kw_val); +} + +static mp_obj_t py_image_find_markers(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"); + + int margin = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_margin), 2); + + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_feature_filter), MP_MAP_LOOKUP); + mp_obj_t kw_val = (kw_arg != NULL) ? kw_arg->value : MP_OBJ_NULL; + + return imlib_find_markers(args[0], args[1], margin, kw_val); +} + static mp_obj_t py_image_scale(mp_obj_t image_obj, mp_obj_t size_obj) { int w,h; @@ -986,30 +1052,6 @@ static mp_obj_t py_image_compress(mp_obj_t image_obj, mp_obj_t quality) return py_image_from_struct(&cimage); } -static mp_obj_t py_image_find_blobs(mp_obj_t image_obj) -{ - // Get image pointer - image_t *image = py_image_cobj(image_obj); - - // Run blob detector - array_t *blobs = imlib_count_blobs(image); - - // Add detected blobs to a new Python list - mp_obj_t objects_list = mp_obj_new_list(0, NULL); - if (array_length(blobs)) { - for (int j=0; jx), mp_obj_new_int(r->y), mp_obj_new_int(r->w), - mp_obj_new_int(r->h), mp_obj_new_int(r->c), mp_obj_new_int(r->id) - }; - mp_obj_list_append(objects_list, mp_obj_new_tuple(6, blob)); - } - } - array_free(blobs); - return objects_list; -} - static mp_obj_t py_image_find_features(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { rectangle_t roi; @@ -1232,6 +1274,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_midpoint_obj, 2, py_image_midpoint); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_mean_obj, py_image_mean); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_mode_obj, py_image_mode); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_median_obj, 2, py_image_median); +/* Color Tracking */ +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_blobs_obj, 2, py_image_find_blobs); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_markers_obj, 2, py_image_find_markers); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_scale_obj, py_image_scale); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_scaled_obj, py_image_scaled); @@ -1243,7 +1288,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_image_threshold_obj, py_image_threshold); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_rainbow_obj, py_image_rainbow); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_compress_obj, py_image_compress); -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_find_blobs_obj, py_image_find_blobs); STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_image_find_template_obj, py_image_find_template); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_features_obj, 2, py_image_find_features); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_keypoints_obj, 1, py_image_find_keypoints); @@ -1294,6 +1338,9 @@ static const mp_map_elem_t locals_dict_table[] = { {MP_OBJ_NEW_QSTR(MP_QSTR_mean), (mp_obj_t)&py_image_mean_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_mode), (mp_obj_t)&py_image_mode_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_median), (mp_obj_t)&py_image_median_obj}, + /* Color Tracking */ + {MP_OBJ_NEW_QSTR(MP_QSTR_find_blobs), (mp_obj_t)&py_image_find_blobs_obj}, + {MP_OBJ_NEW_QSTR(MP_QSTR_find_markers), (mp_obj_t)&py_image_find_markers_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_scale), (mp_obj_t)&py_image_scale_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_scaled), (mp_obj_t)&py_image_scaled_obj}, @@ -1305,7 +1352,6 @@ static const mp_map_elem_t locals_dict_table[] = { {MP_OBJ_NEW_QSTR(MP_QSTR_rainbow), (mp_obj_t)&py_image_rainbow_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_compress), (mp_obj_t)&py_image_compress_obj}, /* objects/feature detection */ - {MP_OBJ_NEW_QSTR(MP_QSTR_find_blobs), (mp_obj_t)&py_image_find_blobs_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_find_template), (mp_obj_t)&py_image_find_template_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_find_features), (mp_obj_t)&py_image_find_features_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_find_keypoints), (mp_obj_t)&py_image_find_keypoints_obj}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index f10e0bb3c..482b67ac1 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -58,6 +58,8 @@ Q(midpoint) Q(mean) Q(mode) Q(median) +Q(find_blobs) +Q(find_markers) Q(kp_desc) Q(lbp_desc) Q(Cascade) @@ -69,7 +71,6 @@ Q(compress) Q(rainbow) Q(histeq) Q(threshold) -Q(find_blobs) Q(find_template) Q(find_features) Q(find_keypoints) @@ -82,6 +83,8 @@ Q(mul) Q(add) Q(bias) Q(percentile) +Q(feature_filter) +Q(margin) // Lcd Module Q(lcd) diff --git a/usr/examples/10-Color-Tracking/blob_detection.py b/usr/examples/10-Color-Tracking/blob_detection.py index c948caae7..867782c53 100644 --- a/usr/examples/10-Color-Tracking/blob_detection.py +++ b/usr/examples/10-Color-Tracking/blob_detection.py @@ -1,45 +1,31 @@ import sensor, time, pyb led_r = pyb.LED(1) -led_g = pyb.LED(2) -led_b = pyb.LED(3) sensor.reset() -sensor.set_contrast(2) -sensor.set_framesize(sensor.QCIF) +sensor.set_framesize(sensor.QVGA) sensor.set_pixformat(sensor.RGB565) +# Finds a red blob. +COLOR1 = ( 50, 55, 73, 82, 47, 63) +# Select an aera of the image and click copy color to get +# new color tracking parameters for something in the image. + clock = time.clock() while (True): clock.tick() # Take snapshot image = sensor.snapshot() - # Threshold image with RGB - binary = image.threshold([(255, 0, 0), - (0, 255, 0), - (0, 0, 255)], 80) - - # Image closing - binary.dilate(3) - binary.erode(3) - # Detect blobs in image - blobs = binary.find_blobs() + blob_l = image.find_blobs([COLOR1]) led_r.off() - led_g.off() - led_b.off() # Draw rectangles around detected blobs - for r in blobs: - if r[5]==1: - led_r.on() - if r[5]==2: - led_g.on() - if r[5]==3: - led_b.on() - image.draw_rectangle(r[0:4]) - time.sleep(50) - + for blobs in blob_l: + for r in blobs: + if r[8]==1: + led_r.on() + image.draw_rectangle(r[0:4]) print(clock.fps())