diff --git a/src/omv/img/filter.c b/src/omv/img/filter.c index cef257085..f50b67d56 100644 --- a/src/omv/img/filter.c +++ b/src/omv/img/filter.c @@ -747,7 +747,7 @@ void imlib_midpoint_filter(image_t *img, const int ksize, float bias, bool thres } int min = COLOR_BINARY_MAX, max = COLOR_BINARY_MIN; - + for (int j = -ksize; j <= ksize; j++) { uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); @@ -1107,3 +1107,285 @@ void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, c } } } + +static float gaussian(int x, float sigma) +{ + return fast_expf((x * x) / (-2.0f * sigma * sigma)) / (sigma * 2.506628f); // sqrt(2 * PI) +} + +static float distance(int x, int y) +{ + return fast_sqrtf((x * x) + (y * y)); +} + +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) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.bpp = img->bpp; + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); + + float *gi_lut = fb_alloc((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(float)); + + for (int i = COLOR_BINARY_MIN; i <= COLOR_BINARY_MAX; i++) { + gi_lut[i] = gaussian(i, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float)); + + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + float i_acc = 0, w_acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + float w = gi_lut[abs(this_pixel - pixel)] * gs_lut[(n * (j + ksize)) + (k + ksize)]; + i_acc += pixel * w; + w_acc += w; + } + } + + int pixel = fast_roundf(IM_MIN(IM_DIV(i_acc, w_acc), COLOR_BINARY_MAX)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); + fb_free(); + break; + } + case IMAGE_BPP_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows); + + float *gi_lut = fb_alloc((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(float)); + + for (int i = COLOR_GRAYSCALE_MIN; i <= COLOR_GRAYSCALE_MAX; i++) { + gi_lut[i] = gaussian(i, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float)); + + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + float i_acc = 0, w_acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + float w = gi_lut[abs(this_pixel - pixel)] * gs_lut[(n * (j + ksize)) + (k + ksize)]; + i_acc += pixel * w; + w_acc += w; + } + } + + int pixel = fast_roundf(IM_MIN(IM_DIV(i_acc, w_acc), COLOR_GRAYSCALE_MAX)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); + fb_free(); + break; + } + case IMAGE_BPP_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows); + + float *r_gi_lut = fb_alloc((COLOR_R5_MAX - COLOR_R5_MIN + 1) * sizeof(float)); + float *g_gi_lut = fb_alloc((COLOR_G6_MAX - COLOR_G6_MIN + 1) * sizeof(float)); + float *b_gi_lut = fb_alloc((COLOR_B5_MAX - COLOR_B5_MIN + 1) * sizeof(float)); + + for (int i = COLOR_R5_MIN; i <= COLOR_R5_MAX; i++) { + r_gi_lut[i] = gaussian(i, color_sigma); + } + + for (int i = COLOR_G6_MIN; i <= COLOR_G6_MAX; i++) { + g_gi_lut[i] = gaussian(i, color_sigma); + } + + for (int i = COLOR_B5_MIN; i <= COLOR_B5_MAX; i++) { + b_gi_lut[i] = gaussian(i, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float)); + + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + int r_this_pixel = COLOR_RGB565_TO_R5(this_pixel); + int g_this_pixel = COLOR_RGB565_TO_G6(this_pixel); + int b_this_pixel = COLOR_RGB565_TO_B5(this_pixel); + float r_i_acc = 0, r_w_acc = 0; + float g_i_acc = 0, g_w_acc = 0; + float b_i_acc = 0, b_w_acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + int r_pixel = COLOR_RGB565_TO_R5(pixel); + int g_pixel = COLOR_RGB565_TO_G6(pixel); + int b_pixel = COLOR_RGB565_TO_B5(pixel); + float gs = gs_lut[(n * (j + ksize)) + (k + ksize)]; + float r_w = r_gi_lut[abs(r_this_pixel - r_pixel)] * gs; + float g_w = g_gi_lut[abs(g_this_pixel - g_pixel)] * gs; + float b_w = b_gi_lut[abs(b_this_pixel - b_pixel)] * gs; + r_i_acc += r_pixel * r_w; + r_w_acc += r_w; + g_i_acc += g_pixel * g_w; + g_w_acc += g_w; + b_i_acc += b_pixel * b_w; + b_w_acc += b_w; + } + } + + int pixel = COLOR_R5_G6_B5_TO_RGB565(fast_roundf(IM_MIN(IM_DIV(r_i_acc, r_w_acc), COLOR_R5_MAX)), + fast_roundf(IM_MIN(IM_DIV(g_i_acc, g_w_acc), COLOR_G6_MAX)), + fast_roundf(IM_MIN(IM_DIV(b_i_acc, b_w_acc), COLOR_B5_MAX))); + + if (threshold) { + if (((COLOR_RGB565_TO_Y(pixel) - offset) < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB565_BINARY_MAX; + } else { + pixel = COLOR_RGB565_BINARY_MIN; + } + } + + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); + fb_free(); + fb_free(); + fb_free(); + break; + } + default: { + break; + } + } +} diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 80f3c25ac..7697651bb 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -1250,6 +1250,7 @@ void imlib_median_filter(image_t *img, const int ksize, float percentile, bool t void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); 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); // 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 b5ba575f8..6ac248db3 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1981,6 +1981,34 @@ STATIC mp_obj_t py_image_laplacian(uint n_args, const mp_obj_t *args, mp_map_t * } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_laplacian_obj, 2, py_image_laplacian); +STATIC mp_obj_t py_image_bilateral(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]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + float arg_color_sigma = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_sigma), 6); + PY_ASSERT_TRUE_MSG((0 <= arg_color_sigma), "Error: 0 <= color_sigma!"); + float arg_space_sigma = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_space_sigma), 6); + PY_ASSERT_TRUE_MSG((0 <= arg_space_sigma), "Error: 0 <= space_sigma!"); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 7, kw_args); + + fb_alloc_mark(); + imlib_bilateral_filter(arg_img, arg_ksize, arg_color_sigma, arg_space_sigma, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_bilateral_obj, 2, py_image_bilateral); + ///////////////////////// // Shadow Removal Methods ///////////////////////// @@ -4909,6 +4937,7 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_gaussian), MP_ROM_PTR(&py_image_gaussian_obj)}, {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)}, /* 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 31be1d662..ed08babfa 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -536,6 +536,15 @@ Q(sharpen) // duplicate Q(invert) // duplicate Q(mask) +// Bilateral +Q(bilateral) +Q(color_sigma) +Q(space_sigma) +// duplicate Q(threshold) +// duplicate Q(offset) +// duplicate Q(invert) +// duplicate Q(mask) + // Shadow Removal Q(remove_shadows) diff --git a/usr/examples/04-Image-Filters/color_bilateral_filter.py b/usr/examples/04-Image-Filters/color_bilateral_filter.py new file mode 100644 index 000000000..1e1c24e6c --- /dev/null +++ b/usr/examples/04-Image-Filters/color_bilateral_filter.py @@ -0,0 +1,33 @@ +# Color Bilteral Filter Example +# +# This example shows off using the bilateral filter on color images. + +import sensor, image, time + +sensor.reset() # Initialize the camera sensor. +sensor.set_pixformat(sensor.RGB565) # 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. + + # color_sigma controls how close color wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # space_sigma controls how close space wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # Run the kernel on every pixel of the image. + img.bilateral(3, color_sigma=5, space_sigma=5) + + # Note that the bilateral filter can introduce image defects if you set + # color_sigma/space_sigma to aggresively. Increase the sigma values until + # the defects go away if you see them. + + 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/grayscale_bilateral_filter.py b/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py new file mode 100644 index 000000000..35a3f9427 --- /dev/null +++ b/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py @@ -0,0 +1,33 @@ +# Grayscale Bilteral Filter Example +# +# This example shows off using the bilateral filter on grayscale images. + +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. + + # color_sigma controls how close color wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # space_sigma controls how close space wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # Run the kernel on every pixel of the image. + img.bilateral(3, color_sigma=20, space_sigma=20) + + # Note that the bilateral filter can introduce image defects if you set + # color_sigma/space_sigma to aggresively. Increase the sigma values until + # the defects go away if you see them. + + print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while + # connected to your computer. The FPS should increase once disconnected.