diff --git a/src/omv/img/filter.c b/src/omv/img/filter.c index 3af2a6e26..f60ec7366 100644 --- a/src/omv/img/filter.c +++ b/src/omv/img/filter.c @@ -1409,3 +1409,175 @@ void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, fl } } } + +typedef struct imlib_cartoon_filter_mean_state { + int r_acc, g_acc, b_acc, pixels; +} imlib_cartoon_filter_mean_state_t; + +static void imlib_cartoon_filter_mean(image_t *img, int line, int l, int r, void *data) +{ + imlib_cartoon_filter_mean_state_t *state = (imlib_cartoon_filter_mean_state_t *) data; + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + state->g_acc += IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, i); + state->pixels += 1; + } + break; + } + case IMAGE_BPP_GRAYSCALE: { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + state->g_acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, i); + state->pixels += 1; + } + break; + } + case IMAGE_BPP_RGB565: { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, i); + state->r_acc += COLOR_RGB565_TO_R5(pixel); + state->g_acc += COLOR_RGB565_TO_G6(pixel); + state->b_acc += COLOR_RGB565_TO_B5(pixel); + state->pixels += 1; + } + break; + } + default: { + break; + } + } +} + +typedef struct imlib_cartoon_filter_fill_state { + int mean; +} imlib_cartoon_filter_fill_state_t; + +static void imlib_cartoon_filter_fill(image_t *img, int line, int l, int r, void *data) +{ + imlib_cartoon_filter_fill_state_t *state = (imlib_cartoon_filter_fill_state_t *) data; + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, i, state->mean); + } + break; + } + case IMAGE_BPP_GRAYSCALE: { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, i, state->mean); + } + break; + } + case IMAGE_BPP_RGB565: { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, i, state->mean); + } + break; + } + default: { + break; + } + } +} + +void imlib_cartoon_filter(image_t *img, float seed_threshold, float floating_threshold, image_t *mask) +{ + image_t mean_image, fill_image; + + mean_image.w = img->w; + mean_image.h = img->h; + mean_image.bpp = IMAGE_BPP_BINARY; + mean_image.data = fb_alloc0(image_size(&mean_image)); + + fill_image.w = img->w; + fill_image.h = img->h; + fill_image.bpp = IMAGE_BPP_BINARY; + fill_image.data = fb_alloc0(image_size(&fill_image)); + + if (mask) { + for (int y = 0, yy = fill_image.h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&fill_image, y); + for (int x = 0, xx = fill_image.w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y)) IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x); + } + } + } + + int color_seed_threshold = 0; + int color_floating_threshold = 0; + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + color_seed_threshold = fast_roundf(seed_threshold * COLOR_BINARY_MAX); + color_floating_threshold = fast_roundf(floating_threshold * COLOR_BINARY_MAX); + break; + } + case IMAGE_BPP_GRAYSCALE: { + color_seed_threshold = fast_roundf(seed_threshold * COLOR_GRAYSCALE_MAX); + color_floating_threshold = fast_roundf(floating_threshold * COLOR_GRAYSCALE_MAX); + break; + } + case IMAGE_BPP_RGB565: { + color_seed_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_roundf(seed_threshold * COLOR_R5_MAX), + fast_roundf(seed_threshold * COLOR_G6_MAX), + fast_roundf(seed_threshold * COLOR_B5_MAX)); + color_floating_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_roundf(floating_threshold * COLOR_R5_MAX), + fast_roundf(floating_threshold * COLOR_G6_MAX), + fast_roundf(floating_threshold * COLOR_B5_MAX)); + break; + } + default: { + break; + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&mean_image, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (!IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) { + + imlib_cartoon_filter_mean_state_t mean_state; + memset(&mean_state, 0, sizeof(imlib_cartoon_filter_mean_state_t)); + imlib_flood_fill_int(&mean_image, img, x, y, color_seed_threshold, color_floating_threshold, + imlib_cartoon_filter_mean, &mean_state); + + imlib_cartoon_filter_fill_state_t fill_state; + memset(&fill_state, 0, sizeof(imlib_cartoon_filter_fill_state_t)); + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + fill_state.mean = mean_state.g_acc / mean_state.pixels; + break; + } + case IMAGE_BPP_GRAYSCALE: { + fill_state.mean = mean_state.g_acc / mean_state.pixels; + break; + } + case IMAGE_BPP_RGB565: { + fill_state.mean = COLOR_R5_G6_B5_TO_RGB565(mean_state.r_acc / mean_state.pixels, + mean_state.g_acc / mean_state.pixels, + mean_state.b_acc / mean_state.pixels); + break; + } + default: { + break; + } + } + + imlib_flood_fill_int(&fill_image, img, x, y, color_seed_threshold, color_floating_threshold, + imlib_cartoon_filter_fill, &fill_state); + } + } + } + + fb_free(); + fb_free(); +} diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 2c72bdb3d..80b6a36b7 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1288,6 +1288,7 @@ void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset void imlib_midpoint_filter(image_t *img, const int ksize, float bias, bool threshold, int offset, bool invert, image_t *mask); void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, const int b, bool threshold, int offset, bool invert, image_t *mask); void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, float space_sigma, bool threshold, int offset, bool invert, image_t *mask); +void imlib_cartoon_filter(image_t *img, float seed_threshold, float floating_threshold, image_t *mask); // Image Correction void imlib_logpolar_int(image_t *dst, image_t *src, rectangle_t *roi, bool linear, bool reverse); // helper/internal void imlib_logpolar(image_t *img, bool linear, bool reverse); diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 46eef4b33..20d8fcda4 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -2054,6 +2054,28 @@ STATIC mp_obj_t py_image_bilateral(uint n_args, const mp_obj_t *args, mp_map_t * } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_bilateral_obj, 2, py_image_bilateral); +STATIC mp_obj_t py_image_cartoon(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]); + float arg_seed_threshold = + py_helper_keyword_float(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_seed_threshold), 0.05); + PY_ASSERT_TRUE_MSG((0.0f <= arg_seed_threshold) && (arg_seed_threshold <= 1.0f), + "Error: 0.0 <= seed_threshold <= 1.0!"); + float arg_floating_threshold = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_floating_threshold), 0.05); + PY_ASSERT_TRUE_MSG((0.0f <= arg_floating_threshold) && (arg_floating_threshold <= 1.0f), + "Error: 0.0 <= floating_threshold <= 1.0!"); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_cartoon_filter(arg_img, arg_seed_threshold, arg_floating_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_cartoon_obj, 1, py_image_cartoon); + ///////////////////////// // Shadow Removal Methods ///////////////////////// @@ -4984,6 +5006,7 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_gaussian_blur), MP_ROM_PTR(&py_image_gaussian_obj)}, {MP_ROM_QSTR(MP_QSTR_laplacian), MP_ROM_PTR(&py_image_laplacian_obj)}, {MP_ROM_QSTR(MP_QSTR_bilateral), MP_ROM_PTR(&py_image_bilateral_obj)}, + {MP_ROM_QSTR(MP_QSTR_cartoon), MP_ROM_PTR(&py_image_cartoon_obj)}, /* Shadow Removal Methods */ #ifdef IMLIB_ENABLE_REMOVE_SHADOWS {MP_ROM_QSTR(MP_QSTR_remove_shadows), MP_ROM_PTR(&py_image_remove_shadows_obj)}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index f9fb52cf2..d610cc366 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -560,6 +560,12 @@ Q(space_sigma) // duplicate Q(invert) // duplicate Q(mask) +// Cartoon +Q(cartoon) +// duplicate Q(seed_threshold) +// duplicate Q(floating_threshold) +Q(mask) + // Shadow Removal Q(remove_shadows) diff --git a/usr/examples/04-Image-Filters/cartoon_filter.py b/usr/examples/04-Image-Filters/cartoon_filter.py new file mode 100644 index 000000000..c1703ca47 --- /dev/null +++ b/usr/examples/04-Image-Filters/cartoon_filter.py @@ -0,0 +1,29 @@ +# Cartoon Filter +# +# This example shows off a simple cartoon filter on images. The cartoon +# filter works by joining similar pixel areas of an image and replacing +# the pixels in those areas with the area mean. + +import sensor, image, time + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) # or GRAYSCALE... +sensor.set_framesize(sensor.QVGA) # or QQVGA... +sensor.skip_frames(time = 2000) +clock = time.clock() + +while(True): + clock.tick() + + # seed_threshold controls the maximum area growth of a colored + # region. Making this larger will merge more pixels. + + # floating_threshold controls the maximum pixel-to-pixel difference + # when growing a region. Settings this very high will quickly combine + # all pixels in the image. You should keep this small. + + # cartoon() will grow regions while both thresholds are statisfied... + + img = sensor.snapshot().cartoon(seed_threshold=0.05, floating_thresholds=0.05) + + print(clock.fps())