From 401b7b72a8ac530fc03de793539f7c4f9224a122 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Mon, 4 Apr 2016 20:04:36 -0400 Subject: [PATCH] Added mean, median, and mode filters. Mean filter -> Fast and easy to use. This will likely be the only filter that gets alot of action on the M4. Median filter -> Works really well, but, slow. On grayscale at 160x120 you can get also 10 FPS with it for a 3x3 kernel. That said, it's still slow. Also, the code only works for 3x3 and 5x5 kernels. About the previous histogram filter... technically, that filter should be better. However, it suffers from a startup cost. The operation of finding the median point in the histogram costs too much to compute. This is what causes it to be slow. On very large kernels it will be faster than the sorting median alrogithm I put up... but, large kernels will be too slow for anyone to use anyway. The paper Ibrahim linked to about it showed it being used for like 7x7 kernels and up... so, I think the researcher who thought of the idea was really thinking about the algorithm for large kernels. Mode filter -> Works great on grayscale. Not so much on color. I think it needs to be run on the LAB color space instead of the RGB color space. I say this because it causes pretty strong artifacts around edges. When we get more flash we'll be able to have a reverse lookup table for LAB to make the mode filter better. Until then... --- src/Makefile | 5 +- src/omv/Makefile | 5 +- src/omv/img/fsort.c | 303 +++++++++++++++++++++++++++++++++++++++ src/omv/img/fsort.h | 13 ++ src/omv/img/imlib.h | 4 +- src/omv/img/mean.c | 81 +++++++++++ src/omv/img/median.c | 241 ++++++++++--------------------- src/omv/img/mode.c | 111 ++++++++++++++ src/omv/py/py_image.c | 59 ++++++-- src/omv/py/qstrdefsomv.h | 2 + 10 files changed, 647 insertions(+), 177 deletions(-) create mode 100644 src/omv/img/fsort.c create mode 100644 src/omv/img/fsort.h create mode 100644 src/omv/img/mean.c create mode 100644 src/omv/img/mode.c diff --git a/src/Makefile b/src/Makefile index b899fbc16..daa1187ce 100755 --- a/src/Makefile +++ b/src/Makefile @@ -129,6 +129,7 @@ endif OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/img/,\ blob.o \ fmath.o \ + fsort.o \ haar.o \ imlib.o \ stats.o \ @@ -141,8 +142,10 @@ OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/img/,\ yuv_tab.o \ rainbow_tab.o \ rgb2rgb_tab.o \ - median.o \ midpoint.o \ + mean.o \ + mode.o \ + median.o \ point.o \ rectangle.o \ bmp.o \ diff --git a/src/omv/Makefile b/src/omv/Makefile index d464e3afc..2eb1d3e54 100644 --- a/src/omv/Makefile +++ b/src/omv/Makefile @@ -29,6 +29,7 @@ endif SRCS += $(addprefix img/, \ blob.c \ fmath.c \ + fsort.c \ haar.c \ imlib.c \ stats.c \ @@ -41,8 +42,10 @@ SRCS += $(addprefix img/, \ yuv_tab.c \ rainbow_tab.c \ rgb2rgb_tab.c \ - median.c \ midpoint.c \ + mean.c \ + mode.c \ + median.c \ point.c \ rectangle.c \ bmp.c \ diff --git a/src/omv/img/fsort.c b/src/omv/img/fsort.c new file mode 100644 index 000000000..dec1c23c2 --- /dev/null +++ b/src/omv/img/fsort.c @@ -0,0 +1,303 @@ +/* + * 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. + * + * Fast 9 and 25 bin sort. + * + */ +#include "mdefs.h" +#include "fsort.h" +// http://pages.ripco.net/~jgamble/nw.html + +ALWAYS_INLINE static void cmpswp(uint8_t *a, uint8_t *b) +{ + if ((*b) < (*a)) { + uint8_t tmp = *a; + *a = *b; + *b = tmp; + } +} + +// Network for N=9, using Best Known Arrangement. + +// There are 25 comparators in this network, +// grouped into 9 parallel operations. + +// [[0,1],[3,4],[6,7]] +// [[1,2],[4,5],[7,8]] +// [[0,1],[3,4],[6,7],[2,5]] +// [[0,3],[1,4],[5,8]] +// [[3,6],[4,7],[2,5]] +// [[0,3],[1,4],[5,7],[2,6]] +// [[1,3],[4,6]] +// [[2,4],[5,6]] +// [[2,3]] + +// This is graphed in 17 columns. + +static void fsort9(uint8_t *data) +{ + cmpswp(data+0, data+1); + cmpswp(data+3, data+4); + cmpswp(data+6, data+7); + + cmpswp(data+1, data+2); + cmpswp(data+4, data+5); + cmpswp(data+7, data+8); + + cmpswp(data+0, data+1); + cmpswp(data+3, data+4); + cmpswp(data+6, data+7); + cmpswp(data+2, data+5); + + cmpswp(data+0, data+3); + cmpswp(data+1, data+4); + cmpswp(data+5, data+8); + + cmpswp(data+3, data+6); + cmpswp(data+4, data+7); + cmpswp(data+2, data+5); + + cmpswp(data+0, data+3); + cmpswp(data+1, data+4); + cmpswp(data+5, data+7); + cmpswp(data+2, data+6); + + cmpswp(data+1, data+3); + cmpswp(data+4, data+6); + + cmpswp(data+2, data+4); + cmpswp(data+5, data+6); + + cmpswp(data+2, data+3); +} + +// Network for N=25, using Bose-Nelson Algorithm. + +// There are 154 comparators in this network, +// grouped into 27 parallel operations. + +// [[1,2],[4,5],[7,8],[10,11],[13,14],[16,17],[19,20],[21,22],[23,24]] +// [[0,2],[3,5],[6,8],[9,11],[12,14],[15,17],[18,20],[21,23],[22,24]] +// [[0,1],[3,4],[2,5],[6,7],[9,10],[8,11],[12,13],[15,16],[14,17],[18,19],[22,23],[20,24]] +// [[0,3],[1,4],[6,9],[7,10],[5,11],[12,15],[13,16],[18,22],[19,23],[17,24]] +// [[2,4],[1,3],[8,10],[7,9],[0,6],[14,16],[13,15],[18,21],[20,23],[11,24]] +// [[2,3],[8,9],[1,7],[4,10],[14,15],[19,21],[20,22],[16,23]] +// [[2,8],[1,6],[3,9],[5,10],[20,21],[12,19],[15,22],[17,23]] +// [[2,7],[4,9],[12,18],[13,20],[14,21],[16,22],[10,23]] +// [[2,6],[5,9],[4,7],[14,20],[13,18],[17,22],[11,23]] +// [[3,6],[5,8],[14,19],[16,20],[17,21],[0,13],[9,22]] +// [[5,7],[4,6],[14,18],[15,19],[17,20],[0,12],[8,21],[10,22]] +// [[5,6],[15,18],[17,19],[1,14],[7,20],[11,22]] +// [[16,18],[2,15],[1,12],[6,19],[8,20],[11,21]] +// [[17,18],[2,14],[3,16],[7,19],[10,20]] +// [[2,13],[4,17],[5,18],[8,19],[11,20]] +// [[2,12],[5,17],[4,16],[3,13],[9,19]] +// [[5,16],[3,12],[4,14],[10,19]] +// [[5,15],[4,12],[11,19],[9,16],[10,17]] +// [[5,14],[8,15],[11,18],[10,16]] +// [[5,13],[7,14],[11,17]] +// [[5,12],[6,13],[8,14],[11,16]] +// [[6,12],[8,13],[10,14],[11,15]] +// [[7,12],[9,13],[11,14]] +// [[8,12],[11,13]] +// [[9,12]] +// [[10,12]] +// [[11,12]] + +// This is graphed in 89 columns. + +static void fsort25(uint8_t *data) +{ + cmpswp(data+1, data+2); + cmpswp(data+4, data+5); + cmpswp(data+7, data+8); + cmpswp(data+10, data+11); + cmpswp(data+13, data+14); + cmpswp(data+16, data+17); + cmpswp(data+19, data+20); + cmpswp(data+21, data+22); + cmpswp(data+23, data+24); + + cmpswp(data+0, data+2); + cmpswp(data+3, data+5); + cmpswp(data+6, data+8); + cmpswp(data+9, data+11); + cmpswp(data+12, data+14); + cmpswp(data+15, data+17); + cmpswp(data+18, data+20); + cmpswp(data+21, data+23); + cmpswp(data+22, data+24); + + cmpswp(data+0, data+1); + cmpswp(data+3, data+4); + cmpswp(data+2, data+5); + cmpswp(data+6, data+7); + cmpswp(data+9, data+10); + cmpswp(data+8, data+11); + cmpswp(data+12, data+13); + cmpswp(data+15, data+16); + cmpswp(data+14, data+17); + cmpswp(data+18, data+19); + cmpswp(data+22, data+23); + cmpswp(data+20, data+24); + + cmpswp(data+0, data+3); + cmpswp(data+1, data+4); + cmpswp(data+6, data+9); + cmpswp(data+7, data+10); + cmpswp(data+5, data+11); + cmpswp(data+12, data+15); + cmpswp(data+13, data+16); + cmpswp(data+18, data+22); + cmpswp(data+19, data+23); + cmpswp(data+17, data+24); + + cmpswp(data+2, data+4); + cmpswp(data+1, data+3); + cmpswp(data+8, data+10); + cmpswp(data+7, data+9); + cmpswp(data+0, data+6); + cmpswp(data+14, data+16); + cmpswp(data+13, data+15); + cmpswp(data+18, data+21); + cmpswp(data+20, data+23); + cmpswp(data+11, data+24); + + cmpswp(data+2, data+3); + cmpswp(data+8, data+9); + cmpswp(data+1, data+7); + cmpswp(data+4, data+10); + cmpswp(data+14, data+15); + cmpswp(data+19, data+21); + cmpswp(data+20, data+22); + cmpswp(data+16, data+23); + + cmpswp(data+2, data+8); + cmpswp(data+1, data+6); + cmpswp(data+3, data+9); + cmpswp(data+5, data+10); + cmpswp(data+20, data+21); + cmpswp(data+12, data+19); + cmpswp(data+15, data+22); + cmpswp(data+17, data+23); + + cmpswp(data+2, data+7); + cmpswp(data+4, data+9); + cmpswp(data+12, data+18); + cmpswp(data+13, data+20); + cmpswp(data+14, data+21); + cmpswp(data+16, data+22); + cmpswp(data+10, data+23); + + cmpswp(data+2, data+6); + cmpswp(data+5, data+9); + cmpswp(data+4, data+7); + cmpswp(data+14, data+20); + cmpswp(data+13, data+18); + cmpswp(data+17, data+22); + cmpswp(data+11, data+23); + + cmpswp(data+3, data+6); + cmpswp(data+5, data+8); + cmpswp(data+14, data+19); + cmpswp(data+16, data+20); + cmpswp(data+17, data+21); + cmpswp(data+0, data+13); + cmpswp(data+9, data+22); + + cmpswp(data+5, data+7); + cmpswp(data+4, data+6); + cmpswp(data+14, data+18); + cmpswp(data+15, data+19); + cmpswp(data+17, data+20); + cmpswp(data+0, data+12); + cmpswp(data+8, data+21); + cmpswp(data+10, data+22); + + cmpswp(data+5, data+6); + cmpswp(data+15, data+18); + cmpswp(data+17, data+19); + cmpswp(data+1, data+14); + cmpswp(data+7, data+20); + cmpswp(data+11, data+22); + + cmpswp(data+16, data+18); + cmpswp(data+2, data+15); + cmpswp(data+1, data+12); + cmpswp(data+6, data+19); + cmpswp(data+8, data+20); + cmpswp(data+11, data+21); + + cmpswp(data+17, data+18); + cmpswp(data+2, data+14); + cmpswp(data+3, data+16); + cmpswp(data+7, data+19); + cmpswp(data+10, data+20); + + cmpswp(data+2, data+13); + cmpswp(data+4, data+17); + cmpswp(data+5, data+18); + cmpswp(data+8, data+19); + cmpswp(data+11, data+20); + + cmpswp(data+2, data+12); + cmpswp(data+5, data+17); + cmpswp(data+4, data+16); + cmpswp(data+3, data+13); + cmpswp(data+9, data+19); + + cmpswp(data+5, data+16); + cmpswp(data+3, data+12); + cmpswp(data+4, data+14); + cmpswp(data+10, data+19); + + cmpswp(data+5, data+15); + cmpswp(data+4, data+12); + cmpswp(data+11, data+19); + cmpswp(data+9, data+16); + cmpswp(data+10, data+17); + + cmpswp(data+5, data+14); + cmpswp(data+8, data+15); + cmpswp(data+11, data+18); + cmpswp(data+10, data+16); + + cmpswp(data+5, data+13); + cmpswp(data+7, data+14); + cmpswp(data+11, data+17); + + cmpswp(data+5, data+12); + cmpswp(data+6, data+13); + cmpswp(data+8, data+14); + cmpswp(data+11, data+16); + + cmpswp(data+6, data+12); + cmpswp(data+8, data+13); + cmpswp(data+10, data+14); + cmpswp(data+11, data+15); + + cmpswp(data+7, data+12); + cmpswp(data+9, data+13); + cmpswp(data+11, data+14); + + cmpswp(data+8, data+12); + cmpswp(data+11, data+13); + + cmpswp(data+9, data+12); + + cmpswp(data+10, data+12); + + cmpswp(data+11, data+12); +} + +void fsort(uint8_t *data, int n) +{ + switch(n) { + case 1: return; + case 9: fsort9(data); return; + case 25: fsort25(data); return; + default: return; + } +} diff --git a/src/omv/img/fsort.h b/src/omv/img/fsort.h new file mode 100644 index 000000000..556541c9d --- /dev/null +++ b/src/omv/img/fsort.h @@ -0,0 +1,13 @@ +/* + * 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. + * + * Fast 9 and 25 bin sort. + * + */ +#ifndef __FSORT_H__ +#define __FSORT_H__ +#include +void fsort(uint8_t *data, int n); +#endif /* __FSORT_H__ */ diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index e9c97fa17..e33d3fa7c 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -462,7 +462,9 @@ void imlib_statistics(image_t *img, rectangle_t *r, statistics_t *out); /* Image Filtering */ void imlib_midpoint_filter(image_t *img, const int ksize, const int bias); -void imlib_median_filter(image_t *src, int r); +void imlib_mean_filter(image_t *img, const int ksize); +void imlib_mode_filter(image_t *img, const int ksize); +void imlib_median_filter(image_t *img, const int ksize, const int percentile); /* Clustering functions */ array_t *cluster_kmeans(array_t *points, int k); diff --git a/src/omv/img/mean.c b/src/omv/img/mean.c new file mode 100644 index 000000000..931210428 --- /dev/null +++ b/src/omv/img/mean.c @@ -0,0 +1,81 @@ +/* + * 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. + * + * Mean filtering. + * + */ +#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 + +void imlib_mean_filter(image_t *img, const int ksize) +{ + int n = ((ksize*2)+1)*((ksize*2)+1); + 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; + 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 uint8_t pixel = IM_GET_GS_PIXEL(img, x+k, y+j); + acc += pixel; + } + } + } + // We're writing into the buffer like if it were a window. + buffer[((y%brows)*img->w)+x] = acc/n; + } + 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; + 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 += IM_R565(pixel); + g_acc += IM_G565(pixel); + b_acc += IM_B565(pixel); + } + } + } + // We're writing into the buffer like if it were a window. + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = IM_RGB565(r_acc/n, g_acc/n, b_acc/n); + } + 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/img/median.c b/src/omv/img/median.c index 9580c6f31..33ca916f0 100644 --- a/src/omv/img/median.c +++ b/src/omv/img/median.c @@ -3,176 +3,93 @@ * Copyright (c) 2013/2014 Ibrahim Abdelkader * This work is licensed under the MIT license, see the file LICENSE for details. * - * O(N) median_gs filter with histograms. + * Median filtering. * */ -#include +#include #include "imlib.h" #include "fb_alloc.h" +#include "fsort.h" -typedef struct { - int n; - union { - int h[256]; - struct { - int r[32]; - int g[64]; - int b[32]; - }; - }; -} histo_t; - -void del_pixels(image_t * im, int row, int col, int size, histo_t *h) +void imlib_median_filter(image_t *img, const int ksize, const int percentile) { - if (im->bpp==1) { - for (int i = row - size; i <= row + size && i < im->h; i++) { - if (i < 0) continue; - h->h[im->pixels[i*im->w+col]]--; - h->n--; + int n = ((ksize*2)+1)*((ksize*2)+1); + int brows = ksize + 1; + uint8_t *buffer = fb_alloc(img->w * brows * img->bpp); + if (IM_IS_GS(img)) { + uint8_t data[n]; + for (int y=0; yh; y++) { + for (int x=0; xw; x++) { + uint8_t *data_ptr = data; + 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 uint8_t pixel = IM_GET_GS_PIXEL(img, x+k, y+j); + *data_ptr++ = pixel; + } else { + *data_ptr++ = 0; + } + } + } + fsort(data, n); + int median = data[percentile]; + // We're writing into the buffer like if it were a window. + buffer[((y%brows)*img->w)+x] = median; + } + 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 i = row - size; i <= row + size && i < im->h; i++) { - if (i < 0) continue; - uint16_t c = ((uint16_t*)im->pixels)[i*im->w+col]; - h->r[IM_R565(c)]--; - h->g[IM_G565(c)]--; - h->b[IM_B565(c)]--; - h->n--; + uint8_t r_data[n]; + uint8_t g_data[n]; + uint8_t b_data[n]; + for (int y=0; yh; y++) { + for (int x=0; xw; x++) { + uint8_t *r_data_ptr = r_data; + uint8_t *g_data_ptr = g_data; + uint8_t *b_data_ptr = b_data; + 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_data_ptr++ = IM_R565(pixel); + *g_data_ptr++ = IM_G565(pixel); + *b_data_ptr++ = IM_B565(pixel); + } else { + *r_data_ptr++ = 0; + *g_data_ptr++ = 0; + *b_data_ptr++ = 0; + } + } + } + fsort(r_data, n); + fsort(g_data, n); + fsort(b_data, n); + int r_median = r_data[percentile]; + int g_median = g_data[percentile]; + int b_median = b_data[percentile]; + // We're writing into the buffer like if it were a window. + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = IM_RGB565(r_median, g_median, b_median); + } + 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)); } } -} - -void add_pixels(image_t * im, int row, int col, int size, histo_t *h) -{ - if (im->bpp==1) { - for (int i = row - size; i <= row + size && i < im->h; i++) { - if (i < 0) continue; - h->h[im->pixels[i*im->w+col]]++; - h->n++; - } - } else { - for (int i = row - size; i <= row + size && i < im->h; i++) { - if (i < 0) continue; - uint16_t c = ((uint16_t*)im->pixels)[i*im->w+col]; - h->r[IM_R565(c)]++; - h->g[IM_G565(c)]++; - h->b[IM_B565(c)]++; - h->n++; - } - } -} - -void init_histo(image_t *im, int row, int size, histo_t *h) -{ - memset(h, 0, sizeof(histo_t)); - for (int j = 0; j < size && j < im->w; j++) { - add_pixels(im, row, j, size, h); - } -} - -uint8_t median_gs(histo_t *h) -{ - int i, n; - for (i=0, n=h->n/2; (n -= h->h[i]) >0; i++) { - } - return i; -} - -uint16_t median_rgb(histo_t *h) -{ - int rx = h->n/2; - int gx = h->n/2; - int bx = h->n/2; - uint16_t r=0, g=0, b=0; - - for (int i=0; (rx>0 || gx>0 || bx>0); i++) { - if (rx > 0) { - rx -= h->r[i]; - r = i; - } - - if (gx > 0) { - gx -= h->g[i]; - g = i; - } - - if (bx > 0) { - bx -= h->b[i]; - b = i; - } - } - - return IM_RGB565(r, g, b); -} - -void median_filter_rgb(image_t *in, int size) -{ - int k_rows = 24; - histo_t *h = fb_alloc(sizeof(*h)); - uint16_t *dst = (uint16_t*) in->data; - uint16_t *data = fb_alloc(in->w * k_rows * sizeof(*data)); - - for (int row=0; rowh; row++) { - init_histo(in, row, size, h); - data[(row%k_rows)*in->w+size] = median_rgb(h); - - for (int col=0; colw; col++) { - del_pixels(in, row, col - size, size, h); - add_pixels(in, row, col + size, size, h); - data[(row%k_rows)*in->w+col] = median_rgb(h); - } - - if ((row+1)%k_rows==0) { - memcpy(dst, data, (in->w*(k_rows-size)*2)); - dst += (in->w*(k_rows-size)); - } - - if ((row>size && (row+1)%k_rows==size) || row+1==in->h){ - memcpy(dst, data+in->w*(k_rows-size), (in->w*size*2)); - dst += (in->w*size); - } - - } - fb_free_all(); -} - -void median_filter_gs(image_t *in, int size) -{ - int k_rows = 24; - histo_t *h = fb_alloc(sizeof(*h)); - uint8_t *dst = (uint8_t*) in->data; - uint8_t *data = fb_alloc(in->w * k_rows * sizeof(*data)); - - for (int row=0; rowh; row++) { - init_histo(in, row, size, h); - data[(row%k_rows)*in->w+size] = median_gs(h); - - for (int col=0; colw; col++) { - del_pixels(in, row, col - size, size, h); - add_pixels(in, row, col + size, size, h); - data[(row%k_rows)*in->w+col] = median_gs(h); - } - - if ((row+1)%k_rows==0) { - memcpy(dst, data, (in->w*(k_rows-size))); - dst += (in->w*(k_rows-size)); - } - - if ((row>size && (row+1)%k_rows==size) || row+1==in->h){ - memcpy(dst, data+in->w*(k_rows-size), (in->w*size)); - dst += (in->w*size); - } - - } - fb_free_all(); -} - -void imlib_median_filter(image_t *in, int size) -{ - if (in->bpp == 1) { - //median_filter_test(in, size); - median_filter_gs(in, size); - } else { - median_filter_rgb(in, size); - } + fb_free(); } diff --git a/src/omv/img/mode.c b/src/omv/img/mode.c new file mode 100644 index 000000000..a98cc97be --- /dev/null +++ b/src/omv/img/mode.c @@ -0,0 +1,111 @@ +/* + * 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. + * + * Mode filtering. + * + */ +#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 + +void imlib_mode_filter(image_t *img, const int ksize) +{ + int brows = ksize + 1; + uint8_t *buffer = fb_alloc(img->w * brows * img->bpp); + if (IM_IS_GS(img)) { + uint8_t *bins = fb_alloc(256); + for (int y=0; yh; y++) { + for (int x=0; xw; x++) { + memset(bins, 0, 256); + int mcount = 0, mode = 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 uint8_t pixel = IM_GET_GS_PIXEL(img, x+k, y+j); + bins[pixel]++; + if(bins[pixel] > mcount) { + mcount = bins[pixel]; + mode = pixel; + } + } + } + } + // We're writing into the buffer like if it were a window. + buffer[((y%brows)*img->w)+x] = mode; + } + 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)); + } + fb_free(); + } else { + uint8_t *r_bins = fb_alloc(32); + uint8_t *g_bins = fb_alloc(64); + uint8_t *b_bins = fb_alloc(32); + for (int y=0; yh; y++) { + for (int x=0; xw; x++) { + memset(r_bins, 0, 32); + memset(g_bins, 0, 64); + memset(b_bins, 0, 32); + int r_mcount = 0, r_mode = 0; + int g_mcount = 0, g_mode = 0; + int b_mcount = 0, b_mode = 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); + int red = IM_R565(pixel); + int green = IM_G565(pixel); + int blue = IM_B565(pixel); + r_bins[red]++; + if(r_bins[red] > r_mcount) { + r_mcount = r_bins[red]; + r_mode = red; + } + g_bins[green]++; + if(g_bins[green] > g_mcount) { + g_mcount = g_bins[green]; + g_mode = green; + } + b_bins[blue]++; + if(b_bins[blue] > b_mcount) { + b_mcount = b_bins[blue]; + b_mode = blue; + } + } + } + } + // We're writing into the buffer like if it were a window. + ((uint16_t *) buffer)[((y%brows)*img->w)+x] = IM_RGB565(r_mode, g_mode, b_mode); + } + 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(); + fb_free(); + fb_free(); + } + fb_free(); +} diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index dc19a4ba0..eb210c407 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -710,6 +710,48 @@ static mp_obj_t py_image_midpoint(uint n_args, const mp_obj_t *args, mp_map_t *k return mp_const_none; } +static mp_obj_t py_image_mean(mp_obj_t img_obj, mp_obj_t k_obj) +{ + image_t *arg_img = py_image_cobj(img_obj); + PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), + "Operation not supported on JPEG"); + + int arg_ksize = mp_obj_get_int(k_obj); + PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); + + imlib_mean_filter(arg_img, arg_ksize); + return mp_const_none; +} + +static mp_obj_t py_image_mode(mp_obj_t img_obj, mp_obj_t k_obj) +{ + image_t *arg_img = py_image_cobj(img_obj); + PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), + "Operation not supported on JPEG"); + + int arg_ksize = mp_obj_get_int(k_obj); + PY_ASSERT_TRUE_MSG(arg_ksize >= 0, "Kernel Size must be >= 0"); + + imlib_mode_filter(arg_img, arg_ksize); + return mp_const_none; +} + +static mp_obj_t py_image_median(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"); + PY_ASSERT_TRUE_MSG(arg_ksize <= 2, "Kernel Size must be <= 2"); + + int n = ((arg_ksize*2)+1)*((arg_ksize*2)+1); + int percentile = py_helper_lookup_float(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_percentile), 0.5) * n; + imlib_median_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(percentile, 0), n-1)); + return mp_const_none; +} + static mp_obj_t py_image_scale(mp_obj_t image_obj, mp_obj_t size_obj) { int w,h; @@ -860,17 +902,6 @@ static mp_obj_t py_image_histeq(mp_obj_t image_obj) return mp_const_none; } -static mp_obj_t py_image_median(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) -{ - // Read args - image_t *image = py_image_cobj(args[0]); - int ksize = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 1); - - // Call median filter - imlib_median_filter(image, ksize); - return mp_const_none; -} - static mp_obj_t py_image_threshold(mp_obj_t image_obj, mp_obj_t color_list_obj, mp_obj_t threshold) { color_t *color; @@ -1198,7 +1229,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_morph_obj, 3, py_image_morph); STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_statistics_obj, 1, py_image_statistics); /* Image Filtering */ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_midpoint_obj, 2, py_image_midpoint); -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_median_obj, 1, py_image_median); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_mean_obj, py_image_mean); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_mode_obj, py_image_mode); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_median_obj, 2, py_image_median); 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); @@ -1258,6 +1291,8 @@ static const mp_map_elem_t locals_dict_table[] = { {MP_OBJ_NEW_QSTR(MP_QSTR_statistics), (mp_obj_t)&py_image_statistics_obj}, /* Image Filtering */ {MP_OBJ_NEW_QSTR(MP_QSTR_midpoint), (mp_obj_t)&py_image_midpoint_obj}, + {MP_OBJ_NEW_QSTR(MP_QSTR_mean), (mp_obj_t)&py_image_mean_obj}, + {MP_OBJ_NEW_QSTR(MP_QSTR_mode), (mp_obj_t)&py_image_mode_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_median), (mp_obj_t)&py_image_median_obj}, {MP_OBJ_NEW_QSTR(MP_QSTR_scale), (mp_obj_t)&py_image_scale_obj}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index 97b6b1ac5..b2e0c68be 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -55,6 +55,8 @@ Q(difference) Q(morph) Q(statistics) Q(midpoint) +Q(mean) +Q(mode) Q(median) Q(kp_desc) Q(lbp_desc)