From 3a8ac88f66eaf2b76547f3fb0434afd083b3928a Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sat, 26 Jan 2019 14:46:15 -0800 Subject: [PATCH] Update blob code and examples. --- .../multi_color_blob_tracking.py | 10 +- .../single_color_code_tracking.py | 10 +- .../single_color_grayscale_blob_tracking.py | 14 +- .../single_color_rgb565_blob_tracking.py | 10 +- src/omv/boards/OPENMV2/imlib_config.h | 19 + src/omv/boards/OPENMV3/imlib_config.h | 19 + src/omv/boards/OPENMV4/imlib_config.h | 19 + src/omv/img/blob.c | 906 +++++++++++++----- src/omv/img/imlib.c | 61 ++ src/omv/img/imlib.h | 17 +- src/omv/img/pool.c | 4 + src/omv/py/py_image.c | 582 ++++++++++- src/omv/py/qstrdefsomv.h | 30 +- 13 files changed, 1428 insertions(+), 273 deletions(-) diff --git a/scripts/examples/10-Color-Tracking/multi_color_blob_tracking.py b/scripts/examples/10-Color-Tracking/multi_color_blob_tracking.py index edd9dcfe9..bc94d257d 100644 --- a/scripts/examples/10-Color-Tracking/multi_color_blob_tracking.py +++ b/scripts/examples/10-Color-Tracking/multi_color_blob_tracking.py @@ -2,7 +2,7 @@ # # This example shows off multi color blob tracking using the OpenMV Cam. -import sensor, image, time +import sensor, image, time, math # Color Tracking Thresholds (L Min, L Max, A Min, A Max, B Min, B Max) # The below thresholds track in general red/green things. You may wish to tune them... @@ -28,6 +28,14 @@ while(True): clock.tick() img = sensor.snapshot() for blob in img.find_blobs(thresholds, pixels_threshold=200, area_threshold=200): + # These values depend on the blob not being circular - otherwise they will be shaky. + if blob.elongation() > 0.5: + img.draw_edges(blob.min_corners(), color=(255,0,0)) + img.draw_line(blob.major_axis_line(), color=(0,255,0)) + img.draw_line(blob.minor_axis_line(), color=(0,0,255)) + # These values are stable all the time. img.draw_rectangle(blob.rect()) img.draw_cross(blob.cx(), blob.cy()) + # Note - the blob rotation is unique to 0-180 only. + img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20) print(clock.fps()) diff --git a/scripts/examples/10-Color-Tracking/single_color_code_tracking.py b/scripts/examples/10-Color-Tracking/single_color_code_tracking.py index a7a1acd8b..ed8fa651e 100644 --- a/scripts/examples/10-Color-Tracking/single_color_code_tracking.py +++ b/scripts/examples/10-Color-Tracking/single_color_code_tracking.py @@ -5,7 +5,7 @@ # A color code is a blob composed of two or more colors. The example below will # only track colored objects which have both the colors below in them. -import sensor, image, time +import sensor, image, time, math # Color Tracking Thresholds (L Min, L Max, A Min, A Max, B Min, B Max) # The below thresholds track in general red/green things. You may wish to tune them... @@ -30,6 +30,14 @@ while(True): img = sensor.snapshot() for blob in img.find_blobs(thresholds, pixels_threshold=100, area_threshold=100, merge=True): if blob.code() == 3: # r/g code == (1 << 1) | (1 << 0) + # These values depend on the blob not being circular - otherwise they will be shaky. + if blob.elongation() > 0.5: + img.draw_edges(blob.min_corners(), color=(255,0,0)) + img.draw_line(blob.major_axis_line(), color=(0,255,0)) + img.draw_line(blob.minor_axis_line(), color=(0,0,255)) + # These values are stable all the time. img.draw_rectangle(blob.rect()) img.draw_cross(blob.cx(), blob.cy()) + # Note - the blob rotation is unique to 0-180 only. + img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20) print(clock.fps()) diff --git a/scripts/examples/10-Color-Tracking/single_color_grayscale_blob_tracking.py b/scripts/examples/10-Color-Tracking/single_color_grayscale_blob_tracking.py index 08d34dbb1..8f8b44d3b 100644 --- a/scripts/examples/10-Color-Tracking/single_color_grayscale_blob_tracking.py +++ b/scripts/examples/10-Color-Tracking/single_color_grayscale_blob_tracking.py @@ -2,7 +2,7 @@ # # This example shows off single color grayscale tracking using the OpenMV Cam. -import sensor, image, time +import sensor, image, time, math # Color Tracking Thresholds (Grayscale Min, Grayscale Max) # The below grayscale threshold is set to only find extremely bright white areas. @@ -24,6 +24,14 @@ while(True): clock.tick() img = sensor.snapshot() for blob in img.find_blobs([thresholds], pixels_threshold=100, area_threshold=100, merge=True): - img.draw_rectangle(blob.rect()) - img.draw_cross(blob.cx(), blob.cy()) + # These values depend on the blob not being circular - otherwise they will be shaky. + if blob.elongation() > 0.5: + img.draw_edges(blob.min_corners(), color=0) + img.draw_line(blob.major_axis_line(), color=0) + img.draw_line(blob.minor_axis_line(), color=0) + # These values are stable all the time. + img.draw_rectangle(blob.rect(), color=127) + img.draw_cross(blob.cx(), blob.cy(), color=127) + # Note - the blob rotation is unique to 0-180 only. + img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=40, color=127) print(clock.fps()) diff --git a/scripts/examples/10-Color-Tracking/single_color_rgb565_blob_tracking.py b/scripts/examples/10-Color-Tracking/single_color_rgb565_blob_tracking.py index 132d91a9d..e7dc5bec1 100644 --- a/scripts/examples/10-Color-Tracking/single_color_rgb565_blob_tracking.py +++ b/scripts/examples/10-Color-Tracking/single_color_rgb565_blob_tracking.py @@ -2,7 +2,7 @@ # # This example shows off single color RGB565 tracking using the OpenMV Cam. -import sensor, image, time +import sensor, image, time, math threshold_index = 0 # 0 for red, 1 for green, 2 for blue @@ -28,6 +28,14 @@ while(True): clock.tick() img = sensor.snapshot() for blob in img.find_blobs([thresholds[threshold_index]], pixels_threshold=200, area_threshold=200, merge=True): + # These values depend on the blob not being circular - otherwise they will be shaky. + if blob.elongation() > 0.5: + img.draw_edges(blob.min_corners(), color=(255,0,0)) + img.draw_line(blob.major_axis_line(), color=(0,255,0)) + img.draw_line(blob.minor_axis_line(), color=(0,0,255)) + # These values are stable all the time. img.draw_rectangle(blob.rect()) img.draw_cross(blob.cx(), blob.cy()) + # Note - the blob rotation is unique to 0-180 only. + img.draw_keypoints([(blob.cx(), blob.cy(), int(math.degrees(blob.rotation())))], size=20) print(clock.fps()) diff --git a/src/omv/boards/OPENMV2/imlib_config.h b/src/omv/boards/OPENMV2/imlib_config.h index a2a76ad3e..f187fe301 100644 --- a/src/omv/boards/OPENMV2/imlib_config.h +++ b/src/omv/boards/OPENMV2/imlib_config.h @@ -9,6 +9,12 @@ #ifndef __IMLIB_CONFIG_H__ #define __IMLIB_CONFIG_H__ +// Enable mean pooling +//#define IMLIB_ENABLE_MEAN_POOLING + +// Enable midpoint pooling +//#define IMLIB_ENABLE_MIDPOINT_POOLING + // Enable binary ops //#define IMLIB_ENABLE_BINARY_OPS @@ -111,6 +117,19 @@ // Enable FAST (20+ KBs). //#define IMLIB_ENABLE_FAST +// Enable find_template() +//#define IMLIB_FIND_TEMPLATE + +// Enable find_lbp() +//#define IMLIB_ENABLE_FIND_LBP + +// Enable find_keypoints() +//#define IMLIB_ENABLE_FIND_KEYPOINTS + +#if defined(IMLIB_ENABLE_FIND_LBP) || defined(IMLIB_ENABLE_FIND_KEYPOINTS) + #define IMLIB_ENABLE_DESCRIPTOR +#endif + // Enable find_hog() //#define IMLIB_ENABLE_HOG diff --git a/src/omv/boards/OPENMV3/imlib_config.h b/src/omv/boards/OPENMV3/imlib_config.h index 9cd9b1935..5aededebd 100644 --- a/src/omv/boards/OPENMV3/imlib_config.h +++ b/src/omv/boards/OPENMV3/imlib_config.h @@ -9,6 +9,12 @@ #ifndef __IMLIB_CONFIG_H__ #define __IMLIB_CONFIG_H__ +// Enable mean pooling +#define IMLIB_ENABLE_MEAN_POOLING + +// Enable midpoint pooling +#define IMLIB_ENABLE_MIDPOINT_POOLING + // Enable binary ops #define IMLIB_ENABLE_BINARY_OPS @@ -117,6 +123,19 @@ // Enable FAST (20+ KBs). #define IMLIB_ENABLE_FAST +// Enable find_template() +#define IMLIB_FIND_TEMPLATE + +// Enable find_lbp() +#define IMLIB_ENABLE_FIND_LBP + +// Enable find_keypoints() +#define IMLIB_ENABLE_FIND_KEYPOINTS + +#if defined(IMLIB_ENABLE_FIND_LBP) || defined(IMLIB_ENABLE_FIND_KEYPOINTS) + #define IMLIB_ENABLE_DESCRIPTOR +#endif + // Enable find_hog() #define IMLIB_ENABLE_HOG diff --git a/src/omv/boards/OPENMV4/imlib_config.h b/src/omv/boards/OPENMV4/imlib_config.h index d42324764..1654a3c7d 100644 --- a/src/omv/boards/OPENMV4/imlib_config.h +++ b/src/omv/boards/OPENMV4/imlib_config.h @@ -9,6 +9,12 @@ #ifndef __IMLIB_CONFIG_H__ #define __IMLIB_CONFIG_H__ +// Enable mean pooling +#define IMLIB_ENABLE_MEAN_POOLING + +// Enable midpoint pooling +#define IMLIB_ENABLE_MIDPOINT_POOLING + // Enable binary ops #define IMLIB_ENABLE_BINARY_OPS @@ -117,6 +123,19 @@ // Enable FAST (20+ KBs). #define IMLIB_ENABLE_FAST +// Enable find_template() +#define IMLIB_FIND_TEMPLATE + +// Enable find_lbp() +#define IMLIB_ENABLE_FIND_LBP + +// Enable find_keypoints() +#define IMLIB_ENABLE_FIND_KEYPOINTS + +#if defined(IMLIB_ENABLE_FIND_LBP) || defined(IMLIB_ENABLE_FIND_KEYPOINTS) + #define IMLIB_ENABLE_DESCRIPTOR +#endif + // Enable find_hog() #define IMLIB_ENABLE_HOG diff --git a/src/omv/img/blob.c b/src/omv/img/blob.c index c0f1e122f..2d78f78f9 100644 --- a/src/omv/img/blob.c +++ b/src/omv/img/blob.c @@ -1,28 +1,153 @@ /* This file is part of the OpenMV project. - * Copyright (c) 2013-2017 Ibrahim Abdelkader & Kwabena W. Agyeman + * Copyright (c) 2013-2019 Ibrahim Abdelkader & Kwabena W. Agyeman * This work is licensed under the MIT license, see the file LICENSE for details. */ #include "imlib.h" -typedef struct xylf +typedef struct xylr { - int16_t x, y, l, r; + int16_t x, y, l, r, t_l, b_l; +} +xylr_t; + +static float sign(float x) +{ + return x / fabsf(x); +} + +static int sum_m_to_n(int m, int n) +{ + return ((n * (n + 1)) - (m * (m - 1))) / 2; +} + +static int sum_2_m_to_n(int m, int n) +{ + return ((n * (n + 1) * ((2 * n) + 1)) - (m * (m - 1) * ((2 * m) - 1))) / 6; +} + +static int cumulative_moving_average(int avg, int x, int n) +{ + return (x + (n * avg)) / (n + 1); +} + +static void bin_up(uint16_t *hist, uint16_t size, unsigned int max_size, uint16_t **new_hist, uint16_t *new_size) +{ + int start = -1; + + for (int i = 0; i < size; i++) { + if (hist[i]) { + start = i; + break; + } + } + + if (start != -1) { + int end = start; + + for (int i = start + 1; i < size; i++) { + if (!hist[i]) { + break; + } + end = i; + } + + int bin_count = end - start + 1; // >= 1 + *new_size = IM_MIN(max_size, bin_count); + *new_hist = xalloc0((*new_size) * sizeof(uint16_t)); + float div_value = (*new_size) / ((float) bin_count); // Reversed so we can multiply below. + + for (int i = 0; i < bin_count; i++) { + (*new_hist)[fast_floorf(i*div_value)] += hist[start+i]; + } + } +} + +static void merge_bins(int b_dst_start, int b_dst_end, uint16_t **b_dst_hist, uint16_t *b_dst_hist_len, + int b_src_start, int b_src_end, uint16_t **b_src_hist, uint16_t *b_src_hist_len, + unsigned int max_size) +{ + int start = IM_MIN(b_dst_start, b_src_start); + int end = IM_MAX(b_dst_end, b_src_end); + + int bin_count = end - start + 1; // >= 1 + uint16_t new_size = IM_MIN(max_size, bin_count); + uint16_t *new_hist = xalloc0(new_size * sizeof(uint16_t)); + float div_value = new_size / ((float) bin_count); // Reversed so we can multiply below. + + int b_dst_bin_count = b_dst_end - b_dst_start + 1; // >= 1 + uint16_t b_dst_new_size = IM_MIN((*b_dst_hist_len), b_dst_bin_count); + float b_dst_div_value = b_dst_new_size / ((float) b_dst_bin_count); // Reversed so we can multiply below. + + int b_src_bin_count = b_src_end - b_src_start + 1; // >= 1 + uint16_t b_src_new_size = IM_MIN((*b_src_hist_len), b_src_bin_count); + float b_src_div_value = b_src_new_size / ((float) b_src_bin_count); // Reversed so we can multiply below. + + for(int i = 0; i < bin_count; i++) { + if ((b_dst_start <= (i + start)) && ((i + start) <= b_dst_end)) { + int index = fast_floorf((i + start - b_dst_start) * b_dst_div_value); + new_hist[fast_floorf(i*div_value)] += (*b_dst_hist)[index]; + (*b_dst_hist)[index] = 0; // prevent from adding again... + } + if ((b_src_start <= (i + start)) && ((i + start) <= b_src_end)) { + int index = fast_floorf((i + start - b_src_start) * b_src_div_value); + new_hist[fast_floorf(i*div_value)] += (*b_src_hist)[index]; + (*b_src_hist)[index] = 0; // prevent from adding again... + } + } + + xfree(*b_dst_hist); + xfree(*b_src_hist); + + *b_dst_hist_len = new_size; + (*b_dst_hist) = new_hist; + *b_src_hist_len = 0; + (*b_src_hist) = NULL; +} + +static float calc_roundness(float blob_a, float blob_b, float blob_c) +{ + float roundness_div = fast_sqrtf((blob_b * blob_b) + ((blob_a - blob_c) * (blob_a - blob_c))); + float roundness_sin = IM_DIV(blob_b, roundness_div); + float roundness_cos = IM_DIV(blob_a - blob_c, roundness_div); + float roundness_add = (blob_a + blob_c) / 2; + float roundness_cos_mul = (blob_a - blob_c) / 2; + float roundness_sin_mul = blob_b / 2; + + float roundness_0 = roundness_add + (roundness_cos * roundness_cos_mul) + (roundness_sin * roundness_sin_mul); + float roundness_1 = roundness_add + (roundness_cos * roundness_cos_mul) - (roundness_sin * roundness_sin_mul); + float roundness_2 = roundness_add - (roundness_cos * roundness_cos_mul) + (roundness_sin * roundness_sin_mul); + float roundness_3 = roundness_add - (roundness_cos * roundness_cos_mul) - (roundness_sin * roundness_sin_mul); + + float roundness_max = IM_MAX(roundness_0, IM_MAX(roundness_1, IM_MAX(roundness_2, roundness_3))); + float roundness_min = IM_MIN(roundness_0, IM_MIN(roundness_1, IM_MIN(roundness_2, roundness_3))); + + return IM_DIV(roundness_min, roundness_max); } -xylf_t; void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, - list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, - bool merge, int margin, - 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) + list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, + bool merge, int margin, + 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, + unsigned int x_hist_bins_max, unsigned int y_hist_bins_max) { - bitmap_t bitmap; // Same size as the image so we don't have to translate. - bitmap_alloc(&bitmap, ptr->w * ptr->h); + // Same size as the image so we don't have to translate. + image_t bmp; + bmp.w = ptr->w; + bmp.h = ptr->h; + bmp.bpp = IMAGE_BPP_BINARY; + bmp.data = fb_alloc0(image_size(&bmp)); + + uint16_t *x_hist_bins = NULL; + if (x_hist_bins_max) x_hist_bins = fb_alloc(ptr->w * sizeof(uint16_t)); + + uint16_t *y_hist_bins = NULL; + if (y_hist_bins_max) y_hist_bins = fb_alloc(ptr->h * sizeof(uint16_t)); lifo_t lifo; size_t lifo_len; - lifo_alloc_all(&lifo, &lifo_len, sizeof(xylf_t)); + lifo_alloc_all(&lifo, &lifo_len, sizeof(xylr_t)); list_init(out, sizeof(find_blobs_list_lnk_data_t)); @@ -33,112 +158,168 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int switch(ptr->bpp) { case IMAGE_BPP_BINARY: { - for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) { uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); - size_t row_index = BITMAP_COMPUTE_ROW_INDEX(ptr, y); - for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(row_index, x))) + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { int old_x = x; int old_y = y; - int blob_x1 = x; - int blob_y1 = y; - int blob_x2 = x; - int blob_y2 = y; + float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION]; + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + int corners_n[FIND_BLOBS_CORNERS_RESOLUTION]; + // These values are initialized to their maximum before we minimize. + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + corners[i].x = IM_MAX(IM_MIN(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), x_max), 0); + corners[i].y = IM_MAX(IM_MIN(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), y_max), 0); + corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + corners_n[i] = 1; + } + int blob_pixels = 0; + int blob_perimeter = 0; int blob_cx = 0; int blob_cy = 0; long long blob_a = 0; long long blob_b = 0; long long blob_c = 0; + if (x_hist_bins) memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t)); + if (y_hist_bins) memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t)); + // Scanline Flood Fill Algorithm // for(;;) { int left = x, right = x; uint32_t *row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); - size_t index = BITMAP_COMPUTE_ROW_INDEX(ptr, y); + uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); while ((left > roi->x) - && (!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, left - 1))) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1)) && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, left - 1), &lnk_data, invert)) { left--; } while ((right < (roi->x + roi->w - 1)) - && (!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, right + 1))) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1)) && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, right + 1), &lnk_data, invert)) { right++; } - blob_x1 = IM_MIN(blob_x1, left); - blob_y1 = IM_MIN(blob_y1, y); - blob_x2 = IM_MAX(blob_x2, right); - blob_y2 = IM_MAX(blob_y2, y); for (int i = left; i <= right; i++) { - bitmap_bit_set(&bitmap, BITMAP_COMPUTE_INDEX(index, i)); - blob_pixels += 1; - blob_cx += i; - blob_cy += y; - blob_a += i*i; - blob_b += i*y; - blob_c += y*y; + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i); } + int sum = sum_m_to_n(left, right); + int sum_2 = sum_2_m_to_n(left, right); + int cnt = right - left + 1; + int avg = sum / cnt; + + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] > 0) ? left : + ((cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] == 0) ? avg : + right); + float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z < corners_acc[i]) { + corners_acc[i] = z; + corners[i].x = x_new; + corners[i].y = y; + corners_n[i] = 1; + } else if (z == corners_acc[i]) { + corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]); + corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]); + corners_n[i] += 1; + } + } + + blob_pixels += cnt; + blob_perimeter += 2; + blob_cx += sum; + blob_cy += y * cnt; + blob_a += sum_2; + blob_b += y * sum; + blob_c += y * y * cnt; + + if (y_hist_bins) y_hist_bins[y] += cnt; + if (x_hist_bins) for (int i = left; i <= right; i++) x_hist_bins[i] += 1; + + int top_left = left; + int bot_left = left; bool break_out = false; for(;;) { if (lifo_size(&lifo) < lifo_len) { if (y > roi->y) { row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y - 1); - index = BITMAP_COMPUTE_ROW_INDEX(ptr, y - 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1); bool recurse = false; - for (int i = left; i <= right; i++) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, i))) - && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), &lnk_data, invert)) { - xylf_t context; + for (int i = top_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; lifo_enqueue(&lifo, &context); x = i; y = y - 1; recurse = true; break; } + + blob_perimeter += (!ok) && (i != left) && (i != right); } if (recurse) { break; } + } else { + blob_perimeter += right - left + 1; } if (y < (roi->y + roi->h - 1)) { row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y + 1); - index = BITMAP_COMPUTE_ROW_INDEX(ptr, y + 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1); bool recurse = false; - for (int i = left; i <= right; i++) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, i))) - && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), &lnk_data, invert)) { - xylf_t context; + for (int i = bot_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... lifo_enqueue(&lifo, &context); x = i; y = y + 1; recurse = true; break; } + + blob_perimeter += (!ok) && (i != left) && (i != right); } if (recurse) { break; } + } else { + blob_perimeter += right - left + 1; } + } else { + blob_perimeter += (right - left + 1) * 2; } if (!lifo_size(&lifo)) { @@ -146,12 +327,14 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int break; } - xylf_t context; + xylr_t context; lifo_dequeue(&lifo, &context); x = context.x; y = context.y; left = context.l; right = context.r; + top_left = context.t_l; + bot_left = context.b_l; } if (break_out) { @@ -159,41 +342,72 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int } } - // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf - // https://www.strchr.com/standard_deviation_in_one_pass - // - // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) - // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) - // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) - // - // blob_a = sigma(x*x) - // blob_b = sigma(x*y) - // blob_c = sigma(y*y) - // blob_cx = sigma(x) - // blob_cy = sigma(y) - // blob_pixels = sigma() + rectangle_t rect; + rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; // l + rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; // t + rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x - corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x + 1; // r - l + 1 + rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y - corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y + 1; // b - t + 1 - int mx = blob_cx / blob_pixels; // x centroid - int my = blob_cy / blob_pixels; // y centroid - int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); - int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); - int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { - find_blobs_list_lnk_data_t lnk_blob; - lnk_blob.rect.x = blob_x1; - lnk_blob.rect.y = blob_y1; - lnk_blob.rect.w = blob_x2 - blob_x1; - lnk_blob.rect.h = blob_y2 - blob_y1; - lnk_blob.pixels = blob_pixels; - lnk_blob.centroid.x = mx; - lnk_blob.centroid.y = my; - lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; - lnk_blob.code = 1 << code; - lnk_blob.count = 1; + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() - if (((lnk_blob.rect.w * lnk_blob.rect.h) >= area_threshold) && (lnk_blob.pixels >= pixels_threshold) - && ((threshold_cb_arg == NULL) || threshold_cb(threshold_cb_arg, &lnk_blob))) { - list_push_back(out, &lnk_blob); + float b_mx = blob_cx / ((float) blob_pixels); + float b_my = blob_cy / ((float) blob_pixels); + int mx = fast_roundf(b_mx); // x centroid + int my = fast_roundf(b_my); // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + find_blobs_list_lnk_data_t lnk_blob; + memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t)); + memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t)); + lnk_blob.pixels = blob_pixels; + lnk_blob.perimeter = blob_perimeter; + lnk_blob.code = 1 << code; + lnk_blob.count = 1; + lnk_blob.centroid_x = b_mx; + lnk_blob.centroid_y = b_my; + lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; + lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c); + lnk_blob.x_hist_bins_count = 0; + lnk_blob.x_hist_bins = NULL; + lnk_blob.y_hist_bins_count = 0; + lnk_blob.y_hist_bins = NULL; + // These store the current average accumulation. + lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels; + lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels; + lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels; + + if (x_hist_bins) { + bin_up(x_hist_bins, ptr->w, x_hist_bins_max, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count); + } + + if (y_hist_bins) { + bin_up(y_hist_bins, ptr->h, y_hist_bins_max, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count); + } + + if (((threshold_cb_arg == NULL) || threshold_cb(threshold_cb_arg, &lnk_blob))) { + list_push_back(out, &lnk_blob); + } else { + if (lnk_blob.x_hist_bins) xfree(lnk_blob.x_hist_bins); + if (lnk_blob.y_hist_bins) xfree(lnk_blob.y_hist_bins); + } } x = old_x; @@ -204,112 +418,168 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int break; } case IMAGE_BPP_GRAYSCALE: { - for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) { uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); - size_t row_index = BITMAP_COMPUTE_ROW_INDEX(ptr, y); - for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(row_index, x))) + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { int old_x = x; int old_y = y; - int blob_x1 = x; - int blob_y1 = y; - int blob_x2 = x; - int blob_y2 = y; + float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION]; + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + int corners_n[FIND_BLOBS_CORNERS_RESOLUTION]; + // These values are initialized to their maximum before we minimize. + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + corners[i].x = IM_MAX(IM_MIN(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), x_max), 0); + corners[i].y = IM_MAX(IM_MIN(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), y_max), 0); + corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + corners_n[i] = 1; + } + int blob_pixels = 0; + int blob_perimeter = 0; int blob_cx = 0; int blob_cy = 0; long long blob_a = 0; long long blob_b = 0; long long blob_c = 0; + if (x_hist_bins) memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t)); + if (y_hist_bins) memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t)); + // Scanline Flood Fill Algorithm // for(;;) { int left = x, right = x; uint8_t *row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); - size_t index = BITMAP_COMPUTE_ROW_INDEX(ptr, y); + uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); while ((left > roi->x) - && (!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, left - 1))) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1)) && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), &lnk_data, invert)) { left--; } while ((right < (roi->x + roi->w - 1)) - && (!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, right + 1))) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1)) && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), &lnk_data, invert)) { right++; } - blob_x1 = IM_MIN(blob_x1, left); - blob_y1 = IM_MIN(blob_y1, y); - blob_x2 = IM_MAX(blob_x2, right); - blob_y2 = IM_MAX(blob_y2, y); for (int i = left; i <= right; i++) { - bitmap_bit_set(&bitmap, BITMAP_COMPUTE_INDEX(index, i)); - blob_pixels += 1; - blob_cx += i; - blob_cy += y; - blob_a += i*i; - blob_b += i*y; - blob_c += y*y; + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i); } + int sum = sum_m_to_n(left, right); + int sum_2 = sum_2_m_to_n(left, right); + int cnt = right - left + 1; + int avg = sum / cnt; + + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] > 0) ? left : + ((cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] == 0) ? avg : + right); + float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z < corners_acc[i]) { + corners_acc[i] = z; + corners[i].x = x_new; + corners[i].y = y; + corners_n[i] = 1; + } else if (z == corners_acc[i]) { + corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]); + corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]); + corners_n[i] += 1; + } + } + + blob_pixels += cnt; + blob_perimeter += 2; + blob_cx += sum; + blob_cy += y * cnt; + blob_a += sum_2; + blob_b += y * sum; + blob_c += y * y * cnt; + + if (y_hist_bins) y_hist_bins[y] += cnt; + if (x_hist_bins) for (int i = left; i <= right; i++) x_hist_bins[i] += 1; + + int top_left = left; + int bot_left = left; bool break_out = false; for(;;) { if (lifo_size(&lifo) < lifo_len) { if (y > roi->y) { row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y - 1); - index = BITMAP_COMPUTE_ROW_INDEX(ptr, y - 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1); bool recurse = false; - for (int i = left; i <= right; i++) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, i))) - && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), &lnk_data, invert)) { - xylf_t context; + for (int i = top_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; lifo_enqueue(&lifo, &context); x = i; y = y - 1; recurse = true; break; } + + blob_perimeter += (!ok) && (i != left) && (i != right); } if (recurse) { break; } + } else { + blob_perimeter += right - left + 1; } if (y < (roi->y + roi->h - 1)) { row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y + 1); - index = BITMAP_COMPUTE_ROW_INDEX(ptr, y + 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1); bool recurse = false; - for (int i = left; i <= right; i++) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, i))) - && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), &lnk_data, invert)) { - xylf_t context; + for (int i = bot_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... lifo_enqueue(&lifo, &context); x = i; y = y + 1; recurse = true; break; } + + blob_perimeter += (!ok) && (i != left) && (i != right); } if (recurse) { break; } + } else { + blob_perimeter += right - left + 1; } + } else { + blob_perimeter += (right - left + 1) * 2; } if (!lifo_size(&lifo)) { @@ -317,12 +587,14 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int break; } - xylf_t context; + xylr_t context; lifo_dequeue(&lifo, &context); x = context.x; y = context.y; left = context.l; right = context.r; + top_left = context.t_l; + bot_left = context.b_l; } if (break_out) { @@ -330,41 +602,72 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int } } - // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf - // https://www.strchr.com/standard_deviation_in_one_pass - // - // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) - // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) - // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) - // - // blob_a = sigma(x*x) - // blob_b = sigma(x*y) - // blob_c = sigma(y*y) - // blob_cx = sigma(x) - // blob_cy = sigma(y) - // blob_pixels = sigma() + rectangle_t rect; + rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; // l + rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; // t + rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x - corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x + 1; // r - l + 1 + rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y - corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y + 1; // b - t + 1 - int mx = blob_cx / blob_pixels; // x centroid - int my = blob_cy / blob_pixels; // y centroid - int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); - int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); - int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { - find_blobs_list_lnk_data_t lnk_blob; - lnk_blob.rect.x = blob_x1; - lnk_blob.rect.y = blob_y1; - lnk_blob.rect.w = blob_x2 - blob_x1; - lnk_blob.rect.h = blob_y2 - blob_y1; - lnk_blob.pixels = blob_pixels; - lnk_blob.centroid.x = mx; - lnk_blob.centroid.y = my; - lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; - lnk_blob.code = 1 << code; - lnk_blob.count = 1; + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() - if (((lnk_blob.rect.w * lnk_blob.rect.h) >= area_threshold) && (lnk_blob.pixels >= pixels_threshold) - && ((threshold_cb_arg == NULL) || threshold_cb(threshold_cb_arg, &lnk_blob))) { - list_push_back(out, &lnk_blob); + float b_mx = blob_cx / ((float) blob_pixels); + float b_my = blob_cy / ((float) blob_pixels); + int mx = fast_roundf(b_mx); // x centroid + int my = fast_roundf(b_my); // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + find_blobs_list_lnk_data_t lnk_blob; + memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t)); + memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t)); + lnk_blob.pixels = blob_pixels; + lnk_blob.perimeter = blob_perimeter; + lnk_blob.code = 1 << code; + lnk_blob.count = 1; + lnk_blob.centroid_x = b_mx; + lnk_blob.centroid_y = b_my; + lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; + lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c); + lnk_blob.x_hist_bins_count = 0; + lnk_blob.x_hist_bins = NULL; + lnk_blob.y_hist_bins_count = 0; + lnk_blob.y_hist_bins = NULL; + // These store the current average accumulation. + lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels; + lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels; + lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels; + + if (x_hist_bins) { + bin_up(x_hist_bins, ptr->w, x_hist_bins_max, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count); + } + + if (y_hist_bins) { + bin_up(y_hist_bins, ptr->h, y_hist_bins_max, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count); + } + + if (((threshold_cb_arg == NULL) || threshold_cb(threshold_cb_arg, &lnk_blob))) { + list_push_back(out, &lnk_blob); + } else { + if (lnk_blob.x_hist_bins) xfree(lnk_blob.x_hist_bins); + if (lnk_blob.y_hist_bins) xfree(lnk_blob.y_hist_bins); + } } x = old_x; @@ -375,112 +678,168 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int break; } case IMAGE_BPP_RGB565: { - for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) { uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); - size_t row_index = BITMAP_COMPUTE_ROW_INDEX(ptr, y); - for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(row_index, x))) + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { int old_x = x; int old_y = y; - int blob_x1 = x; - int blob_y1 = y; - int blob_x2 = x; - int blob_y2 = y; + float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION]; + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + int corners_n[FIND_BLOBS_CORNERS_RESOLUTION]; + // Ensures that maximum goes all the way to the edge of the image. + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + corners[i].x = IM_MAX(IM_MIN(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), x_max), 0); + corners[i].y = IM_MAX(IM_MIN(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), y_max), 0); + corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + corners_n[i] = 1; + } + int blob_pixels = 0; + int blob_perimeter = 0; int blob_cx = 0; int blob_cy = 0; long long blob_a = 0; long long blob_b = 0; long long blob_c = 0; + if (x_hist_bins) memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t)); + if (y_hist_bins) memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t)); + // Scanline Flood Fill Algorithm // for(;;) { int left = x, right = x; uint16_t *row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); - size_t index = BITMAP_COMPUTE_ROW_INDEX(ptr, y); + uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); while ((left > roi->x) - && (!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, left - 1))) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1)) && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, left - 1), &lnk_data, invert)) { left--; } while ((right < (roi->x + roi->w - 1)) - && (!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, right + 1))) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1)) && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, right + 1), &lnk_data, invert)) { right++; } - blob_x1 = IM_MIN(blob_x1, left); - blob_y1 = IM_MIN(blob_y1, y); - blob_x2 = IM_MAX(blob_x2, right); - blob_y2 = IM_MAX(blob_y2, y); for (int i = left; i <= right; i++) { - bitmap_bit_set(&bitmap, BITMAP_COMPUTE_INDEX(index, i)); - blob_pixels += 1; - blob_cx += i; - blob_cy += y; - blob_a += i*i; - blob_b += i*y; - blob_c += y*y; + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i); } + int sum = sum_m_to_n(left, right); + int sum_2 = sum_2_m_to_n(left, right); + int cnt = right - left + 1; + int avg = sum / cnt; + + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] > 0) ? left : + ((cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] == 0) ? avg : + right); + float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z < corners_acc[i]) { + corners_acc[i] = z; + corners[i].x = x_new; + corners[i].y = y; + corners_n[i] = 1; + } else if (z == corners_acc[i]) { + corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]); + corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]); + corners_n[i] += 1; + } + } + + blob_pixels += cnt; + blob_perimeter += 2; + blob_cx += sum; + blob_cy += y * cnt; + blob_a += sum_2; + blob_b += y * sum; + blob_c += y * y * cnt; + + if (y_hist_bins) y_hist_bins[y] += cnt; + if (x_hist_bins) for (int i = left; i <= right; i++) x_hist_bins[i] += 1; + + int top_left = left; + int bot_left = left; bool break_out = false; for(;;) { if (lifo_size(&lifo) < lifo_len) { if (y > roi->y) { row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y - 1); - index = BITMAP_COMPUTE_ROW_INDEX(ptr, y - 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1); bool recurse = false; - for (int i = left; i <= right; i++) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, i))) - && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), &lnk_data, invert)) { - xylf_t context; + for (int i = top_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; lifo_enqueue(&lifo, &context); x = i; y = y - 1; recurse = true; break; } + + blob_perimeter += (!ok) && (i != left) && (i != right); } if (recurse) { break; } + } else { + blob_perimeter += right - left + 1; } if (y < (roi->y + roi->h - 1)) { row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y + 1); - index = BITMAP_COMPUTE_ROW_INDEX(ptr, y + 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1); bool recurse = false; - for (int i = left; i <= right; i++) { - if ((!bitmap_bit_get(&bitmap, BITMAP_COMPUTE_INDEX(index, i))) - && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), &lnk_data, invert)) { - xylf_t context; + for (int i = bot_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... lifo_enqueue(&lifo, &context); x = i; y = y + 1; recurse = true; break; } + + blob_perimeter += (!ok) && (i != left) && (i != right); } if (recurse) { break; } + } else { + blob_perimeter += right - left + 1; } + } else { + blob_perimeter += (right - left + 1) * 2; } if (!lifo_size(&lifo)) { @@ -488,12 +847,14 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int break; } - xylf_t context; + xylr_t context; lifo_dequeue(&lifo, &context); x = context.x; y = context.y; left = context.l; right = context.r; + top_left = context.t_l; + bot_left = context.b_l; } if (break_out) { @@ -501,41 +862,72 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int } } - // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf - // https://www.strchr.com/standard_deviation_in_one_pass - // - // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) - // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) - // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) - // - // blob_a = sigma(x*x) - // blob_b = sigma(x*y) - // blob_c = sigma(y*y) - // blob_cx = sigma(x) - // blob_cy = sigma(y) - // blob_pixels = sigma() + rectangle_t rect; + rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; // l + rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; // t + rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x - corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x + 1; // r - l + 1 + rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y - corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y + 1; // b - t + 1 - int mx = blob_cx / blob_pixels; // x centroid - int my = blob_cy / blob_pixels; // y centroid - int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); - int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); - int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { - find_blobs_list_lnk_data_t lnk_blob; - lnk_blob.rect.x = blob_x1; - lnk_blob.rect.y = blob_y1; - lnk_blob.rect.w = blob_x2 - blob_x1; - lnk_blob.rect.h = blob_y2 - blob_y1; - lnk_blob.pixels = blob_pixels; - lnk_blob.centroid.x = mx; - lnk_blob.centroid.y = my; - lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; - lnk_blob.code = 1 << code; - lnk_blob.count = 1; + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() - if (((lnk_blob.rect.w * lnk_blob.rect.h) >= area_threshold) && (lnk_blob.pixels >= pixels_threshold) - && ((threshold_cb_arg == NULL) || threshold_cb(threshold_cb_arg, &lnk_blob))) { - list_push_back(out, &lnk_blob); + float b_mx = blob_cx / ((float) blob_pixels); + float b_my = blob_cy / ((float) blob_pixels); + int mx = fast_roundf(b_mx); // x centroid + int my = fast_roundf(b_my); // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + find_blobs_list_lnk_data_t lnk_blob; + memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t)); + memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t)); + lnk_blob.pixels = blob_pixels; + lnk_blob.perimeter = blob_perimeter; + lnk_blob.code = 1 << code; + lnk_blob.count = 1; + lnk_blob.centroid_x = b_mx; + lnk_blob.centroid_y = b_my; + lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; + lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c); + lnk_blob.x_hist_bins_count = 0; + lnk_blob.x_hist_bins = NULL; + lnk_blob.y_hist_bins_count = 0; + lnk_blob.y_hist_bins = NULL; + // These store the current average accumulation. + lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels; + lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels; + lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels; + + if (x_hist_bins) { + bin_up(x_hist_bins, ptr->w, x_hist_bins_max, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count); + } + + if (y_hist_bins) { + bin_up(y_hist_bins, ptr->h, y_hist_bins_max, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count); + } + + if (((threshold_cb_arg == NULL) || threshold_cb(threshold_cb_arg, &lnk_blob))) { + list_push_back(out, &lnk_blob); + } else { + if (lnk_blob.x_hist_bins) xfree(lnk_blob.x_hist_bins); + if (lnk_blob.y_hist_bins) xfree(lnk_blob.y_hist_bins); + } } x = old_x; @@ -554,7 +946,9 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int } lifo_free(&lifo); - bitmap_free(&bitmap); + if (y_hist_bins) fb_free(); + if (x_hist_bins) fb_free(); + fb_free(); // bitmap if (merge) { for(;;) { @@ -579,15 +973,43 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int if (rectangle_overlap(&(lnk_blob.rect), &temp) && ((merge_cb_arg == NULL) || merge_cb(merge_cb_arg, &lnk_blob, &tmp_blob))) { + // Have to merge these first before merging rects. + if (x_hist_bins_max) merge_bins(lnk_blob.rect.x, lnk_blob.rect.x + lnk_blob.rect.w - 1, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count, + tmp_blob.rect.x, tmp_blob.rect.x + tmp_blob.rect.w - 1, &tmp_blob.x_hist_bins, &tmp_blob.x_hist_bins_count, + x_hist_bins_max); + if (y_hist_bins_max) merge_bins(lnk_blob.rect.y, lnk_blob.rect.y + lnk_blob.rect.h - 1, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count, + tmp_blob.rect.y, tmp_blob.rect.y + tmp_blob.rect.h - 1, &tmp_blob.y_hist_bins, &tmp_blob.y_hist_bins_count, + y_hist_bins_max); + // Merge corners... + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + float z_dst = (lnk_blob.corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (lnk_blob.corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + float z_src = (tmp_blob.corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (tmp_blob.corners[i].y * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z_src < z_dst) { + lnk_blob.corners[i].x = tmp_blob.corners[i].x; + lnk_blob.corners[i].y = tmp_blob.corners[i].y; + } + } + // Merge rects... rectangle_united(&(lnk_blob.rect), &(tmp_blob.rect)); - lnk_blob.centroid.x = ((lnk_blob.centroid.x * lnk_blob.pixels) + (tmp_blob.centroid.x * tmp_blob.pixels)) / (lnk_blob.pixels + tmp_blob.pixels); - lnk_blob.centroid.y = ((lnk_blob.centroid.y * lnk_blob.pixels) + (tmp_blob.centroid.y * tmp_blob.pixels)) / (lnk_blob.pixels + tmp_blob.pixels); - float sin_mean = ((sinf(lnk_blob.rotation) * lnk_blob.pixels) + (sinf(tmp_blob.rotation) * tmp_blob.pixels)) / (lnk_blob.pixels + tmp_blob.pixels); - float cos_mean = ((cosf(lnk_blob.rotation) * lnk_blob.pixels) + (cosf(tmp_blob.rotation) * tmp_blob.pixels)) / (lnk_blob.pixels + tmp_blob.pixels); - lnk_blob.rotation = fast_atan2f(sin_mean, cos_mean); + // Merge counters... lnk_blob.pixels += tmp_blob.pixels; // won't overflow - lnk_blob.code |= tmp_blob.code; - lnk_blob.count = IM_MAX(IM_MIN(lnk_blob.count + tmp_blob.count, UINT16_MAX), 0); + lnk_blob.perimeter += tmp_blob.perimeter; // won't overflow + lnk_blob.code |= tmp_blob.code; // won't overflow + lnk_blob.count += tmp_blob.count; // won't overflow + // Merge accumulators... + lnk_blob.centroid_x_acc += tmp_blob.centroid_x_acc; + lnk_blob.centroid_y_acc += tmp_blob.centroid_y_acc; + lnk_blob.rotation_acc_x += tmp_blob.rotation_acc_x; + lnk_blob.rotation_acc_y += tmp_blob.rotation_acc_y; + lnk_blob.roundness_acc += tmp_blob.roundness_acc; + // Compute current values... + lnk_blob.centroid_x = lnk_blob.centroid_x_acc / lnk_blob.pixels; + lnk_blob.centroid_y = lnk_blob.centroid_y_acc / lnk_blob.pixels; + lnk_blob.rotation = fast_atan2f(lnk_blob.rotation_acc_y / lnk_blob.pixels, + lnk_blob.rotation_acc_x / lnk_blob.pixels); + lnk_blob.roundness = lnk_blob.roundness_acc / lnk_blob.pixels; merge_occured = true; } else { list_push_back(out, &tmp_blob); @@ -612,7 +1034,7 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, { lifo_t lifo; size_t lifo_len; - lifo_alloc_all(&lifo, &lifo_len, sizeof(xylf_t)); + lifo_alloc_all(&lifo, &lifo_len, sizeof(xylr_t)); switch(img->bpp) { case IMAGE_BPP_BINARY: { @@ -641,6 +1063,8 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); } + int top_left = left; + int bot_left = left; bool break_out = false; for(;;) { if (lifo_size(&lifo) < lifo_len) { @@ -651,16 +1075,18 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); bool recurse = false; - for (int i = left; i <= right; i++) { + for (int i = top_left; i <= right; i++) { if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), seed_pixel, seed_threshold) && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), IMAGE_GET_BINARY_PIXEL_FAST(old_row, i), floating_threshold)) { - xylf_t context; + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; lifo_enqueue(&lifo, &context); x = i; y = y - 1; @@ -678,16 +1104,18 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); bool recurse = false; - for (int i = left; i <= right; i++) { + for (int i = bot_left; i <= right; i++) { if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), seed_pixel, seed_threshold) && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), IMAGE_GET_BINARY_PIXEL_FAST(old_row, i), floating_threshold)) { - xylf_t context; + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... lifo_enqueue(&lifo, &context); x = i; y = y + 1; @@ -708,12 +1136,14 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, break; } - xylf_t context; + xylr_t context; lifo_dequeue(&lifo, &context); x = context.x; y = context.y; left = context.l; right = context.r; + top_left = context.t_l; + bot_left = context.b_l; } if (break_out) { @@ -748,6 +1178,8 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); } + int top_left = left; + int bot_left = left; bool break_out = false; for(;;) { if (lifo_size(&lifo) < lifo_len) { @@ -758,16 +1190,18 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); bool recurse = false; - for (int i = left; i <= right; i++) { + for (int i = top_left; i <= right; i++) { if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold) && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) { - xylf_t context; + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; lifo_enqueue(&lifo, &context); x = i; y = y - 1; @@ -785,16 +1219,18 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); bool recurse = false; - for (int i = left; i <= right; i++) { + for (int i = bot_left; i <= right; i++) { if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold) && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) { - xylf_t context; + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... lifo_enqueue(&lifo, &context); x = i; y = y + 1; @@ -815,12 +1251,14 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, break; } - xylf_t context; + xylr_t context; lifo_dequeue(&lifo, &context); x = context.x; y = context.y; left = context.l; right = context.r; + top_left = context.t_l; + bot_left = context.b_l; } if (break_out) { @@ -855,6 +1293,8 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); } + int top_left = left; + int bot_left = left; bool break_out = false; for(;;) { if (lifo_size(&lifo) < lifo_len) { @@ -865,16 +1305,18 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); bool recurse = false; - for (int i = left; i <= right; i++) { + for (int i = top_left; i <= right; i++) { if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), seed_pixel, seed_threshold) && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), IMAGE_GET_RGB565_PIXEL_FAST(old_row, i), floating_threshold)) { - xylf_t context; + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; lifo_enqueue(&lifo, &context); x = i; y = y - 1; @@ -892,16 +1334,18 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); bool recurse = false; - for (int i = left; i <= right; i++) { + for (int i = bot_left; i <= right; i++) { if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), seed_pixel, seed_threshold) && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), IMAGE_GET_RGB565_PIXEL_FAST(old_row, i), floating_threshold)) { - xylf_t context; + xylr_t context; context.x = x; context.y = y; context.l = left; context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... lifo_enqueue(&lifo, &context); x = i; y = y + 1; @@ -922,12 +1366,14 @@ void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, break; } - xylf_t context; + xylr_t context; lifo_dequeue(&lifo, &context); x = context.x; y = context.y; left = context.l; right = context.r; + top_left = context.t_l; + bot_left = context.b_l; } if (break_out) { diff --git a/src/omv/img/imlib.c b/src/omv/img/imlib.c index 9ffa02944..1789102f7 100644 --- a/src/omv/img/imlib.c +++ b/src/omv/img/imlib.c @@ -42,6 +42,67 @@ int point_quadrance(point_t *ptr0, point_t *ptr1) return (delta_x * delta_x) + (delta_y * delta_y); } +void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y) +{ + x -= center_x; + y -= center_y; + *new_x = (x * cosf(r)) - (y * sinf(r)) + center_x; + *new_y = (x * sinf(r)) + (y * cosf(r)) + center_y; +} + +void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len) // Corners need to be sorted! +{ + int i_min = 0; + int i_min_area = INT_MAX; + int i_x0 = 0, i_y0 = 0; + int i_x1 = 0, i_y1 = 0; + int i_x2 = 0, i_y2 = 0; + int i_x3 = 0, i_y3 = 0; + float i_r = 0; + + // This algorithm aligns the 4 edges produced by the 4 corners to the x axis and then computes the + // min area rect for each alignment. The smallest rect is choosen and then re-rotated and returned. + for (int i = 0; i < corners_len; i++) { + int16_t x0 = corners[i].x, y0 = corners[i].y; + int x_diff = corners[(i+1)%corners_len].x - corners[i].x; + int y_diff = corners[(i+1)%corners_len].y - corners[i].y; + float r = -fast_atan2f(y_diff, x_diff); + + int16_t x1[corners_len-1]; + int16_t y1[corners_len-1]; + for (int j = 0, jj = corners_len - 1; j < jj; j++) { + point_rotate(corners[(i+j+1)%corners_len].x, corners[(i+j+1)%corners_len].y, r, x0, y0, x1 + j, y1 + j); + } + + int minx = x0; + int maxx = x0; + int miny = y0; + int maxy = y0; + for (int j = 0, jj = corners_len - 1; j < jj; j++) { + minx = MIN(minx, x1[j]); + maxx = MAX(maxx, x1[j]); + miny = MIN(miny, y1[j]); + maxy = MAX(maxy, y1[j]); + } + + int area = (maxx - minx + 1) * (maxy - miny + 1); + if (area < i_min_area) { + i_min = i; + i_min_area = area; + i_x0 = minx, i_y0 = miny; + i_x1 = maxx, i_y1 = miny; + i_x2 = maxx, i_y2 = maxy; + i_x3 = minx, i_y3 = maxy; + i_r = r; + } + } + + point_rotate(i_x0, i_y0, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[0].x, &new_corners[0].y); + point_rotate(i_x1, i_y1, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[1].x, &new_corners[1].y); + point_rotate(i_x2, i_y2, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[2].x, &new_corners[2].y); + point_rotate(i_x3, i_y3, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[3].x, &new_corners[3].y); +} + //////////////// // Line Stuff // //////////////// diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 57681299f..ea7fc2cc5 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -83,6 +83,8 @@ void point_init(point_t *ptr, int x, int y); void point_copy(point_t *dst, point_t *src); bool point_equal_fast(point_t *ptr0, point_t *ptr1); int point_quadrance(point_t *ptr0, point_t *ptr1); +void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y); +void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len); //////////////// // Line Stuff // @@ -1049,12 +1051,16 @@ typedef struct statistics { int8_t BMean, BMedian, BMode, BSTDev, BMin, BMax, BLQ, BUQ; } statistics_t; +#define FIND_BLOBS_CORNERS_RESOLUTION 20 // multiple of 4 +#define FIND_BLOBS_ANGLE_RESOLUTION (360 / FIND_BLOBS_CORNERS_RESOLUTION) + typedef struct find_blobs_list_lnk_data { + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; rectangle_t rect; - uint32_t pixels; - point_t centroid; - float rotation; - uint16_t code, count; + uint32_t pixels, perimeter, code, count; + float centroid_x, centroid_y, rotation, roundness; + uint16_t x_hist_bins_count, y_hist_bins_count, *x_hist_bins, *y_hist_bins; + float centroid_x_acc, centroid_y_acc, rotation_acc_x, rotation_acc_y, roundness_acc; } find_blobs_list_lnk_data_t; typedef struct find_lines_list_lnk_data { @@ -1341,7 +1347,8 @@ void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, bool merge, int margin, 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); + bool (*merge_cb)(void*,find_blobs_list_lnk_data_t*,find_blobs_list_lnk_data_t*), void *merge_cb_arg, + unsigned int x_hist_bins_max, unsigned int y_hist_bins_max); // Shape Detection size_t trace_line(image_t *ptr, line_t *l, int *theta_buffer, uint32_t *mag_buffer, point_t *point_buffer); // helper/internal void merge_alot(list_t *out, int threshold, int theta_threshold); // helper/internal diff --git a/src/omv/img/pool.c b/src/omv/img/pool.c index 3b2c0648e..bd2e2a94e 100644 --- a/src/omv/img/pool.c +++ b/src/omv/img/pool.c @@ -8,6 +8,7 @@ */ #include "imlib.h" +#ifdef IMLIB_ENABLE_MIDPOINT_POOLING void imlib_midpoint_pool(image_t *img_i, image_t *img_o, int x_div, int y_div, const int bias) { int min_bias = (256-bias); @@ -55,7 +56,9 @@ void imlib_midpoint_pool(image_t *img_i, image_t *img_o, int x_div, int y_div, c } } } +#endif // IMLIB_ENABLE_MIDPOINT_POOLING +#ifdef IMLIB_ENABLE_MEAN_POOLING void imlib_mean_pool(image_t *img_i, image_t *img_o, int x_div, int y_div) { int n = x_div * y_div; @@ -93,3 +96,4 @@ void imlib_mean_pool(image_t *img_i, image_t *img_o, int x_div, int y_div) } } } +#endif // IMLIB_ENABLE_MEAN_POOLING diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 98f296d55..f4f3c0ae3 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -57,6 +57,8 @@ static const mp_obj_type_t py_cascade_type = { // Keypoints object /////////////////////////////////////////////////////////// +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS + typedef struct _py_kp_obj_t { mp_obj_base_t base; array_t *kpts; @@ -112,8 +114,12 @@ py_kp_obj_t *py_kpts_obj(mp_obj_t kpts_obj) return kpts_obj; } +#endif // IMLIB_ENABLE_FIND_KEYPOINTS + // LBP descriptor ///////////////////////////////////////////////////////////// +#ifdef IMLIB_ENABLE_FIND_LBP + typedef struct _py_lbp_obj_t { mp_obj_base_t base; uint8_t *hist; @@ -130,7 +136,12 @@ static const mp_obj_type_t py_lbp_type = { .print = py_lbp_print, }; -// Keypoints match object ///////////////////////////////////////////////////// +#endif // IMLIB_ENABLE_FIND_LBP + +// Keypoints Match Object ///////////////////////////////////////////////////// + +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS + #define kptmatch_obj_size 9 typedef struct _py_kptmatch_obj_t { mp_obj_base_t base; @@ -227,6 +238,8 @@ static const mp_obj_type_t py_kptmatch_type = { .locals_dict = (mp_obj_t) &py_kptmatch_locals_dict }; +#endif // IMLIB_ENABLE_FIND_KEYPOINTS + // Image ////////////////////////////////////////////////////////////////////// typedef struct _py_image_obj_t { @@ -630,6 +643,7 @@ STATIC mp_obj_t py_image_set_pixel(uint n_args, const mp_obj_t *args, mp_map_t * } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_set_pixel_obj, 2, py_image_set_pixel); +#ifdef IMLIB_ENABLE_MEAN_POOLING static mp_obj_t py_image_mean_pool(mp_obj_t img_obj, mp_obj_t x_div_obj, mp_obj_t y_div_obj) { image_t *arg_img = py_helper_arg_to_image_mutable(img_obj); @@ -681,7 +695,9 @@ static mp_obj_t py_image_mean_pooled(mp_obj_t img_obj, mp_obj_t x_div_obj, mp_ob return py_image_from_struct(&out_img); } STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_image_mean_pooled_obj, py_image_mean_pooled); +#endif // IMLIB_ENABLE_MEAN_POOLING +#ifdef IMLIB_ENABLE_MIDPOINT_POOLING static mp_obj_t py_image_midpoint_pool(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); @@ -739,6 +755,7 @@ static mp_obj_t py_image_midpoint_pooled(uint n_args, const mp_obj_t *args, mp_m return py_image_from_struct(&out_img); } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_midpoint_pooled_obj, 3, py_image_midpoint_pooled); +#endif // IMLIB_ENABLE_MIDPOINT_POOLING static mp_obj_t py_image_to_bitmap(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { @@ -1424,6 +1441,52 @@ STATIC mp_obj_t py_image_draw_arrow(uint n_args, const mp_obj_t *args, mp_map_t } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_arrow_obj, 2, py_image_draw_arrow); +STATIC mp_obj_t py_image_draw_edges(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(args[1], 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, 2, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 0); + int arg_thickness = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_line(arg_img, x0, y0, x1, y1, arg_c, arg_thickness); + imlib_draw_line(arg_img, x1, y1, x2, y2, arg_c, arg_thickness); + imlib_draw_line(arg_img, x2, y2, x3, y3, arg_c, arg_thickness); + imlib_draw_line(arg_img, x3, y3, x0, y0, arg_c, arg_thickness); + + if (arg_s >= 1) { + imlib_draw_circle(arg_img, x0, y0, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x1, y1, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x2, y2, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x3, y3, arg_s, arg_c, arg_thickness, arg_fill); + } + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_edges_obj, 2, py_image_draw_edges); + STATIC mp_obj_t py_image_draw_image(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); @@ -1482,6 +1545,7 @@ STATIC mp_obj_t py_image_draw_keypoints(uint n_args, const mp_obj_t *args, mp_ma imlib_draw_circle(arg_img, cx, cy, (arg_s - 2) / 2, arg_c, arg_thickness, arg_fill); } } else { +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS py_kp_obj_t *kpts_obj = py_kpts_obj(args[1]); for (int i = 0, ii = array_length(kpts_obj->kpts); i < ii; i++) { kp_t *kp = array_at(kpts_obj->kpts, i); @@ -1493,6 +1557,9 @@ STATIC mp_obj_t py_image_draw_keypoints(uint n_args, const mp_obj_t *args, mp_ma imlib_draw_line(arg_img, cx, cy, cx + co, cy + si, arg_c, arg_thickness); imlib_draw_circle(arg_img, cx, cy, (arg_s - 2) / 2, arg_c, arg_thickness, arg_fill); } +#else + PY_ASSERT_TRUE_MSG(false, "Expected a list of tuples!"); +#endif // IMLIB_ENABLE_FIND_KEYPOINTS } return args[0]; @@ -3743,27 +3810,35 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_get_regression_obj, 2, py_image_get_r /////////////// // Blob Object // -#define py_blob_obj_size 10 +#define py_blob_obj_size 12 typedef struct py_blob_obj { mp_obj_base_t base; - mp_obj_t x, y, w, h, pixels, cx, cy, rotation, code, count; + mp_obj_t corners; + mp_obj_t min_corners; + mp_obj_t x, y, w, h, pixels, cx, cy, rotation, code, count, perimeter, roundness; + mp_obj_t x_hist_bins; + mp_obj_t y_hist_bins; } py_blob_obj_t; static void py_blob_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { py_blob_obj_t *self = self_in; mp_printf(print, - "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"pixels\":%d, \"cx\":%d, \"cy\":%d, \"rotation\":%f, \"code\":%d, \"count\":%d}", + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d," + " \"pixels\":%d, \"cx\":%d, \"cy\":%d, \"rotation\":%f, \"code\":%d, \"count\":%d," + " \"perimeter\":%d, \"roundness:%f\"}", 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->pixels), - mp_obj_get_int(self->cx), - mp_obj_get_int(self->cy), + fast_roundf(mp_obj_get_float(self->cx)), + fast_roundf(mp_obj_get_float(self->cy)), (double) mp_obj_get_float(self->rotation), mp_obj_get_int(self->code), - mp_obj_get_int(self->count)); + mp_obj_get_int(self->count), + mp_obj_get_int(self->perimeter), + (double) mp_obj_get_float(self->roundness)); } static mp_obj_t py_blob_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) @@ -3785,16 +3860,20 @@ static mp_obj_t py_blob_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) case 2: return self->w; case 3: return self->h; case 4: return self->pixels; - case 5: return self->cx; - case 6: return self->cy; + case 5: return mp_obj_new_int(fast_roundf(mp_obj_get_float(self->cx))); + case 6: return mp_obj_new_int(fast_roundf(mp_obj_get_float(self->cy))); case 7: return self->rotation; case 8: return self->code; case 9: return self->count; + case 10: return self->perimeter; + case 11: return self->roundness; } } return MP_OBJ_NULL; // op not supported } +mp_obj_t py_blob_corners(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->corners; } +mp_obj_t py_blob_min_corners(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->min_corners; } mp_obj_t py_blob_rect(mp_obj_t self_in) { return mp_obj_new_tuple(4, (mp_obj_t []) {((py_blob_obj_t *) self_in)->x, @@ -3808,20 +3887,261 @@ mp_obj_t py_blob_y(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->y; } mp_obj_t py_blob_w(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->w; } mp_obj_t py_blob_h(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->h; } mp_obj_t py_blob_pixels(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->pixels; } -mp_obj_t py_blob_cx(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->cx; } -mp_obj_t py_blob_cy(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->cy; } +mp_obj_t py_blob_cx(mp_obj_t self_in) { return mp_obj_new_int(fast_roundf(mp_obj_get_float(((py_blob_obj_t *) self_in)->cx))); } +mp_obj_t py_blob_cxf(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->cx; } +mp_obj_t py_blob_cy(mp_obj_t self_in) { return mp_obj_new_int(fast_roundf(mp_obj_get_float(((py_blob_obj_t *) self_in)->cy))); } +mp_obj_t py_blob_cyf(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->cy; } mp_obj_t py_blob_rotation(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->rotation; } +mp_obj_t py_blob_rotation_deg(mp_obj_t self_in) { return mp_obj_new_int(IM_RAD2DEG(mp_obj_get_float(((py_blob_obj_t *) self_in)->rotation))); } +mp_obj_t py_blob_rotation_rad(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->rotation; } mp_obj_t py_blob_code(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->code; } mp_obj_t py_blob_count(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->count; } +mp_obj_t py_blob_perimeter(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->perimeter; } +mp_obj_t py_blob_roundness(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->roundness; } +mp_obj_t py_blob_elongation(mp_obj_t self_in) { return mp_obj_new_float(1 - mp_obj_get_float(((py_blob_obj_t *) self_in)->roundness)); } mp_obj_t py_blob_area(mp_obj_t self_in) { return mp_obj_new_int(mp_obj_get_int(((py_blob_obj_t *) self_in)->w) * mp_obj_get_int(((py_blob_obj_t *) self_in)->h)); } mp_obj_t py_blob_density(mp_obj_t self_in) { int area = mp_obj_get_int(((py_blob_obj_t *) self_in)->w) * mp_obj_get_int(((py_blob_obj_t *) self_in)->h); - if (area) return mp_obj_new_float(mp_obj_get_int(((py_blob_obj_t *) self_in)->pixels) / ((float) area)); - return mp_obj_new_float(0.0f); + int pixels = mp_obj_get_int(((py_blob_obj_t *) self_in)->pixels); + return mp_obj_new_float(IM_DIV(pixels, ((float) area))); +} +// Rect-area versus pixels (e.g. blob area) -> Above. +// Rect-area versus perimeter -> Basically the same as the above with a different scale factor. +// Rect-perimeter versus pixels (e.g. blob area) -> Basically the same as the above with a different scale factor. +// Rect-perimeter versus perimeter -> Basically the same as the above with a different scale factor. +mp_obj_t py_blob_compactness(mp_obj_t self_in) { + int pixels = mp_obj_get_int(((py_blob_obj_t *) self_in)->pixels); + float perimeter = mp_obj_get_int(((py_blob_obj_t *) self_in)->perimeter); + return mp_obj_new_float(IM_DIV((pixels * 4 * M_PI), (perimeter * perimeter))); +} +mp_obj_t py_blob_solidity(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + // Shoelace Formula + float min_area = (((x0*y1)+(x1*y2)+(x2*y3)+(x3*y0))-((y0*x1)+(y1*x2)+(y2*x3)+(y3*x0)))/2.0f; + int pixels = mp_obj_get_int(((py_blob_obj_t *) self_in)->pixels); + return mp_obj_new_float(IM_MIN(IM_DIV(pixels, min_area), 1)); +} +mp_obj_t py_blob_convexity(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + float d0 = fast_sqrtf(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1))); + float d1 = fast_sqrtf(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2))); + float d2 = fast_sqrtf(((x2 - x3) * (x2 - x3)) + ((y2 - y3) * (y2 - y3))); + float d3 = fast_sqrtf(((x3 - x0) * (x3 - x0)) + ((y3 - y0) * (y3 - y0))); + int perimeter = mp_obj_get_int(((py_blob_obj_t *) self_in)->perimeter); + return mp_obj_new_float(IM_MIN(IM_DIV(d0 + d1 + d2 + d3, perimeter), 1)); +} +// Min rect-area versus pixels (e.g. blob area) -> Above. +// Min rect-area versus perimeter -> Basically the same as the above with a different scale factor. +// Min rect-perimeter versus pixels (e.g. blob area) -> Basically the same as the above with a different scale factor. +// Min rect-perimeter versus perimeter -> Above +mp_obj_t py_blob_x_hist_bins(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->x_hist_bins; } +mp_obj_t py_blob_y_hist_bins(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->y_hist_bins; } +mp_obj_t py_blob_major_axis_line(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = fast_sqrtf(((m0x - m2x) * (m0x - m2x)) + ((m0y - m2y) * (m0y - m2y))); + float l1 = fast_sqrtf(((m1x - m3x) * (m1x - m3x)) + ((m1y - m3y) * (m1y - m3y))); + + if (l0 >= l1) { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m0x), + mp_obj_new_int(m0y), + mp_obj_new_int(m2x), + mp_obj_new_int(m2y)}); + } else { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m1x), + mp_obj_new_int(m1y), + mp_obj_new_int(m3x), + mp_obj_new_int(m3y)}); + } +} +mp_obj_t py_blob_minor_axis_line(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = fast_sqrtf(((m0x - m2x) * (m0x - m2x)) + ((m0y - m2y) * (m0y - m2y))); + float l1 = fast_sqrtf(((m1x - m3x) * (m1x - m3x)) + ((m1y - m3y) * (m1y - m3y))); + + if (l0 < l1) { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m0x), + mp_obj_new_int(m0y), + mp_obj_new_int(m2x), + mp_obj_new_int(m2y)}); + } else { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m1x), + mp_obj_new_int(m1y), + mp_obj_new_int(m3x), + mp_obj_new_int(m3y)}); + } +} +mp_obj_t py_blob_enclosing_circle(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = fast_sqrtf(((x0 - cx) * (x0 - cx)) + ((y0 - cy) * (y0 - cy))); + float d1 = fast_sqrtf(((x1 - cx) * (x1 - cx)) + ((y1 - cy) * (y1 - cy))); + float d2 = fast_sqrtf(((x2 - cx) * (x2 - cx)) + ((y2 - cy) * (y2 - cy))); + float d3 = fast_sqrtf(((x3 - cx) * (x3 - cx)) + ((y3 - cy) * (y3 - cy))); + float d = IM_MAX(d0, IM_MAX(d1, IM_MAX(d2, d3))); + + return mp_obj_new_tuple(3, (mp_obj_t []) {mp_obj_new_int(cx), + mp_obj_new_int(cy), + mp_obj_new_int(fast_roundf(d))}); +} +mp_obj_t py_blob_enclosed_ellipse(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = fast_sqrtf(((m0x - cx) * (m0x - cx)) + ((m0y - cy) * (m0y - cy))); + float d1 = fast_sqrtf(((m1x - cx) * (m1x - cx)) + ((m1y - cy) * (m1y - cy))); + float d2 = fast_sqrtf(((m2x - cx) * (m2x - cx)) + ((m2y - cy) * (m2y - cy))); + float d3 = fast_sqrtf(((m3x - cx) * (m3x - cx)) + ((m3y - cy) * (m3y - cy))); + float a = IM_MIN(d0, d2); + float b = IM_MIN(d1, d3); + + float l0 = fast_sqrtf(((m0x - m2x) * (m0x - m2x)) + ((m0y - m2y) * (m0y - m2y))); + float l1 = fast_sqrtf(((m1x - m3x) * (m1x - m3x)) + ((m1y - m3y) * (m1y - m3y))); + + float r; + + if (l0 >= l1) { + r = IM_RAD2DEG(fast_atan2f(m0y - m2y, m0x - m2x)); + } else { + r = IM_RAD2DEG(fast_atan2f(m1y - m3y, m1x - m3x) + M_PI_2); + } + + return mp_obj_new_tuple(5, (mp_obj_t []) {mp_obj_new_int(cx), + mp_obj_new_int(cy), + mp_obj_new_int(a), + mp_obj_new_int(b), + mp_obj_new_int(r)}); } +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_corners_obj, py_blob_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_min_corners_obj, py_blob_min_corners); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rect_obj, py_blob_rect); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_x_obj, py_blob_x); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_y_obj, py_blob_y); @@ -3829,14 +4149,32 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_w_obj, py_blob_w); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_h_obj, py_blob_h); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_pixels_obj, py_blob_pixels); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cx_obj, py_blob_cx); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cxf_obj, py_blob_cxf); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cy_obj, py_blob_cy); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cyf_obj, py_blob_cyf); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_obj, py_blob_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_deg_obj, py_blob_rotation_deg); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_rad_obj, py_blob_rotation_rad); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_code_obj, py_blob_code); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_count_obj, py_blob_count); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_perimeter_obj, py_blob_perimeter); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_roundness_obj, py_blob_roundness); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_elongation_obj, py_blob_elongation); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_area_obj, py_blob_area); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_density_obj, py_blob_density); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_compactness_obj, py_blob_compactness); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_solidity_obj, py_blob_solidity); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_convexity_obj, py_blob_convexity); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_x_hist_bins_obj, py_blob_x_hist_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_y_hist_bins_obj, py_blob_y_hist_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_major_axis_line_obj, py_blob_major_axis_line); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_minor_axis_line_obj, py_blob_minor_axis_line); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_enclosing_circle_obj, py_blob_enclosing_circle); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_enclosed_ellipse_obj, py_blob_enclosed_ellipse); STATIC const mp_rom_map_elem_t py_blob_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_blob_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_min_corners), MP_ROM_PTR(&py_blob_min_corners_obj) }, { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_blob_rect_obj) }, { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_blob_x_obj) }, { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_blob_y_obj) }, @@ -3844,12 +4182,29 @@ STATIC const mp_rom_map_elem_t py_blob_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_blob_h_obj) }, { MP_ROM_QSTR(MP_QSTR_pixels), MP_ROM_PTR(&py_blob_pixels_obj) }, { MP_ROM_QSTR(MP_QSTR_cx), MP_ROM_PTR(&py_blob_cx_obj) }, + { MP_ROM_QSTR(MP_QSTR_cxf), MP_ROM_PTR(&py_blob_cxf_obj) }, { MP_ROM_QSTR(MP_QSTR_cy), MP_ROM_PTR(&py_blob_cy_obj) }, + { MP_ROM_QSTR(MP_QSTR_cyf), MP_ROM_PTR(&py_blob_cyf_obj) }, { MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&py_blob_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation_deg), MP_ROM_PTR(&py_blob_rotation_deg_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation_rad), MP_ROM_PTR(&py_blob_rotation_rad_obj) }, { MP_ROM_QSTR(MP_QSTR_code), MP_ROM_PTR(&py_blob_code_obj) }, { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&py_blob_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_perimeter), MP_ROM_PTR(&py_blob_perimeter_obj) }, + { MP_ROM_QSTR(MP_QSTR_roundness), MP_ROM_PTR(&py_blob_roundness_obj) }, + { MP_ROM_QSTR(MP_QSTR_elongation), MP_ROM_PTR(&py_blob_elongation_obj) }, { MP_ROM_QSTR(MP_QSTR_area), MP_ROM_PTR(&py_blob_area_obj) } , - { MP_ROM_QSTR(MP_QSTR_density), MP_ROM_PTR(&py_blob_density_obj) } + { MP_ROM_QSTR(MP_QSTR_density), MP_ROM_PTR(&py_blob_density_obj) }, + { MP_ROM_QSTR(MP_QSTR_extent), MP_ROM_PTR(&py_blob_density_obj) }, + { MP_ROM_QSTR(MP_QSTR_compactness), MP_ROM_PTR(&py_blob_compactness_obj) }, + { MP_ROM_QSTR(MP_QSTR_solidity), MP_ROM_PTR(&py_blob_solidity_obj) }, + { MP_ROM_QSTR(MP_QSTR_convexity), MP_ROM_PTR(&py_blob_convexity_obj) }, + { MP_ROM_QSTR(MP_QSTR_x_hist_bins), MP_ROM_PTR(&py_blob_x_hist_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_y_hist_bins), MP_ROM_PTR(&py_blob_y_hist_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_major_axis_line), MP_ROM_PTR(&py_blob_major_axis_line_obj) }, + { MP_ROM_QSTR(MP_QSTR_minor_axis_line), MP_ROM_PTR(&py_blob_minor_axis_line_obj) }, + { MP_ROM_QSTR(MP_QSTR_enclosing_circle), MP_ROM_PTR(&py_blob_enclosing_circle_obj) }, + { MP_ROM_QSTR(MP_QSTR_enclosed_ellipse), MP_ROM_PTR(&py_blob_enclosed_ellipse_obj) } }; STATIC MP_DEFINE_CONST_DICT(py_blob_locals_dict, py_blob_locals_dict_table); @@ -3866,16 +4221,44 @@ static bool py_image_find_blobs_threshold_cb(void *fun_obj, find_blobs_list_lnk_ { py_blob_obj_t *o = m_new_obj(py_blob_obj_t); o->base.type = &py_blob_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_corners[4]; + point_min_area_rectangle(blob->corners, min_corners, FIND_BLOBS_CORNERS_RESOLUTION); + o->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[0].x), mp_obj_new_int(min_corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[1].x), mp_obj_new_int(min_corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[2].x), mp_obj_new_int(min_corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[3].x), mp_obj_new_int(min_corners[3].y)})}); o->x = mp_obj_new_int(blob->rect.x); o->y = mp_obj_new_int(blob->rect.y); o->w = mp_obj_new_int(blob->rect.w); o->h = mp_obj_new_int(blob->rect.h); o->pixels = mp_obj_new_int(blob->pixels); - o->cx = mp_obj_new_int(blob->centroid.x); - o->cy = mp_obj_new_int(blob->centroid.y); + o->cx = mp_obj_new_float(blob->centroid_x); + o->cy = mp_obj_new_float(blob->centroid_y); o->rotation = mp_obj_new_float(blob->rotation); o->code = mp_obj_new_int(blob->code); o->count = mp_obj_new_int(blob->count); + o->perimeter = mp_obj_new_int(blob->perimeter); + o->roundness = mp_obj_new_float(blob->roundness); + o->x_hist_bins = mp_obj_new_list(blob->x_hist_bins_count, NULL); + o->y_hist_bins = mp_obj_new_list(blob->y_hist_bins_count, NULL); + + for (int i = 0; i < blob->x_hist_bins_count; i++) { + ((mp_obj_list_t *) o->x_hist_bins)->items[i] = mp_obj_new_int(blob->x_hist_bins[i]); + } + + for (int i = 0; i < blob->y_hist_bins_count; i++) { + ((mp_obj_list_t *) o->y_hist_bins)->items[i] = mp_obj_new_int(blob->y_hist_bins[i]); + } return mp_obj_is_true(mp_call_function_1(fun_obj, o)); } @@ -3884,29 +4267,85 @@ static bool py_image_find_blobs_merge_cb(void *fun_obj, find_blobs_list_lnk_data { py_blob_obj_t *o0 = m_new_obj(py_blob_obj_t); o0->base.type = &py_blob_type; + o0->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_area_rect_corners0[4]; + point_min_area_rectangle(blob0->corners, min_area_rect_corners0, FIND_BLOBS_CORNERS_RESOLUTION); + o0->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[0].x), mp_obj_new_int(min_area_rect_corners0[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[1].x), mp_obj_new_int(min_area_rect_corners0[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[2].x), mp_obj_new_int(min_area_rect_corners0[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[3].x), mp_obj_new_int(min_area_rect_corners0[3].y)})}); o0->x = mp_obj_new_int(blob0->rect.x); o0->y = mp_obj_new_int(blob0->rect.y); o0->w = mp_obj_new_int(blob0->rect.w); o0->h = mp_obj_new_int(blob0->rect.h); o0->pixels = mp_obj_new_int(blob0->pixels); - o0->cx = mp_obj_new_int(blob0->centroid.x); - o0->cy = mp_obj_new_int(blob0->centroid.y); + o0->cx = mp_obj_new_float(blob0->centroid_x); + o0->cy = mp_obj_new_float(blob0->centroid_y); o0->rotation = mp_obj_new_float(blob0->rotation); o0->code = mp_obj_new_int(blob0->code); o0->count = mp_obj_new_int(blob0->count); + o0->perimeter = mp_obj_new_int(blob0->perimeter); + o0->roundness = mp_obj_new_float(blob0->roundness); + o0->x_hist_bins = mp_obj_new_list(blob0->x_hist_bins_count, NULL); + o0->y_hist_bins = mp_obj_new_list(blob0->y_hist_bins_count, NULL); + + for (int i = 0; i < blob0->x_hist_bins_count; i++) { + ((mp_obj_list_t *) o0->x_hist_bins)->items[i] = mp_obj_new_int(blob0->x_hist_bins[i]); + } + + for (int i = 0; i < blob0->y_hist_bins_count; i++) { + ((mp_obj_list_t *) o0->y_hist_bins)->items[i] = mp_obj_new_int(blob0->y_hist_bins[i]); + } py_blob_obj_t *o1 = m_new_obj(py_blob_obj_t); o1->base.type = &py_blob_type; + o1->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_area_rect_corners1[4]; + point_min_area_rectangle(blob1->corners, min_area_rect_corners1, FIND_BLOBS_CORNERS_RESOLUTION); + o1->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[0].x), mp_obj_new_int(min_area_rect_corners1[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[1].x), mp_obj_new_int(min_area_rect_corners1[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[2].x), mp_obj_new_int(min_area_rect_corners1[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[3].x), mp_obj_new_int(min_area_rect_corners1[3].y)})}); o1->x = mp_obj_new_int(blob1->rect.x); o1->y = mp_obj_new_int(blob1->rect.y); o1->w = mp_obj_new_int(blob1->rect.w); o1->h = mp_obj_new_int(blob1->rect.h); o1->pixels = mp_obj_new_int(blob1->pixels); - o1->cx = mp_obj_new_int(blob1->centroid.x); - o1->cy = mp_obj_new_int(blob1->centroid.y); + o1->cx = mp_obj_new_float(blob1->centroid_x); + o1->cy = mp_obj_new_float(blob1->centroid_y); o1->rotation = mp_obj_new_float(blob1->rotation); o1->code = mp_obj_new_int(blob1->code); o1->count = mp_obj_new_int(blob1->count); + o1->perimeter = mp_obj_new_int(blob1->perimeter); + o1->roundness = mp_obj_new_float(blob1->roundness); + o1->x_hist_bins = mp_obj_new_list(blob1->x_hist_bins_count, NULL); + o1->y_hist_bins = mp_obj_new_list(blob1->y_hist_bins_count, NULL); + + for (int i = 0; i < blob1->x_hist_bins_count; i++) { + ((mp_obj_list_t *) o1->x_hist_bins)->items[i] = mp_obj_new_int(blob1->x_hist_bins[i]); + } + + for (int i = 0; i < blob1->y_hist_bins_count; i++) { + ((mp_obj_list_t *) o1->y_hist_bins)->items[i] = mp_obj_new_int(blob1->y_hist_bins[i]); + } return mp_obj_is_true(mp_call_function_2(fun_obj, o0, o1)); } @@ -3924,22 +4363,34 @@ static mp_obj_t py_image_find_blobs(uint n_args, const mp_obj_t *args, mp_map_t rectangle_t roi; py_helper_keyword_rectangle_roi(arg_img, n_args, args, 3, kw_args, &roi); - unsigned int x_stride = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_stride), 2); + unsigned int x_stride = + py_helper_keyword_int(n_args, args, 4, 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_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_stride), 1); + unsigned int y_stride = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_stride), 1); PY_ASSERT_TRUE_MSG(y_stride > 0, "y_stride must not be zero."); - unsigned int area_threshold = py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_area_threshold), 10); - unsigned int pixels_threshold = py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_pixels_threshold), 10); - bool merge = py_helper_keyword_int(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_merge), false); - int margin = py_helper_keyword_int(n_args, args, 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_margin), 0); - mp_obj_t threshold_cb = py_helper_keyword_object(n_args, args, 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold_cb)); - mp_obj_t merge_cb = py_helper_keyword_object(n_args, args, 11, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_merge_cb)); + unsigned int area_threshold = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_area_threshold), 10); + unsigned int pixels_threshold = + py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_pixels_threshold), 10); + bool merge = + py_helper_keyword_int(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_merge), false); + int margin = + py_helper_keyword_int(n_args, args, 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_margin), 0); + mp_obj_t threshold_cb = + py_helper_keyword_object(n_args, args, 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold_cb)); + mp_obj_t merge_cb = + py_helper_keyword_object(n_args, args, 11, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_merge_cb)); + unsigned int x_hist_bins_max = + py_helper_keyword_int(n_args, args, 12, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_hist_bins_max), 0); + unsigned int y_hist_bins_max = + py_helper_keyword_int(n_args, args, 13, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_hist_bins_max), 0); list_t out; fb_alloc_mark(); imlib_find_blobs(&out, arg_img, &roi, x_stride, y_stride, &thresholds, invert, area_threshold, pixels_threshold, merge, margin, - py_image_find_blobs_threshold_cb, threshold_cb, py_image_find_blobs_merge_cb, merge_cb); + py_image_find_blobs_threshold_cb, threshold_cb, py_image_find_blobs_merge_cb, merge_cb, x_hist_bins_max, y_hist_bins_max); fb_alloc_free_till_mark(); list_free(&thresholds); @@ -3950,18 +4401,48 @@ static mp_obj_t py_image_find_blobs(uint n_args, const mp_obj_t *args, mp_map_t py_blob_obj_t *o = m_new_obj(py_blob_obj_t); o->base.type = &py_blob_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[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_corners[4]; + point_min_area_rectangle(lnk_data.corners, min_corners, FIND_BLOBS_CORNERS_RESOLUTION); + o->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[0].x), mp_obj_new_int(min_corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[1].x), mp_obj_new_int(min_corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[2].x), mp_obj_new_int(min_corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[3].x), mp_obj_new_int(min_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->pixels = mp_obj_new_int(lnk_data.pixels); - o->cx = mp_obj_new_int(lnk_data.centroid.x); - o->cy = mp_obj_new_int(lnk_data.centroid.y); + o->cx = mp_obj_new_float(lnk_data.centroid_x); + o->cy = mp_obj_new_float(lnk_data.centroid_y); o->rotation = mp_obj_new_float(lnk_data.rotation); o->code = mp_obj_new_int(lnk_data.code); o->count = mp_obj_new_int(lnk_data.count); + o->perimeter = mp_obj_new_int(lnk_data.perimeter); + o->roundness = mp_obj_new_float(lnk_data.roundness); + o->x_hist_bins = mp_obj_new_list(lnk_data.x_hist_bins_count, NULL); + o->y_hist_bins = mp_obj_new_list(lnk_data.y_hist_bins_count, NULL); + + for (int i = 0; i < lnk_data.x_hist_bins_count; i++) { + ((mp_obj_list_t *) o->x_hist_bins)->items[i] = mp_obj_new_int(lnk_data.x_hist_bins[i]); + } + + for (int i = 0; i < lnk_data.y_hist_bins_count; i++) { + ((mp_obj_list_t *) o->y_hist_bins)->items[i] = mp_obj_new_int(lnk_data.y_hist_bins[i]); + } objects_list->items[i] = o; + if (lnk_data.x_hist_bins) xfree(lnk_data.x_hist_bins); + if (lnk_data.y_hist_bins) xfree(lnk_data.y_hist_bins); } return objects_list; @@ -5117,6 +5598,7 @@ static mp_obj_t py_image_find_displacement(uint n_args, const mp_obj_t *args, mp STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_displacement_obj, 2, py_image_find_displacement); #endif // IMLIB_ENABLE_FIND_DISPLACEMENT +#ifdef IMLIB_FIND_TEMPLATE static mp_obj_t py_image_find_template(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image_grayscale(args[0]); @@ -5158,6 +5640,7 @@ static mp_obj_t py_image_find_template(uint n_args, const mp_obj_t *args, mp_map return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_template_obj, 3, py_image_find_template); +#endif // IMLIB_FIND_TEMPLATE static mp_obj_t py_image_find_features(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { @@ -5212,6 +5695,7 @@ static mp_obj_t py_image_find_eye(uint n_args, const mp_obj_t *args, mp_map_t *k } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_eye_obj, 2, py_image_find_eye); +#ifdef IMLIB_ENABLE_FIND_LBP static mp_obj_t py_image_find_lbp(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image_grayscale(args[0]); @@ -5225,7 +5709,9 @@ static mp_obj_t py_image_find_lbp(uint n_args, const mp_obj_t *args, mp_map_t *k return lbp_obj; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_lbp_obj, 2, py_image_find_lbp); +#endif // IMLIB_ENABLE_FIND_LBP +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS static mp_obj_t py_image_find_keypoints(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); @@ -5263,6 +5749,7 @@ static mp_obj_t py_image_find_keypoints(uint n_args, const mp_obj_t *args, mp_ma return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_keypoints_obj, 1, py_image_find_keypoints); +#endif // IMLIB_ENABLE_FIND_KEYPOINTS #ifdef IMLIB_ENABLE_BINARY_OPS static mp_obj_t py_image_find_edges(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) @@ -5355,10 +5842,20 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_image_size_obj)}, {MP_ROM_QSTR(MP_QSTR_get_pixel), MP_ROM_PTR(&py_image_get_pixel_obj)}, {MP_ROM_QSTR(MP_QSTR_set_pixel), MP_ROM_PTR(&py_image_set_pixel_obj)}, +#ifdef IMLIB_ENABLE_MEAN_POOLING {MP_ROM_QSTR(MP_QSTR_mean_pool), MP_ROM_PTR(&py_image_mean_pool_obj)}, {MP_ROM_QSTR(MP_QSTR_mean_pooled), MP_ROM_PTR(&py_image_mean_pooled_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_mean_pool), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_mean_pooled), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_MIDPOINT_POOLING {MP_ROM_QSTR(MP_QSTR_midpoint_pool), MP_ROM_PTR(&py_image_midpoint_pool_obj)}, {MP_ROM_QSTR(MP_QSTR_midpoint_pooled), MP_ROM_PTR(&py_image_midpoint_pooled_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_midpoint_pool), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_midpoint_pooled), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif {MP_ROM_QSTR(MP_QSTR_to_bitmap), MP_ROM_PTR(&py_image_to_bitmap_obj)}, {MP_ROM_QSTR(MP_QSTR_to_grayscale), MP_ROM_PTR(&py_image_to_grayscale_obj)}, {MP_ROM_QSTR(MP_QSTR_to_rgb565), MP_ROM_PTR(&py_image_to_rgb565_obj)}, @@ -5378,6 +5875,7 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_draw_string), MP_ROM_PTR(&py_image_draw_string_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_cross), MP_ROM_PTR(&py_image_draw_cross_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_arrow), MP_ROM_PTR(&py_image_draw_arrow_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_edges), MP_ROM_PTR(&py_image_draw_edges_obj)}, {MP_ROM_QSTR(MP_QSTR_draw_image), MP_ROM_PTR(&py_image_draw_image_obj)}, #ifdef IMLIB_ENABLE_FLOOD_FILL {MP_ROM_QSTR(MP_QSTR_flood_fill), MP_ROM_PTR(&py_image_flood_fill_obj)}, @@ -5602,11 +6100,23 @@ static const mp_rom_map_elem_t locals_dict_table[] = { #else {MP_ROM_QSTR(MP_QSTR_find_displacement), MP_ROM_PTR(&py_func_unavailable_obj)}, #endif +#ifdef IMLIB_FIND_TEMPLATE {MP_ROM_QSTR(MP_QSTR_find_template), MP_ROM_PTR(&py_image_find_template_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_template), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif {MP_ROM_QSTR(MP_QSTR_find_features), MP_ROM_PTR(&py_image_find_features_obj)}, {MP_ROM_QSTR(MP_QSTR_find_eye), MP_ROM_PTR(&py_image_find_eye_obj)}, +#ifdef IMLIB_ENABLE_FIND_LBP {MP_ROM_QSTR(MP_QSTR_find_lbp), MP_ROM_PTR(&py_image_find_lbp_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_lbp), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS {MP_ROM_QSTR(MP_QSTR_find_keypoints), MP_ROM_PTR(&py_image_find_keypoints_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_keypoints), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif #ifdef IMLIB_ENABLE_BINARY_OPS {MP_ROM_QSTR(MP_QSTR_find_edges), MP_ROM_PTR(&py_image_find_edges_obj)}, #else @@ -5976,6 +6486,7 @@ mp_obj_t py_image_load_cascade(uint n_args, const mp_obj_t *args, mp_map_t *kw_a } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_load_cascade_obj, 1, py_image_load_cascade); +#ifdef IMLIB_ENABLE_DESCRIPTOR mp_obj_t py_image_load_descriptor(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { FIL fp; @@ -6169,6 +6680,7 @@ static mp_obj_t py_image_match_descriptor(uint n_args, const mp_obj_t *args, mp_ return match_obj; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_match_descriptor_obj, 2, py_image_match_descriptor); +#endif // IMLIB_ENABLE_DESCRIPTOR int py_image_descriptor_from_roi(image_t *img, const char *path, rectangle_t *roi) { @@ -6194,8 +6706,10 @@ int py_image_descriptor_from_roi(image_t *img, const char *path, rectangle_t *ro static const mp_rom_map_elem_t globals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_image)}, +#ifdef IMLIB_FIND_TEMPLATE {MP_ROM_QSTR(MP_QSTR_SEARCH_EX), MP_ROM_INT(SEARCH_EX)}, {MP_ROM_QSTR(MP_QSTR_SEARCH_DS), MP_ROM_INT(SEARCH_DS)}, +#endif {MP_ROM_QSTR(MP_QSTR_EDGE_CANNY), MP_ROM_INT(EDGE_CANNY)}, {MP_ROM_QSTR(MP_QSTR_EDGE_SIMPLE), MP_ROM_INT(EDGE_SIMPLE)}, {MP_ROM_QSTR(MP_QSTR_CORNER_FAST), MP_ROM_INT(CORNER_FAST)}, @@ -6234,9 +6748,15 @@ static const mp_rom_map_elem_t globals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_grayscale_to_rgb), MP_ROM_PTR(&py_image_grayscale_to_rgb_obj)}, {MP_ROM_QSTR(MP_QSTR_Image), MP_ROM_PTR(&py_image_load_image_obj)}, {MP_ROM_QSTR(MP_QSTR_HaarCascade), MP_ROM_PTR(&py_image_load_cascade_obj)}, +#ifdef IMLIB_ENABLE_DESCRIPTOR {MP_ROM_QSTR(MP_QSTR_load_descriptor), MP_ROM_PTR(&py_image_load_descriptor_obj)}, {MP_ROM_QSTR(MP_QSTR_save_descriptor), MP_ROM_PTR(&py_image_save_descriptor_obj)}, {MP_ROM_QSTR(MP_QSTR_match_descriptor), MP_ROM_PTR(&py_image_match_descriptor_obj)} +#else + {MP_ROM_QSTR(MP_QSTR_load_descriptor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_save_descriptor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_match_descriptor), MP_ROM_PTR(&py_func_unavailable_obj)} +#endif }; STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 9757e1997..f72b08dee 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -428,6 +428,13 @@ Q(draw_arrow) // duplicate Q(size) // duplicate Q(thickness) +// Draw Edges +Q(draw_edges) +// duplicate Q(color) +// duplicate Q(size) +// duplicate Q(thickness) +// duplicate Q(fill) + // Draw Image Q(draw_image) Q(x_scale) @@ -811,8 +818,12 @@ Q(merge) Q(margin) Q(threshold_cb) Q(merge_cb) +Q(x_hist_bins_max) +Q(y_hist_bins_max) // Blob Object Q(blob) +Q(corners) +Q(min_corners) Q(rect) Q(x) Q(y) @@ -820,12 +831,29 @@ Q(w) Q(h) Q(pixels) Q(cx) +Q(cxf) Q(cy) +Q(cyf) // duplicate Q(rotation) +Q(rotation_deg) +Q(rotation_rad) Q(code) Q(count) +Q(perimeter) +Q(roundness) +Q(elongation) Q(area) Q(density) +Q(extent) +Q(compactness) +Q(solidity) +Q(convexity) +Q(x_hist_bins) +Q(y_hist_bins) +Q(major_axis_line) +Q(minor_axis_line) +Q(enclosing_circle) +Q(enclosed_ellipse) // Find Lines Q(find_lines) @@ -868,7 +896,7 @@ Q(find_rects) // duplicate Q(threshold) // Rect Object // duplicate Q(rect) -Q(corners) +// duplicate Q(corners) // duplicate Q(rect) // duplicate Q(x) // duplicate Q(y)