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)