From 5377f6f57535afe1baef20f3a6f97d2da9956627 Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Sun, 28 Jan 2018 19:23:28 -0500 Subject: [PATCH] Add single image shadow remover code. Calling remove_shadows() on an image without a background source of truth image now works. However, that said, the shadow remover isn't suitable for anything other than removing shadow on an image of concrete flow or somthing of the like. In general, it can only remove shadows from a scene that has nothing else in it except for a hard edge shadow. Improving this to work for anything is about a month of work. I've researched enough about shadow removal to now know the optimal way to do it. However, it requires many steps and a large amount of RAM. On the H7 I may revist this as being possible. ... In order to get the shadow remover working well I had to add a few features to the image library and fix some of the convolution code. These fixes will likely be more useful than the shadow removal code. Note the addition of the new get_threshold() method. This computes otsu's threshold on a histogram allowing you to pick the optimal color bounds. --- src/Makefile | 1 + src/omv/Makefile | 1 + src/omv/img/imlib.c | 335 +---------------------- src/omv/img/imlib.h | 27 +- src/omv/img/mean.c | 24 +- src/omv/img/median.c | 30 +-- src/omv/img/midpoint.c | 38 +-- src/omv/img/mode.c | 58 ++-- src/omv/img/morph.c | 18 +- src/omv/img/shadow_removal.c | 503 +++++++++++++++++++++++++++++++++++ src/omv/img/stats.c | 60 ++++- src/omv/py/py_image.c | 157 ++++++++++- src/omv/py/qstrdefsomv.h | 7 + 13 files changed, 825 insertions(+), 434 deletions(-) diff --git a/src/Makefile b/src/Makefile index d05ea03bc..d014fa72f 100755 --- a/src/Makefile +++ b/src/Makefile @@ -181,6 +181,7 @@ FIRM_OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/img/,\ orb.o \ template.o \ phasecorrelation.o \ + shadow_removal.o \ font.o \ jpeg.o \ lbp.o \ diff --git a/src/omv/Makefile b/src/omv/Makefile index 63584ccfb..db7e64017 100644 --- a/src/omv/Makefile +++ b/src/omv/Makefile @@ -59,6 +59,7 @@ SRCS += $(addprefix img/, \ orb.c \ template.c \ phasecorrelation.c \ + shadow_removal.c \ font.c \ jpeg.c \ lbp.c \ diff --git a/src/omv/img/imlib.c b/src/omv/img/imlib.c index 88b50b6d4..fffa45f60 100644 --- a/src/omv/img/imlib.c +++ b/src/omv/img/imlib.c @@ -836,13 +836,9 @@ static void imlib_erode_dilate(image_t *img, int ksize, int threshold, int e_or_ int acc = e_or_d ? 0 : -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)) { - acc += !!IM_GET_GS_PIXEL(img, x+k, y+j); - } else { // outer pixels should not affect result. - acc += e_or_d ? 0 : 1; - // 1 for erode prevents acc from being lower. - // 0 for dilate prevents acc from being higher. - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + acc += !!IM_GET_GS_PIXEL(img, x_k, y_j); } } if (!e_or_d) { @@ -876,13 +872,9 @@ static void imlib_erode_dilate(image_t *img, int ksize, int threshold, int e_or_ int acc = e_or_d ? 0 : -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)) { - acc += !!IM_GET_RGB565_PIXEL(img, x+k, y+j); - } else { // outer pixels should not affect result. - acc += e_or_d ? 0 : 1; - // 1 for erode prevents acc from being lower. - // 0 for dilate prevents acc from being higher. - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + acc += !!IM_GET_RGB565_PIXEL(img, x_k, y_j); } } if (!e_or_d) { @@ -1074,323 +1066,8 @@ void imlib_min(image_t *img, const char *path, image_t *other) imlib_image_operation(img, path, other, imlib_min_line_op, NULL); } -// http://arma.sourceforge.net/shadows/ -// http://dx.doi.org/10.1016/j.patcog.2011.10.001 - -#define imlib_remove_shadows_kernel_rank 1 -#define imlib_remove_shadows_kernel_size ((imlib_remove_shadows_kernel_rank * 2) + 1) - -typedef struct imlib_remove_shadows_line_op_state { - uint16_t *img_lines[imlib_remove_shadows_kernel_size]; - uint16_t *other_lines[imlib_remove_shadows_kernel_size]; - uint16_t *out_lines[imlib_remove_shadows_kernel_size]; - int lines_processed; -} imlib_remove_shadows_line_op_state_t; - -static void imlib_remove_shadows_sub_sub_line_op(image_t *img, int line, void *data, bool vflipped) -{ - imlib_remove_shadows_line_op_state_t *state = (imlib_remove_shadows_line_op_state_t *) data; - - state->lines_processed += 1; - - if (state->lines_processed >= imlib_remove_shadows_kernel_size) { - int y = vflipped ? ((line - 1) + imlib_remove_shadows_kernel_size) : ((line + 1) - imlib_remove_shadows_kernel_size); - int index = y % imlib_remove_shadows_kernel_size; - memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), state->out_lines[index], img->w * sizeof(uint16_t)); - } -} - -static void imlib_remove_shadows_sub_line_op(image_t *img, int line, void *data, bool vflipped) -{ - imlib_remove_shadows_line_op_state_t *state = (imlib_remove_shadows_line_op_state_t *) data; - - if (state->lines_processed >= imlib_remove_shadows_kernel_rank) { - int y = vflipped ? (line + imlib_remove_shadows_kernel_rank) : (line - imlib_remove_shadows_kernel_rank); - int index = y % imlib_remove_shadows_kernel_size; - - for (int x = 0, xx = img->w; x < xx; x++) { - int img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->img_lines[index], x); - int img_r = COLOR_RGB565_TO_R8(img_pixel); - int img_g = COLOR_RGB565_TO_G8(img_pixel); - int img_b = COLOR_RGB565_TO_B8(img_pixel); - float img_v = IM_MAX(IM_MAX(img_r, img_g), img_b); - int other_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->other_lines[index], x); - int other_r = COLOR_RGB565_TO_R8(other_pixel); - int other_g = COLOR_RGB565_TO_G8(other_pixel); - int other_b = COLOR_RGB565_TO_B8(other_pixel); - float other_v = IM_MAX(IM_MAX(other_r, other_g), other_b); - float ratio = img_v / other_v; - - if ((0.3f < ratio) && (ratio < 1.0f)) { - int minY = IM_MAX(y - imlib_remove_shadows_kernel_rank, 0); - int maxY = IM_MIN(y + imlib_remove_shadows_kernel_rank, img->h - 1); - int minX = IM_MAX(x - imlib_remove_shadows_kernel_rank, 0); - int maxX = IM_MIN(x + imlib_remove_shadows_kernel_rank, img->w - 1); - int windowArea = (maxX - minX + 1) * (maxY - minY + 1); - int hDiffSum = 0; - int sDiffSum = 0; - - for (int k_y = minY; k_y <= maxY; k_y++) { - int k_index = k_y % imlib_remove_shadows_kernel_size; - - for (int k_x = minX; k_x <= maxX; k_x++) { - int k_img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->img_lines[k_index], k_x); - int k_img_r = COLOR_RGB565_TO_R8(k_img_pixel); - int k_img_g = COLOR_RGB565_TO_G8(k_img_pixel); - int k_img_b = COLOR_RGB565_TO_B8(k_img_pixel); - int k_img_cmax = IM_MAX(IM_MAX(k_img_r, k_img_g), k_img_b); - int k_img_cmin = IM_MAX(IM_MAX(k_img_r, k_img_g), k_img_b); - float k_img_cdel = k_img_cmax - k_img_cmin; - float k_img_h = 0; - float k_img_s = k_img_cmax ? (k_img_cdel / k_img_cmax) : 0; - int k_other_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->other_lines[k_index], k_x); - int k_other_r = COLOR_RGB565_TO_R8(k_other_pixel); - int k_other_g = COLOR_RGB565_TO_G8(k_other_pixel); - int k_other_b = COLOR_RGB565_TO_B8(k_other_pixel); - int k_other_cmax = IM_MAX(IM_MAX(k_other_r, k_other_g), k_other_b); - int k_other_cmin = IM_MAX(IM_MAX(k_other_r, k_other_g), k_other_b); - float k_other_cdel = k_other_cmax - k_other_cmin; - float k_other_h = 0; - float k_other_s = k_other_cmax ? (k_other_cdel / k_other_cmax) : 0; - - if (k_img_cdel) { - if (k_img_cmax == k_img_r) { - k_img_h = ((k_img_g - k_img_b) / k_img_cdel) + 0; - } else if (k_img_cmax == k_img_g) { - k_img_h = ((k_img_b - k_img_r) / k_img_cdel) + 2; - } else if (k_img_cmax == k_img_b) { - k_img_h = ((k_img_r - k_img_g) / k_img_cdel) + 4; - } - k_img_h *= 60; - if (k_img_h < 0) k_img_h += 360.0; - } - - if (k_other_cdel) { - if (k_other_cmax == k_other_r) { - k_other_h = ((k_other_g - k_other_b) / k_other_cdel) + 0; - } else if (k_other_cmax == k_other_g) { - k_other_h = ((k_other_b - k_other_r) / k_other_cdel) + 2; - } else if (k_other_cmax == k_other_b) { - k_other_h = ((k_other_r - k_other_g) / k_other_cdel) + 4; - } - k_other_h *= 60; - if (k_other_h < 0) k_other_h += 360.0; - } - - int hDiff = abs(k_img_h - k_other_h); - hDiffSum += (hDiff > 90) ? (180 - hDiff) : hDiff; - sDiffSum += k_img_s - k_other_s; - } - } - - bool hIsShadow = (hDiffSum / windowArea) < 48; - bool sIsShadow = (sDiffSum / windowArea) < 40; - IMAGE_PUT_RGB565_PIXEL_FAST(state->out_lines[index], x, (hIsShadow && sIsShadow) ? other_pixel : img_pixel); - } else { - IMAGE_PUT_RGB565_PIXEL_FAST(state->out_lines[index], x, img_pixel); - } - } - } - - imlib_remove_shadows_sub_sub_line_op(img, line, data, vflipped); -} - -static void imlib_remove_shadows_line_op(image_t *img, int line, uint8_t *other, void *data, bool vflipped) -{ - imlib_remove_shadows_line_op_state_t *state = (imlib_remove_shadows_line_op_state_t *) data; - - memcpy(state->img_lines[line % imlib_remove_shadows_kernel_size], - IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line), img->w * sizeof(uint16_t)); - - memcpy(state->other_lines[line % imlib_remove_shadows_kernel_size], - (uint16_t *) other, img->w * sizeof(uint16_t)); - - imlib_remove_shadows_sub_line_op(img, line, data, vflipped); - - if (state->lines_processed == img->h) { - for (int i = 0; i < imlib_remove_shadows_kernel_rank; i++) { - line += vflipped ? -1 : 1; - imlib_remove_shadows_sub_line_op(img, line, data, vflipped); - } - - for (int i = 0; i < imlib_remove_shadows_kernel_rank; i++) { - line += vflipped ? -1 : 1; - imlib_remove_shadows_sub_sub_line_op(img, line, data, vflipped); - } - } -} - -void imlib_remove_shadows(image_t *img, const char *path, image_t *other) -{ - imlib_remove_shadows_line_op_state_t state; - - for (int i = 0; i < imlib_remove_shadows_kernel_size; i++) { - state.img_lines[i] = fb_alloc(img->w * sizeof(uint16_t)); - state.other_lines[i] = fb_alloc(img->w * sizeof(uint16_t)); - state.out_lines[i] = fb_alloc(img->w * sizeof(uint16_t)); - } - - state.lines_processed = 0; - - imlib_image_operation(img, path, other, imlib_remove_shadows_line_op, (void *) &state); - - for (int i = 0; i < imlib_remove_shadows_kernel_size; i++) { - fb_free(); - fb_free(); - fb_free(); - } -} - -#undef imlib_remove_shadows_kernel_size -#undef imlib_remove_shadows_kernel_rank - //////////////////////////////////////////////////////////////////////////////// -void imlib_chrominvar(image_t *img) -{ - switch(img->bpp) { - case IMAGE_BPP_BINARY: { - break; - } - case IMAGE_BPP_GRAYSCALE: { - break; - } - case IMAGE_BPP_RGB565: { - for (int y = 0, yy = img->h; y < yy; y++) { - uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); - for (int x = 0, xx = img->w; x < xx; x++) { - int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); - float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; - float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; - float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; - - float lin_sum = r_lin + g_lin + b_lin; - - float r_lin_div = 0.0f; - float g_lin_div = 0.0f; - float b_lin_div = 0.0f; - - if (lin_sum > 0.0f) { - lin_sum = 1.0f / lin_sum; - r_lin_div = r_lin * lin_sum; - g_lin_div = g_lin * lin_sum; - b_lin_div = b_lin * lin_sum; - } - - int r_lin_div_int = IM_MAX(IM_MIN(r_lin_div * 255.0f, COLOR_R8_MAX), COLOR_R8_MIN); - int g_lin_div_int = IM_MAX(IM_MIN(g_lin_div * 255.0f, COLOR_G8_MAX), COLOR_G8_MIN); - int b_lin_div_int = IM_MAX(IM_MIN(b_lin_div * 255.0f, COLOR_B8_MAX), COLOR_B8_MIN); - - IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, COLOR_R8_G8_B8_TO_RGB565(r_lin_div_int, g_lin_div_int, b_lin_div_int)); - } - } - - break; - } - default: { - break; - } - } -} - -extern const uint16_t invariant_table[65536]; - -void imlib_illuminvar(image_t *img) // http://ai.stanford.edu/~alireza/publication/cic15.pdf -{ - switch(img->bpp) { - case IMAGE_BPP_BINARY: { - break; - } - case IMAGE_BPP_GRAYSCALE: { - break; - } - case IMAGE_BPP_RGB565: { - for (int y = 0, yy = img->h; y < yy; y++) { - uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); - for (int x = 0, xx = img->w; x < xx; x++) { - int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); -#ifdef OMV_HAVE_INVARIANT_TABLE - int rgb565 = invariant_table[pixel]; -#else - float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)] + 1.0; - float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)] + 1.0; - float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)] + 1.0; - - float r_lin_sharp = (r_lin * 0.9968f) + (g_lin * 0.0228f) + (b_lin * 0.0015f); - float g_lin_sharp = (r_lin * -0.0071f) + (g_lin * 0.9933f) + (b_lin * 0.0146f); - float b_lin_sharp = (r_lin * 0.0103f) + (g_lin * -0.0161f) + (b_lin * 0.9839f); - - float lin_sharp_avg = r_lin_sharp * g_lin_sharp * b_lin_sharp; - lin_sharp_avg = (lin_sharp_avg > 0.0f) ? fast_cbrtf(lin_sharp_avg) : 0.0f; - - float r_lin_sharp_div = 0.0f; - float g_lin_sharp_div = 0.0f; - float b_lin_sharp_div = 0.0f; - - if (lin_sharp_avg > 0.0f) { - lin_sharp_avg = 1.0f / lin_sharp_avg; - r_lin_sharp_div = r_lin_sharp * lin_sharp_avg; - g_lin_sharp_div = g_lin_sharp * lin_sharp_avg; - b_lin_sharp_div = b_lin_sharp * lin_sharp_avg; - } - - float r_lin_sharp_div_log = (r_lin_sharp_div > 0.0f) ? fast_log(r_lin_sharp_div) : 0.0f; - float g_lin_sharp_div_log = (g_lin_sharp_div > 0.0f) ? fast_log(g_lin_sharp_div) : 0.0f; - float b_lin_sharp_div_log = (b_lin_sharp_div > 0.0f) ? fast_log(b_lin_sharp_div) : 0.0f; - - float chi_x = (r_lin_sharp_div_log * 0.7071f) + (g_lin_sharp_div_log * -0.7071f) + (b_lin_sharp_div_log * 0.0000f); - float chi_y = (r_lin_sharp_div_log * 0.4082f) + (g_lin_sharp_div_log * 0.4082f) + (b_lin_sharp_div_log * -0.8164f); - - float e_t_x = 0.9326f; - float e_t_y = -0.3609f; - - float p_th_00 = e_t_x * e_t_x; - float p_th_01 = e_t_x * e_t_y; - float p_th_10 = e_t_y * e_t_x; - float p_th_11 = e_t_y * e_t_y; - - float x_th_x = (p_th_00 * chi_x) + (p_th_01 * chi_y); - float x_th_y = (p_th_10 * chi_x) + (p_th_11 * chi_y); - - float r_chi = (x_th_x * 0.7071f) + (x_th_y * 0.4082f); - float g_chi = (x_th_x * -0.7071f) + (x_th_y * 0.4082f); - float b_chi = (x_th_x * 0.0000f) + (x_th_y * -0.8164f); - - float r_chi_invariant = fast_expf(r_chi); - float g_chi_invariant = fast_expf(g_chi); - float b_chi_invariant = fast_expf(b_chi); - - float chi_invariant_sum = r_chi_invariant + g_chi_invariant + b_chi_invariant; - - float r_chi_invariant_m = 0.0f; - float g_chi_invariant_m = 0.0f; - float b_chi_invariant_m = 0.0f; - - if (chi_invariant_sum > 0.0f) { - chi_invariant_sum = 1.0f / chi_invariant_sum; - r_chi_invariant_m = r_chi_invariant * chi_invariant_sum; - g_chi_invariant_m = g_chi_invariant * chi_invariant_sum; - b_chi_invariant_m = b_chi_invariant * chi_invariant_sum; - } - - int r_chi_invariant_m_int = IM_MAX(IM_MIN(r_chi_invariant_m * 255.0f, COLOR_R8_MAX), COLOR_R8_MIN); - int g_chi_invariant_m_int = IM_MAX(IM_MIN(g_chi_invariant_m * 255.0f, COLOR_G8_MAX), COLOR_G8_MIN); - int b_chi_invariant_m_int = IM_MAX(IM_MIN(b_chi_invariant_m * 255.0f, COLOR_B8_MAX), COLOR_B8_MIN); - - int rgb565 = COLOR_R8_G8_B8_TO_RGB565(r_chi_invariant_m_int, g_chi_invariant_m_int, b_chi_invariant_m_int); -#endif - IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, rgb565); - } - } - - break; - } - default: { - break; - } - } -} - void imlib_histeq(image_t *img) { switch(img->bpp) { diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index d89bb7b1e..7f8dd69a8 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -172,8 +172,8 @@ color_thresholds_list_lnk_data_t; #define COLOR_Y_MIN -128 #define COLOR_Y_MAX 127 #define COLOR_U_MIN -128 -#define COLOR_V_MAX 127 -#define COLOR_U_MIN -128 +#define COLOR_U_MAX 127 +#define COLOR_V_MIN -128 #define COLOR_V_MAX 127 extern const uint8_t rb528_table[32]; @@ -243,10 +243,10 @@ extern const int8_t yuv_table[196608]; _r_lin = (_r_lin > 0.0031308f) ? ((1.055f * powf(_r_lin, 0.416666f)) - 0.055f) : (_r_lin * 12.92f); \ _g_lin = (_g_lin > 0.0031308f) ? ((1.055f * powf(_g_lin, 0.416666f)) - 0.055f) : (_g_lin * 12.92f); \ _b_lin = (_b_lin > 0.0031308f) ? ((1.055f * powf(_b_lin, 0.416666f)) - 0.055f) : (_b_lin * 12.92f); \ - unsigned int _r = IM_MAX(IM_MIN(roundf(_r_lin * COLOR_R5_MAX), COLOR_R5_MAX), COLOR_R5_MIN); \ - unsigned int _g = IM_MAX(IM_MIN(roundf(_g_lin * COLOR_G6_MAX), COLOR_G6_MAX), COLOR_G6_MIN); \ - unsigned int _b = IM_MAX(IM_MIN(roundf(_b_lin * COLOR_B5_MAX), COLOR_B5_MAX), COLOR_B5_MIN); \ - COLOR_R5_G6_B5_TO_RGB565(_r, _g, _b); \ + unsigned int _rgb565_r = IM_MAX(IM_MIN(roundf(_r_lin * COLOR_R5_MAX), COLOR_R5_MAX), COLOR_R5_MIN); \ + unsigned int _rgb565_g = IM_MAX(IM_MIN(roundf(_g_lin * COLOR_G6_MAX), COLOR_G6_MAX), COLOR_G6_MIN); \ + unsigned int _rgb565_b = IM_MAX(IM_MIN(roundf(_b_lin * COLOR_B5_MAX), COLOR_B5_MAX), COLOR_B5_MIN); \ + COLOR_R5_G6_B5_TO_RGB565(_rgb565_r, _rgb565_g, _rgb565_b); \ }) // https://en.wikipedia.org/wiki/YCbCr -> JPEG Conversion @@ -941,6 +941,12 @@ typedef struct percentile { int8_t BValue; } percentile_t; +typedef struct threshold { + uint8_t LValue; + int8_t AValue; + int8_t BValue; +} threshold_t; + typedef struct statistics { uint8_t LMean, LMedian, LMode, LSTDev, LMin, LMax, LLQ, LUQ; int8_t AMean, AMedian, AMode, ASTDev, AMin, AMax, ALQ, AUQ; @@ -1133,10 +1139,10 @@ int imlib_image_mean(image_t *src); // grayscale only int imlib_image_std(image_t *src); // grayscale only /* Image Filtering */ -void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool threshold, int offset, bool invert); -void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert); -void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert); -void imlib_median_filter(image_t *img, const int ksize, const int percentile, bool threshold, int offset, bool invert); +void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool threshold, int offset, bool invert, image_t *mask); +void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); +void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); +void imlib_median_filter(image_t *img, const int ksize, const int percentile, bool threshold, int offset, bool invert, image_t *mask); void imlib_histeq(image_t *img); void imlib_mask_ellipse(image_t *img); @@ -1221,6 +1227,7 @@ void imlib_rotation_corr(image_t *img, float x_rotation, float y_rotation, void imlib_get_similarity(image_t *img, const char *path, image_t *other, float *avg, float *std, float *min, float *max); void imlib_get_histogram(histogram_t *out, image_t *ptr, rectangle_t *roi); void imlib_get_percentile(percentile_t *out, image_bpp_t bpp, histogram_t *ptr, float percentile); +void imlib_get_threshold(threshold_t *out, image_bpp_t bpp, histogram_t *ptr); void imlib_get_statistics(statistics_t *out, image_bpp_t bpp, histogram_t *ptr); bool imlib_get_regression(find_lines_list_lnk_data_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, bool robust); diff --git a/src/omv/img/mean.c b/src/omv/img/mean.c index 7d003fe72..f2dca02f4 100644 --- a/src/omv/img/mean.c +++ b/src/omv/img/mean.c @@ -15,7 +15,7 @@ // ... // krn_s == n -> ((n*2)+1)x((n*2)+1) kernel -void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert) +void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask) { int n = ((ksize*2)+1)*((ksize*2)+1); int brows = ksize + 1; @@ -26,14 +26,15 @@ void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset 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; - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + 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. uint8_t pixel = acc/n; + if (mask && (!IM_GET_GS_PIXEL(mask, x, y))) pixel = IM_GET_GS_PIXEL(img, x, y); buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { @@ -55,16 +56,17 @@ void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset 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); - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + 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 pixel = IM_RGB565(r_acc/n, g_acc/n, b_acc/n); + if (mask && (!IM_GET_RGB565_PIXEL(mask, x, y))) pixel = IM_GET_RGB565_PIXEL(img, x, y); ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { diff --git a/src/omv/img/median.c b/src/omv/img/median.c index c174c87cc..cc690f713 100644 --- a/src/omv/img/median.c +++ b/src/omv/img/median.c @@ -11,7 +11,7 @@ #include "fb_alloc.h" #include "fsort.h" -void imlib_median_filter(image_t *img, const int ksize, const int percentile, bool threshold, int offset, bool invert) +void imlib_median_filter(image_t *img, const int ksize, const int percentile, bool threshold, int offset, bool invert, image_t *mask) { int n = ((ksize*2)+1)*((ksize*2)+1); int brows = ksize + 1; @@ -23,18 +23,17 @@ void imlib_median_filter(image_t *img, const int ksize, const int percentile, bo 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; - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + const uint8_t pixel = IM_GET_GS_PIXEL(img, x_k, y_j); + *data_ptr++ = pixel; } } fsort(data, n); int median = data[percentile]; // We're writing into the buffer like if it were a window. uint8_t pixel = median; + if (mask && (!IM_GET_GS_PIXEL(mask, x, y))) pixel = IM_GET_GS_PIXEL(img, x, y); buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { @@ -59,16 +58,12 @@ void imlib_median_filter(image_t *img, const int ksize, const int percentile, bo 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; - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + 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); } } fsort(r_data, n); @@ -79,6 +74,7 @@ void imlib_median_filter(image_t *img, const int ksize, const int percentile, bo int b_median = b_data[percentile]; // We're writing into the buffer like if it were a window. uint16_t pixel = IM_RGB565(r_median, g_median, b_median); + if (mask && (!IM_GET_RGB565_PIXEL(mask, x, y))) pixel = IM_GET_RGB565_PIXEL(img, x, y); ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { diff --git a/src/omv/img/midpoint.c b/src/omv/img/midpoint.c index bbee25397..2de4e27d8 100644 --- a/src/omv/img/midpoint.c +++ b/src/omv/img/midpoint.c @@ -17,7 +17,7 @@ // bias == 0 to 256 -> 0.0 to 1.0 (0.0==min filter, 1.0==max filter) -void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool threshold, int offset, bool invert) +void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool threshold, int offset, bool invert, image_t *mask) { int min_bias = (256-bias); int max_bias = bias; @@ -29,15 +29,16 @@ void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool t int min = 255, max = 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); - min = IM_MIN(min, pixel); - max = IM_MAX(max, pixel); - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + const uint8_t pixel = IM_GET_GS_PIXEL(img, x_k, y_j); + min = IM_MIN(min, pixel); + max = IM_MAX(max, pixel); } } // We're writing into the buffer like if it were a window. int pixel = ((min*min_bias)+(max*max_bias))>>8; + if (mask && (!IM_GET_GS_PIXEL(mask, x, y))) pixel = IM_GET_GS_PIXEL(img, x, y); buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { @@ -59,24 +60,25 @@ void imlib_midpoint_filter(image_t *img, const int ksize, const int bias, bool t int b_min = 255, b_max = 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_min = IM_MIN(r_min, red); - r_max = IM_MAX(r_max, red); - g_min = IM_MIN(g_min, green); - g_max = IM_MAX(g_max, green); - b_min = IM_MIN(b_min, blue); - b_max = IM_MAX(b_max, blue); - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + 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_min = IM_MIN(r_min, red); + r_max = IM_MAX(r_max, red); + g_min = IM_MIN(g_min, green); + g_max = IM_MAX(g_max, green); + b_min = IM_MIN(b_min, blue); + b_max = IM_MAX(b_max, blue); } } // We're writing into the buffer like if it were a window. uint16_t pixel = IM_RGB565(((r_min*min_bias)+(r_max*max_bias))>>8, ((g_min*min_bias)+(g_max*max_bias))>>8, ((b_min*min_bias)+(b_max*max_bias))>>8); + if (mask && (!IM_GET_RGB565_PIXEL(mask, x, y))) pixel = IM_GET_RGB565_PIXEL(img, x, y); ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { diff --git a/src/omv/img/mode.c b/src/omv/img/mode.c index deba05d98..c7e898117 100644 --- a/src/omv/img/mode.c +++ b/src/omv/img/mode.c @@ -15,7 +15,7 @@ // ... // krn_s == n -> ((n*2)+1)x((n*2)+1) kernel -void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert) +void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask) { int brows = ksize + 1; uint8_t *buffer = fb_alloc(img->w * brows * img->bpp); @@ -27,18 +27,19 @@ void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset 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; - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + 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. uint8_t pixel = mode; + if (mask && (!IM_GET_GS_PIXEL(mask, x, y))) pixel = IM_GET_GS_PIXEL(img, x, y); buffer[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((pixel-offset)=ksize) { @@ -67,31 +68,32 @@ void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset 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; - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + 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 pixel = IM_RGB565(r_mode, g_mode, b_mode); + if (mask && (!IM_GET_RGB565_PIXEL(mask, x, y))) pixel = IM_GET_RGB565_PIXEL(img, x, y); ((uint16_t *) buffer)[((y%brows)*img->w)+x] = (!threshold) ? pixel : ((((COLOR_RGB565_TO_Y(pixel)-offset)=ksize) { diff --git a/src/omv/img/morph.c b/src/omv/img/morph.c index 7be169022..a5c1e3fc2 100644 --- a/src/omv/img/morph.c +++ b/src/omv/img/morph.c @@ -30,9 +30,9 @@ void imlib_morph(image_t *img, const int ksize, const int8_t *krn, const float m 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); - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + acc += krn[ptr++] * IM_GET_GS_PIXEL(img, x_k, y_j); } } acc = (acc * m) + b; // scale, offset, and clamp @@ -60,12 +60,12 @@ void imlib_morph(image_t *img, const int ksize, const int8_t *krn, const float m 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); - } + int x_k = IM_MIN(IM_MAX(x+k, 0), img->w-1); + int y_j = IM_MIN(IM_MAX(y+j, 0), img->h-1); + 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 diff --git a/src/omv/img/shadow_removal.c b/src/omv/img/shadow_removal.c index e69de29bb..0d6b9c426 100644 --- a/src/omv/img/shadow_removal.c +++ b/src/omv/img/shadow_removal.c @@ -0,0 +1,503 @@ +/* This file is part of the OpenMV project. + * Copyright (c) 2013-2018 Ibrahim Abdelkader & Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + */ + +#include "imlib.h" + +// http://arma.sourceforge.net/shadows/ +// http://dx.doi.org/10.1016/j.patcog.2011.10.001 + +#define imlib_remove_shadows_kernel_rank 1 +#define imlib_remove_shadows_kernel_size ((imlib_remove_shadows_kernel_rank * 2) + 1) + +typedef struct imlib_remove_shadows_line_op_state { + uint16_t *img_lines[imlib_remove_shadows_kernel_size]; + uint16_t *other_lines[imlib_remove_shadows_kernel_size]; + uint16_t *out_lines[imlib_remove_shadows_kernel_size]; + int lines_processed; +} imlib_remove_shadows_line_op_state_t; + +static void imlib_remove_shadows_sub_sub_line_op(image_t *img, int line, void *data, bool vflipped) +{ + imlib_remove_shadows_line_op_state_t *state = (imlib_remove_shadows_line_op_state_t *) data; + + state->lines_processed += 1; + + if (state->lines_processed >= imlib_remove_shadows_kernel_size) { + int y = vflipped ? ((line - 1) + imlib_remove_shadows_kernel_size) : ((line + 1) - imlib_remove_shadows_kernel_size); + int index = y % imlib_remove_shadows_kernel_size; + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), state->out_lines[index], img->w * sizeof(uint16_t)); + } +} + +static void imlib_remove_shadows_sub_line_op(image_t *img, int line, void *data, bool vflipped) +{ + imlib_remove_shadows_line_op_state_t *state = (imlib_remove_shadows_line_op_state_t *) data; + + if (state->lines_processed >= imlib_remove_shadows_kernel_rank) { + int y = vflipped ? (line + imlib_remove_shadows_kernel_rank) : (line - imlib_remove_shadows_kernel_rank); + int index = y % imlib_remove_shadows_kernel_size; + + for (int x = 0, xx = img->w; x < xx; x++) { + int img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->img_lines[index], x); + int img_r = COLOR_RGB565_TO_R8(img_pixel); + int img_g = COLOR_RGB565_TO_G8(img_pixel); + int img_b = COLOR_RGB565_TO_B8(img_pixel); + float img_v = IM_MAX(IM_MAX(img_r, img_g), img_b); + int other_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->other_lines[index], x); + int other_r = COLOR_RGB565_TO_R8(other_pixel); + int other_g = COLOR_RGB565_TO_G8(other_pixel); + int other_b = COLOR_RGB565_TO_B8(other_pixel); + float other_v = IM_MAX(IM_MAX(other_r, other_g), other_b); + float ratio = img_v / other_v; + + if ((0.3f < ratio) && (ratio < 1.0f)) { + int minY = IM_MAX(y - imlib_remove_shadows_kernel_rank, 0); + int maxY = IM_MIN(y + imlib_remove_shadows_kernel_rank, img->h - 1); + int minX = IM_MAX(x - imlib_remove_shadows_kernel_rank, 0); + int maxX = IM_MIN(x + imlib_remove_shadows_kernel_rank, img->w - 1); + int windowArea = (maxX - minX + 1) * (maxY - minY + 1); + int hDiffSum = 0; + int sDiffSum = 0; + + for (int k_y = minY; k_y <= maxY; k_y++) { + int k_index = k_y % imlib_remove_shadows_kernel_size; + + for (int k_x = minX; k_x <= maxX; k_x++) { + int k_img_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->img_lines[k_index], k_x); + int k_img_r = COLOR_RGB565_TO_R8(k_img_pixel); + int k_img_g = COLOR_RGB565_TO_G8(k_img_pixel); + int k_img_b = COLOR_RGB565_TO_B8(k_img_pixel); + int k_img_cmax = IM_MAX(IM_MAX(k_img_r, k_img_g), k_img_b); + int k_img_cmin = IM_MAX(IM_MAX(k_img_r, k_img_g), k_img_b); + float k_img_cdel = k_img_cmax - k_img_cmin; + float k_img_h = 0; + float k_img_s = k_img_cmax ? (k_img_cdel / k_img_cmax) : 0; + int k_other_pixel = IMAGE_GET_RGB565_PIXEL_FAST(state->other_lines[k_index], k_x); + int k_other_r = COLOR_RGB565_TO_R8(k_other_pixel); + int k_other_g = COLOR_RGB565_TO_G8(k_other_pixel); + int k_other_b = COLOR_RGB565_TO_B8(k_other_pixel); + int k_other_cmax = IM_MAX(IM_MAX(k_other_r, k_other_g), k_other_b); + int k_other_cmin = IM_MAX(IM_MAX(k_other_r, k_other_g), k_other_b); + float k_other_cdel = k_other_cmax - k_other_cmin; + float k_other_h = 0; + float k_other_s = k_other_cmax ? (k_other_cdel / k_other_cmax) : 0; + + if (k_img_cdel) { + if (k_img_cmax == k_img_r) { + k_img_h = ((k_img_g - k_img_b) / k_img_cdel) + 0; + } else if (k_img_cmax == k_img_g) { + k_img_h = ((k_img_b - k_img_r) / k_img_cdel) + 2; + } else if (k_img_cmax == k_img_b) { + k_img_h = ((k_img_r - k_img_g) / k_img_cdel) + 4; + } + k_img_h *= 60; + if (k_img_h < 0) k_img_h += 360.0; + } + + if (k_other_cdel) { + if (k_other_cmax == k_other_r) { + k_other_h = ((k_other_g - k_other_b) / k_other_cdel) + 0; + } else if (k_other_cmax == k_other_g) { + k_other_h = ((k_other_b - k_other_r) / k_other_cdel) + 2; + } else if (k_other_cmax == k_other_b) { + k_other_h = ((k_other_r - k_other_g) / k_other_cdel) + 4; + } + k_other_h *= 60; + if (k_other_h < 0) k_other_h += 360.0; + } + + int hDiff = abs(k_img_h - k_other_h); + hDiffSum += (hDiff >= 90) ? (180 - hDiff) : hDiff; + sDiffSum += k_img_s - k_other_s; + } + } + + bool hIsShadow = (hDiffSum / windowArea) < 48; + bool sIsShadow = (sDiffSum / windowArea) < 40; + IMAGE_PUT_RGB565_PIXEL_FAST(state->out_lines[index], x, (hIsShadow && sIsShadow) ? other_pixel : img_pixel); + } else { + IMAGE_PUT_RGB565_PIXEL_FAST(state->out_lines[index], x, img_pixel); + } + } + } + + imlib_remove_shadows_sub_sub_line_op(img, line, data, vflipped); +} + +static void imlib_remove_shadows_line_op(image_t *img, int line, uint8_t *other, void *data, bool vflipped) +{ + imlib_remove_shadows_line_op_state_t *state = (imlib_remove_shadows_line_op_state_t *) data; + + memcpy(state->img_lines[line % imlib_remove_shadows_kernel_size], + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line), img->w * sizeof(uint16_t)); + + memcpy(state->other_lines[line % imlib_remove_shadows_kernel_size], + (uint16_t *) other, img->w * sizeof(uint16_t)); + + imlib_remove_shadows_sub_line_op(img, line, data, vflipped); + + if (state->lines_processed == img->h) { + for (int i = 0; i < imlib_remove_shadows_kernel_rank; i++) { + line += vflipped ? -1 : 1; + imlib_remove_shadows_sub_line_op(img, line, data, vflipped); + } + + for (int i = 0; i < imlib_remove_shadows_kernel_rank; i++) { + line += vflipped ? -1 : 1; + imlib_remove_shadows_sub_sub_line_op(img, line, data, vflipped); + } + } +} + +void imlib_remove_shadows(image_t *img, const char *path, image_t *other) +{ + if (path || other) { + imlib_remove_shadows_line_op_state_t state; + + for (int i = 0; i < imlib_remove_shadows_kernel_size; i++) { + state.img_lines[i] = fb_alloc(img->w * sizeof(uint16_t)); + state.other_lines[i] = fb_alloc(img->w * sizeof(uint16_t)); + state.out_lines[i] = fb_alloc(img->w * sizeof(uint16_t)); + } + + state.lines_processed = 0; + + imlib_image_operation(img, path, other, imlib_remove_shadows_line_op, (void *) &state); + + for (int i = 0; i < imlib_remove_shadows_kernel_size; i++) { + fb_free(); + fb_free(); + fb_free(); + } + } else { + + // Create Shadow Mask + + image_t temp_image; + temp_image.w = img->w; + temp_image.h = img->h; + temp_image.bpp = img->bpp; + temp_image.data = fb_alloc(image_size(img)); + + memcpy(temp_image.data, img->data, image_size(img)); + + rectangle_t r; + r.x = 0; + r.y = 0; + r.w = temp_image.w; + r.h = temp_image.h; + + histogram_t h; + h.LBinCount = COLOR_L_MAX - COLOR_L_MIN + 1; + h.ABinCount = COLOR_A_MAX - COLOR_A_MIN + 1; + h.BBinCount = COLOR_B_MAX - COLOR_B_MIN + 1; + h.LBins = fb_alloc(h.LBinCount * sizeof(float)); + h.ABins = fb_alloc(h.ABinCount * sizeof(float)); + h.BBins = fb_alloc(h.BBinCount * sizeof(float)); + imlib_get_histogram(&h, &temp_image, &r); + + statistics_t s; + imlib_get_statistics(&s, temp_image.bpp, &h); + + int sum = 0; + int mean = s.LMean * 0.8f; + + for (int y = 0, yy = temp_image.h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&temp_image, y); + + for (int x = 0, xx = temp_image.w; x < xx; x++) { + sum += COLOR_RGB565_TO_L(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)) < mean; + } + } + + if (sum > ((temp_image.w * temp_image.h) / 20)) { // Don't do anything if the image is mostly flat. + + threshold_t t; + imlib_get_threshold(&t, temp_image.bpp, &h); + + simple_color_t t_l, t_h; + t_l.L = COLOR_L_MIN; + t_l.A = COLOR_A_MIN; + t_l.B = COLOR_B_MIN; + t_h.L = t.LValue; + t_h.A = COLOR_A_MAX; + t_h.B = COLOR_B_MAX; + imlib_binary(&temp_image, 1, &t_l, &t_h, false); + + imlib_erode(&temp_image, 3, 30); + imlib_dilate(&temp_image, 1, 1); + + // Get Shadow Average + + image_t temp_image_2; + temp_image_2.w = temp_image.w; + temp_image_2.h = temp_image.h; + temp_image_2.bpp = temp_image.bpp; + temp_image_2.data = fb_alloc(image_size(&temp_image)); + + memcpy(temp_image_2.data, temp_image.data, image_size(&temp_image)); + imlib_erode(&temp_image_2, 3, 48); + + int shadow_r_sum = 0; + int shadow_g_sum = 0; + int shadow_b_sum = 0; + int shadow_count = 0; + + for (int y = 0, yy = temp_image_2.h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&temp_image_2, y); + uint16_t *row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + + for (int x = 0, xx = temp_image_2.w; x < xx; x++) { + if (IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr_2, x); + int r = COLOR_RGB565_TO_R8(pixel); + int g = COLOR_RGB565_TO_G8(pixel); + int b = COLOR_RGB565_TO_R8(pixel); + shadow_r_sum += r; + shadow_g_sum += g; + shadow_b_sum += b; + shadow_count += 1; + } + } + } + + memcpy(temp_image_2.data, temp_image.data, image_size(&temp_image)); + imlib_invert(&temp_image_2); + imlib_erode(&temp_image_2, 5, 120); + imlib_invert(&temp_image_2); + imlib_b_xor(&temp_image_2, NULL, &temp_image); + imlib_erode(&temp_image_2, 2, 24); + + int not_shadow_r_sum = 0; + int not_shadow_g_sum = 0; + int not_shadow_b_sum = 0; + int not_shadow_count = 0; + + for (int y = 0, yy = temp_image_2.h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&temp_image_2, y); + uint16_t *row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + + for (int x = 0, xx = temp_image_2.w; x < xx; x++) { + if (IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr_2, x); + int r = COLOR_RGB565_TO_R8(pixel); + int g = COLOR_RGB565_TO_G8(pixel); + int b = COLOR_RGB565_TO_R8(pixel); + not_shadow_r_sum += r; + not_shadow_g_sum += g; + not_shadow_b_sum += b; + not_shadow_count += 1; + } + } + } + + // Fill in the umbra... (inner part of the shadow)... + + memcpy(temp_image_2.data, temp_image.data, image_size(&temp_image)); + + imlib_mean_filter(&temp_image, 2, false, 0, false, NULL); + + if (shadow_count && not_shadow_count) { + + float shadow_r_average = ((float) shadow_r_sum) / ((float) shadow_count); + float shadow_g_average = ((float) shadow_g_sum) / ((float) shadow_count); + float shadow_b_average = ((float) shadow_b_sum) / ((float) shadow_count); + + float not_shadow_r_average = ((float) not_shadow_r_sum) / ((float) not_shadow_count); + float not_shadow_g_average = ((float) not_shadow_g_sum) / ((float) not_shadow_count); + float not_shadow_b_average = ((float) not_shadow_b_sum) / ((float) not_shadow_count); + + float diff_r = not_shadow_r_average - shadow_r_average; + float diff_g = not_shadow_g_average - shadow_g_average; + float diff_b = not_shadow_b_average - shadow_b_average; + + for (int y = 0; y < img->h; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&temp_image, y); + uint16_t *row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + + for (int x = 0; x < img->w; x++) { + float alpha = ((float) (COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)) - COLOR_Y_MIN)) / ((float) (COLOR_Y_MAX - COLOR_Y_MIN)); + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr_2, x); + int r = COLOR_RGB565_TO_R8(pixel); + int g = COLOR_RGB565_TO_G8(pixel); + int b = COLOR_RGB565_TO_B8(pixel); + + int r_new = IM_MIN(IM_MAX(r + (diff_r * alpha), COLOR_R8_MIN), COLOR_R8_MAX); + int g_new = IM_MIN(IM_MAX(g + (diff_g * alpha), COLOR_G8_MIN), COLOR_G8_MAX); + int b_new = IM_MIN(IM_MAX(b + (diff_b * alpha), COLOR_B8_MIN), COLOR_B8_MAX); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr_2, x, COLOR_R8_G8_B8_TO_RGB565(r_new, g_new, b_new)); + } + } + } + + // Fill in the penumbra... (outer part of the shadow)... + + memcpy(temp_image.data, temp_image_2.data, image_size(&temp_image_2)); + + imlib_erode(&temp_image_2, 1, 8); + imlib_b_xor(&temp_image, NULL, &temp_image_2); + imlib_dilate(&temp_image, 3, 0); + imlib_median_filter(img, 2, 12, false, 0, false, &temp_image); + + fb_free(); // temp_image_2 + } + + fb_free(); // BBins + fb_free(); // ABins + fb_free(); // LBins + + fb_free(); // temp_image + } +} + +#undef imlib_remove_shadows_kernel_size +#undef imlib_remove_shadows_kernel_rank + +extern const float xyz_table[256]; + +void imlib_chrominvar(image_t *img) +{ + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + break; + } + case IMAGE_BPP_GRAYSCALE: { + break; + } + case IMAGE_BPP_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; + float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; + float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; + + float lin_sum = r_lin + g_lin + b_lin; + + float r_lin_div = 0.0f; + float g_lin_div = 0.0f; + float b_lin_div = 0.0f; + + if (lin_sum > 0.0f) { + lin_sum = 1.0f / lin_sum; + r_lin_div = r_lin * lin_sum; + g_lin_div = g_lin * lin_sum; + b_lin_div = b_lin * lin_sum; + } + + int r_lin_div_int = IM_MAX(IM_MIN(r_lin_div * 255.0f, COLOR_R8_MAX), COLOR_R8_MIN); + int g_lin_div_int = IM_MAX(IM_MIN(g_lin_div * 255.0f, COLOR_G8_MAX), COLOR_G8_MIN); + int b_lin_div_int = IM_MAX(IM_MIN(b_lin_div * 255.0f, COLOR_B8_MAX), COLOR_B8_MIN); + + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, COLOR_R8_G8_B8_TO_RGB565(r_lin_div_int, g_lin_div_int, b_lin_div_int)); + } + } + + break; + } + default: { + break; + } + } +} + +extern const uint16_t invariant_table[65536]; + +void imlib_illuminvar(image_t *img) // http://ai.stanford.edu/~alireza/publication/cic15.pdf +{ + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + break; + } + case IMAGE_BPP_GRAYSCALE: { + break; + } + case IMAGE_BPP_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); +#ifdef OMV_HAVE_INVARIANT_TABLE + int rgb565 = invariant_table[pixel]; +#else + float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)] + 1.0; + float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)] + 1.0; + float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)] + 1.0; + + float r_lin_sharp = (r_lin * 0.9968f) + (g_lin * 0.0228f) + (b_lin * 0.0015f); + float g_lin_sharp = (r_lin * -0.0071f) + (g_lin * 0.9933f) + (b_lin * 0.0146f); + float b_lin_sharp = (r_lin * 0.0103f) + (g_lin * -0.0161f) + (b_lin * 0.9839f); + + float lin_sharp_avg = r_lin_sharp * g_lin_sharp * b_lin_sharp; + lin_sharp_avg = (lin_sharp_avg > 0.0f) ? fast_cbrtf(lin_sharp_avg) : 0.0f; + + float r_lin_sharp_div = 0.0f; + float g_lin_sharp_div = 0.0f; + float b_lin_sharp_div = 0.0f; + + if (lin_sharp_avg > 0.0f) { + lin_sharp_avg = 1.0f / lin_sharp_avg; + r_lin_sharp_div = r_lin_sharp * lin_sharp_avg; + g_lin_sharp_div = g_lin_sharp * lin_sharp_avg; + b_lin_sharp_div = b_lin_sharp * lin_sharp_avg; + } + + float r_lin_sharp_div_log = (r_lin_sharp_div > 0.0f) ? fast_log(r_lin_sharp_div) : 0.0f; + float g_lin_sharp_div_log = (g_lin_sharp_div > 0.0f) ? fast_log(g_lin_sharp_div) : 0.0f; + float b_lin_sharp_div_log = (b_lin_sharp_div > 0.0f) ? fast_log(b_lin_sharp_div) : 0.0f; + + float chi_x = (r_lin_sharp_div_log * 0.7071f) + (g_lin_sharp_div_log * -0.7071f) + (b_lin_sharp_div_log * 0.0000f); + float chi_y = (r_lin_sharp_div_log * 0.4082f) + (g_lin_sharp_div_log * 0.4082f) + (b_lin_sharp_div_log * -0.8164f); + + float e_t_x = 0.9326f; + float e_t_y = -0.3609f; + + float p_th_00 = e_t_x * e_t_x; + float p_th_01 = e_t_x * e_t_y; + float p_th_10 = e_t_y * e_t_x; + float p_th_11 = e_t_y * e_t_y; + + float x_th_x = (p_th_00 * chi_x) + (p_th_01 * chi_y); + float x_th_y = (p_th_10 * chi_x) + (p_th_11 * chi_y); + + float r_chi = (x_th_x * 0.7071f) + (x_th_y * 0.4082f); + float g_chi = (x_th_x * -0.7071f) + (x_th_y * 0.4082f); + float b_chi = (x_th_x * 0.0000f) + (x_th_y * -0.8164f); + + float r_chi_invariant = fast_expf(r_chi); + float g_chi_invariant = fast_expf(g_chi); + float b_chi_invariant = fast_expf(b_chi); + + float chi_invariant_sum = r_chi_invariant + g_chi_invariant + b_chi_invariant; + + float r_chi_invariant_m = 0.0f; + float g_chi_invariant_m = 0.0f; + float b_chi_invariant_m = 0.0f; + + if (chi_invariant_sum > 0.0f) { + chi_invariant_sum = 1.0f / chi_invariant_sum; + r_chi_invariant_m = r_chi_invariant * chi_invariant_sum; + g_chi_invariant_m = g_chi_invariant * chi_invariant_sum; + b_chi_invariant_m = b_chi_invariant * chi_invariant_sum; + } + + int r_chi_invariant_m_int = IM_MAX(IM_MIN(r_chi_invariant_m * 255.0f, COLOR_R8_MAX), COLOR_R8_MIN); + int g_chi_invariant_m_int = IM_MAX(IM_MIN(g_chi_invariant_m * 255.0f, COLOR_G8_MAX), COLOR_G8_MIN); + int b_chi_invariant_m_int = IM_MAX(IM_MIN(b_chi_invariant_m * 255.0f, COLOR_B8_MAX), COLOR_B8_MIN); + + int rgb565 = COLOR_R8_G8_B8_TO_RGB565(r_chi_invariant_m_int, g_chi_invariant_m_int, b_chi_invariant_m_int); +#endif + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, rgb565); + } + } + + break; + } + default: { + break; + } + } +} diff --git a/src/omv/img/stats.c b/src/omv/img/stats.c index 710f13597..baf670528 100644 --- a/src/omv/img/stats.c +++ b/src/omv/img/stats.c @@ -290,7 +290,7 @@ void imlib_get_percentile(percentile_t *out, image_bpp_t bpp, histogram_t *ptr, for (int i = 0, j = ptr->BBinCount; i < j; i++) { if ((median_count < percentile) && (percentile <= (median_count + ptr->BBins[i]))) { - out->BValue = fast_roundf((i * mult) + COLOR_A_MIN); + out->BValue = fast_roundf((i * mult) + COLOR_B_MIN); break; } @@ -305,6 +305,64 @@ void imlib_get_percentile(percentile_t *out, image_bpp_t bpp, histogram_t *ptr, } } +static int ostu(int bincount, float *bins) +{ + float cdf[bincount]; memset(cdf, 0, bincount * sizeof(float)); + float weighted_cdf[bincount]; memset(weighted_cdf, 0, bincount * sizeof(float)); + + cdf[0] = bins[0]; + weighted_cdf[0] = 0 * bins[0]; + + for (int i = 1; i < bincount; i++) { + cdf[i] = cdf[i - 1] + bins[i]; + weighted_cdf[i] = weighted_cdf[i - 1] + (i * bins[i]); + } + + float variance[bincount]; memset(variance, 0, bincount * sizeof(float)); + float max_variance = 0.0f; + int threshold = 0; + + for (int i = 0, ii = bincount - 1; i < ii; i++) { + + if ((cdf[i] != 0.0f) && (cdf[i] != 1.0f)) { + variance[i] = powf((cdf[i] * weighted_cdf[bincount - 1]) - weighted_cdf[i], 2.0f) / (cdf[i] * (1.0f - cdf[i])); + } else { + variance[i] = 0.0f; + } + + if (variance[i] > max_variance) { + max_variance = variance[i]; + threshold = i; + } + } + + return threshold; +} + +void imlib_get_threshold(threshold_t *out, image_bpp_t bpp, histogram_t *ptr) +{ + memset(out, 0, sizeof(threshold_t)); + switch(bpp) { + case IMAGE_BPP_BINARY: { + out->LValue = (ostu(ptr->LBinCount, ptr->LBins) * (COLOR_BINARY_MAX - COLOR_BINARY_MIN)) / (ptr->LBinCount - 1); + break; + } + case IMAGE_BPP_GRAYSCALE: { + out->LValue = (ostu(ptr->LBinCount, ptr->LBins) * (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN)) / (ptr->LBinCount - 1); + break; + } + case IMAGE_BPP_RGB565: { + out->LValue = (ostu(ptr->LBinCount, ptr->LBins) * (COLOR_L_MAX - COLOR_L_MIN)) / (ptr->LBinCount - 1); + out->AValue = (ostu(ptr->ABinCount, ptr->ABins) * (COLOR_A_MAX - COLOR_A_MIN)) / (ptr->ABinCount - 1); + out->BValue = (ostu(ptr->BBinCount, ptr->BBins) * (COLOR_B_MAX - COLOR_B_MIN)) / (ptr->BBinCount - 1); + break; + } + default: { + break; + } + } +} + void imlib_get_statistics(statistics_t *out, image_bpp_t bpp, histogram_t *ptr) { memset(out, 0, sizeof(statistics_t)); diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 64c41aadb..c6a2403ae 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1214,9 +1214,11 @@ static mp_obj_t py_image_midpoint(uint n_args, const mp_obj_t *args, mp_map_t *k int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), MP_MAP_LOOKUP); int bias = py_helper_lookup_float(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bias), 0.5) * 256; - imlib_midpoint_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(bias, 0), 256), arg_threshold, arg_offset, arg_invert); + imlib_midpoint_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(bias, 0), 256), arg_threshold, arg_offset, arg_invert, + (kw_arg != NULL) ? py_image_cobj(kw_arg->value) : NULL); return args[0]; } @@ -1231,8 +1233,10 @@ static mp_obj_t py_image_mean(uint n_args, const mp_obj_t *args, mp_map_t *kw_ar int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), MP_MAP_LOOKUP); - imlib_mean_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert); + imlib_mean_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert, + (kw_arg != NULL) ? py_image_cobj(kw_arg->value) : NULL); return args[0]; } @@ -1247,8 +1251,10 @@ static mp_obj_t py_image_mode(uint n_args, const mp_obj_t *args, mp_map_t *kw_ar int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), MP_MAP_LOOKUP); - imlib_mode_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert); + imlib_mode_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert, + (kw_arg != NULL) ? py_image_cobj(kw_arg->value) : NULL); return args[0]; } @@ -1264,10 +1270,12 @@ static mp_obj_t py_image_median(uint n_args, const mp_obj_t *args, mp_map_t *kw_ int arg_threshold = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); int arg_offset = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); int arg_invert = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), MP_MAP_LOOKUP); 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), arg_threshold, arg_offset, arg_invert); + imlib_median_filter(arg_img, arg_ksize, IM_MIN(IM_MAX(percentile, 0), n-1), arg_threshold, arg_offset, arg_invert, + (kw_arg != NULL) ? py_image_cobj(kw_arg->value) : NULL); return args[0]; } @@ -1327,23 +1335,27 @@ static mp_obj_t py_image_min(mp_obj_t img_obj, mp_obj_t other_obj) } #ifdef OMV_ENABLE_REMOVE_SHADOWS -static mp_obj_t py_image_remove_shadows(mp_obj_t img_obj, mp_obj_t other_obj) +static mp_obj_t py_image_remove_shadows(uint n_args, const mp_obj_t *args) { - image_t *arg_img = py_image_cobj(img_obj); + image_t *arg_img = py_image_cobj(args[0]); PY_ASSERT_TRUE_MSG(IM_IS_RGB565(arg_img), "Image format is not supported."); - if (MP_OBJ_IS_STR(other_obj)) { + if (n_args < 2) { fb_alloc_mark(); - imlib_remove_shadows(arg_img, mp_obj_str_get_str(other_obj), NULL); + imlib_remove_shadows(arg_img, NULL, NULL); + fb_alloc_mark(); + } else if (MP_OBJ_IS_STR(args[1])) { + fb_alloc_mark(); + imlib_remove_shadows(arg_img, mp_obj_str_get_str(args[1]), NULL); fb_alloc_mark(); } else { - image_t *arg_other = py_image_cobj(other_obj); + image_t *arg_other = py_image_cobj(args[1]); fb_alloc_mark(); imlib_remove_shadows(arg_img, NULL, arg_other); fb_alloc_free_till_mark(); } - return img_obj; + return args[0]; } #endif // OMV_ENABLE_REMOVE_SHADOWS @@ -1875,6 +1887,86 @@ static const mp_obj_type_t py_percentile_type = { .locals_dict = (mp_obj_t) &py_percentile_locals_dict, }; +// Threshold Object // +#define py_threshold_obj_size 3 +typedef struct py_threshold_obj { + mp_obj_base_t base; + image_bpp_t bpp; + mp_obj_t LValue, AValue, BValue; +} py_threshold_obj_t; + +static void py_threshold_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_threshold_obj_t *self = self_in; + switch(self->bpp) { + case IMAGE_BPP_BINARY: { + mp_printf(print, "{value:%d}", mp_obj_get_int(self->LValue)); + break; + } + case IMAGE_BPP_GRAYSCALE: { + mp_printf(print, "{value:%d}", mp_obj_get_int(self->LValue)); + break; + } + case IMAGE_BPP_RGB565: { + mp_printf(print, "{l_value:%d, a_value:%d, b_value:%d}", mp_obj_get_int(self->LValue), mp_obj_get_int(self->AValue), mp_obj_get_int(self->BValue)); + break; + } + default: { + mp_printf(print, "{}"); + break; + } + } +} + +static mp_obj_t py_threshold_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_threshold_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_threshold_obj_size, index, &slice)) { + nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->LValue) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_threshold_obj_size, index, false)) { + case 0: return self->LValue; + case 1: return self->AValue; + case 2: return self->BValue; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_threshold_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->LValue; } +mp_obj_t py_threshold_l_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->LValue; } +mp_obj_t py_threshold_a_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->AValue; } +mp_obj_t py_threshold_b_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->BValue; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_value_obj, py_threshold_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_l_value_obj, py_threshold_l_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_a_value_obj, py_threshold_a_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_b_value_obj, py_threshold_b_value); + +STATIC const mp_rom_map_elem_t py_threshold_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&py_threshold_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_value), MP_ROM_PTR(&py_threshold_l_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_value), MP_ROM_PTR(&py_threshold_a_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_value), MP_ROM_PTR(&py_threshold_b_value_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_threshold_locals_dict, py_threshold_locals_dict_table); + +static const mp_obj_type_t py_threshold_type = { + { &mp_type_type }, + .name = MP_QSTR_threshold, + .print = py_threshold_print, + .subscr = py_threshold_subscr, + .locals_dict = (mp_obj_t) &py_threshold_locals_dict, +}; + // Histogram Object // #define py_histogram_obj_size 3 typedef struct py_histogram_obj { @@ -1984,6 +2076,47 @@ mp_obj_t py_histogram_get_percentile(mp_obj_t self_in, mp_obj_t percentile) return o; } +mp_obj_t py_histogram_get_threshold(mp_obj_t self_in) +{ + histogram_t hist; + hist.LBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->len; + hist.ABinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->len; + hist.BBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->len; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float)); + hist.ABins = fb_alloc(hist.ABinCount * sizeof(float)); + hist.BBins = fb_alloc(hist.BBinCount * sizeof(float)); + + for (int i = 0; i < hist.LBinCount; i++) { + hist.LBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->items[i]); + } + + for (int i = 0; i < hist.ABinCount; i++) { + hist.ABins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->items[i]); + } + + for (int i = 0; i < hist.BBinCount; i++) { + hist.BBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->items[i]); + } + + threshold_t t; + imlib_get_threshold(&t, ((py_histogram_obj_t *) self_in)->bpp, &hist); + if (hist.BBinCount) fb_free(); + if (hist.ABinCount) fb_free(); + if (hist.LBinCount) fb_free(); + fb_alloc_free_till_mark(); + + py_threshold_obj_t *o = m_new_obj(py_threshold_obj_t); + o->base.type = &py_threshold_type; + o->bpp = ((py_threshold_obj_t *) self_in)->bpp; + + o->LValue = mp_obj_new_int(t.LValue); + o->AValue = mp_obj_new_int(t.AValue); + o->BValue = mp_obj_new_int(t.BValue); + + return o; +} + mp_obj_t py_histogram_get_statistics(mp_obj_t self_in) { histogram_t hist; @@ -2051,6 +2184,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_l_bins_obj, py_histogram_l_bins); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_a_bins_obj, py_histogram_a_bins); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_b_bins_obj, py_histogram_b_bins); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_histogram_get_percentile_obj, py_histogram_get_percentile); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_get_threshold_obj, py_histogram_get_threshold); STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_get_statistics_obj, py_histogram_get_statistics); STATIC const mp_rom_map_elem_t py_histogram_locals_dict_table[] = { @@ -2059,6 +2193,7 @@ STATIC const mp_rom_map_elem_t py_histogram_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_a_bins), MP_ROM_PTR(&py_histogram_a_bins_obj) }, { MP_ROM_QSTR(MP_QSTR_b_bins), MP_ROM_PTR(&py_histogram_b_bins_obj) }, { MP_ROM_QSTR(MP_QSTR_get_percentile), MP_ROM_PTR(&py_histogram_get_percentile_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_threshold), MP_ROM_PTR(&py_histogram_get_threshold_obj) }, { MP_ROM_QSTR(MP_QSTR_get_stats), MP_ROM_PTR(&py_histogram_get_statistics_obj) }, { MP_ROM_QSTR(MP_QSTR_get_statistics), MP_ROM_PTR(&py_histogram_get_statistics_obj) }, { MP_ROM_QSTR(MP_QSTR_statistics), MP_ROM_PTR(&py_histogram_get_statistics_obj) } @@ -4219,7 +4354,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_blend_obj, 2, py_image_blend); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_max_obj, py_image_max); STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_min_obj, py_image_min); #ifdef OMV_ENABLE_REMOVE_SHADOWS -STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_remove_shadows_obj, py_image_remove_shadows); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_image_remove_shadows_obj, 1, 2, py_image_remove_shadows); #endif /* Image Morphing */ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_morph_obj, 3, py_image_morph); diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index c00b2a235..23efe27c3 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -413,6 +413,7 @@ Q(histogram) // duplicate Q(a_bins) // duplicate Q(b_bins) Q(get_percentile) +Q(get_threshold) Q(get_stats) Q(get_statistics) Q(statistics) @@ -422,6 +423,12 @@ Q(value) Q(l_value) Q(a_value) Q(b_value) +// Threshold Object +// duplicate Q(threshold) +// duplicate Q(value) +// duplicate Q(l_value) +// duplicate Q(a_value) +// duplicate Q(b_value) // Get Statistics // duplicate Q(get_stats)