diff --git a/src/omv/imlib/imlib.c b/src/omv/imlib/imlib.c index 1234356b1..d2f81de09 100644 --- a/src/omv/imlib/imlib.c +++ b/src/omv/imlib/imlib.c @@ -423,6 +423,95 @@ void imlib_fill_image_from_float(image_t *img, int w, int h, float *data, float } } +// Unpacks src into dst. dst must be an array of src->w*src->h*dtype*channels bytes, where channels is +// 1 for grayscale and 3 for RGB. +void imlib_unpack(void *dst, image_t *src, const char dtype, float *scale, float *mean, float *stdev) { + // src will be unpacked into dst in reverse order so that we can handle in-place unpacking. + int size = (src->w * src->h) - 1; // must be int per countdown loop + float fscale = 1.0f, fadd = 0.0f; + + if (scale[0] == 0.0f && scale[1] == 1.0f) { + fscale = 1.0f / 255.0f; + } else if (scale[0] == -1.0f && scale[1] == 1.0f) { + fscale = 2.0f / 255.0f; + fadd = -1.0f; + } else if (scale[0] == -128.0f && scale[1] == 127.0f) { + fadd = -128.0f; + } + + float fscale_r = fscale, fadd_r = fadd; + float fscale_g = fscale, fadd_g = fadd; + float fscale_b = fscale, fadd_b = fadd; + + // To normalize the input image we need to subtract the mean and divide by the standard deviation. + // We can do this by applying the normalization to fscale and fadd outside the loop. + // Red + fadd_r = (fadd_r - mean[0]) / stdev[0]; + fscale_r /= stdev[0]; + + // Green + fadd_g = (fadd_g - mean[1]) / stdev[1]; + fscale_g /= stdev[1]; + + // Blue + fadd_b = (fadd_b - mean[2]) / stdev[2]; + fscale_b /= stdev[2]; + + // Grayscale -> Y = 0.299R + 0.587G + 0.114B + float m = (mean[0] * 0.299f) + (mean[1] * 0.587f) + (mean[2] * 0.114f); + float s = (stdev[0] * 0.299f) + (stdev[1] * 0.587f) + (stdev[2] * 0.114f); + fadd = (fadd - m) / s; + fscale /= s; + + if (src->pixfmt == PIXFORMAT_GRAYSCALE) { + uint8_t *input_u8 = (uint8_t *) src->data; + if (dtype == 'f') { + // convert u8 -> f32 + float *output_f32 = (float *) dst; + for (; size >= 0; size -= 1) { + output_f32[size] = (input_u8[size] * fscale) + fadd; + } + } else { + // convert u8 -> s8 + #if (__ARM_ARCH > 6) + uint32_t *input_u32 = (uint32_t *) src->data; + uint32_t *output_u32 = (uint32_t *) dst; + for (; size >= 3; size -= 4) { + output_u32[size / 4] = input_u32[size / 4] ^ 0x80808080; + } + #endif + uint8_t *input_u8 = (uint8_t *) src->data; + uint8_t *output_u8 = (uint8_t *) dst; + for (; size >= 0; size -= 1) { + output_u8[size] = input_u8[size] ^ 128; + } + } + } else if (src->pixfmt == PIXFORMAT_RGB565) { + int rgb_size = size * 3; // must be int per countdown loop + if (dtype == 'f') { + uint16_t *input_u16 = (uint16_t *) src->data; + float *output_f32 = (float *) dst; + for (; size >= 0; size -= 1, rgb_size -= 3) { + int pixel = input_u16[size]; + output_f32[rgb_size + 0] = (COLOR_RGB565_TO_R8(pixel) * fscale_r) + fadd_r; + output_f32[rgb_size + 1] = (COLOR_RGB565_TO_G8(pixel) * fscale_g) + fadd_g; + output_f32[rgb_size + 2] = (COLOR_RGB565_TO_B8(pixel) * fscale_b) + fadd_b; + } + } else { + uint16_t *input_u16 = (uint16_t *) src->data; + uint8_t *output_u8 = (uint8_t *) dst; + for (; size >= 0; size -= 1, rgb_size -= 3) { + int pixel = input_u16[size]; + output_u8[rgb_size + 0] = COLOR_RGB565_TO_R8(pixel) ^ 128; + output_u8[rgb_size + 1] = COLOR_RGB565_TO_G8(pixel) ^ 128; + output_u8[rgb_size + 2] = COLOR_RGB565_TO_B8(pixel) ^ 128; + } + } + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected input channels to be 1 or 3")); + } +} + int8_t imlib_rgb565_to_l(uint16_t pixel) { float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; diff --git a/src/omv/imlib/imlib.h b/src/omv/imlib/imlib.h index 903ef322c..85754c812 100644 --- a/src/omv/imlib/imlib.h +++ b/src/omv/imlib/imlib.h @@ -1157,6 +1157,7 @@ void imlib_deinit_all(); // Generic Helper Functions void imlib_fill_image_from_float(image_t *img, int w, int h, float *data, float min, float max, bool mirror, bool flip, bool dst_transpose, bool src_transpose); +void imlib_unpack(void *dst, image_t *src, const char dtype, float *scale, float *mean, float *stdev); // Bayer Image Processing pixformat_t imlib_bayer_shift(pixformat_t pixfmt, int x, int y, bool transpose); diff --git a/src/omv/modules/py_image.c b/src/omv/modules/py_image.c index c33633f53..393d4718b 100644 --- a/src/omv/modules/py_image.c +++ b/src/omv/modules/py_image.c @@ -724,6 +724,85 @@ static mp_obj_t py_image_bytearray(mp_obj_t img_obj) { } static MP_DEFINE_CONST_FUN_OBJ_1(py_image_bytearray_obj, py_image_bytearray); +static mp_obj_t py_image_unpack(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_buffer, ARG_dtype, ARG_scale, ARG_mean, ARG_stdev }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_buffer, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_dtype, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_scale, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_mean, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE } }, + { MP_QSTR_stdev, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE } }, + }; + + image_t *image = py_helper_arg_to_image(pos_args[0], ARG_IMAGE_ANY); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo = {0}; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + + int dtype_code; + int dtype_size; + + if (mp_obj_is_integer(args[ARG_dtype].u_obj)) { + dtype_code = mp_obj_get_int(args[ARG_dtype].u_obj); + } else { + // The first character is either 0 or the typecode. + dtype_code = mp_obj_str_get_str(args[ARG_dtype].u_obj)[0]; + } + + switch (dtype_code) { + case 'c': + case 'b': + case 'B': { + dtype_size = 1; + break; + } + case 'f': { + dtype_size = 4; + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("Unsupported dtype")); + break; + } + } + + int channels; + switch (image->pixfmt) { + case PIXFORMAT_GRAYSCALE: { + channels = 1; + break; + } + case PIXFORMAT_RGB565: { + channels = 3; + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("Unsupported pixformat")); + break; + } + } + + if ((image->w * image->h * dtype_size * channels) > bufinfo.len) { + mp_raise_ValueError(MP_ERROR_TEXT("Buffer size is too small")); + } + + // scale, offset + float scale[2] = {0.0f, 1.0f}; + py_helper_arg_to_float_array(args[ARG_scale].u_obj, scale, 2); + + float mean[3] = {0.0f, 0.0f, 0.0f}; + py_helper_arg_to_float_array(args[ARG_mean].u_obj, mean, 3); + + float stdev[3] = {1.0f, 1.0f, 1.0f}; + py_helper_arg_to_float_array(args[ARG_stdev].u_obj, stdev, 3); + + imlib_unpack(bufinfo.buf, image, dtype_code, scale, mean, stdev); + return pos_args[0]; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(py_image_unpack_obj, 1, py_image_unpack); + static mp_obj_t py_image_get_pixel(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { image_t *arg_img = py_helper_arg_to_image(args[0], ARG_IMAGE_UNCOMPRESSED); @@ -6366,6 +6445,7 @@ static const mp_rom_map_elem_t locals_dict_table[] = { {MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&py_image_format_obj)}, {MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_image_size_obj)}, {MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&py_image_bytearray_obj)}, + {MP_ROM_QSTR(MP_QSTR_unpack), MP_ROM_PTR(&py_image_unpack_obj)}, {MP_ROM_QSTR(MP_QSTR_get_pixel), MP_ROM_PTR(&py_image_get_pixel_obj)}, {MP_ROM_QSTR(MP_QSTR_set_pixel), MP_ROM_PTR(&py_image_set_pixel_obj)}, {MP_ROM_QSTR(MP_QSTR_to_bitmap), MP_ROM_PTR(&py_image_to_bitmap_obj)},