mirror of
https://github.com/openmv/openmv.git
synced 2025-11-04 14:49:50 +08:00
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:
parent
b5c20cd022
commit
98f4dde21f
@ -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 \
|
||||
|
||||
@ -31,6 +31,7 @@ SRCS += $(addprefix img/, \
|
||||
fmath.c \
|
||||
haar.c \
|
||||
imlib.c \
|
||||
morph.c \
|
||||
integral.c \
|
||||
integral_mw.c \
|
||||
kmeans.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)) {
|
||||
|
||||
@ -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
93
src/omv/img/morph.c
Normal 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();
|
||||
}
|
||||
@ -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},
|
||||
|
||||
23
usr/examples/edge_detection.py
Normal file
23
usr/examples/edge_detection.py
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user