diff --git a/src/Makefile b/src/Makefile index abb23568a..afff99087 100755 --- a/src/Makefile +++ b/src/Makefile @@ -128,6 +128,7 @@ OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/img/,\ fmath.o \ haar.o \ imlib.o \ + morph.o \ integral.o \ integral_mw.o \ kmeans.o \ diff --git a/src/omv/Makefile b/src/omv/Makefile index a5104b051..4695a7c5f 100644 --- a/src/omv/Makefile +++ b/src/omv/Makefile @@ -31,6 +31,7 @@ SRCS += $(addprefix img/, \ fmath.c \ haar.c \ imlib.c \ + morph.c \ integral.c \ integral_mw.c \ kmeans.c \ diff --git a/src/omv/img/imlib.c b/src/omv/img/imlib.c index 2142e3d4c..8af63b24e 100644 --- a/src/omv/img/imlib.c +++ b/src/omv/img/imlib.c @@ -614,7 +614,7 @@ static void imlib_erode_dilate(image_t *img, int ksize, int threshold, int e_or_ if ((!!buffer[buffer_idx]) == e_or_d) { continue; // short circuit (makes this very fast - usually) } - int acc = 0; + int acc = -1; // don't count center pixel... for (int j=-ksize; j<=ksize; j++) { for (int k=-ksize; k<=ksize; k++) { if (IM_X_INSIDE(img, x+k) && IM_Y_INSIDE(img, y+j)) { @@ -648,7 +648,7 @@ static void imlib_erode_dilate(image_t *img, int ksize, int threshold, int e_or_ if ((!!((uint16_t *) buffer)[buffer_idx]) == e_or_d) { continue; // short circuit (makes this very fast - usually) } - int acc = 0; + int acc = -1; // don't count center pixel... for (int j=-ksize; j<=ksize; j++) { for (int k=-ksize; k<=ksize; k++) { if (IM_X_INSIDE(img, x+k) && IM_Y_INSIDE(img, y+j)) { diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index f5b686fd4..6a041dd9e 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -407,6 +407,9 @@ void imlib_dilate(image_t *img, int ksize, int threshold); void imlib_negate(image_t *img); void imlib_difference(image_t *img, const char *path, image_t *other); +/* Image Morphing */ +void imlib_morph(image_t *img, const int ksize, const int8_t *krn, const float m, const int b); + /* Clustering functions */ array_t *cluster_kmeans(array_t *points, int k); diff --git a/src/omv/img/morph.c b/src/omv/img/morph.c new file mode 100644 index 000000000..7be169022 --- /dev/null +++ b/src/omv/img/morph.c @@ -0,0 +1,93 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Generic image convolution function. + * + */ +#include +#include "imlib.h" +#include "fb_alloc.h" + +// krn_s == 0 -> 1x1 kernel +// krn_s == 1 -> 3x3 kernel +// ... +// krn_s == n -> ((n*2)+1)x((n*2)+1) kernel +// +// pixel = (krn_sum / m) + b +// +// http://www.fmwconcepts.com/imagemagick/digital_image_filtering.pdf + +void imlib_morph(image_t *img, const int ksize, const int8_t *krn, const float m, const int b) +{ + int brows = ksize + 1; + uint8_t *buffer = fb_alloc(img->w * brows * img->bpp); + if (IM_IS_GS(img)) { + for (int y=0; yh; y++) { + for (int x=0; xw; x++) { + int acc = 0; + int ptr = 0; + for (int j=-ksize; j<=ksize; j++) { + for (int k=-ksize; k<=ksize; k++) { + if (IM_X_INSIDE(img, x+k) && IM_Y_INSIDE(img, y+j)) { + acc += krn[ptr++] * IM_GET_GS_PIXEL(img, x+k, y+j); + } + } + } + acc = (acc * m) + b; // scale, offset, and clamp + acc = IM_MAX(IM_MIN(acc, IM_MAX_GS), 0); + // We're writing into the buffer like if it were a window. + buffer[((y%brows)*img->w)+x] = acc; + } + if (y>=ksize) { + memcpy(img->pixels+((y-ksize)*img->w), + buffer+(((y-ksize)%brows)*img->w), + img->w * sizeof(uint8_t)); + } + } + for (int y=img->h-ksize; yh; y++) { + memcpy(img->pixels+(y*img->w), + buffer+((y%brows)*img->w), + img->w * sizeof(uint8_t)); + } + } else { + for (int y=0; yh; y++) { + for (int x=0; xw; x++) { + int r_acc = 0; + int g_acc = 0; + int b_acc = 0; + int ptr = 0; + for (int j=-ksize; j<=ksize; j++) { + for (int k=-ksize; k<=ksize; k++) { + if (IM_X_INSIDE(img, x+k) && IM_Y_INSIDE(img, y+j)) { + const uint16_t pixel = IM_GET_RGB565_PIXEL(img, x+k, y+j); + r_acc += krn[ptr] * IM_R565(pixel); + g_acc += krn[ptr] * IM_G565(pixel); + b_acc += krn[ptr++] * IM_B565(pixel); + } + } + } + r_acc = (r_acc * m) + b; // scale, offset, and clamp + r_acc = IM_MAX(IM_MIN(r_acc, IM_MAX_R5), 0); + g_acc = (g_acc * m) + b; // scale, offset, and clamp + g_acc = IM_MAX(IM_MIN(g_acc, IM_MAX_G6), 0); + b_acc = (b_acc * m) + b; // scale, offset, and clamp + b_acc = IM_MAX(IM_MIN(b_acc, IM_MAX_B5), 0); + // We're writing into the buffer like if it were a window. + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = IM_RGB565(r_acc, g_acc, b_acc); + } + if (y>=ksize) { + memcpy(((uint16_t *) img->pixels)+((y-ksize)*img->w), + ((uint16_t *) buffer)+(((y-ksize)%brows)*img->w), + img->w * sizeof(uint16_t)); + } + } + for (int y=img->h-ksize; yh; y++) { + memcpy(((uint16_t *) img->pixels)+(y*img->w), + ((uint16_t *) buffer)+((y%brows)*img->w), + img->w * sizeof(uint16_t)); + } + } + fb_free(); +} diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 1edc0e865..d1d4b814f 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -363,7 +363,6 @@ static mp_obj_t py_image_draw_keypoints(uint n_args, const mp_obj_t *args, mp_ma } else { py_kp_obj_t *kpts_obj = ((py_kp_obj_t*)args[1]); PY_ASSERT_TYPE(kpts_obj, &py_kp_type); - for (int i=0; ikpts); i++) { kp_t *kp = array_at(kpts_obj->kpts, i); imlib_draw_circle(arg_img, kp->x, kp->y, (arg_s-2)/2, arg_c); @@ -581,11 +580,11 @@ static mp_obj_t py_image_erode(uint n_args, const mp_obj_t *args, mp_map_t *kw_a PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), "Operation not supported on JPEG"); - int ksize = mp_obj_get_int(args[1]); - PY_ASSERT_TRUE_MSG(ksize >= 0, "Kernel Size must be >= 0"); - imlib_erode(arg_img, ksize, + int arg_ksize = mp_obj_get_int(args[1]); + PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); + imlib_erode(arg_img, arg_ksize, py_helper_lookup_int(kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_threshold), ((ksize*2)+1)*((ksize*2)+1))); + MP_OBJ_NEW_QSTR(MP_QSTR_threshold), ((arg_ksize*2)+1)*((arg_ksize*2)+1)-1)); return mp_const_none; } @@ -595,11 +594,11 @@ static mp_obj_t py_image_dilate(uint n_args, const mp_obj_t *args, mp_map_t *kw_ PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), "Operation not supported on JPEG"); - int ksize = mp_obj_get_int(args[1]); - PY_ASSERT_TRUE_MSG(ksize >= 0, "Kernel Size must be >= 0"); - imlib_dilate(arg_img, ksize, + int arg_ksize = mp_obj_get_int(args[1]); + PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); + imlib_dilate(arg_img, arg_ksize, py_helper_lookup_int(kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 1)); + MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0)); return mp_const_none; } @@ -628,6 +627,41 @@ static mp_obj_t py_image_difference(mp_obj_t img_obj, mp_obj_t other_obj) return mp_const_none; } +static mp_obj_t py_image_morph(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), + "Operation not supported on JPEG"); + + int arg_ksize = mp_obj_get_int(args[1]); + PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); + + int array_size = ((arg_ksize*2)+1)*((arg_ksize*2)+1); + mp_obj_t *krn; + mp_obj_get_array_fixed_n(args[2], array_size, &krn); + + int8_t arg_krn[array_size]; + int arg_m = 0; + for (int i = 0; i < array_size; i++) { + int value = mp_obj_get_int(krn[i]); + PY_ASSERT_FALSE_MSG((value < -128) || (127 < value), + "Kernel Values must be between [-128:127] inclusive"); + arg_krn[i] = value; + arg_m += arg_krn[i]; + } + + if (arg_m == 0) { + arg_m = 1; + } + + imlib_morph(arg_img, arg_ksize, arg_krn, + py_helper_lookup_float(kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_mul), 1.0 / ((float) arg_m)), + py_helper_lookup_int(kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_add), 0)); + return mp_const_none; +} + static mp_obj_t py_image_scale(mp_obj_t image_obj, mp_obj_t size_obj) { int w,h; @@ -1110,6 +1144,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_dilate_obj, 2, py_image_dilate); /* Background Subtraction (Frame Differencing) functions */ STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_negate_obj, py_image_negate); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_difference_obj, py_image_difference); +/* Image Morphing */ +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_morph_obj, 3, py_image_morph); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_scale_obj, py_image_scale); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_scaled_obj, py_image_scaled); @@ -1164,6 +1200,8 @@ static const mp_map_elem_t locals_dict_table[] = { /* Background Subtraction (Frame Differencing) functions */ {MP_OBJ_NEW_QSTR(MP_QSTR_negate), (mp_obj_t)&py_image_negate_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_difference), (mp_obj_t)&py_image_difference_obj}, + /* Image Morphing */ + {MP_OBJ_NEW_QSTR(MP_QSTR_morph), (mp_obj_t)&py_image_morph_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_scale), (mp_obj_t)&py_image_scale_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_scaled), (mp_obj_t)&py_image_scaled_obj}, diff --git a/usr/examples/edge_detection.py b/usr/examples/edge_detection.py new file mode 100644 index 000000000..f34019398 --- /dev/null +++ b/usr/examples/edge_detection.py @@ -0,0 +1,23 @@ +# Edge Detection Example: +# +# This example demonstrates using the morph function on an image to do edge +# detection and then thresholding and filtering that image afterwards. + +import sensor, image + +kernel_size = 1 # kernel width = (size*2)+1, kernel height = (size*2)+1 +kernel = [-1, -1, -1,\ + -1, +8, -1,\ + -1, -1, -1] +# This is a high pass filter kernel. ee here for more kernels: +# http://www.fmwconcepts.com/imagemagick/digital_image_filtering.pdf +thresholds = [(100, 255)] # grayscale thresholds + +sensor.reset() +sensor.set_framesize(sensor.QQVGA) # smaller resolution to go faster +sensor.set_pixformat(sensor.GRAYSCALE) +while(True): + img = sensor.snapshot() + img.morph(kernel_size, kernel) + img.binary(thresholds) + img.erode(1, threshold = 2) # erode pixels with less than 2 neighbors