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.
This commit is contained in:
Kwabena W. Agyeman 2016-02-28 22:49:40 -05:00
parent b5c20cd022
commit 98f4dde21f
7 changed files with 170 additions and 11 deletions

View File

@ -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 \

View File

@ -31,6 +31,7 @@ SRCS += $(addprefix img/, \
fmath.c \
haar.c \
imlib.c \
morph.c \
integral.c \
integral_mw.c \
kmeans.c \

View File

@ -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)) {

View File

@ -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);

93
src/omv/img/morph.c Normal file
View File

@ -0,0 +1,93 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013-2016 Kwabena W. Agyeman <kwagyeman@openmv.io>
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* Generic image convolution function.
*
*/
#include <string.h>
#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; y<img->h; y++) {
for (int x=0; x<img->w; 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; y<img->h; y++) {
memcpy(img->pixels+(y*img->w),
buffer+((y%brows)*img->w),
img->w * sizeof(uint8_t));
}
} else {
for (int y=0; y<img->h; y++) {
for (int x=0; x<img->w; 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; y<img->h; y++) {
memcpy(((uint16_t *) img->pixels)+(y*img->w),
((uint16_t *) buffer)+((y%brows)*img->w),
img->w * sizeof(uint16_t));
}
}
fb_free();
}

View File

@ -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; i<array_length(kpts_obj->kpts); 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},

View File

@ -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