diff --git a/src/omv/img/binary.c b/src/omv/img/binary.c index 73719c57c..37163b90c 100644 --- a/src/omv/img/binary.c +++ b/src/omv/img/binary.c @@ -13,6 +13,26 @@ void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero) switch(img->bpp) { case IMAGE_BPP_BINARY: { + if (!zero) { + for (uint32_t *start = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, 0), + *end = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, img->h); + start < end; start++) { + for (int i = 0; i < UINT32_T_BITS; i++) { + IMAGE_PUT_BINARY_PIXEL_FAST(start, i, + COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(start, i), &lnk_data, invert) + ? COLOR_BINARY_MAX : COLOR_BINARY_MIN); + } + } + } else { + for (uint32_t *start = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, 0), + *end = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, img->h); + start < end; start++) { + for (int i = 0; i < UINT32_T_BITS; i++) { + if (COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(start, i), &lnk_data, invert)) + IMAGE_PUT_BINARY_PIXEL_FAST(start, i, COLOR_BINARY_MIN); + } + } + } break; } case IMAGE_BPP_GRAYSCALE: { @@ -27,8 +47,8 @@ void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero) for (uint8_t *start = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, 0), *end = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, img->h); start < end; start++) { - if (COLOR_THRESHOLD_GRAYSCALE(*start, &lnk_data, invert)) *start = - COLOR_GRAYSCALE_BINARY_MIN; + if (COLOR_THRESHOLD_GRAYSCALE(*start, &lnk_data, invert)) + *start = COLOR_GRAYSCALE_BINARY_MIN; } } break; @@ -45,8 +65,8 @@ void imlib_binary(image_t *img, list_t *thresholds, bool invert, bool zero) for (uint16_t *start = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, 0), *end = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, img->h); start < end; start++) { - if (COLOR_THRESHOLD_RGB565(*start, &lnk_data, invert)) *start = - COLOR_RGB565_BINARY_MIN; + if (COLOR_THRESHOLD_RGB565(*start, &lnk_data, invert)) + *start = COLOR_RGB565_BINARY_MIN; } } break; @@ -711,3 +731,41 @@ void imlib_dilate(image_t *img, int ksize, int threshold, image_t *mask) // Dilate normally requires one pixel to be 1. imlib_erode_dilate(img, ksize, threshold, 1, mask); } + +void imlib_open(image_t *img, int ksize, int threshold, image_t *mask) +{ + imlib_erode(img, ksize, (((ksize*2)+1)*((ksize*2)+1))-1 - threshold, mask); + imlib_dilate(img, ksize, 0 + threshold, mask); +} + +void imlib_close(image_t *img, int ksize, int threshold, image_t *mask) +{ + imlib_dilate(img, ksize, 0 + threshold, mask); + imlib_erode(img, ksize, (((ksize*2)+1)*((ksize*2)+1))-1 - threshold, mask); +} + +void imlib_top_hat(image_t *img, int ksize, int threshold, image_t *mask) +{ + image_t temp; + temp.w = img->w; + temp.h = img->h; + temp.bpp = img->bpp; + temp.data = fb_alloc(image_size(img)); + memcpy(temp.data, img->data, image_size(img)); + imlib_open(&temp, ksize, threshold, mask); + imlib_difference(img, NULL, &temp, 0, mask); + fb_free(); +} + +void imlib_black_hat(image_t *img, int ksize, int threshold, image_t *mask) +{ + image_t temp; + temp.w = img->w; + temp.h = img->h; + temp.bpp = img->bpp; + temp.data = fb_alloc(image_size(img)); + memcpy(temp.data, img->data, image_size(img)); + imlib_close(&temp, ksize, threshold, mask); + imlib_difference(img, NULL, &temp, 0, mask); + fb_free(); +} diff --git a/src/omv/img/filter.c b/src/omv/img/filter.c index fa2281005..f50b67d56 100644 --- a/src/omv/img/filter.c +++ b/src/omv/img/filter.c @@ -10,7 +10,28 @@ void imlib_histeq(image_t *img) { switch(img->bpp) { case IMAGE_BPP_BINARY: { - // Can't run this on a binary image. + int a = img->w * img->h; + float s = (COLOR_BINARY_MAX-COLOR_BINARY_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_BINARY_MAX-COLOR_BINARY_MIN+1)*sizeof(uint32_t)); + uint32_t *pixels = (uint32_t *) img->data; + + // Compute the image histogram + for (int i=0; ibpp) { case IMAGE_BPP_BINARY: { - // Can't run this on a binary image. + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + + int pixel = fast_roundf(acc * over_n); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(); break; } case IMAGE_BPP_GRAYSCALE: { @@ -224,7 +296,62 @@ void imlib_median_filter(image_t *img, const int ksize, float percentile, bool t switch(img->bpp) { case IMAGE_BPP_BINARY: { - // Can't run this on a binary image. + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); + int *data = fb_alloc(n*sizeof(int)); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int *data_ptr = data; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + *data_ptr++ = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + + fsort(data, n); + + int pixel = data[int_percentile]; + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); break; } case IMAGE_BPP_GRAYSCALE: { @@ -372,7 +499,68 @@ void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset switch(img->bpp) { case IMAGE_BPP_BINARY: { - // Can't run this on a binary image. + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); + int *bins = fb_alloc((COLOR_BINARY_MAX-COLOR_BINARY_MIN+1)*sizeof(int)); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + memset(bins, 0, (COLOR_BINARY_MAX-COLOR_BINARY_MIN+1)*sizeof(int)); + + int mcount = 0, mode = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + bins[pixel]++; + + if (bins[pixel] > mcount) { + mcount = bins[pixel]; + mode = pixel; + } + } + } + + int pixel = mode; + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); break; } case IMAGE_BPP_GRAYSCALE: { @@ -546,7 +734,60 @@ void imlib_midpoint_filter(image_t *img, const int ksize, float bias, bool thres switch(img->bpp) { case IMAGE_BPP_BINARY: { - // Can't run this on a binary image. + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int min = COLOR_BINARY_MAX, max = COLOR_BINARY_MIN; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + min = IM_MIN(min, pixel); + max = IM_MAX(max, pixel); + } + } + + int pixel = fast_roundf((min*min_bias)+(max*max_bias)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(); break; } case IMAGE_BPP_GRAYSCALE: { @@ -692,7 +933,58 @@ void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, c switch(img->bpp) { case IMAGE_BPP_BINARY: { - // Can't run this on a binary image. + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int acc = 0, ptr = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += krn[ptr++] * IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + + int pixel = IM_MAX(IM_MIN(fast_roundf(acc * m) + b, COLOR_BINARY_MAX), COLOR_BINARY_MIN); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(); break; } case IMAGE_BPP_GRAYSCALE: { @@ -815,3 +1107,285 @@ void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, c } } } + +static float gaussian(int x, float sigma) +{ + return fast_expf((x * x) / (-2.0f * sigma * sigma)) / (sigma * 2.506628f); // sqrt(2 * PI) +} + +static float distance(int x, int y) +{ + return fast_sqrtf((x * x) + (y * y)); +} + +void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, float space_sigma, bool threshold, int offset, bool invert, image_t *mask) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.bpp = img->bpp; + + switch(img->bpp) { + case IMAGE_BPP_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows); + + float *gi_lut = fb_alloc((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(float)); + + for (int i = COLOR_BINARY_MIN; i <= COLOR_BINARY_MAX; i++) { + gi_lut[i] = gaussian(i, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float)); + + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + float i_acc = 0, w_acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + float w = gi_lut[abs(this_pixel - pixel)] * gs_lut[(n * (j + ksize)) + (k + ksize)]; + i_acc += pixel * w; + w_acc += w; + } + } + + int pixel = fast_roundf(IM_MIN(IM_DIV(i_acc, w_acc), COLOR_BINARY_MAX)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); + fb_free(); + break; + } + case IMAGE_BPP_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows); + + float *gi_lut = fb_alloc((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(float)); + + for (int i = COLOR_GRAYSCALE_MIN; i <= COLOR_GRAYSCALE_MAX; i++) { + gi_lut[i] = gaussian(i, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float)); + + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + float i_acc = 0, w_acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + float w = gi_lut[abs(this_pixel - pixel)] * gs_lut[(n * (j + ksize)) + (k + ksize)]; + i_acc += pixel * w; + w_acc += w; + } + } + + int pixel = fast_roundf(IM_MIN(IM_DIV(i_acc, w_acc), COLOR_GRAYSCALE_MAX)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); + fb_free(); + break; + } + case IMAGE_BPP_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows); + + float *r_gi_lut = fb_alloc((COLOR_R5_MAX - COLOR_R5_MIN + 1) * sizeof(float)); + float *g_gi_lut = fb_alloc((COLOR_G6_MAX - COLOR_G6_MIN + 1) * sizeof(float)); + float *b_gi_lut = fb_alloc((COLOR_B5_MAX - COLOR_B5_MIN + 1) * sizeof(float)); + + for (int i = COLOR_R5_MIN; i <= COLOR_R5_MAX; i++) { + r_gi_lut[i] = gaussian(i, color_sigma); + } + + for (int i = COLOR_G6_MIN; i <= COLOR_G6_MAX; i++) { + g_gi_lut[i] = gaussian(i, color_sigma); + } + + for (int i = COLOR_B5_MIN; i <= COLOR_B5_MAX; i++) { + b_gi_lut[i] = gaussian(i, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float)); + + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y), space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + int r_this_pixel = COLOR_RGB565_TO_R5(this_pixel); + int g_this_pixel = COLOR_RGB565_TO_G6(this_pixel); + int b_this_pixel = COLOR_RGB565_TO_B5(this_pixel); + float r_i_acc = 0, r_w_acc = 0; + float g_i_acc = 0, g_w_acc = 0; + float b_i_acc = 0, b_w_acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + int r_pixel = COLOR_RGB565_TO_R5(pixel); + int g_pixel = COLOR_RGB565_TO_G6(pixel); + int b_pixel = COLOR_RGB565_TO_B5(pixel); + float gs = gs_lut[(n * (j + ksize)) + (k + ksize)]; + float r_w = r_gi_lut[abs(r_this_pixel - r_pixel)] * gs; + float g_w = g_gi_lut[abs(g_this_pixel - g_pixel)] * gs; + float b_w = b_gi_lut[abs(b_this_pixel - b_pixel)] * gs; + r_i_acc += r_pixel * r_w; + r_w_acc += r_w; + g_i_acc += g_pixel * g_w; + g_w_acc += g_w; + b_i_acc += b_pixel * b_w; + b_w_acc += b_w; + } + } + + int pixel = COLOR_R5_G6_B5_TO_RGB565(fast_roundf(IM_MIN(IM_DIV(r_i_acc, r_w_acc), COLOR_R5_MAX)), + fast_roundf(IM_MIN(IM_DIV(g_i_acc, g_w_acc), COLOR_G6_MAX)), + fast_roundf(IM_MIN(IM_DIV(b_i_acc, b_w_acc), COLOR_B5_MAX))); + + if (threshold) { + if (((COLOR_RGB565_TO_Y(pixel) - offset) < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB565_BINARY_MAX; + } else { + pixel = COLOR_RGB565_BINARY_MIN; + } + } + + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = img->h - ksize, yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(); + fb_free(); + fb_free(); + fb_free(); + fb_free(); + break; + } + default: { + break; + } + } +} diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index 3a2347633..7697651bb 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -127,7 +127,13 @@ typedef struct color_thresholds_list_lnk_data } color_thresholds_list_lnk_data_t; -#define COLOR_THRESHOLD_BINARY(pixel, threshold, invert) ((pixel) ^ (invert)) +#define COLOR_THRESHOLD_BINARY(pixel, threshold, invert) \ +({ \ + __typeof__ (pixel) _pixel = (pixel); \ + __typeof__ (threshold) _threshold = (threshold); \ + __typeof__ (invert) _invert = (invert); \ + ((_threshold->LMin <= _pixel) && (_pixel <= _threshold->LMax)) ^ _invert; \ +}) #define COLOR_THRESHOLD_GRAYSCALE(pixel, threshold, invert) \ ({ \ @@ -1222,6 +1228,10 @@ void imlib_b_xor(image_t *img, const char *path, image_t *other, int scalar, ima void imlib_b_xnor(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); void imlib_erode(image_t *img, int ksize, int threshold, image_t *mask); void imlib_dilate(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_open(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_close(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_top_hat(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_black_hat(image_t *img, int ksize, int threshold, image_t *mask); // Math Functions void imlib_negate(image_t *img); void imlib_replace(image_t *img, const char *path, image_t *other, int scalar, bool hmirror, bool vflip); @@ -1240,6 +1250,7 @@ void imlib_median_filter(image_t *img, const int ksize, float percentile, bool t void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); void imlib_midpoint_filter(image_t *img, const int ksize, float bias, bool threshold, int offset, bool invert, image_t *mask); void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, const int b, bool threshold, int offset, bool invert, image_t *mask); +void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, float space_sigma, bool threshold, int offset, bool invert, image_t *mask); // Image Correction void imlib_logpolar_int(image_t *dst, image_t *src, rectangle_t *roi, bool linear, bool reverse); // helper/internal void imlib_logpolar(image_t *img, bool linear, bool reverse); diff --git a/src/omv/py/py_image.c b/src/omv/py/py_image.c index 3a95317d4..6ac248db3 100644 --- a/src/omv/py/py_image.c +++ b/src/omv/py/py_image.c @@ -1401,6 +1401,70 @@ STATIC mp_obj_t py_image_dilate(uint n_args, const mp_obj_t *args, mp_map_t *kw_ } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_dilate_obj, 2, py_image_dilate); +STATIC mp_obj_t py_image_open(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_open(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_open_obj, 2, py_image_open); + +STATIC mp_obj_t py_image_close(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_close(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_close_obj, 2, py_image_close); + +STATIC mp_obj_t py_image_top_hat(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_top_hat(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_top_hat_obj, 2, py_image_top_hat); + +STATIC mp_obj_t py_image_black_hat(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_black_hat(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_black_hat_obj, 2, py_image_black_hat); + /////////////// // Math Methods /////////////// @@ -1917,6 +1981,34 @@ STATIC mp_obj_t py_image_laplacian(uint n_args, const mp_obj_t *args, mp_map_t * } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_laplacian_obj, 2, py_image_laplacian); +STATIC mp_obj_t py_image_bilateral(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + float arg_color_sigma = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_sigma), 6); + PY_ASSERT_TRUE_MSG((0 <= arg_color_sigma), "Error: 0 <= color_sigma!"); + float arg_space_sigma = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_space_sigma), 6); + PY_ASSERT_TRUE_MSG((0 <= arg_space_sigma), "Error: 0 <= space_sigma!"); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 7, kw_args); + + fb_alloc_mark(); + imlib_bilateral_filter(arg_img, arg_ksize, arg_color_sigma, arg_space_sigma, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_bilateral_obj, 2, py_image_bilateral); + ///////////////////////// // Shadow Removal Methods ///////////////////////// @@ -4819,6 +4911,10 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_b_xnor), MP_ROM_PTR(&py_image_b_xnor_obj)}, {MP_ROM_QSTR(MP_QSTR_erode), MP_ROM_PTR(&py_image_erode_obj)}, {MP_ROM_QSTR(MP_QSTR_dilate), MP_ROM_PTR(&py_image_dilate_obj)}, + {MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&py_image_open_obj)}, + {MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&py_image_close_obj)}, + {MP_ROM_QSTR(MP_QSTR_top_hat), MP_ROM_PTR(&py_image_top_hat_obj)}, + {MP_ROM_QSTR(MP_QSTR_black_hat), MP_ROM_PTR(&py_image_black_hat_obj)}, /* Math Methods */ {MP_ROM_QSTR(MP_QSTR_negate), MP_ROM_PTR(&py_image_negate_obj)}, {MP_ROM_QSTR(MP_QSTR_replace), MP_ROM_PTR(&py_image_replace_obj)}, @@ -4841,6 +4937,7 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_gaussian), MP_ROM_PTR(&py_image_gaussian_obj)}, {MP_ROM_QSTR(MP_QSTR_gaussian_blur), MP_ROM_PTR(&py_image_gaussian_obj)}, {MP_ROM_QSTR(MP_QSTR_laplacian), MP_ROM_PTR(&py_image_laplacian_obj)}, + {MP_ROM_QSTR(MP_QSTR_bilateral), MP_ROM_PTR(&py_image_bilateral_obj)}, /* Shadow Removal Methods */ #ifdef IMLIB_ENABLE_REMOVE_SHADOWS {MP_ROM_QSTR(MP_QSTR_remove_shadows), MP_ROM_PTR(&py_image_remove_shadows_obj)}, diff --git a/src/omv/py/qstrdefsomv.h b/src/omv/py/qstrdefsomv.h index aa0888dcf..ed08babfa 100644 --- a/src/omv/py/qstrdefsomv.h +++ b/src/omv/py/qstrdefsomv.h @@ -408,6 +408,26 @@ Q(dilate) // duplicate Q(threshold) // duplicate Q(mask) +// Open +// duplicate Q(open) +// duplicate Q(threshold) +// duplicate Q(mask) + +// Close +// duplicate Q(close) +// duplicate Q(threshold) +// duplicate Q(mask) + +// Top Hat +Q(top_hat) +// duplicate Q(threshold) +// duplicate Q(mask) + +// Black Hat +Q(black_hat) +// duplicate Q(threshold) +// duplicate Q(mask) + // Negate Q(negate) @@ -516,6 +536,15 @@ Q(sharpen) // duplicate Q(invert) // duplicate Q(mask) +// Bilateral +Q(bilateral) +Q(color_sigma) +Q(space_sigma) +// duplicate Q(threshold) +// duplicate Q(offset) +// duplicate Q(invert) +// duplicate Q(mask) + // Shadow Removal Q(remove_shadows) diff --git a/usr/examples/04-Image-Filters/color_bilateral_filter.py b/usr/examples/04-Image-Filters/color_bilateral_filter.py new file mode 100644 index 000000000..1e1c24e6c --- /dev/null +++ b/usr/examples/04-Image-Filters/color_bilateral_filter.py @@ -0,0 +1,33 @@ +# Color Bilteral Filter Example +# +# This example shows off using the bilateral filter on color images. + +import sensor, image, time + +sensor.reset() # Initialize the camera sensor. +sensor.set_pixformat(sensor.RGB565) # or sensor.RGB565 +sensor.set_framesize(sensor.QQVGA) # or sensor.QVGA (or others) +sensor.skip_frames(time = 2000) # Let new settings take affect. +clock = time.clock() # Tracks FPS. + +while(True): + clock.tick() # Track elapsed milliseconds between snapshots(). + img = sensor.snapshot() # Take a picture and return the image. + + # color_sigma controls how close color wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # space_sigma controls how close space wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # Run the kernel on every pixel of the image. + img.bilateral(3, color_sigma=5, space_sigma=5) + + # Note that the bilateral filter can introduce image defects if you set + # color_sigma/space_sigma to aggresively. Increase the sigma values until + # the defects go away if you see them. + + print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while + # connected to your computer. The FPS should increase once disconnected. diff --git a/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py b/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py new file mode 100644 index 000000000..35a3f9427 --- /dev/null +++ b/usr/examples/04-Image-Filters/grayscale_bilateral_filter.py @@ -0,0 +1,33 @@ +# Grayscale Bilteral Filter Example +# +# This example shows off using the bilateral filter on grayscale images. + +import sensor, image, time + +sensor.reset() # Initialize the camera sensor. +sensor.set_pixformat(sensor.GRAYSCALE) # or sensor.RGB565 +sensor.set_framesize(sensor.QQVGA) # or sensor.QVGA (or others) +sensor.skip_frames(time = 2000) # Let new settings take affect. +clock = time.clock() # Tracks FPS. + +while(True): + clock.tick() # Track elapsed milliseconds between snapshots(). + img = sensor.snapshot() # Take a picture and return the image. + + # color_sigma controls how close color wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # space_sigma controls how close space wise pixels have to be to each other to be + # blured togheter. A smaller value means they have to be closer. + # A larger value is less strict. + + # Run the kernel on every pixel of the image. + img.bilateral(3, color_sigma=20, space_sigma=20) + + # Note that the bilateral filter can introduce image defects if you set + # color_sigma/space_sigma to aggresively. Increase the sigma values until + # the defects go away if you see them. + + print(clock.fps()) # Note: Your OpenMV Cam runs about half as fast while + # connected to your computer. The FPS should increase once disconnected.