From 98f4dde21f6e4943a83b0b533e801057c8f6b848 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 28 Feb 2016 22:49:40 -0500 Subject: [PATCH] Add new morph function. The morph function lets you convolve the image with a kernel. It's decently fast right now. But, in the future we'll have to optimize it by a lot (unrolling loops, using SIMD instructions, etc.). Anyway, along with morph I added an edge detection test script showing how you can use a high pass filter on an image to get all the edges in it. This is not as good as canny edge dection... but, it's about the same and fast enough. We'll need a Hough Transform system in the future to make edge dection useful. Not sure how that will be implemented... so, that's going to be far away for now. --- src/Makefile | 1 + src/omv/Makefile | 1 + src/omv/img/imlib.c | 4 +- src/omv/img/imlib.h | 3 ++ src/omv/img/morph.c | 93 ++++++++++++++++++++++++++++++++++ src/omv/py/py_image.c | 56 ++++++++++++++++---- usr/examples/edge_detection.py | 23 +++++++++ 7 files changed, 170 insertions(+), 11 deletions(-) create mode 100644 src/omv/img/morph.c create mode 100644 usr/examples/edge_detection.py 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