From cbbead0785a80303dd854ee460dafe82d75cb94e Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Wed, 27 Dec 2017 19:21:55 -0500 Subject: [PATCH] Add adaptive thresholding to filters. I still need to go back and optimizing and cleanup the code. I just wanted to get the feature in first. --- src/omv/img/imlib.h | 9 ++-- src/omv/img/mean.c | 8 ++-- src/omv/img/median.c | 8 ++-- src/omv/img/midpoint.c | 14 +++--- src/omv/img/mode.c | 8 ++-- src/omv/py/py_image.c | 44 +++++++++++++------ .../mean_adaptive_threshold_filter.py | 25 +++++++++++ .../median_adaptive_threshold_filter.py | 27 ++++++++++++ .../midpoint_adaptive_threshold_filter.py | 28 ++++++++++++ .../mode_adaptive_threshold_filter.py | 25 +++++++++++ 10 files changed, 162 insertions(+), 34 deletions(-) create mode 100644 usr/examples/04-Image-Filters/mean_adaptive_threshold_filter.py create mode 100644 usr/examples/04-Image-Filters/median_adaptive_threshold_filter.py create mode 100644 usr/examples/04-Image-Filters/midpoint_adaptive_threshold_filter.py create mode 100644 usr/examples/04-Image-Filters/mode_adaptive_threshold_filter.py diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 0c54c39f4..deaf6412f 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1130,10 +1130,11 @@ int imlib_image_mean(image_t *src); // grayscale only int imlib_image_std(image_t *src); // grayscale only /* Image Filtering */ -void imlib_midpoint_filter(image_t *img, const int ksize, const int bias); -void imlib_mean_filter(image_t *img, const int ksize); -void imlib_mode_filter(image_t *img, const int ksize); -void imlib_median_filter(image_t *img, const int ksize, const int percentile); +void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool threshold, int offset, bool invert); +void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert); +void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert); +void imlib_median_filter(image_t *img, const int ksize, const int percentile, bool threshold, int offset, bool invert); +void imlib_histeq(image_t *img); void imlib_mask_ellipse(image_t *img); /* Template Matching */ diff --git a/src/omv/img/mean.c b/src/omv/img/mean.c index 931210428..7d003fe72 100644 --- a/src/omv/img/mean.c +++ b/src/omv/img/mean.c @@ -15,7 +15,7 @@ // ... // krn_s == n -> ((n*2)+1)x((n*2)+1) kernel -void imlib_mean_filter(image_t *img, const int ksize) +void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert) { int n = ((ksize*2)+1)*((ksize*2)+1); int brows = ksize + 1; @@ -33,7 +33,8 @@ void imlib_mean_filter(image_t *img, const int ksize) } } // We're writing into the buffer like if it were a window. - buffer[((y%brows)*img->w)+x] = acc/n; + uint8_t pixel = acc/n; + buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { memcpy(img->pixels+((y-ksize)*img->w), @@ -63,7 +64,8 @@ void imlib_mean_filter(image_t *img, const int ksize) } } // We're writing into the buffer like if it were a window. - ((uint16_t *) buffer)[((y%brows)*img->w)+x] = IM_RGB565(r_acc/n, g_acc/n, b_acc/n); + uint16_t pixel = IM_RGB565(r_acc/n, g_acc/n, b_acc/n); + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { memcpy(((uint16_t *) img->pixels)+((y-ksize)*img->w), diff --git a/src/omv/img/median.c b/src/omv/img/median.c index 33ca916f0..c174c87cc 100644 --- a/src/omv/img/median.c +++ b/src/omv/img/median.c @@ -11,7 +11,7 @@ #include "fb_alloc.h" #include "fsort.h" -void imlib_median_filter(image_t *img, const int ksize, const int percentile) +void imlib_median_filter(image_t *img, const int ksize, const int percentile, bool threshold, int offset, bool invert) { int n = ((ksize*2)+1)*((ksize*2)+1); int brows = ksize + 1; @@ -34,7 +34,8 @@ void imlib_median_filter(image_t *img, const int ksize, const int percentile) fsort(data, n); int median = data[percentile]; // We're writing into the buffer like if it were a window. - buffer[((y%brows)*img->w)+x] = median; + uint8_t pixel = median; + buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { memcpy(img->pixels+((y-ksize)*img->w), @@ -77,7 +78,8 @@ void imlib_median_filter(image_t *img, const int ksize, const int percentile) int g_median = g_data[percentile]; int b_median = b_data[percentile]; // We're writing into the buffer like if it were a window. - ((uint16_t *) buffer)[((y%brows)*img->w)+x] = IM_RGB565(r_median, g_median, b_median); + uint16_t pixel = IM_RGB565(r_median, g_median, b_median); + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { memcpy(((uint16_t *) img->pixels)+((y-ksize)*img->w), diff --git a/src/omv/img/midpoint.c b/src/omv/img/midpoint.c index 71b0fb20b..bbee25397 100644 --- a/src/omv/img/midpoint.c +++ b/src/omv/img/midpoint.c @@ -17,7 +17,7 @@ // bias == 0 to 256 -> 0.0 to 1.0 (0.0==min filter, 1.0==max filter) -void imlib_midpoint_filter(image_t *img, const int ksize, const int bias) +void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool threshold, int offset, bool invert) { int min_bias = (256-bias); int max_bias = bias; @@ -37,8 +37,8 @@ void imlib_midpoint_filter(image_t *img, const int ksize, const int bias) } } // We're writing into the buffer like if it were a window. - buffer[((y%brows)*img->w)+x] = - ((min*min_bias)+(max*max_bias))>>8; + int pixel = ((min*min_bias)+(max*max_bias))>>8; + buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { memcpy(img->pixels+((y-ksize)*img->w), @@ -74,10 +74,10 @@ void imlib_midpoint_filter(image_t *img, const int ksize, const int bias) } } // We're writing into the buffer like if it were a window. - ((uint16_t *) buffer)[((y%brows)*img->w)+x] = - IM_RGB565(((r_min*min_bias)+(r_max*max_bias))>>8, - ((g_min*min_bias)+(g_max*max_bias))>>8, - ((b_min*min_bias)+(b_max*max_bias))>>8); + uint16_t pixel = IM_RGB565(((r_min*min_bias)+(r_max*max_bias))>>8, + ((g_min*min_bias)+(g_max*max_bias))>>8, + ((b_min*min_bias)+(b_max*max_bias))>>8); + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { memcpy(((uint16_t *) img->pixels)+((y-ksize)*img->w), diff --git a/src/omv/img/mode.c b/src/omv/img/mode.c index a98cc97be..deba05d98 100644 --- a/src/omv/img/mode.c +++ b/src/omv/img/mode.c @@ -15,7 +15,7 @@ // ... // krn_s == n -> ((n*2)+1)x((n*2)+1) kernel -void imlib_mode_filter(image_t *img, const int ksize) +void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert) { int brows = ksize + 1; uint8_t *buffer = fb_alloc(img->w * brows * img->bpp); @@ -38,7 +38,8 @@ void imlib_mode_filter(image_t *img, const int ksize) } } // We're writing into the buffer like if it were a window. - buffer[((y%brows)*img->w)+x] = mode; + uint8_t pixel = mode; + buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { memcpy(img->pixels+((y-ksize)*img->w), @@ -90,7 +91,8 @@ void imlib_mode_filter(image_t *img, const int ksize) } } // We're writing into the buffer like if it were a window. - ((uint16_t *) buffer)[((y%brows)*img->w)+x] = IM_RGB565(r_mode, g_mode, b_mode); + uint16_t pixel = IM_RGB565(r_mode, g_mode, b_mode); + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { memcpy(((uint16_t *) img->pixels)+((y-ksize)*img->w), diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 3ed09b04a..3589cd10f 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1206,33 +1206,45 @@ static mp_obj_t py_image_midpoint(uint n_args, const mp_obj_t *args, mp_map_t *k int arg_ksize = mp_obj_get_int(args[1]); PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); + int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + int bias = py_helper_lookup_float(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bias), 0.5) * 256; - imlib_midpoint_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(bias, 0), 256)); + imlib_midpoint_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(bias, 0), 256), arg_threshold, arg_offset, arg_invert); return args[0]; } -static mp_obj_t py_image_mean(mp_obj_t img_obj, mp_obj_t k_obj) +static mp_obj_t py_image_mean(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_image_cobj(img_obj); + image_t *arg_img = py_image_cobj(args[0]); PY_ASSERT_TRUE_MSG(IM_IS_MUTABLE(arg_img), "Image format is not supported."); - int arg_ksize = mp_obj_get_int(k_obj); + int arg_ksize = mp_obj_get_int(args[1]); PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); - imlib_mean_filter(arg_img, arg_ksize); - return img_obj; + int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + + imlib_mean_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert); + return args[0]; } -static mp_obj_t py_image_mode(mp_obj_t img_obj, mp_obj_t k_obj) +static mp_obj_t py_image_mode(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_image_cobj(img_obj); + image_t *arg_img = py_image_cobj(args[0]); PY_ASSERT_TRUE_MSG(IM_IS_MUTABLE(arg_img), "Image format is not supported."); - int arg_ksize = mp_obj_get_int(k_obj); + int arg_ksize = mp_obj_get_int(args[1]); PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); - imlib_mode_filter(arg_img, arg_ksize); - return img_obj; + int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + + imlib_mode_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert); + return args[0]; } static mp_obj_t py_image_median(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) @@ -1244,9 +1256,13 @@ static mp_obj_t py_image_median(uint n_args, const mp_obj_t *args, mp_map_t *kw_ PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); PY_ASSERT_TRUE_MSG(arg_ksize <= 2, "Kernel Size must be <= 2"); + int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + int n = ((arg_ksize*2)+1)*((arg_ksize*2)+1); int percentile = py_helper_lookup_float(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_percentile), 0.5) * n; - imlib_median_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(percentile, 0), n-1)); + imlib_median_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(percentile, 0), n-1), arg_threshold, arg_offset, arg_invert); return args[0]; } @@ -4000,8 +4016,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_blend_obj, 2, py_image_blend); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_morph_obj, 3, py_image_morph); /* Image Filtering */ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_midpoint_obj, 2, py_image_midpoint); -STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_mean_obj, py_image_mean); -STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_mode_obj, py_image_mode); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mean_obj, 2, py_image_mean); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mode_obj, 2, py_image_mode); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_median_obj, 2, py_image_median); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_gaussian_obj, 1, py_image_gaussian); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_chrominvar_obj, py_image_chrominvar); diff --git a/usr/examples/04-Image-Filters/mean_adaptive_threshold_filter.py b/usr/examples/04-Image-Filters/mean_adaptive_threshold_filter.py new file mode 100644 index 000000000..2d140ecc4 --- /dev/null +++ b/usr/examples/04-Image-Filters/mean_adaptive_threshold_filter.py @@ -0,0 +1,25 @@ +# Mean Adaptive Threshold Filter Example +# +# This example shows off mean filtering with adaptive thresholding. +# When mean(threshold=True) the mean() method adaptive thresholds the image +# by comparing the mean of the pixels around a pixel, minus an offset, with that pixel. + +import sensor, image, time + +sensor.reset() # Initialize the camera sensor. +sensor.set_pixformat(sensor.RGB565) # or sensor.GRAYSCALE +sensor.set_framesize(sensor.QQVGA) # or sensor.QVGA (or others) +sensor.skip_frames(time = 2000) # Let new settings take affect. +clock = time.clock() # Tracks FPS. + +while(True): + clock.tick() # Track elapsed milliseconds between snapshots(). + img = sensor.snapshot() # Take a picture and return the image. + + # The first argument is the kernel size. N coresponds to a ((N*2)+1)^2 + # kernel size. E.g. 1 == 3x3 kernel, 2 == 5x5 kernel, etc. Note: You + # shouldn't ever need to use a value bigger than 2. + img.mean(1, threshold=True, offset=5, invert=True) + + print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while + # connected to your computer. The FPS should increase once disconnected. diff --git a/usr/examples/04-Image-Filters/median_adaptive_threshold_filter.py b/usr/examples/04-Image-Filters/median_adaptive_threshold_filter.py new file mode 100644 index 000000000..673b28482 --- /dev/null +++ b/usr/examples/04-Image-Filters/median_adaptive_threshold_filter.py @@ -0,0 +1,27 @@ +# Median Adaptive Threshold Filter Example +# +# This example shows off median filtering with adaptive thresholding. +# When median(threshold=True) the median() method adaptive thresholds the image +# by comparing the median of the pixels around a pixel, minus an offset, with that pixel. + +import sensor, image, time + +sensor.reset() # Initialize the camera sensor. +sensor.set_pixformat(sensor.RGB565) # or sensor.GRAYSCALE +sensor.set_framesize(sensor.QQVGA) # or sensor.QVGA (or others) +sensor.skip_frames(time = 2000) # Let new settings take affect. +clock = time.clock() # Tracks FPS. + +while(True): + clock.tick() # Track elapsed milliseconds between snapshots(). + img = sensor.snapshot() # Take a picture and return the image. + + # The first argument to the median filter is the kernel size, it can be + # either 0, 1, or 2 for a 1x1, 3x3, or 5x5 kernel respectively. The second + # argument "percentile" is the percentile number to choose from the NxN + # neighborhood. 0.5 is the median, 0.25 is the lower quartile, and 0.75 + # would be the upper quartile. + img.median(1, percentile=0.5, threshold=True, offset=5, invert=True) + + print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while + # connected to your computer. The FPS should increase once disconnected. diff --git a/usr/examples/04-Image-Filters/midpoint_adaptive_threshold_filter.py b/usr/examples/04-Image-Filters/midpoint_adaptive_threshold_filter.py new file mode 100644 index 000000000..adaeaaa5d --- /dev/null +++ b/usr/examples/04-Image-Filters/midpoint_adaptive_threshold_filter.py @@ -0,0 +1,28 @@ +# Midpoint Adaptive Threshold Filter Example +# +# This example shows off midpoint filtering with adaptive thresholding. +# When midpoint(threshold=True) the midpoint() method adaptive thresholds the image +# by comparing the midpoint of the pixels around a pixel, minus an offset, with that pixel. + +import sensor, image, time + +sensor.reset() # Initialize the camera sensor. +sensor.set_pixformat(sensor.RGB565) # or sensor.GRAYSCALE +sensor.set_framesize(sensor.QQVGA) # or sensor.QVGA (or others) +sensor.skip_frames(time = 2000) # Let new settings take affect. +clock = time.clock() # Tracks FPS. + +while(True): + clock.tick() # Track elapsed milliseconds between snapshots(). + img = sensor.snapshot() # Take a picture and return the image. + + # The first argument is the kernel size. N coresponds to a ((N*2)+1)^2 + # kernel size. E.g. 1 == 3x3 kernel, 2 == 5x5 kernel, etc. Note: You + # shouldn't ever need to use a value bigger than 2. The "bias" argument + # lets you select between min and max blending. 0.5 == midpoint filter, + # 0.0 == min filter, and 1.0 == max filter. Note that the min filter + # makes images darker while the max filter makes images lighter. + img.midpoint(1, bias=0.5, threshold=True, offset=5, invert=True) + + print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while + # connected to your computer. The FPS should increase once disconnected. diff --git a/usr/examples/04-Image-Filters/mode_adaptive_threshold_filter.py b/usr/examples/04-Image-Filters/mode_adaptive_threshold_filter.py new file mode 100644 index 000000000..8ab9a0675 --- /dev/null +++ b/usr/examples/04-Image-Filters/mode_adaptive_threshold_filter.py @@ -0,0 +1,25 @@ +# Mode Adaptive Threshold Filter Example +# +# This example shows off mode filtering with adaptive thresholding. +# When mode(threshold=True) the mode() method adaptive thresholds the image +# by comparing the mode of the pixels around a pixel, minus an offset, with that pixel. +# Avoid using the mode filter on RGB565 images. It will cause artifacts on image edges... + +import sensor, image, time + +sensor.reset() # Initialize the camera sensor. +sensor.set_pixformat(sensor.GRAYSCALE) # or sensor.RGB565 +sensor.set_framesize(sensor.QQVGA) # or sensor.QVGA (or others) +sensor.skip_frames(time = 2000) # Let new settings take affect. +clock = time.clock() # Tracks FPS. + +while(True): + clock.tick() # Track elapsed milliseconds between snapshots(). + img = sensor.snapshot() # Take a picture and return the image. + + # The only argument to the median filter is the kernel size, it can be + # either 0, 1, or 2 for a 1x1, 3x3, or 5x5 kernel respectively. + img.mode(1, threshold=True, offset=5, invert=True) + + print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while + # connected to your computer. The FPS should increase once disconnected.