From b99a8358a7e36eb07ccc04a0ccb2170952bf9f8a Mon Sep 17 00:00:00 2001 From: BuGu <1220627966@qq.com> Date: Tue, 18 Feb 2025 19:01:41 +0800 Subject: [PATCH] Add components/vision, replace the MaixCDK vision --- vision/components/vision/CMakeLists.txt | 77 + vision/components/vision/Kconfig | 0 .../vision/include/base/maix_camera_base.hpp | 109 + .../components/vision/include/maix_camera.hpp | 305 ++ .../components/vision/include/maix_image.hpp | 1540 ++++++++++ .../vision/include/maix_image_color.hpp | 296 ++ .../vision/include/maix_image_def.hpp | 270 ++ .../vision/include/maix_image_obj.hpp | 2543 +++++++++++++++++ .../components/vision/include/maix_vision.hpp | 4 + .../include_private/maix_image_util.hpp | 17 + .../vision/port/maixcam/maix_camera_mmf.hpp | 317 ++ vision/components/vision/src/maix_camera.cpp | 425 +++ vision/components/vision/src/maix_image.cpp | 1654 +++++++++++ .../components/vision/src/maix_image_ops.cpp | 1209 ++++++++ 14 files changed, 8766 insertions(+) create mode 100644 vision/components/vision/CMakeLists.txt create mode 100644 vision/components/vision/Kconfig create mode 100644 vision/components/vision/include/base/maix_camera_base.hpp create mode 100644 vision/components/vision/include/maix_camera.hpp create mode 100644 vision/components/vision/include/maix_image.hpp create mode 100644 vision/components/vision/include/maix_image_color.hpp create mode 100644 vision/components/vision/include/maix_image_def.hpp create mode 100644 vision/components/vision/include/maix_image_obj.hpp create mode 100644 vision/components/vision/include/maix_vision.hpp create mode 100644 vision/components/vision/include_private/maix_image_util.hpp create mode 100644 vision/components/vision/port/maixcam/maix_camera_mmf.hpp create mode 100644 vision/components/vision/src/maix_camera.cpp create mode 100644 vision/components/vision/src/maix_image.cpp create mode 100644 vision/components/vision/src/maix_image_ops.cpp diff --git a/vision/components/vision/CMakeLists.txt b/vision/components/vision/CMakeLists.txt new file mode 100644 index 0000000..c683409 --- /dev/null +++ b/vision/components/vision/CMakeLists.txt @@ -0,0 +1,77 @@ +# Config enable component2 or not in Kconfig +################# Add include ################# +list(APPEND ADD_INCLUDE "include" + "include/base" +) +if(PLATFORM_LINUX) + list(APPEND ADD_PRIVATE_INCLUDE "port/linux") +elseif(PLATFORM_MAIXCAM) + list(APPEND ADD_PRIVATE_INCLUDE "port/maixcam") +endif() +list(APPEND ADD_PRIVATE_INCLUDE "include_private") +############################################### + +############## Add source files ############### +# list(APPEND ADD_SRCS "src/lib2.c" +# ) +# FILE(GLOB_RECURSE EXTRA_SRC "src/*.c") +# FILE(GLOB EXTRA_SRC "src/*.c") +# list(APPEND ADD_SRCS ${EXTRA_SRC}) +# aux_source_directory(src ADD_SRCS) # collect all source file in src dir, will set var ADD_SRCS +append_srcs_dir(ADD_SRCS "src") # append source file in src dir to var ADD_SRCS +if(PLATFORM_LINUX) + append_srcs_dir(ADD_SRCS "port/linux") +elseif(PLATFORM_MAIXCAM) + append_srcs_dir(ADD_SRCS "port/maixcam") +endif() +# list(REMOVE_ITEM COMPONENT_SRCS "src/test.c") +# set(ADD_ASM_SRCS "src/asm.S") +# list(APPEND ADD_SRCS ${ADD_ASM_SRCS}) +# SET_PROPERTY(SOURCE ${ADD_ASM_SRCS} PROPERTY LANGUAGE C) # set .S ASM file as C language +# SET_SOURCE_FILES_PROPERTIES(${ADD_ASM_SRCS} PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp -D BBBBB") +############################################### + + +###### Add required/dependent components ###### +list(APPEND ADD_REQUIREMENTS basic opencv opencv_freetype websocket peripheral) +list(APPEND ADD_REQUIREMENTS omv) +list(APPEND ADD_REQUIREMENTS kvm_mmf) +############################################### + +###### Add link search path for requirements/libs ###### +# list(APPEND ADD_LINK_SEARCH_PATH "${CONFIG_TOOLCHAIN_PATH}/lib") +# list(APPEND ADD_REQUIREMENTS pthread m) # add system libs, pthread and math lib for example here +# set (OpenCV_DIR opencv/lib/cmake/opencv4) +# find_package(OpenCV REQUIRED) +############################################### + +############ Add static libs ################## +# list(APPEND ADD_STATIC_LIB "lib/libtest.a") +############################################### + +############ Add dynamic libs ################## +# list(APPEND ADD_DYNAMIC_LIB "lib/arch/v831/libmaix_nn.so" +# "lib/arch/v831/libmaix_cam.so" +# ) +############################################### + +#### Add compile option for this component #### +#### Just for this component, won't affect other +#### modules, including component that depend +#### on this component +# list(APPEND ADD_DEFINITIONS_PRIVATE -DAAAAA=1) + +#### Add compile option for this component +#### and components denpend on this component +# list(APPEND ADD_DEFINITIONS -DAAAAA222=1 +# -DAAAAA333=1) +############################################### + +############ Add static libs ################## +#### Update parent's variables like CMAKE_C_LINK_FLAGS +# set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -Wl,--start-group libmaix/libtest.a -ltest2 -Wl,--end-group" PARENT_SCOPE) +############################################### + +# register component, DYNAMIC or SHARED flags will make component compiled to dynamic(shared) lib +register_component() + diff --git a/vision/components/vision/Kconfig b/vision/components/vision/Kconfig new file mode 100644 index 0000000..e69de29 diff --git a/vision/components/vision/include/base/maix_camera_base.hpp b/vision/components/vision/include/base/maix_camera_base.hpp new file mode 100644 index 0000000..2e4eb79 --- /dev/null +++ b/vision/components/vision/include/base/maix_camera_base.hpp @@ -0,0 +1,109 @@ +/** + * @file maix_camera_base.hpp + * @brief Maix camera SDL implementation + * @author neucrack@sipeed.com + * @license Apache 2.0 Sipeed Ltd + * @update date 2023-10-23 Create by neucrack +*/ + +#pragma once + +#include +#include "maix_image.hpp" +#include "maix_err.hpp" + +namespace maix::camera +{ + class CameraBase + { + public: + /** + * @brief Construct a new Camera object + * @param device camera device name, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device + * @param width camera width, by default(value is -1) means auto detect, + * if width > max device supported width, will auto set to max device supported width + * @param height camera height, by default(value is -1) means auto detect, + * if height > max device supported height, will auto set to max device supported height + * @param format camera format, by default(value is FMT_RGB888) + * @param buff_num camera buffer number, by default(value is 3) + */ + CameraBase(const char *device = nullptr, int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3){}; + + /** + * @brief Judge if the given format is supported by the camera + */ + virtual bool is_support_format(image::Format format) = 0; + + /** + * @brief open camera device + * @param width camera width, by default(value is -1) means auto detect, + * if width > max device supported width, will auto set to max device supported width + * @param height camera height, by default(value is -1) means auto detect, + * if height > max device supported height, will auto set to max device supported height + * @param format camera format, by default(value is FMT_RGB888) + * @param buff_num camera buffer number, by default(value is 3) + * @return error code + */ + virtual err::Err open(int width = -1, int height = -1, image::Format format = image::Format::FMT_RGB888, int buff_num = 3) = 0; + + /** + * @brief read a frame from camera + * @param buff buffer to store image data, if NULL, will alloc a new buffer + * @return image data + */ + virtual image::Image *read(void *buff = NULL, size_t buff_size = 0) = 0; + + /** + * @brief close camera device + * @return none + */ + virtual void close() = 0; + + /** + * Add a new channel and return a new Camera object, you can use close() to close this channel. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is RGB888 + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @return new Camera object + */ + virtual camera::CameraBase *add_channel(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int buff_num = 3) = 0; + + /** + * @brief clear all buffer + * @return none + */ + virtual void clear_buff() = 0; + + /** + * @brief check camera device is opened or not + * @return opened or not, bool type + */ + virtual bool is_opened() = 0; + + /** + * Get camera supported channels(layers) + */ + virtual int get_ch_nums() = 0; + + /** + * Get channel number of camera. + */ + virtual int get_channel() = 0; + + /** + * Set/Get camera mirror + * @param en enable/disable mirror + */ + virtual int hmirror(int en) = 0; + + /** + * Set/Get camera flip + * @param en enable/disable flip + */ + virtual int vflip(int en) = 0; + }; + +} + diff --git a/vision/components/vision/include/maix_camera.hpp b/vision/components/vision/include/maix_camera.hpp new file mode 100644 index 0000000..ef15de3 --- /dev/null +++ b/vision/components/vision/include/maix_camera.hpp @@ -0,0 +1,305 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_tensor.hpp" +#include "maix_log.hpp" +#include "maix_image.hpp" +#include "maix_err.hpp" +#include "maix_camera_base.hpp" +#include +#include +#include +#include +#include + +/** + * @brief maix.camera module, access camera device and get image from it + * @maixpy maix.camera +*/ +namespace maix::camera +{ + /** + * List all supported camera devices. + * @return Returns the path to the camera device. + * @maixpy maix.camera.list_devices + */ + std::vector list_devices(); + + /** + * Enable set camera registers, default is false, if set to true, will not set camera registers, you can manually set registers by write_reg API. + * @param enable enable/disable set camera registers + * @maixpy maix.camera.set_regs_enable + */ + void set_regs_enable(bool enable = true); + + /** + * Camera class + * @maixpy maix.camera.Camera + */ + class Camera + { + public: + /** + * @brief Construct a new Camera object. + * Maximum resolution support 2560x1440. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is image.Format.FMT_RGB888 + * @param device camera device path, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @param open If true, camera will automatically call open() after creation. default is true. + * @maixpy maix.camera.Camera.__init__ + * @maixcdk maix.camera.Camera.Camera + */ + Camera(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, const char *device = nullptr, int fps = -1, int buff_num = 3, bool open = true); + + /** + * @brief Construct a new Camera object. + * @attention CameraBase * parameter need to be set manually, otherwise the operation of this object will be invalid. + * @param device camera device path, you can get devices by list_devices method, by default(value is NULL(None in MaixPy)) means the first device + * @param base basic operation objects. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is image.Format.FMT_RGB888 + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @param open If true, camera will automatically call open() after creation. default is true. + * @maixcdk maix.camera.Camera.Camera + */ + Camera(const char *device, CameraBase *base, int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int fps = -1, int buff_num = -1, bool open = true); + + ~Camera(); + + void restart(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, const char *device = nullptr, int fps = -1, int buff_num = 3, bool open = true); + + /** + * Get the number of channels supported by the camera. + * @return Returns the maximum number of channels. + * @maixpy maix.camera.Camera.get_ch_nums + */ + int get_ch_nums(); + + /** + * Open camera and run + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default same as the constructor's format argument + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.open + */ + err::Err open(int width = -1, int height = -1, image::Format format = image::FMT_INVALID, int fps = -1, int buff_num = -1); + + /** + * Get one frame image from camera buffer, must call open method before read. + * If open method not called, will call it automatically, if open failed, will throw exception! + * So call open method before read is recommended. + * @param buff buffer to store image data, if buff is nullptr, will alloc memory automatically. + * In MaixPy, default to None, you can create a image.Image object, then pass img.data() to buff. + * @param block block read, default is true, means block util read image successfully, + * if set to false, will return nullptr if no image in buffer + * @return image::Image object, if failed, return nullptr, you should delete if manually in C++ + * @maixpy maix.camera.Camera.read + */ + image::Image *read(void *buff = nullptr, size_t buff_size = 0, bool block = true); + + /** + * Clear buff to ensure the next read image is the latest image + * @maixpy maix.camera.Camera.clear_buff + */ + void clear_buff(); + + /** + * Read some frames and drop, this is usually used avoid read not stable image when camera just opened. + * @param num number of frames to read and drop + * @maixpy maix.camera.Camera.skip_frames + */ + void skip_frames(int num); + + /** + * Close camera + * @maixpy maix.camera.Camera.close + */ + void close(); + + /** + * Add a new channel and return a new Camera object, you can use close() to close this channel. + * @param width camera width, default is -1, means auto, mostly means max width of camera support + * @param height camera height, default is -1, means auto, mostly means max height of camera support + * @param format camera output format, default is RGB888 + * @param fps camera fps, default is -1, means auto, mostly means max fps of camera support + * @param buff_num camera buffer number, default is 3, means 3 buffer, one used by user, one used for cache the next frame, + * more than one buffer will accelerate image read speed, but will cost more memory. + * @param open If true, camera will automatically call open() after creation. default is true. + * @return new Camera object + * @maixpy maix.camera.Camera.add_channel + */ + camera::Camera *add_channel(int width = -1, int height = -1, image::Format format = image::FMT_RGB888, int fps = -1, int buff_num = 3, bool open = true); + + /** + * Check if camera is opened + * @return true if camera is opened, false if not + * @maixpy maix.camera.Camera.is_opened + */ + bool is_opened(); + + /** + * @brief check camera device is closed or not + * @return closed or not, bool type + * @maixpy maix.camera.Camera.is_closed + */ + bool is_closed() { return !is_opened();} + + /** + * Get camera width + * @return camera width + * @maixpy maix.camera.Camera.width + */ + int width() + { + return _width; + } + + /** + * Get camera height + * @return camera height + * @maixpy maix.camera.Camera.height + */ + int height() + { + return _height; + } + + /** + * Get camera fps + * @return camera fps + * @maixpy maix.camera.Camera.fps + */ + int fps() + { + return _fps; + } + + /** + * Get camera output format + * @return camera output format, image::Format object + * @maixpy maix.camera.Camera.format + */ + image::Format format() + { + return _format; + } + + /** + * Get camera buffer number + * @return camera buffer number + * @maixpy maix.camera.Camera.buff_num + */ + int buff_num() + { + return _buff_num; + } + + /** + * Set/Get camera horizontal mirror + * @return camera horizontal mirror + * @maixpy maix.camera.Camera.hmirror + */ + int hmirror(int value = -1); + + /** + * Set/Get camera vertical flip + * @return camera vertical flip + * @maixpy maix.camera.Camera.vflip + */ + int vflip(int value = -1); + + /** + * Get camera device path + * @return camera device path + * @maixpy maix.camera.Camera.device + */ + std::string device() + { + return _device; + } + + /** + * Write camera register + * @param addr register address + * @param data register data + * @param bit_width register data bit width, default is 8 + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.write_reg + */ + err::Err write_reg(int addr, int data, int bit_width = 8) + { + return err::ERR_NONE; + } + + /** + * Read camera register + * @param addr register address + * @return register data, -1 means failed + * @param bit_width register data bit width, default is 8 + * @maixpy maix.camera.Camera.read_reg + */ + int read_reg(int addr, int bit_width = 8) + { + return -1; + } + + /** + * Camera output color bar image for test + * @param enable enable/disable color bar + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.show_colorbar + */ + err::Err show_colorbar(bool enable); + + /** + * Get channel of camera + * @return channel number + * @maixpy maix.camera.Camera.get_channel + */ + int get_channel(); + + /** + * Set camera resolution + * @param width new width + * @param height new height + * @return error code, err::ERR_NONE means success, others means failed + * @maixpy maix.camera.Camera.set_resolution + */ + err::Err set_resolution(int width, int height); + + private: + std::string _device; + int _ch; + int _width; + int _height; + int _fps; + int _buff_num; + image::Format _format; + image::Format _format_impl; // used by implement code and need convert to _format + int _hmirror; + int _vflip; + float _exposure; + float _gain; + bool _show_colorbar; + bool _open_set_regs; + CameraBase *_impl; // used by implement code + bool _check_format(image::Format format); + }; +} diff --git a/vision/components/vision/include/maix_image.hpp b/vision/components/vision/include/maix_image.hpp new file mode 100644 index 0000000..7422bfe --- /dev/null +++ b/vision/components/vision/include/maix_image.hpp @@ -0,0 +1,1540 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_tensor.hpp" +#include "maix_log.hpp" +#include "maix_err.hpp" +#include "maix_fs.hpp" +#include "maix_image_def.hpp" +#include "maix_image_color.hpp" +#include "maix_image_obj.hpp" +#include "maix_type.hpp" +#include + +/** + * @brief maix.image module, image related definition and functions + * @maixpy maix.image + */ +namespace maix::image +{ + + /** + * map point position or rectangle position from one image size to another image size(resize) + * @param int w_in original image width + * @param int h_in original image height + * @param int w_out target image width + * @param int h_out target image height + * @param fit resize method, see maix.image.Fit + * @param x original point x, or rectagle left-top point's x + * @param y original point y, or rectagle left-top point's y + * @param w original rectagle width, can be -1 if not use this arg, default -1. + * @param h original rectagle height, can be -1 if not use this arg, default -1. + * @return list type, [x, y] if map point, [x, y, w, h] if resize rectangle. + * @maixpy maix.image.resize_map_pos + */ + std::vector resize_map_pos(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w = -1, int h = -1); + + /** + * reverse resize_map_pos method, when we call image.resize method resiz image 'a' to image 'b', we want to known the original position on 'a' whith a knew point on 'b' + * @param int w_in original image width + * @param int h_in original image height + * @param int w_out image width after resized + * @param int h_out image height after resized + * @param fit resize method, see maix.image.Fit + * @param x point on resized image x, or rectagle left-top point's x + * @param y original point y, or rectagle left-top point's y + * @param w original rectagle width, can be -1 if not use this arg, default -1. + * @param h original rectagle height, can be -1 if not use this arg, default -1. + * @return list type, [x, y] if map point, [x, y, w, h] if resize rectangle. + * @maixpy maix.image.resize_map_pos_reverse + */ + std::vector resize_map_pos_reverse(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w = -1, int h = -1); + + /** + * Image class + * @maixpy maix.image.Image + */ + class Image + { + public: + /** + * Image constructor + * + * @param width image width, should > 0 + * @param height image height, should > 0 + * @param format image format @see image::Format + * @maixpy maix.image.Image.__init__ + * @maixcdk maix.image.Image.Image + */ + Image(int width, int height, image::Format format = image::Format::FMT_RGB888); + // Image(int width, int height, image::Format format = image::Format::FMT_RGB888, Bytes *data = nullptr, bool copy = true); + + /** + * Image constructor + * + * @param width image width, should > 0 + * @param height image height, should > 0 + * @param format image format @see image::Format + * @param data image data, if data is nullptr, will malloc memory for image data + * If the image is in jpeg format, data must be filled in. + * @param data_size image data size, only for compressed format like jpeg png, data_size must be filled in, or should be -1, default is -1. + * @param copy if true and data is not nullptr, will copy data to new buffer, else will use data directly. default is true to avoid memory leak. + * @maixcdk maix.image.Image.Image + */ + Image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy); + + Image() { + _width = 0; + _height = 0; + _format = image::Format::FMT_INVALID; + _data = nullptr; + _data_size = 0; + _is_malloc = false; + } + ~Image(); + + void operator=(const image::Image &img); + + //************************** get and set basic info **************************// + + /** + * Get image's format @see image.Format + * @maixpy maix.image.Image.format + */ + image::Format format() { return this->_format; } + + /** + * Get image's size, [width, height] + * @maixpy maix.image.Image.size + */ + image::Size size() { return Size(_width, _height); } + + std::vector shape() { return std::vector{_height, _width, (int)fmt_size[_format]}; } + + /** + * Get image's data size + * @maixpy maix.image.Image.data_size + */ + int data_size() { return _data_size; } + + /** + * Get image's width + * @maixpy maix.image.Image.width + */ + int width() { return _width; } + + /** + * Get image's height + * @maixpy maix.image.Image.height + */ + int height() { return _height; } + + /** + * Get image's data pointer. + * In MaixPy is capsule object. + * @maixpy maix.image.Image.data + */ + void *data(){ return _data; } + + /** + * To string method + * @maixpy maix.image.Image.__str__ + */ + std::string __str__(); + + /** + * To string method + * @maixpy maix.image.Image.to_str + */ + std::string to_str() { return __str__(); } + + /** + * Get pixel of image + * @param x pixel's coordinate x. x must less than image's width + * @param y pixel's coordinate y. y must less than image's height + * @param rgbtuple switch return value method. rgbtuple decides whether to split the return or not. default is false. + * @return pixel value, + * According to image format and rgbtuple, return different value: + * format is FMT_RGB888, rgbtuple is true, return [R, G, B]; rgbtuple is false, return [RGB] + * foramt is FMT_BGR888, rgbtuple is true, return [B, G, R]; rgbtuple is false, return [BGR] + * format is FMT_GRAYSCALE, return [GRAY]; + * + * @maixpy maix.image.Image.get_pixel + */ + std::vector get_pixel(int x, int y, bool rgbtuple = false) { + std::vector pixels; + if (!(_format == image::Format::FMT_RGB888 || _format == image::Format::FMT_BGR888 || + _format == image::Format::FMT_RGB565 || _format == image::Format::FMT_BGR565 || + _format == image::Format::FMT_GRAYSCALE)) { + log::error("get_pixel not support format: %d\r\n", _format); + return pixels; + } + + if (x < 0 || x >= _width || y < 0 || y >= _height) { + log::error("get_pixel out of range: (%d, %d)\r\n", x, y); + return pixels; + } + + switch (_format) { + case image::Format::FMT_RGB888: // fall through + case image::Format::FMT_BGR888: + { + uint8_t v0 = ((uint8_t *)_data)[y * _width * 3 + x * 3 + 0]; + uint8_t v1 = ((uint8_t *)_data)[y * _width * 3 + x * 3 + 1]; + uint8_t v2 = ((uint8_t *)_data)[y * _width * 3 + x * 3 + 2]; + if (!rgbtuple) { + int value = v0 << 16 | v1 << 8 | v2; + pixels.push_back(value); + } else { + pixels.push_back(v0); + pixels.push_back(v1); + pixels.push_back(v2); + } + break; + } + case image::Format::FMT_GRAYSCALE: + pixels.push_back(((uint8_t *)_data)[y * _width + x]); + break; + case image::Format::FMT_BGR565: // fall through + case image::Format::FMT_RGB565: + { + if (!rgbtuple) { + int value = ((uint16_t *)_data)[y * _width + x] & 0xffff; + pixels.push_back(value); + } else { + int value = ((uint16_t *)_data)[y * _width + x] & 0xffff; + int v0 = (value >> 11) & 0x1F; + int v1 = (value >> 5) & 0x3F; + int v2 = value & 0x1F; + pixels.push_back(v0); + pixels.push_back(v1); + pixels.push_back(v2); + } + break; + } + default: + log::error("get_pixel not support format: %d\r\n", _format); + break; + } + + return pixels; + } + + /** + * Set pixel of image + * @param x pixel's coordinate x. x must less than image's width + * @param y pixel's coordinate y. y must less than image's height + * @param pixel pixel value, according to image format and size of pixel, has different operation: + * format is FMT_RGB888, pixel size must be 1 or 3, if size is 1, will split pixel[0] to [R, G, B]; if size is 3, will use pixel directly + * format is FMT_BGR888, pixel size must be 1 or 3, if size is 1, will split pixel[0] to [B, G, R]; if size is 3, will use pixel directly + * format is FMT_GRAYSCALE, pixel size must be 1, will use pixel directly + * @return error code, Err::ERR_NONE is ok, other is error + * @maixpy maix.image.Image.set_pixel + */ + err::Err set_pixel(int x, int y, std::vector pixel) { + if (!(_format == image::Format::FMT_RGB888 || _format == image::Format::FMT_BGR888 || + _format == image::Format::FMT_RGB565 || _format == image::Format::FMT_BGR565 || + _format == image::Format::FMT_GRAYSCALE)) { + log::error("get_pixel not support format: %d\r\n", _format); + return err::Err::ERR_RUNTIME; + } + + if (x < 0 || x >= _width || y < 0 || y >= _height) { + log::error("get_pixel out of range: (%d, %d)\r\n", x, y); + return err::Err::ERR_RUNTIME; + } + + switch (_format) { + case image::Format::FMT_RGB888: // fall through + case image::Format::FMT_BGR888: + if (pixel.size() == 1) { + uint8_t v0 = pixel[0]; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 0] = (v0 >> 16) & 0xFF; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 1] = (v0 >> 8) & 0xFF; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 2] = v0 & 0xFF; + } else if (pixel.size() == 3) { + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 0] = pixel[0]; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 1] = pixel[1]; + ((uint8_t *)_data)[y * _width * 3 + x * 3 + 2] = pixel[2]; + } else { + log::error("set_pixel pixel size must be 1 or 3, but %d\r\n", pixel.size()); + return err::Err::ERR_RUNTIME; + } + break; + case image::Format::FMT_GRAYSCALE: + if (pixel.size() == 1) { + ((uint8_t *)_data)[y * _width + x] = pixel[0]; + } else { + log::error("set_pixel pixel size must be 1, but %d\r\n", pixel.size()); + return err::Err::ERR_RUNTIME; + } + break; + case image::Format::FMT_BGR565: // fall through + case image::Format::FMT_RGB565: + { + if (pixel.size() == 1) { + ((uint16_t *)_data)[y * _width + x] = pixel[0]; + } else if (pixel.size() == 3) { + int num = ((pixel[0] & 0x1F) << 11) | ((pixel[1] & 0x3F) << 5) | (pixel[2] & 0x1F); + ((uint16_t *)_data)[y * _width + x] = num; + } else { + log::error("set_pixel pixel size must be 1 or 3, but %d\r\n", pixel.size()); + return err::Err::ERR_RUNTIME; + } + break; + } + + default: + log::error("get_pixel not support format: %d\r\n", _format); + break; + } + + return err::Err::ERR_NONE; + } + + //************************** convert format **************************// + // more maixpy convert func in MaixPy project's convert_image.hpp + + /** + * Convert Image object to tensor::Tensor object + * @param chw if true, the shape of tensor is [C, H, W], else [H, W, C] + * @param copy if true, will alloc memory for tensor data, else will use the memory of Image object + * @return tensor::Tensor object pointer, an allocated tensor object + * @maixpy maix.image.Image.to_tensor + */ + tensor::Tensor *to_tensor(bool chw = false, bool copy = true); + + /** + * Get image's data and convert to array bytes + * @param copy if true, will alloc memory and copy data to new buffer, + * else will use the memory of Image object, delete bytes object will not affect Image object, + * but delete Image object will make bytes object invalid, it may cause program crash !!!! + * So use this param carefully. + * @return image's data bytes, need be delete by caller in C++. + * @maixpy maix.image.Image.to_bytes + */ + Bytes *to_bytes(bool copy = true); + + /** + * Convert image to specific format + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE, JPEG. + * @return new image object. Need be delete by caller in C++. + * @throw err.Exception, if two images' format not support, **or already the format**, will raise exception + * @maixpy maix.image.Image.to_format + */ + image::Image *to_format(const image::Format &format); + + + /** + * Convert image to specific format + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE, JPEG. + * @param buff user's buffer, if buff is nullptr, will malloc memory for new image data, else will use buff directly + * @return new image object. Need be delete by caller in C++. + * @throw err.Exception, if two images' format not support, **or already the format**, will raise exception + * @maixcdk maix.image.Image.to_format + */ + image::Image *to_format(const image::Format &format, void *buff, size_t buff_size); + + /** + * Convert image to jpeg + * @param quality the quality of jpg, default is 95. range is (50, 100]. + * @return new image object. Need be delete by caller in C++. + * @throw err.Exception, if two images' format not support, **or already the format**, will raise exception + * @maixpy maix.image.Image.to_jpeg + */ + image::Image *to_jpeg(int quality = 95); + + //************************** draw **************************// + + /** + * Draw image on this image + * @param x left top corner of image point's coordinate x + * @param y left top corner of image point's coordinate y + * @param img image object to draw, the caller's channel must <= the args' channel, + * e.g. caller is RGB888, args is RGBA8888, will throw exception, but caller is RGBA8888, args is RGB888 or RGBA8888 is ok + * @return this image object self + * @maixpy maix.image.Image.draw_image + */ + image::Image *draw_image(int x, int y, image::Image &img); + + /** + * Fill rectangle color to image + * @param x left top corner of rectangle point's coordinate x + * @param y left top corner of rectangle point's coordinate y + * @param w rectangle width + * @param h rectangle height + * @param color rectangle color + * @param thickness rectangle thickness(line width), by default(value is 1), -1 means fill rectangle + * @return this image object self + * @maixpy maix.image.Image.draw_rect + */ + image::Image *draw_rect(int x, int y, int w, int h, const image::Color &color, int thickness = 1); + + /** + * Draw line on image + * @param x1 start point's coordinate x + * @param y1 start point's coordinate y + * @param x2 end point's coordinate x + * @param y2 end point's coordinate y + * @param color line color @see image::Color + * @param thickness line thickness(line width), by default(value is 1) + * @return this image object self + * @maixpy maix.image.Image.draw_line + */ + image::Image *draw_line(int x1, int y1, int x2, int y2, const image::Color &color, int thickness = 1); + + /** + * Draw circle on image + * @param x circle center point's coordinate x + * @param y circle center point's coordinate y + * @param radius circle radius + * @param color circle color @see image::Color + * @param thickness circle thickness(line width), by default(value is 1), -1 means fill circle + * @return this image object self + * @maixpy maix.image.Image.draw_circle + */ + image::Image *draw_circle(int x, int y, int radius, const image::Color &color, int thickness = 1); + + /** + * Draw ellipse on image + * @param x ellipse center point's coordinate x + * @param y ellipse center point's coordinate y + * @param a ellipse major axis length + * @param b ellipse minor axis length + * @param angle ellipse rotation angle + * @param start_angle ellipse start angle + * @param end_angle ellipse end angle + * @param color ellipse color @see image::Color + * @param thickness ellipse thickness(line width), by default(value is 1), -1 means fill ellipse + * @return this image object self + * @maixpy maix.image.Image.draw_ellipse + */ + image::Image *draw_ellipse(int x, int y, int a, int b, float angle, float start_angle, float end_angle, const image::Color &color, int thickness = 1); + + /** + * Draw text on image + * @param x text left top point's coordinate x + * @param y text left top point's coordinate y + * @param string text content + * @param color text color @see image::Color, default is white + * @param scale font scale, by default(value is 1) + * @param thickness text thickness(line width), if negative, the glyph is filled, by default(value is -1) + * @param wrap if true, will auto wrap text to next line if text width > image width, by default(value is true) + * @return this image object self + * @maixpy maix.image.Image.draw_string + */ + image::Image *draw_string(int x, int y, const std::string &textstring, const image::Color &color = image::COLOR_WHITE, float scale = 1, int thickness = -1, + bool wrap = true, int wrap_space = 4, const std::string &font = ""); + + /** + * Draw cross on image + * @param x cross center point's coordinate x + * @param y cross center point's coordinate y + * @param color cross color @see image::Color + * @param size how long the lines of the cross extend, by default(value is 5). So the line length is `2 * size + thickness` + * @param thickness cross thickness(line width), by default(value is 1) + * @maixpy maix.image.Image.draw_cross + */ + image::Image *draw_cross(int x, int y, const image::Color &color, int size = 5, int thickness = 1); + + /** + * Draw arrow on image + * @param x0 start coordinate of the arrow x0 + * @param y0 start coordinate of the arrow y0 + * @param x1 end coordinate of the arrow x1 + * @param y1 end coordinate of the arrow y1 + * @param color cross color @see image::Color + * @param thickness cross thickness(line width), by default(value is 1) + * @return this image object self + * @maixpy maix.image.Image.draw_arrow + */ + image::Image *draw_arrow(int x0, int y0, int x1, int y1, const image::Color &color, int thickness = 1); + + /** + * Draw edges on image + * @param corners edges, [[x0, y0], [x1, y1], [x2, y2], [x3, y3]] + * @param color edges color @see image::Color + * @param size the circle of radius size. TODO: support in the feature + * @param thickness edges thickness(line width), by default(value is 1) + * @param fill if true, will fill edges, by default(value is false) + * @return this image object self + * @maixpy maix.image.Image.draw_edges + */ + image::Image *draw_edges(std::vector> corners, const image::Color &color, int size = 0, int thickness = 1, bool fill = false); + + /** + * Draw keypoints on image + * @param keypoints keypoints, [x1, y1, x2, y2...] or [x, y, rotation_andle_in_degrees, x2, y2, rotation_andle_in_degrees2](TODO: rotation_andle_in_degrees support in the feature) + * @param color keypoints color @see image::Color + * @param size size of keypoints + * @param thickness keypoints thickness(line width), by default(value is -1 means fill circle) + * @return this image object self + * @maixpy maix.image.Image.draw_keypoints + */ + image::Image *draw_keypoints(std::vector keypoints, const image::Color &color, int size = 10, int thickness = -1); + + //************************** image operations **************************// + + /** + * Resize image, will create a new resized image object + * @param width new width, if value is -1, will use height to calculate aspect ratio + * @param height new height, if value is -1, will use width to calculate aspect ratio + * @param object_fit fill, contain, cover, by default is fill + * @param method resize method, by default is bilinear + * @return Always return a new resized image object even size not change, So in C++ you should take care of the return value to avoid memory leak. + * And it's better to judge whether the size has changed before calling this function to make the program more efficient. + * e.g. + * if img->width() != width || img->height() != height: + * img = img->resize(width, height); + * @maixpy maix.image.Image.resize + */ + image::Image *resize(int width, int height, image::Fit object_fit = image::Fit::FIT_FILL, image::ResizeMethod method = image::ResizeMethod::NEAREST); + + /** + * Affine transform image, will create a new transformed image object + * @param src_points three source points, [x1, y1, x2, y2, x3, y3] + * @param dst_points three destination points, [x1, y1, x2, y2, x3, y3] + * @param width new width, if value is -1, will use height to calculate aspect ratio + * @param height new height, if value is -1, will use width to calculate aspect ratio + * @param method resize method, by default is bilinear + * @return new transformed image object + * @maixpy maix.image.Image.affine + */ + image::Image *affine(std::vector src_points, std::vector dst_points, int width = -1, int height = -1, image::ResizeMethod method = image::ResizeMethod::BILINEAR); + + /** + * Copy image, will create a new copied image object + * @return new copied image object + * @maixpy maix.image.Image.copy + */ + image::Image *copy(); + + /** + * Crop image, will create a new cropped image object + * @param x left top corner of crop rectangle point's coordinate x + * @param y left top corner of crop rectangle point's coordinate y + * @param w crop rectangle width + * @param h crop rectangle height + * @return new cropped image object + * @maixpy maix.image.Image.crop + */ + image::Image *crop(int x, int y, int w, int h); + + /** + * Rotate image, will create a new rotated image object + * @param angle anti-clock wise rotate angle, if angle is 90 or 270, and width or height is -1, will swap width and height, or will throw exception + * @param width new width, if value is -1, will use height to calculate aspect ratio + * @param height new height, if value is -1, will use width to calculate aspect ratio + * @param method resize method, by default is bilinear + * @return new rotated image object + * @maixpy maix.image.Image.rotate + */ + image::Image *rotate(float angle, int width = -1, int height = -1, image::ResizeMethod method = image::ResizeMethod::BILINEAR); + + /** + * @brief Finds the mean of x_div * y_div squares in the image and returns the modified image composed of the mean of each square. + * @param x_div The width of the squares. + * @param y_div The height of the squares. + * @param copy Select whether to return a new image or modify the original image. default is false. + * If true, returns a new image composed of the mean of each square; If false, returns the modified image composed of the mean of each square. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mean_pool + */ + image::Image *mean_pool(int x_div, int y_div, bool copy = false); + + /** + * @brief Finds the midpoint of x_div * y_div squares in the image and returns the modified image composed of the mean of each square. + * @param x_div The width of the squares. + * @param y_div The height of the squares. + * @param bias The bias of the midpoint. default is 0.5. + * midpoint value is equal to (max * bias + min * (1 - bias)) + * @param copy Select whether to return a new image or modify the original image. default is false. + * If true, returns a new image composed of the midpoint of each square; If false, returns the modified image composed of the midpoint of each square. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.midpoint_pool + */ + image::Image *midpoint_pool(int x_div, int y_div, double bias = 0.5, bool copy = false); + + /** + * @brief JPEG compresses the image in place, the same as to_jpeg functioin, it's recommend to use to_jpeg instead. + * @param quality The quality of the compressed image. default is 95. + * @return Returns the compressed JPEG image + * @maixpy maix.image.Image.compress + */ + image::Image *compress(int quality = 95); + + /** + * @brief Sets all pixels in the image to zero + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.clear + */ + image::Image *clear(image::Image *mask = nullptr); + + /** + * @brief Zeros a rectangular part of the image. If no arguments are supplied this method zeros the center of the image. + * @param x The x coordinate of the top left corner of the rectangle. + * @param y The y coordinate of the top left corner of the rectangle. + * @param w The width of the rectangle. + * @param h The height of the rectangle. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mask_rectange + */ + image::Image *mask_rectange(int x = -1, int y = -1, int w = -1, int h = -1); + + /** + * @brief Zeros a circular part of the image. If no arguments are supplied this method zeros the center of the image. + * @param x The x coordinate of the center of the circle. + * @param y The y coordinate of the center of the circle. + * @param radius The radius of the circle. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mask_circle + */ + image::Image *mask_circle(int x = -1, int y = -1, int radius = -1); + + /** + * @brief Zeros a ellipse part of the image. If no arguments are supplied this method zeros the center of the image. + * @param x The x coordinate of the center of the ellipse. + * @param y The y coordinate of the center of the ellipse. + * @param radius_x The radius of the ellipse in the x direction. + * @param radius_y The radius of the ellipse in the y direction. + * @param rotation_angle_in_degrees The rotation angle of the ellipse in degrees. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mask_ellipse + */ + image::Image *mask_ellipse(int x = -1, int y = -1, int radius_x = -1, int radius_y = -1, float rotation_angle_in_degrees = 0); + + /** + * @brief Sets all pixels in the image to black or white depending on if the pixel is inside of a threshold in the threshold list thresholds or not. + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the thresholds will be inverted before the operation. default is false. + * @param zero If zero is true, the image will be set the pixels within the threshold to 0, other pixels remain unchanged. If zero is false, the image will be set to black or white. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @param to_bitmap If true, the image will be converted to a bitmap image before thresholding. default is false. TODO: support in the feature + * @param copy Select whether to return a new image or modify the original image. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.binary + */ + image::Image *binary(std::vector> thresholds = std::vector>(), bool invert = false, bool zero = false, image::Image *mask = nullptr, bool to_bitmap = false, bool copy = false); + + /** + * @brief Inverts the image in place. + * @return Returns the image after the operation is completed + * @maixpy maix.image.Image.invert + */ + image::Image *invert(); + + /** + * @brief Performs a bitwise and operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_and + */ + image::Image *b_and(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise nand operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_nand + */ + image::Image *b_nand(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise or operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_or + */ + image::Image *b_or(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise nor operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_nor + */ + image::Image *b_nor(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise xor operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_xor + */ + image::Image *b_xor(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs a bitwise xnor operation between the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.b_xnor + */ + image::Image *b_xnor(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Performs an auto white balance operation on the image. TODO: support in the feature + * @param max if True uses the white-patch algorithm instead. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.awb + */ + image::Image *awb(bool max = false); + + /** + * @brief Multiples the passed (3x3) or (4x3) floating-point color-correction-matrix with the image. + * note: Grayscale format is not support. + * @param matrix The color correction matrix to use. 3x3 or 4x3 matrix. + * Weights may either be positive or negative, and the sum of each column in the 3x3 matrix should generally be 1. + * example: + * { + * 1, 0, 0, + * 0, 1, 0, + * 0, 0, 1, + * } + * + * Where the last row of the 4x3 matrix is an offset per color channel. If you add an offset you may wish to make the + * weights sum to less than 1 to account for the offset. + * example: + * { + * 1, 0, 0, + * 0, 1, 0, + * 0, 0, 1, + * 0, 0, 0, + * } + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.ccm + */ + image::Image *ccm(std::vector &matrix); + + /** + * @brief Quickly changes the image gamma, contrast, and brightness. Create a array whose size is usually 255, + * and use the parameters gamma, contrast, and brightness to calculate the value of the array, and then map the + * image pixel value through the value of the array. + * The calculation method for array is: array[array_idx] = (powf((array_idx / 255.0), (1 / gamma)) * contrast + brightness) * scale, + * `powf` is a function used to calculate floating point power. + * `array` is the array used for mapping. + * `array_idx` is the index of the array, the maximum value is determined according to the image format, usually 255. + * `scale` is a constant, the value is determined by the image format, usually 255. + * Mapping method: + * Assume that a pixel value in the image is 128, then map the pixel value to the value of array[128] + * Users can adjust the value of the array through the gamma, contrast, and brightness parameters. + * @param gamma The contrast gamma greater than 1.0 makes the image darker in a non-linear manner while less than 1.0 makes the image brighter. default is 1.0. + * @param contrast The contrast value greater than 1.0 makes the image brighter in a linear manner while less than 1.0 makes the image darker. default is 1.0. + * @param brightness The brightness value greater than 0.0 makes the image brighter in a constant manner while less than 0.0 makes the image darker. default is 0.0. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.gamma + */ + image::Image *gamma(double gamma = 1.0, double contrast = 1.0, double brightness = 0.0); + + /** + * @brief Alias for Image.gamma. + * @param gamma The contrast gamma greater than 1.0 makes the image darker in a non-linear manner while less than 1.0 makes the image brighter. default is 1.0. + * @param contrast The contrast value greater than 1.0 makes the image brighter in a linear manner while less than 1.0 makes the image darker. default is 1.0. + * @param brightness The brightness value greater than 0.0 makes the image brighter in a constant manner while less than 0.0 makes the image darker. default is 0.0. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.gamma_corr + */ + image::Image *gamma_corr(double gamma, double contrast = 1.0, double brightness = 0.0); + + /** + * @brief Flips (numerically inverts) all pixels values in an image + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.negate + */ + image::Image *negate(); + + /** + * @brief Replaces all pixels in the image with the corresponding pixels in the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param hmirror If true, the image will be horizontally mirrored before the operation. default is false. + * @param vflip If true, the image will be vertically flipped before the operation. default is false. + * @param transpose If true, the image can be used to rotate 90 degrees or 270 degrees. + * hmirror = false, vflip = false, transpose = false, the image will not be rotated. + * hmirror = false, vflip = true, transpose = true, the image will be rotated 90 degrees. + * hmirror = true, vflip = true, transpose = false, the image will be rotated 180 degrees. + * hmirror = true, vflip = false, transpose = true, the image will be rotated 270 degrees. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.replace + */ + image::Image *replace(image::Image *other = nullptr, bool hmirror = false, bool vflip = false, bool transpose = false, image::Image *mask = nullptr); + + /** + * @brief Alias for Image::replace. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param hmirror If true, the image will be horizontally mirrored before the operation. default is false. + * @param vflip If true, the image will be vertically flipped before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.set + */ + image::Image *set(image::Image *other, bool hmirror = false, bool vflip = false, bool transpose = false, image::Image *mask = nullptr); + + /** + * @brief Adds the other image to the image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.add + */ + image::Image *add(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Subtracts the other image from the image. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param reverse If true, the image will be reversed before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.sub + */ + image::Image *sub(image::Image *other, bool reverse = false, image::Image *mask = nullptr); + + /** + * @brief Multiplies the image by the other image. + * Note: This method is meant for image blending and cannot multiply the pixels in the image by a scalar like 2. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param invert If true, the image will be change the multiplication operation from a*b to 1/((1/a)*(1/b)). + * In particular, this lightens the image instead of darkening it (e.g. multiply versus burn operations). default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mul + */ + image::Image *mul(image::Image *other, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Divides the image by the other image. + * This method is meant for image blending and cannot divide the pixels in the image by a scalar like 2. + * @param other The other image should be an image and should be the same size as the image being operated on. TODO: support path? + * @param invert If true, the image will be change the division direction from a/b to b/a. default is false. + * @param mod If true, the image will be change the division operation to the modulus operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.div + */ + image::Image *div(image::Image *other, bool invert = false, bool mod = false, image::Image *mask = nullptr); + + /** + * @brief Caculate the minimum of each pixel in the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.min + */ + image::Image *min(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Caculate the maximum of each pixel in the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.max + */ + image::Image *max(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Caculate the absolute value of the difference between each pixel in the image and the other image. + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.difference + */ + image::Image *difference(image::Image *other, image::Image *mask = nullptr); + + /** + * @brief Blends the image with the other image. + * res = alpha * this_img / 256 + (256 - alpha) * other_img / 256 + * @param other The other image should be an image and should be the same size as the image being operated on. + * @param alpha The alpha value of the blend, the value range is [0, 256],default is 128. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.blend + */ + image::Image *blend(image::Image *other, int alpha = 128, image::Image *mask = nullptr); + + /** + * @brief Runs the histogram equalization algorithm on the image. + * @param adaptive If true, an adaptive histogram equalization method will be run on the image instead which as generally better results than non-adaptive histogram qualization but a longer run time. default is false. + * @param clip_limit Provides a way to limit the contrast of the adaptive histogram qualization. Use a small value for this, like 10, to produce good histogram equalized contrast limited images. default is -1. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.histeq + */ + image::Image *histeq(bool adaptive = false, int clip_limit = -1, image::Image *mask = nullptr); + + /** + * @brief Standard mean blurring filter using a box filter. + * The parameters offset and invert are valid when threshold is True. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mean + */ + image::Image *mean(int size, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Runs the median filter on the image. The median filter is the best filter for smoothing surfaces while preserving edges but it is very slow. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param percentile This parameter controls the percentile of the value used in the kernel. You can set this to 0 for a min filter, 0.25 for a lower quartile filter, 0.75 for an upper quartile filter, and 1.0 for a max filter. default is 0.5. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.median + */ + image::Image *median(int size, double percentile = 0.5, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Runs the mode filter on the image by replacing each pixel with the mode of their neighbors. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.mode + */ + image::Image *mode(int size, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Runs the midpoint filter on the image.This filter finds the midpoint (max * bias + min * (1 - bias)) of each pixel neighborhood in the image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param bias The bias of the midpoint. default is 0.5. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.midpoint + */ + image::Image *midpoint(int size, double bias = 0.5, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a filter kernel. This allows you to do general purpose convolutions on an image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param kernel The kernel used for convolution. The kernel should be a list of lists of numbers. The kernel should be the same size as the actual kernel size. + * @param mul This parameter is used to multiply the convolved pixel results. default is auto. + * @param add This parameter is the value to be added to each convolution pixel result. default is 0.0. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.morph + */ + image::Image *morph(int size, std::vector kernel, float mul = -1, float add = 0.0, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a smoothing guassian kernel. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param unsharp If true, this method will perform an unsharp mask operation instead of gaussian filtering operation, this improves the clarity of image edges. default is false. + * @param mul This parameter is used to multiply the convolved pixel results. default is auto. + * @param add This parameter is the value to be added to each convolution pixel result. default is 0.0. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.gaussian + */ + image::Image *gaussian(int size, bool unsharp = false, float mul = -1, float add = 0.0, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a edge detecting laplacian kernel. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param sharpen If True, this method will sharpen the image instead of an unthresholded edge detection image. Then increase the kernel size to improve image clarity. default is false. + * @param mul This parameter is used to multiply the convolved pixel results. default is auto. + * @param add This parameter is the value to be added to each convolution pixel result. default is 0.0. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.laplacian + */ + image::Image *laplacian(int size, bool sharpen = false, float mul = -1, float add = 0.0, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Convolves the image by a bilateral filter. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param color_sigma Controls how closely colors are matched using the bilateral filter. default is 0.1. + * @param space_sigma Controls how closely pixels space-wise are blurred with each other. default is 1. + * @param threshold If true, which will enable adaptive thresholding of the image which sets pixels to white or black based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. + * default is false. + * @param offset The larger the offset value, the lower brightness pixels on the original image will be set to white. default is 0. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.bilateral + */ + image::Image *bilateral(int size, double color_sigma = 0.1, double space_sigma = 1, bool threshold = false, int offset = 0, bool invert = false, image::Image *mask = nullptr); + + /** + * @brief Re-project’s and image from cartessian coordinates to linear polar coordinates. + * @param reverse If true, the image will be reverse polar transformed. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.linpolar + */ + image::Image *linpolar(bool reverse = false); + + /** + * @brief Re-project’s and image from cartessian coordinates to log polar coordinates. + * @param reverse If true, the image will be reverse polar transformed. default is false. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.logpolar + */ + image::Image *logpolar(bool reverse = false); + + /** + * @brief Performs a lens correction operation on the image. TODO: support in the feature + * @param strength The strength of the lens correction. default is 1.8. + * @param zoom The zoom of the lens correction. default is 1.0. + * @param x_corr The x correction of the lens correction. default is 0.0. + * @param y_corr The y correction of the lens correction. default is 0.0. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.lens_corr + */ + image::Image *lens_corr(double strength = 1.8, double zoom = 1.0, double x_corr = 0.0, double y_corr = 0.0); + + /** + * @brief Performs a rotation correction operation on the image. TODO: support in the feature + * @param x_rotation The x rotation of the rotation correction. default is 0.0. + * @param y_rotation The y rotation of the rotation correction. default is 0.0. + * @param z_rotation The z rotation of the rotation correction. default is 0.0. + * @param x_translation The x translation of the rotation correction. default is 0.0. + * @param y_translation The y translation of the rotation correction. default is 0.0. + * @param zoom The zoom of the rotation correction. default is 1.0. + * @param fov The fov of the rotation correction. default is 60.0. + * @param corners The corners of the rotation correction. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.rotation_corr + */ + image::Image *rotation_corr(double x_rotation = 0.0, double y_rotation = 0.0, double z_rotation = 0.0, double x_translation = 0.0, double y_translation = 0.0, double zoom = 1.0, double fov = 60.0, std::vector corners = std::vector()); + + /** + * @brief Gets the histogram of the image. + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the thresholds will be inverted before the operation. default is false. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param bins The number of bins to use for the histogram. + * In GRAYSCALE format, setting range is [2, 256], default is 100. + * In rgb888 format, setting range is [2, 100], default is 100. + * @param l_bins The number of bins to use for the l channel of the histogram. Only valid in RGB888 format. + * If an invalid value is set, bins will be used instead. The setting range is [2, 100], default is 100. + * @param a_bins The number of bins to use for the a channel of the histogram. + * Only valid in RGB888 format.The setting range is [2, 256], default is 256. + * @param b_bins The number of bins to use for the b channel of the histogram. + * Only valid in RGB888 format. The setting range is [2, 256], default is 256. + * @param difference difference may be set to an image object to cause this method to operate on the difference image between the current image and the difference image object. + * default is None. + * @return Returns the histogram of the image + * @maixpy maix.image.Image.get_histogram + */ + std::map> get_histogram(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int bins = -1, int l_bins = 100, int a_bins = 256, int b_bins = 256, image::Image *difference = nullptr); + + /** + * @brief Gets the statistics of the image. TODO: support in the feature + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param bins The number of bins to use for the statistics. default is -1. + * @param l_bins The number of bins to use for the l channel of the statistics. default is -1. + * @param a_bins The number of bins to use for the a channel of the statistics. default is -1. + * @param b_bins The number of bins to use for the b channel of the statistics. default is -1. + * @param difference The difference image to use for the statistics. default is None. + * @return Returns the statistics of the image + * @maixpy maix.image.Image.get_statistics + */ + image::Statistics get_statistics(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int bins = -1, int l_bins = -1, int a_bins = -1, int b_bins = -1, image::Image *difference = nullptr); + + /** + * @brief Gets the regression of the image. + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride The x stride to use for the regression. default is 2. + * @param y_stride The y stride to use for the regression. default is 1. + * @param area_threshold The area threshold to use for the regression. default is 10. + * @param pixels_threshold The pixels threshold to use for the regression. default is 10. + * @param robust If true, the regression will be robust. default is false. + * @return Returns the regression of the image + * @maixpy maix.image.Image.get_regression + */ + std::vector get_regression(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, int area_threshold = 10, int pixels_threshold = 10, bool robust = false); + + //************************** image with filesystem **************************// + /** + * Save image to file + * @param path file path + * @param quality image quality, by default(value is 95), support jpeg and png format + * @return error code, err::ERR_NONE is ok, other is error + * @maixpy maix.image.Image.save + */ + err::Err save(const char *path, int quality = 95); + + //************************** application algorithms **************************// + /** + * @brief Flood fills a region of the image starting from location x, y. + * @param x The x coordinate of the seed point. + * @param y The y coordinate of the seed point. + * @param seed_threshold The seed_threshold value controls how different any pixel in the fill area may be from the original starting pixel. default is 0.05. + * @param floating_threshold The floating_threshold value controls how different any pixel in the fill area may be from any neighbor pixels. default is 0.05. + * @param color The color to fill the region with. default is white. + * @param invert If true, the image will be inverted before the operation. default is false. + * @param clear_background If true, the background will be cleared before the operation. default is false. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. FIXME: the mask image works abnormally + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.flood_fill + */ + image::Image *flood_fill(int x, int y, float seed_threshold = 0.05, float floating_threshold = 0.05, image::Color color = image::COLOR_WHITE, bool invert = false, bool clear_background = false, image::Image *mask = nullptr); + + /** + * @brief Erodes the image in place. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold The number of pixels in the kernel that are not 0. If it is less than or equal to the threshold, set the center pixel to black. default is (kernel_size - 1). + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.erode + */ + image::Image *erode(int size, int threshold = -1, image::Image *mask = nullptr); + + /** + * @brief Dilates the image in place. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold The number of pixels in the kernel that are not 0. If it is greater than or equal to the threshold, set the center pixel to white. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.dilate + */ + image::Image *dilate(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Performs erosion and dilation on an image in order. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for erosion and dilation, the actual threshold for erosion is (kernel_size - 1 - threshold), the actual threshold for dialation is threshold. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.open + */ + image::Image *open(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Performs dilation and erosion on an image in order. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for erosion and dilation, the actual threshold for erosion is (kernel_size - 1 - threshold), the actual threshold for dialation is threshold. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.close + */ + image::Image *close(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Returns the image difference of the image and Image.open()’ed image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for open method. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.top_hat + */ + image::Image *top_hat(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * @brief Returns the image difference of the image and Image.close()’ed image. + * @param size Kernel size. The actual kernel size is ((size * 2) + 1) * ((size * 2) + 1). Use 1(3x3 kernel), 2(5x5 kernel). + * @param threshold As the threshold for close method. default is 0. + * @param mask Mask is another image to use as a pixel level mask for the operation. The mask should be an image with just black or white pixels and should be the same size as the image being operated on. + * Only pixels set in the mask are modified. default is None. + * @return Returns the image after the operation is completed. + * @maixpy maix.image.Image.black_hat + */ + image::Image *black_hat(int size, int threshold = 0, image::Image *mask = nullptr); + + /** + * Finds all blobs in the image and returns a list of image.Blob class which describe each Blob. + * Please see the image.Blob object more more information. + * + * @param thresholds You can define multiple thresholds. + * For GRAYSCALE format, you can use {{Lmin, Lmax}, ...} to define one or more thresholds. + * For RGB888 format, you can use {{Lmin, Lmax, Amin, Amax, Bmin, Bmax}, ...} to define one or more thresholds. + * Where the upper case L,A,B represent the L,A,B channels of the LAB image format, and min, max represent the minimum and maximum values of the corresponding channels. + * @param invert if true, will invert thresholds before find blobs, default is false + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride x stride is the number of x pixels to skip when doing the hough transform. default is 2 + * @param y_stride y_stride is the number of y pixels to skip when doing the hough transform. default is 1 + * @param area_threshold area threshold, if the blob area is smaller than area_threshold, the blob is not returned, default is 10 + * @param pixels_threshold pixels threshold, if the blob pixels is smaller than area_threshold, the blob is not returned,, default is 10. + * when x_stride and y_stride is equal to 1, pixels_threshold is equivalent to area_threshold + * @param merge if True merges all not filtered out blobs whos bounding rectangles intersect each other. default is false + * @param margin margin can be used to increase or decrease the size of the bounding rectangles for blobs during the intersection test. + * For example, with a margin of 1 blobs whos bounding rectangles are 1 pixel away from each other will be merged. default is 0 + * @param x_hist_bins_max if set to non-zero populates a histogram buffer in each blob object with an x_histogram projection of all columns in the object. This value then sets the number of bins for that projection. + * @param y_hist_bins_max if set to non-zero populates a histogram buffer in each blob object with an y_histogram projection of all rows in the object. This value then sets the number of bins for that projection. + * + * @return Return the blob when found blobs, format is (blob1, blob2, ...), you can use blob class methods to do more operations. + * @maixpy maix.image.Image.find_blobs + */ + std::vector find_blobs(std::vector> thresholds = std::vector>(), bool invert = false, std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, int area_threshold = 10, int pixels_threshold = 10, bool merge = false, int margin = 0, int x_hist_bins_max = 0, int y_hist_bins_max = 0); + + /** + * Find lines in image + * + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride x stride is the number of x pixels to skip when doing the hough transform. default is 2 + * @param y_stride y_stride is the number of y pixels to skip when doing the hough transform. default is 1 + * @param threshold threshold threshold controls what lines are detected from the hough transform. Only lines with a magnitude greater than or equal to threshold are returned. + * The right value of threshold for your application is image dependent. default is 1000. + * @param theta_margin theta_margin controls the merging of detected lines. default is 25. + * @param rho_margin rho_margin controls the merging of detected lines. default is 25. + * + * @return Return the line when found lines, format is (line1, line2, ...), you can use line class methods to do more operations + * @maixpy maix.image.Image.find_lines + */ + std::vector find_lines(std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, double threshold = 1000, double theta_margin = 25, double rho_margin = 25); + + /** + * @brief Finds all line segments in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param merge_distance The maximum distance between two lines to merge them. default is 0. + * @param max_theta_difference The maximum difference between two lines to merge them. default is 15. + * @return Return the line when found lines, format is (line1, line2, ...), you can use line class methods to do more operations + * @maixpy maix.image.Image.find_line_segments + */ + std::vector find_line_segments(std::vector roi = std::vector(), int merge_distance = 0, int max_theta_difference = 15); + + /** + * Find circles in image + * + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param x_stride x stride is the number of x pixels to skip when doing the hough transform. default is 2 + * @param y_stride y_stride is the number of y pixels to skip when doing the hough transform. default is 1 + * @param threshold threshold controls what circles are detected from the hough transform. Only circles with a magnitude greater than or equal to threshold are returned. + * The right value of threshold for your application is image dependent. + * @param x_margin x_margin controls the merging of detected circles. Circles which are x_margin, y_margin, and r_margin pixels apart are merged. default is 10 + * @param y_margin y_margin controls the merging of detected circles. Circles which are x_margin, y_margin, and r_margin pixels apart are merged. default is 10 + * @param r_margin r_margin controls the merging of detected circles. Circles which are x_margin, y_margin, and r_margin pixels apart are merged. default is 10 + * @param r_min r_min controls the minimum circle radius detected. Increase this to speed up the algorithm. default is 2 + * @param r_max r_max controls the maximum circle radius detected. Decrease this to speed up the algorithm. default is min(roi.w / 2, roi.h / 2) + * @param r_step r_step controls how to step the radius detection by. default is 2. + * + * @return Return the circle when found circles, format is (circle1, circle2, ...), you can use circle class methods to do more operations + * @maixpy maix.image.Image.find_circles + */ + std::vector find_circles(std::vector roi = std::vector(), int x_stride = 2, int y_stride = 1, int threshold = 2000, int x_margin = 10, int y_margin = 10, int r_margin = 10, int r_min = 2, int r_max = -1, int r_step = 2); + + /** + * @brief Finds all rects in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param threshold The threshold to use for the rects. default is 10000. + * @return Returns the rects of the image + * @maixpy maix.image.Image.find_rects + */ + std::vector find_rects(std::vector roi = std::vector(), int threshold = 10000); + + /** + * @brief Finds all qrcodes in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the qrcodes of the image + * @maixpy maix.image.Image.find_qrcodes + */ + std::vector find_qrcodes(std::vector roi = std::vector()); + + /** + * @brief Finds all apriltags in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param families The families to use for the apriltags. default is TAG36H11. + * @param fx The camera X focal length in pixels, default is -1. + * @param fy The camera Y focal length in pixels, default is -1. + * @param cx The camera X center in pixels, default is image.width / 2. + * @param cy The camera Y center in pixels, default is image.height / 2. + * @return Returns the apriltags of the image + * @maixpy maix.image.Image.find_apriltags + */ + std::vector find_apriltags(std::vector roi = std::vector(), image::ApriltagFamilies families = image::ApriltagFamilies::TAG36H11, float fx = -1, float fy = -1, int cx = -1, int cy = -1); + + /** + * @brief Finds all datamatrices in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param effort Controls how much time to spend trying to find data matrix matches. default is 200. + * @return Returns the datamatrices of the image + * @maixpy maix.image.Image.find_datamatrices + */ + std::vector find_datamatrices(std::vector roi = std::vector(), int effort = 200); + + /** + * @brief Finds all barcodes in the image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the barcodes of the image + * @maixpy maix.image.Image.find_barcodes + */ + std::vector find_barcodes(std::vector roi = std::vector()); + + /** + * @brief Finds the displacement between the image and the template. TODO: support in the feature + * note: this method must be used on power-of-2 image sizes + * @param template_image The template image. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param template_roi The region-of-interest rectangle (x, y, w, h) to work in. If not specified, it is equal to the image rectangle. + * @param logpolar If true, it will instead find rotation and scale changes between the two images. default is false. + * @return Returns the displacement of the image + * @maixpy maix.image.Image.find_displacement + */ + image::Displacement find_displacement(image::Image &template_image, std::vector roi = std::vector(), std::vector template_roi = std::vector(), bool logpolar = false); + + /** + * @brief Finds the template in the image. + * @param template_image The template image. + * @param threshold Threshold is floating point number (0.0-1.0) where a higher threshold prevents false positives while lowering the detection rate while a lower threshold does the opposite. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. Only valid in SEARCH_EX mode. + * @param step The step size to use for the template. default is 2. Only valid in SEARCH_EX mode + * @param search The search method to use for the template. default is SEARCH_EX. + * @return Returns a bounding box tuple (x, y, w, h) for the matching location otherwise None. + * @maixpy maix.image.Image.find_template + */ + std::vector find_template(image::Image &template_image, float threshold, std::vector roi = std::vector(), int step = 2, image::TemplateMatch search = image::TemplateMatch::SEARCH_EX); + + /** + * @brief Finds the features in the image. TODO: support in the feature + * @param cascade The cascade to use for the features. default is CASCADE_FRONTALFACE_ALT. + * @param threshold The threshold to use for the features. default is 0.5. + * @param scale The scale to use for the features. default is 1.5. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the features of the image + * @maixpy maix.image.Image.find_features + */ + std::vector find_features(int cascade, float threshold = 0.5, float scale = 1.5, std::vector roi = std::vector()); + + /** + * @brief Finds the lbp in the image. TODO: support in the feature. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @return Returns the lbp of the image + * @maixpy maix.image.Image.find_lbp + */ + image::LBPKeyPoint find_lbp(std::vector roi = std::vector()); + + /** + * @brief Finds the keypoints in the image. TODO: support in the feature. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param threshold The threshold to use for the keypoints. default is 20. + * @param normalized If true, the image will be normalized before the operation. default is false. + * @param scale_factor The scale factor to use for the keypoints. default is 1.5. + * @param max_keypoints The maximum number of keypoints to use for the keypoints. default is 100. + * @param corner_detector The corner detector to use for the keypoints. default is CORNER_AGAST. + * @return Returns the keypoints of the image + * @maixpy maix.image.Image.find_keypoints + */ + image::ORBKeyPoint find_keypoints(std::vector roi = std::vector(), int threshold = 20, bool normalized = false, float scale_factor = 1.5, int max_keypoints = 100, image::CornerDetector corner_detector = image::CornerDetector::CORNER_AGAST); + + /** + * @brief Finds the edges in the image. + * @param edge_type The edge type to use for the edges. default is EDGE_CANNY. + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param threshold The threshold to use for the edges. default is 20. + * @return Returns the edges of the image + * @maixpy maix.image.Image.find_edges + */ + image::Image* find_edges(image::EdgeDetector edge_type, std::vector roi = std::vector(), std::vector threshold = std::vector({100, 200})); + + /** + * @brief Finds the hog in the image. TODO: support in the feature + * @param roi The region of interest, input in the format of (x, y, w, h), x and y are the coordinates of the upper left corner, w and h are the width and height of roi. + * default is None, means whole image. + * @param size The size to use for the hog. default is 8. + * @return Returns the hog of the image + * @maixpy maix.image.Image.find_hog + */ + image::Image* find_hog(std::vector roi = std::vector(), int size = 8); + + /** + * @brief Matches the lbp descriptor of the image. TODO: support in the feature + * @param desc1 The descriptor to use for the match. + * @param desc2 The descriptor to use for the match. + * @return Returns the match of the image + * @maixpy maix.image.Image.match_lbp_descriptor + */ + int match_lbp_descriptor(image::LBPKeyPoint &desc1, image::LBPKeyPoint &desc2); + + /** + * @brief Matches the orb descriptor of the image. TODO: support in the feature + * @param desc1 The descriptor to use for the match. + * @param desc2 The descriptor to use for the match. + * @param threshold The threshold to use for the match. default is 95. + * @param filter_outliers If true, the image will be filter_outliers before the operation. default is false. + * @return Returns the match of the image + * @maixpy maix.image.Image.match_orb_descriptor + */ + image::KPTMatch match_orb_descriptor(image::ORBKeyPoint &desc1, image::ORBKeyPoint &desc2, int threshold = 95, bool filter_outliers = false); + + /** + * map point position or rectangle position from this image size to another image size(resize) + * @param int w_out target image width + * @param int h_out target image height + * @param fit resize method, see maix.image.Fit + * @param x original point x, or rectagle left-top point's x + * @param y original point y, or rectagle left-top point's y + * @param w original rectagle width, can be -1 if not use this arg, default -1. + * @param h original rectagle height, can be -1 if not use this arg, default -1. + * @return list type, [x, y] if map point, [x, y, w, h] if resize rectangle. + * @maixpy maix.image.resize_map_pos + */ + std::vector resize_map_pos(int w_out, int h_out, image::Fit fit, int x, int y, int w = -1, int h = -1) + { + return image::resize_map_pos(_width, _height, w_out, h_out, fit, x, y, w, h); + } + + private: + void *_actual_data; + void *_data; + int _width; + int _height; + int _data_size; + Format _format; + bool _is_malloc; + + int _get_cv_pixel_num(image::Format &format); + std::vector _get_available_roi(std::vector roi, std::vector other_roi = std::vector()); + void _create_image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy); + }; // class Image + + /** + * Load image from file, and convert to Image object + * @param path image file path + * @param format read as this format, if not match, will convert to this format, by default is RGB888 + * @return Image object, if load failed, will return None(nullptr in C++), so you should care about it. + * @maixpy maix.image.load + */ + image::Image *load(const char *path, image::Format format = image::Format::FMT_RGB888); + + /** + * Create image from bytes + * @param width image width + * @param height image height + * @param format image format + * @param data image data, if data is None, will malloc memory for image data + * If the image is in jpeg format, data must be filled in. + * @param copy if true and data is not None, will copy data to new buffer, else will use data directly. default is true to avoid memory leak. + * Use it carefully!!! + * @return Image object + * @maixpy maix.image.from_bytes + */ + image::Image *from_bytes(int width, int height, image::Format format, Bytes *data, bool copy = true); + + /** + * Load font from file + * @param name font name, used to identify font + * @param path font file path, support ttf, ttc, otf + * @param size font size, font height, by default is 16 + * @return error code, err::ERR_NONE is ok, other is error + * @maixpy maix.image.load_font + */ + err::Err load_font(const std::string &name, const char *path, int size = 16); + + /** + * Set default font, if not call this method, default is hershey_plain + * @param name font name, supported names can be get by fonts() + * @return error code, err::ERR_NONE is ok, other is error + * @maixpy maix.image.set_default_font + */ + err::Err set_default_font(const std::string &name); + + /** + * Get all loaded fonts + * @return all loaded fonts, string list type + * @maixpy maix.image.fonts + */ + std::vector *fonts(); + + /** + * Get text rendered width and height + * @param string text content + * @param scale font scale, by default(value is 1) + * @param thickness text thickness(line width), by default(value is 1) + * @return text rendered width and height, [width, height] + * @maixpy maix.image.string_size + */ + image::Size string_size(std::string string, float scale = 1, int thickness = 1, const std::string &font = ""); +} // namespace maix::image diff --git a/vision/components/vision/include/maix_image_color.hpp b/vision/components/vision/include/maix_image_color.hpp new file mode 100644 index 0000000..18de6f0 --- /dev/null +++ b/vision/components/vision/include/maix_image_color.hpp @@ -0,0 +1,296 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_image_def.hpp" +#include "maix_log.hpp" + +namespace maix::image +{ + /** + * Color class + * @maixpy maix.image.Color + */ + class Color + { + public: + /** + * Color constructor + * @param alpha alpha channel, value range: 0 ~ 1 + * @maixpy maix.image.Color.__init__ + */ + Color(uint8_t ch1, uint8_t ch2 = 0, uint8_t ch3 = 0, float alpha = 0, image::Format format = image::FMT_GRAYSCALE) + { + if(alpha > 1 || alpha < 0) + throw std::runtime_error("alpha value range: 0 ~ 1"); + this->format = format; + switch (format) + { + case image::FMT_RGB888: + r = ch1; + g = ch2; + b = ch3; + this->alpha = 1; + break; + case image::FMT_BGR888: + b = ch1; + g = ch2; + r = ch3; + this->alpha = 1; + break; + case image::FMT_GRAYSCALE: + gray = ch1; + break; + case image::FMT_BGRA8888: + b = ch1; + g = ch2; + r = ch3; + this->alpha = alpha; + break; + case image::FMT_RGBA8888: + r = ch1; + g = ch2; + b = ch3; + this->alpha = alpha; + break; + default: + throw std::runtime_error("not support format"); + break; + } + } + + /** + * Color red channel + * @maixpy maix.image.Color.r + */ + uint8_t r; + + /** + * Color green channel + * @maixpy maix.image.Color.g + */ + uint8_t g; + + /** + * Color blue channel + * @maixpy maix.image.Color.b + */ + uint8_t b; + + /** + * Color alpha channel, value from 0.0 to 1.0, float value + * @maixpy maix.image.Color.alpha + */ + float alpha; + + /** + * Color gray channel + * @maixpy maix.image.Color.gray + */ + uint8_t gray; + + /** + * Color format + * @maixpy maix.image.Color.format + */ + image::Format format; + + /** + * Get color's hex value + * @maixpy maix.image.Color.hex + */ + uint32_t hex() + { + uint32_t hex = 0; + switch (format) + { + case image::FMT_RGB888: + hex = r | (g << 8) | (b << 16); + break; + case image::FMT_BGR888: + hex = b | (g << 8) | (r << 16); + break; + case image::FMT_GRAYSCALE: + hex = gray; + break; + case image::FMT_BGRA8888: + hex = b | (g << 8) | (r << 16) | ((uint8_t)(alpha*255) << 24); + break; + case image::FMT_RGBA8888: + hex = r | (g << 8) | (b << 16) | ((uint8_t)(alpha*255) << 24); + break; + default: + throw std::runtime_error("not support format"); + break; + } + return hex; + } + + /** + * Create Color object from RGB channels + * @maixpy maix.image.Color.from_rgb + */ + static image::Color from_rgb(uint8_t r, uint8_t g, uint8_t b) + { + return Color(r, g, b, 1, image::Format::FMT_RGB888); + } + + /** + * Create Color object from BGR channels + * @maixpy maix.image.Color.from_bgr + */ + static image::Color from_bgr(uint8_t b, uint8_t g, uint8_t r) + { + return Color(b, g, r, 1, image::Format::FMT_BGR888); + } + + /** + * Create Color object from gray channel + * @maixpy maix.image.Color.from_gray + */ + static image::Color from_gray(uint8_t gray) + { + return Color(gray); + } + + /** + * Create Color object from RGBA channels + * @param alpha alpha channel, float value, value range: 0 ~ 1 + * @maixpy maix.image.Color.from_rgba + */ + static image::Color from_rgba(uint8_t r, uint8_t g, uint8_t b, float alpha) + { + return Color(r, g, b, alpha, image::Format::FMT_RGBA8888); + } + + /** + * Create Color object from BGRA channels + * * @param alpha alpha channel, float value, value range: 0 ~ 1 + * @maixpy maix.image.Color.from_bgra + */ + static image::Color from_bgra(uint8_t b, uint8_t g, uint8_t r, float alpha) + { + return Color(b, g, r, alpha, image::Format::FMT_BGRA8888); + } + + /** + * Create Color object from hex value + * @param hex hex value, e.g. 0x0000FF00, lower address if first channel + * @param format color format, @see image::Format + * @maixpy maix.image.Color.from_hex + */ + static image::Color from_hex(uint32_t hex, image::Format &format) + { + return Color(hex & 0xFF, hex & 0xFF00, hex & 0xFF0000, (hex & 0xFF000000)/255.0, format); + } + + /** + * Convert Color format + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE. + * @maixpy maix.image.Color.to_format + */ + void to_format(const image::Format &format) + { + if(!(format == image::FMT_RGB888 || format == image::FMT_BGR888 || + format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888 || + format == image::FMT_GRAYSCALE)) + { + log::error("convert format failed, not support format %d\n", format); + return; + } + if(this->format == format) + return; + if((this->format == image::FMT_RGB888 || this->format == image::FMT_BGR888) && + (format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888)) + { + this->alpha = 1; + } + else if(this->format == image::FMT_GRAYSCALE && format != image::FMT_GRAYSCALE) + { + this->r = this->gray; + this->g = this->gray; + this->b = this->gray; + this->alpha = 1; + } + else if((this->format == image::FMT_RGBA8888 || this->format == image::FMT_BGRA8888) && + (format == image::FMT_RGB888 || format == image::FMT_BGR888)) + { + this->alpha = 0; + } + else if(this->format != image::FMT_GRAYSCALE && format == image::FMT_GRAYSCALE) + { + this->gray = (this->r + this->g + this->b) / 3; + this->r = this->gray; + this->g = this->gray; + this->b = this->gray; + this->alpha = 0; + } + this->format = format; + } + + /** + * Convert color format and return a new Color object + * @param format format want to convert to, @see image::Format, only support RGB888, BGR888, RGBA8888, BGRA8888, GRAYSCALE. + * @return new Color object, you need to delete it manually in C++. + * @maixpy maix.image.Color.to_format2 + */ + image::Color *to_format2(const image::Format &format) + { + image::Color *color = new image::Color(*this); + color->to_format(format); + return color; + } + }; + + /** + * Predefined color white + * @maixpy maix.image.COLOR_WHITE + */ + const image::Color COLOR_WHITE = image::Color::from_rgb(255, 255, 255); + /** + * Predefined color black + * @maixpy maix.image.COLOR_BLACK + */ + const image::Color COLOR_BLACK = image::Color::from_rgb(0, 0, 0); + /** + * Predefined color red + * @maixpy maix.image.COLOR_RED + */ + const image::Color COLOR_RED = image::Color::from_rgb(255, 0, 0); + /** + * Predefined color green + * @maixpy maix.image.COLOR_GREEN + */ + const image::Color COLOR_GREEN = image::Color::from_rgb(0, 255, 0); + /** + * Predefined color blue + * @maixpy maix.image.COLOR_BLUE + */ + const image::Color COLOR_BLUE = image::Color::from_rgb(0, 0, 255); + /** + * Predefined color yellow + * @maixpy maix.image.COLOR_YELLOW + */ + const image::Color COLOR_YELLOW = image::Color::from_rgb(255, 255, 0); + /** + * Predefined color purple + * @maixpy maix.image.COLOR_PURPLE + */ + const image::Color COLOR_PURPLE = image::Color::from_rgb(143, 0, 255); + /** + * Predefined color orange + * @maixpy maix.image.COLOR_ORANGE + */ + const image::Color COLOR_ORANGE = image::Color::from_rgb(255, 127, 0); + /** + * Predefined color gray + * @maixpy maix.image.COLOR_GRAY + */ + const image::Color COLOR_GRAY = image::Color::from_rgb(127, 127, 127); + +} diff --git a/vision/components/vision/include/maix_image_def.hpp b/vision/components/vision/include/maix_image_def.hpp new file mode 100644 index 0000000..aa84e45 --- /dev/null +++ b/vision/components/vision/include/maix_image_def.hpp @@ -0,0 +1,270 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace maix::image +{ + + /** + * Image formats + * @attention for developers, update this enum will also need to update the fmt_size in maix_image.cpp + * @maixpy maix.image.Format + */ + enum Format + { + FMT_RGB888 = 0, // RGBRGB...RGB, R at the lowest address + FMT_BGR888, // BGRBGR...BGR, B at the lowest address + FMT_RGBA8888, // RGBARGBA...RGBA, R at the lowest address + FMT_BGRA8888, // BGRABGRA...BGRA, B at the lowest address + FMT_RGB565, + FMT_BGR565, + FMT_YUV422SP, // YYY...UVUVUV...UVUV + FMT_YUV422P, // YYY...UUU...VVV + FMT_YVU420SP, // YYY...VUVUVU...VUVU, NV21 + FMT_YUV420SP, // YYY...UVUVUV...UVUV, NV12 + FMT_YVU420P, // YYY...VVV...UUU + FMT_YUV420P, // YYY...UUU...VVV + FMT_GRAYSCALE, + FMT_UNCOMPRESSED_MAX, + + // compressed format below, not compressed should define upper + FMT_COMPRESSED_MIN, + FMT_JPEG, + FMT_PNG, + FMT_COMPRESSED_MAX, + + FMT_INVALID = 0xFF // format not valid + }; + // enum Format + // { + // FMT_RGB888 = 0, // RGBRGB...RGB, R at the lowest address + // FMT_BGR888 = 1, // BGRBGR...BGR, B at the lowest address + // FMT_RGBA8888 = 2, // RGBARGBA...RGBA, R at the lowest address + // FMT_BGRA8888 = 3, // BGRABGRA...BGRA, B at the lowest address + // FMT_RGB565 = 4, + // FMT_BGR565 = 5, + // FMT_YUV422SP = 6, // YYY...UVUVUV...UVUV + // FMT_YUV422P = 7, // YYY...UUU...VVV + // FMT_YVU420SP = 8, // YYY...VUVUVU...VUVU, NV21 + // FMT_YUV420SP = 9, // YYY...UVUVUV...UVUV, NV12 + // FMT_YVU420P = 10, // YYY...VVV...UUU + // FMT_YUV420P = 11, // YYY...UUU...VVV + // FMT_GRAYSCALE = 12, // YYY... + // FMT_UNCOMPRESSED_MAX, + + // // compressed format below, not compressed should define upper + // FMT_COMPRESSED_MIN = 200, + // FMT_JPEG = 201, + // FMT_PNG = 202, + // FMT_COMPRESSED_MAX, + + // FMT_INVALID = 0xFF // format not valid + // }; + /** + * Image format size in bytes + * @attention It's a copy of this variable in MaixPy, + * so change it in C++ (e.g. update var in hello function) will not take effect the var inMaixPy. + * So we add const for this var to avoid this mistake. + * @maixpy maix.image.fmt_size + */ + const std::vector fmt_size = { + 3, + 3, + 4, + 4, + 2, + 2, + 2, + 2, + 1.5, + 1.5, + 1.5, + 1.5, + 1, + 0, + 0, + 0, + 0, + 0 + }; + + /** + * Image format string + * @maixpy maix.image.fmt_names + */ + const std::vector fmt_names = { + "RGB888", + "BGR888", + "RGBA8888", + "BGRA8888", + "RGB565", + "BGR565", + "YUV422SP", + "YUV422P", + "YVU420SP", + "YUV420SP", + "YVU420P" + "YUV420P", + "GRAYSCALE", + "MAX"}; + + /** + * Image size type + * @maixpy maix.image.Size + */ + class Size + { + public: + /** + * Construct a new Size object + * @param width image width + * @param height image height + * @maixpy maix.image.Size.__init__ + */ + Size(int width = 0, int height = 0) + { + this->_width = width; + this->_height = height; + } + + /** + * width of size + * @param width set new width, if not set, only return current width + * @maixpy maix.image.Size.width + */ + int width(int width = -1) + { + if(width != -1) + { + this->_width = width; + } + return this->_width; + } + + /** + * height of size + * @param height set new height, if not set, only return current height + * @maixpy maix.image.Size.height + */ + int height(int height = -1) + { + if(height != -1) + { + this->_height = height; + } + return this->_height; + } + + /** + * Subscript operator + * @param index 0 for width, 1 for height + * @return int& width or height + * @maixpy maix.image.Size.__getitem__ + * @maixcdk maix.image.Size.[] + */ + int &operator[](int index) + { + if (index == 0) + return _width; + else if (index == 1) + return _height; + else + throw std::out_of_range("Size index out of range"); + } + + /** + * to string + * @maixpy maix.image.Size.__str__ + */ + std::string __str__() + { + return "Size(" + std::to_string(_width) + "x" + std::to_string(_height) + ")"; + } + private: + int _width; + int _height; + }; + + /** + * Object fit method + * @maixpy maix.image.Fit + */ + enum Fit + { + FIT_NONE = -1, // no object fit, keep original + FIT_FILL = 0, // width to new width, height to new height, may be stretch + FIT_CONTAIN, // keep aspect ratio, fill blank area with black color + FIT_COVER, // keep aspect ratio, crop image to fit new size + FIT_MAX + }; + + /** + * Resize method + * @maixpy maix.image.ResizeMethod + */ + enum ResizeMethod + { + NEAREST = 0, + BILINEAR, + BICUBIC, + AREA, + LANCZOS, + HAMMING, + RESIZE_METHOD_MAX + }; + + /** + * Family of apriltag + * @maixpy maix.image.ApriltagFamilies + */ + enum ApriltagFamilies + { + TAG16H5 = 1, + TAG25H7 = 2, + TAG25H9 = 4, + TAG36H10 = 8, + TAG36H11 = 16, + ARTOOLKIT = 32 + }; + + /** + * Template match method + * @maixpy maix.image.TemplateMatch + */ + enum TemplateMatch + { + SEARCH_EX, // Exhaustive search + SEARCH_DS, // Diamond search + }; + + /** + * CornerDetector class + * @maixpy maix.image.CornerDetector + */ + enum CornerDetector + { + CORNER_FAST, + CORNER_AGAST + }; + + /** + * EdgeDetector class + * @maixpy maix.image.EdgeDetector + */ + enum EdgeDetector + { + EDGE_CANNY, + EDGE_SIMPLE, + }; +} diff --git a/vision/components/vision/include/maix_image_obj.hpp b/vision/components/vision/include/maix_image_obj.hpp new file mode 100644 index 0000000..d5a2a2c --- /dev/null +++ b/vision/components/vision/include/maix_image_obj.hpp @@ -0,0 +1,2543 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#pragma once + +#include "maix_err.hpp" +#include +#include + +namespace maix::image +{ + /** + * Line class + * @maixpy maix.image.Line + */ + class Line + { + private: + int _x1; + int _y1; + int _x2; + int _y2; + int _length; + int _magnitude; + int _theta; + int _rho; + + public: + /** + * Line constructor + * + * @param x1 coordinate x1 of the straight line + * @param y1 coordinate y1 of the straight line + * @param x2 coordinate x2 of the straight line + * @param y2 coordinate y2 of the straight line + * @param magnitude magnitude of the straight line after Hough transformation + * @param theta angle of the straight line after Hough transformation + * @param rho p-value of the straight line after Hough transformation + * @maixpy maix.image.Line.__init__ + */ + Line(int x1, int y1, int x2, int y2, int magnitude = 0, int theta = 0, int rho = 0) + { + _x1 = x1; + _y1 = y1; + _x2 = x2; + _y2 = y2; + int x_diff = x2 - x1; + int y_diff = y2 - y1; + _length = (int)sqrtf(x_diff * x_diff + y_diff * y_diff); + _magnitude = magnitude; + _theta = theta; + _rho = rho; + } + ~Line(){}; + + /** + * Subscript operator + * @param index + * [0] get x1 of line + * [1] get y1 of line + * [2] get x2 of line + * [3] get y2 of line + * [4] get length of line + * [5] get magnitude of the straight line after Hough transformation + * [6] get angle of the straight line after Hough transformation (0-179 degrees) + * [7] get p-value of the straight line after Hough transformation + * @return int& + * @maixpy maix.image.Line.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x1; + case 1: return _y1; + case 2: return _x2; + case 3: return _y2; + case 4: return _length; + case 5: return _magnitude; + case 6: return _theta; + case 7: return _rho; + default:throw std::out_of_range("Line index out of range"); + } + } + + /** + * @brief get x1 of line + * @return return x1 of the line, type is int + * @maixpy maix.image.Line.x1 + */ + int x1() + { + return _x1; + }; + + /** + * @brief get y1 of line + * @return return y1 of the line, type is int + * @maixpy maix.image.Line.y1 + */ + int y1() + { + return _y1; + }; + + /** + * @brief get x2 of line + * @return return x2 of the line, type is int + * @maixpy maix.image.Line.x2 + */ + int x2() + { + return _x2; + }; + + /** + * @brief get y2 of line + * @return return y2 of the line, type is int + * @maixpy maix.image.Line.y2 + */ + int y2() + { + return _y2; + }; + + /** + * @brief get length of line + * @return return length of the line, type is int + * @maixpy maix.image.Line.length + */ + int length() + { + return _length; + }; + + /** + * @brief get magnitude of the straight line after Hough transformation + * @return return magnitude, type is int + * @maixpy maix.image.Line.magnitude + */ + int magnitude() + { + return _magnitude; + }; + + /** + * @brief get angle of the straight line after Hough transformation (0-179 degrees) + * @return return angle, type is int + * @maixpy maix.image.Line.theta + */ + int theta() + { + return _theta; + }; + + /** + * @brief get p-value of the straight line after Hough transformation + * @return return p-value, type is int + * @maixpy maix.image.Line.rho + */ + int rho() + { + return _rho; + }; + }; + + /** + * Rect class + * @maixpy maix.image.Rect + */ + class Rect + { + private: + int _x; + int _y; + int _w; + int _h; + int _magnitude; + std::vector> _corners; + public: + /** + * Rect constructor + * @param corners corners of rect + * @param x coordinate x of the straight line + * @param y coordinate y of the straight line + * @param w coordinate w of the straight line + * @param h coordinate h of the straight line + * @param magnitude magnitude of the straight line after Hough transformation + * @maixpy maix.image.Rect.__init__ + */ + Rect(std::vector> &corners, int x, int y, int w, int h, int magnitude = 0) + { + _x = x; + _y = y; + _w = w; + _h = h; + _magnitude = magnitude; + _corners = corners; + } + ~Rect(){}; + + /** + * Subscript operator + * @param index + * [0] get x of rect + * [1] get y of rect + * [2] get w of rect + * [3] get h of rect + * [4] get magnitude of the straight line after Hough transformation + * @return int& + * @maixpy maix.image.Rect.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: return _magnitude; + default:throw std::out_of_range("Rect index out of range"); + } + } + + + /** + * @brief get corners of rect + * @return return the coordinate of the rect. + * @maixpy maix.image.Rect.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of rect + * @return return the rectangle of the rect. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.Rect.rect + */ + std::vector rect() { + std::vector coord = {_x, _y, _w, _h}; + return coord; + }; + + /** + * @brief get x of rect + * @return return x of the rect, type is int + * @maixpy maix.image.Rect.x + */ + int x() { + return _x; + }; + + /** + * @brief get y of rect + * @return return y of the rect, type is int + * @maixpy maix.image.Rect.y + */ + int y() { + return _y; + } + + /** + * @brief get w of rect + * @return return w of the rect, type is int + * @maixpy maix.image.Rect.w + */ + int w() { + return _w; + } + + /** + * @brief get h of rect + * @return return h of the rect, type is int + * @maixpy maix.image.Rect.h + */ + int h() { + return _h; + } + + /** + * @brief get magnitude of the straight line after Hough transformation + * @return return magnitude, type is int + * @maixpy maix.image.Rect.magnitude + */ + int magnitude() { + return _magnitude; + } + }; + + /** + * circle class + * @maixpy maix.image.Circle + */ + class Circle + { + private: + int _x; + int _y; + int _r; + int _magnitude; + public: + /** + * Circle constructor + * + * @param x coordinate x of the circle + * @param y coordinate y of the circle + * @param r coordinate r of the circle + * @param magnitude coordinate y2 of the straight line + * @maixpy maix.image.Circle.__init__ + */ + Circle(int x, int y, int r, int magnitude) + { + _x = x; + _y = y; + _r = r; + _magnitude = magnitude; + } + ~Circle(){}; + + /** + * Subscript operator + * @param index + * [0] get x of circle + * [1] get y of circle + * [2] get r of circle + * [3] get magnitude of the circle after Hough transformation + * @return int& + * @maixpy maix.image.Circle.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _r; + case 3: return _magnitude; + default:throw std::out_of_range("Circle index out of range"); + } + } + + /** + * @brief get x of circle + * @return return x of the circle, type is int + * @maixpy maix.image.Circle.x + */ + int x() + { + return _x; + }; + + /** + * @brief get y of circle + * @return return y of the circle, type is int + * @maixpy maix.image.Circle.y + */ + int y() + { + return _y; + }; + + /** + * @brief get r of circle + * @return return r of the circle, type is int + * @maixpy maix.image.Circle.r + */ + int r() + { + return _r; + }; + + /** + * @brief get magnitude of the circle after Hough transformation + * @return return magnitude, type is int + * @maixpy maix.image.Circle.magnitude + */ + int magnitude() + { + return _magnitude; + }; + }; + + /** + * Blob class + * @maixpy maix.image.Blob + */ + class Blob + { + private: + int _x; + int _y; + int _w; + int _h; + int _cx; + int _cy; + int _rotation; + float _cxf; + float _cyf; + int _pixels; + float _rotation_f; + int _code; + int _count; + int _merge_cnt; + int _perimeter; + int _roundness; + std::vector _x_hist_bins; + std::vector _y_hist_bins; + std::vector> _corners; + std::vector> _mini_corners; + public: + // /** + // * Blob constructor + // * + // * @param rect blob rect, type is + // * @attention this constructor will be optimized in the future + // */ + // Blob(cv::RotatedRect &rect, int merge_cnt = 0) + // { + // cv::Point2f box_points[4]; + // rect.points(box_points); + // _x = box_points[0].x; + // _y = box_points[0].y; + // _w = rect.size.width; + // _h = rect.size.height; + // _cxf = rect.center.x; + // _cyf = rect.center.y; + // _cx = (int)rect.center.x; + // _cy = (int)rect.center.y; + // _rotation_f = rect.angle; + // _rotation = (int)_rotation_f; + // _merge_cnt = merge_cnt; + // for (int i = 0; i < 4; i++) + // { + // _corners.push_back({(int)box_points[i].x, (int)box_points[i].y}); + // } + // } + + /** + * Blob constructor + * + * @param rect blob rect, type is std::vector + * @param corners blob corners, type is std::vector> + * @param mini_corners blob mini_corners, type is std::vector> + * @param cx blob center x, type is float + * @param cy blob center y, type is float + * @param pixels blob pixels, type is int + * @param rotation blob rotation, type is float + * @param code blob code, type is int + * @param count blob count, type is int + * @param perimeter blob perimeter, type is int + * @param roundness blob roundness, type is float + * @param x_hist_bins blob x_hist_bins, type is std::vector + * @param y_hist_bins blob y_hist_bins, type is std::vector + * @maixpy maix.image.Blob.__init__ + */ + Blob(std::vector &rect, std::vector> &corners, std::vector> &mini_corners,float cx, float cy, int pixels, float rotation, int code, int count, int perimeter, float roundness, std::vector &x_hist_bins, std::vector &y_hist_bins) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _cxf = cx; + _cyf = cy; + _cx = (int)cx; + _cy = (int)cy; + _pixels = pixels; + _rotation_f = rotation; + _rotation = (int)_rotation_f; + _code = code; + _count = count; + _perimeter = perimeter; + _roundness = roundness; + _x_hist_bins = x_hist_bins; + _y_hist_bins = y_hist_bins; + _corners = corners; + _mini_corners = mini_corners; + } + + ~Blob(){}; + + /** + * Subscript operator + * @param index + * [0] Returns the blob’s bounding box x coordinate + * [1] Returns the blob’s bounding box y coordinate + * [2] Returns the blob’s bounding box w coordinate + * [3] Returns the blob’s bounding box h coordinate + * [4] Returns the number of pixels that are part of this blob + * [5] Returns the centroid x position of the blob + * [6] Returns the centroid y position of the blob + * @return int& width or height + * @maixpy maix.image.Blob.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: return _pixels; + case 5: return _cx; + case 6: return _cy; + case 7: return _rotation; + default:throw std::out_of_range("Blob index out of range"); + } + } + + /** + * @brief get blob corners + * @return Returns a list of 4 (x,y) tuples of the 4 corners of the object. + * (x0, y0)___________(x1, y1) + * | | + * | | + * | | + * |___________| + * (x3, y3) (x2, y2) + * note: the order of corners may change + * @maixpy maix.image.Blob.corners + */ + std::vector> corners() + { + return _corners; + } + + /** + * @brief get blob mini corners + * @return Returns a list of 4 (x,y) tuples of the 4 corners than bound the min area rectangle of the blob. + * (x0, y0)___________(x1, y1) + * | | + * | | + * | | + * |___________| + * (x3, y3) (x2, y2) + * note: the order of corners may change + * @maixpy maix.image.Blob.mini_corners + */ + std::vector> mini_corners() + { + return _mini_corners; + } + + /** + * @brief get blob rect + * @return Returns the center coordinates and width and height of the rectangle. format is (x, y, w, h) + * w + * (x, y) ___________ + * | | + * | | h + * | | + * |___________| + * + * @maixpy maix.image.Blob.rect + */ + std::vector rect() + { + std::vector rect = {_x, _y, _w, _h}; + return rect; + }; + + /** + * @brief get blob x of the upper left coordinate + * @return Returns the x coordinate of the upper left corner of the rectangle. + * @maixpy maix.image.Blob.x + */ + int x() + { + return _x; + }; + + /** + * @brief get blob y of the upper left coordinate + * @return Returns the y coordinate of the upper left corner of the rectangle. + * @maixpy maix.image.Blob.y + */ + int y() + { + return _y; + }; + + /** + * @brief get blob width + * @return Returns the blob’s bounding box w coordinate + * @maixpy maix.image.Blob.w + */ + int w() + { + return _w; + }; + + /** + * @brief get blob height + * @return Returns the blob’s bounding box h coordinate + * @maixpy maix.image.Blob.h + */ + int h() + { + return _h; + }; + + /** + * @brief get blob pixels + * @return Returns the number of pixels that are part of this blob. + * @maixpy maix.image.Blob.pixels + */ + int pixels() + { + return _pixels; + }; + + /** + * @brief get blob center x + * @return Returns the centroid x position of the blob + * @maixpy maix.image.Blob.cx + */ + int cx() + { + return _cx; + }; + + /** + * @brief get blob center y + * @return Returns the centroid y position of the blob + * @maixpy maix.image.Blob.cy + */ + int cy() + { + return _cy; + }; + + /** + * @brief get blob center x + * @return Returns the centroid x position of the blob + * @maixpy maix.image.Blob.cxf + */ + float cxf() + { + return _cxf; + }; + + /** + * @brief get blob center y + * @return Returns the centroid y position of the blob + * @maixpy maix.image.Blob.cyf + */ + float cyf() + { + return _cyf; + }; + + /** + * @brief get blob rotation + * @return Returns the rotation of the blob in radians (float). If the blob is like a pencil or pen this value will be unique for 0-180 degrees. + * @maixpy maix.image.Blob.rotation + */ + float rotation() + { + return _rotation_f; + }; + + /** + * @brief get blob rotation_rad + * @return Returns the rotation of the blob in radians + * @maixpy maix.image.Blob.rotation_rad + */ + float rotation_rad() + { + return _rotation_f; + }; + + /** + * @brief get blob rotation_deg + * @return Returns the rotation of the blob in degrees. + * @maixpy maix.image.Blob.rotation_deg + */ + int rotation_deg() + { + return _rotation * 180 / 3.1415926; + }; + + /** + * @brief get blob code + * @return Returns a 32-bit binary number with a bit set in it for each color threshold that’s part of this blob + * @maixpy maix.image.Blob.code + */ + int code() { + return _code; + } + + /** + * @brief get blob count + * @return Returns the number of blobs merged into this blob. + * @maixpy maix.image.Blob.count + */ + int count() { + return _count; + } + + /** + * @brief get blob merge_cnt + * @return Returns the number of pixels on this blob’s perimeter. + * @maixpy maix.image.Blob.perimeter + */ + int perimeter() { + return _perimeter; + } + + /** + * @brief get blob roundness + * @return Returns a value between 0 and 1 representing how round the object is + * @maixpy maix.image.Blob.roundness + */ + float roundness() { + return _roundness; + } + + /** + * @brief get blob elongation + * @returnReturns a value between 0 and 1 representing how long (not round) the object is + * @maixpy maix.image.Blob.elongation + */ + float elongation() { + return 1 - _roundness; + } + + /** + * @brief get blob area + * @return Returns the area of the bounding box around the blob + * @maixpy maix.image.Blob.area + */ + int area() + { + return _w * _h; + }; + + /** + * @brief get blob density + * @return Returns the density ratio of the blob + * @maixpy maix.image.Blob.density + */ + float density() { + int area = _w * _h; + if (area == 0) { + return 0; + } + return (float)_pixels / (float)(_w * _h); + } + + /** + * @brief Alias for blob.density() + * @return Returns the density ratio of the blob + * @maixpy maix.image.Blob.extent + */ + float extent() { + return this->density(); + } + + /** + * @brief get blob compactness + * @return Returns the compactness ratio of the blob + * @maixpy maix.image.Blob.compactness + */ + float compactness() { + float perimeter = _perimeter; + if (perimeter == 0) { + return 0; + } + return (_pixels * 4 * 3.1415926) / (perimeter * perimeter); + } + + /** + * @brief get blob solidity + * @return Returns the solidity ratio of the blob + * @maixpy maix.image.Blob.solidity + */ + float solidity() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + float min_area = (((x0 * y1) + (x1 * y2) + (x2 * y3) + (x3 * y0)) - ((y0 * x1) + (y1 * x2) + (y2 * x3) + (y3 * x0))) / 2.0f; + if (min_area == 0) { + return 0; + } + int pixels = _pixels; + float solidity = (float)pixels / min_area; + return solidity > 1 ? 1 : solidity; + } + + /** + * @brief get blob convexity + * @return Returns a value between 0 and 1 representing how convex the object is + * @maixpy maix.image.Blob.convexity + */ + float convexity() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + float d0 = sqrtf((x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1)); + float d1 = sqrtf((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + float d2 = sqrtf((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3)); + float d3 = sqrtf((x3 - x0) * (x3 - x0) + (y3 - y0) * (y3 - y0)); + int perimeter = _perimeter; + if (perimeter == 0) { + return 0; + } + float convexity = (d0 + d1 + d2 + d3) / perimeter; + return convexity > 1 ? convexity : 1; + } + + /** + * @brief get blob x_hist_bins + * @return Returns the x_hist_bins of the blob + * @maixpy maix.image.Blob.x_hist_bins + */ + std::vector x_hist_bins() { + return _x_hist_bins; + } + + /** + * @brief get blob y_hist_bins + * @return Returns the y_hist_bins of the blob + * @maixpy maix.image.Blob.y_hist_bins + */ + std::vector y_hist_bins() { + return _y_hist_bins; + } + + /** + * @brief get blob major_axis_line + * @return Returns a line tuple (x1, y1, x2, y2) of the minor axis of the blob. + * @maixpy maix.image.Blob.major_axis_line + */ + std::vector major_axis_line() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = sqrtf((m0x - m2x) * (m0x - m2x) + (m0y - m2y) * (m0y - m2y)); + float l1 = sqrtf((m1x - m3x) * (m1x - m3x) + (m1y - m3y) * (m1y - m3y)); + + if (l0 > l1) { + return {m0x, m0y, m2x, m2y}; + } else { + return {m1x, m1y, m3x, m3y}; + } + } + + /** + * @brief get blob minor_axis_line + * @return Returns a line tuple (x1, y1, x2, y2) of the minor axis of the blob. + * @maixpy maix.image.Blob.minor_axis_line + */ + std::vector minor_axis_line() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = sqrtf((m0x - m2x) * (m0x - m2x) + (m0y - m2y) * (m0y - m2y)); + float l1 = sqrtf((m1x - m3x) * (m1x - m3x) + (m1y - m3y) * (m1y - m3y)); + + if (l0 < l1) { + return {m0x, m0y, m2x, m2y}; + } else { + return {m1x, m1y, m3x, m3y}; + } + } + + /** + * @brief get blob enclosing_circle + * @return Returns a circle tuple (x, y, r) of the circle that encloses the min area rectangle of a blob. + * @maixpy maix.image.Blob.enclosing_circle + */ + std::vector enclosing_circle() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = sqrtf((x0 - cx) * (x0 - cx) + (y0 - cy) * (y0 - cy)); + float d1 = sqrtf((x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy)); + float d2 = sqrtf((x2 - cx) * (x2 - cx) + (y2 - cy) * (y2 - cy)); + float d3 = sqrtf((x3 - cx) * (x3 - cx) + (y3 - cy) * (y3 - cy)); + float d = std::max(d0, std::max(d1, std::max(d2, d3))); + + return {cx, cy, (int)d}; + } + + /** + * @brief get blob enclosed_ellipse + * @return Returns an ellipse tuple (x, y, rx, ry, rotation) of the ellipse that fits inside of the min area rectangle of a blob. + * @maixpy maix.image.Blob.enclosed_ellipse + */ + std::vector enclosed_ellipse() { + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = _corners[0][0]; + y0 = _corners[0][1]; + x1 = _corners[1][0]; + y1 = _corners[1][1]; + x2 = _corners[2][0]; + y2 = _corners[2][1]; + x3 = _corners[3][0]; + y3 = _corners[3][1]; + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = sqrtf((m0x - cx) * (m0x - cx) + (m0y - cy) * (m0y - cy)); + float d1 = sqrtf((m1x - cx) * (m1x - cx) + (m1y - cy) * (m1y - cy)); + float d2 = sqrtf((m2x - cx) * (m2x - cx) + (m2y - cy) * (m2y - cy)); + float d3 = sqrtf((m3x - cx) * (m3x - cx) + (m3y - cy) * (m3y - cy)); + float a = std::min(d0, d2); + float b = std::min(d1, d3); + + float l0 = sqrtf((m0x - m2x) * (m0x - m2x) + (m0y - m2y) * (m0y - m2y)); + float l1 = sqrtf((m1x - m3x) * (m1x - m3x) + (m1y - m3y) * (m1y - m3y)); + + float r; + if (l0 >= l1) { + if (m0x - m2x == 0) + r = 0; + else + r = atan2f(m0y - m2y, m0x - m2x); + } else { + if (m1x - m3x == 0) + r = 3.1415926 / 2; + else + r = atan2f(m1y - m3y, m1x - m3x) + 3.1415926 / 2; + } + return {cx, cy, (int)a, (int)b, (int)r}; + } + }; // class Blob + + /** + * QRCode class + * @maixpy maix.image.QRCode + */ + class QRCode + { + private: + int _x; + int _y; + int _w; + int _h; + std::string _payload; + int _version; + int _ecc_level; + int _mask; + int _data_type; + int _eci; + std::vector> _corners; + public: + /** + * QRCode constructor + * + * @param rect rect of corners, type is std::vector + * @param corners corners of QRCode + * @param payload payload of the QRCode + * @param version version of the QRCode + * @param ecc_level ecc_level of the QRCode + * @param mask mask of the QRCode + * @param data_type data_type of the QRCode + * @param eci eci of the QRCode + * @maixpy maix.image.QRCode.__init__ + */ + QRCode(std::vector &rect, std::vector> &corners, std::string &payload, int version, int ecc_level, int mask, int data_type, int eci) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _payload = payload; + _version = version; + _ecc_level = ecc_level; + _mask = mask; + _data_type = data_type; + _eci = eci; + _corners = corners; + } + + ~QRCode(){}; + + /** + * Subscript operator + * @param index + * [0] Returns the qrcode’s bounding box x coordinate + * [1] Returns the qrcode’s bounding box y coordinate + * [2] Returns the qrcode’s bounding box w coordinate + * [3] Returns the qrcode’s bounding box h coordinate + * [4] Not support this index, try to use payload() method + * [5] Returns the version of qrcode + * [6] Returns the error correction level of qrcode + * [7] Returns the mask of qrcode + * [8] Returns the datatype of qrcode + * [9] Returns the eci of qrcode + * @return int& + * @maixpy maix.image.QRCode.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: throw err::Exception("Not support this index, try to use payload() method"); + case 5: return _version; + case 6: return _ecc_level; + case 7: return _mask; + case 8: return _data_type; + case 9: return _eci; + default:throw std::out_of_range("QRcode index out of range"); + } + } + + /** + * @brief get coordinate of QRCode + * @return return the coordinate of the QRCode. + * @maixpy maix.image.QRCode.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of QRCode + * @return return the rectangle of the QRCode. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.QRCode.rect + */ + std::vector rect() { + std::vector coord = {_x, _y, _w, _h}; + return coord; + } + + /** + * @brief get x of QRCode + * @return return x of the QRCode, type is int + * @maixpy maix.image.QRCode.x + */ + int x() { + return _x; + } + + /** + * @brief get y of QRCode + * @return return y of the QRCode, type is int + * @maixpy maix.image.QRCode.y + */ + int y() { + return _y; + } + + /** + * @brief get w of QRCode + * @return return w of the QRCode, type is int + * @maixpy maix.image.QRCode.w + */ + int w() { + return _w; + } + + /** + * @brief get h of QRCode + * @return return h of the QRCode, type is int + * @maixpy maix.image.QRCode.h + */ + int h() { + return _h; + } + + /** + * @brief get QRCode payload + * @return return area of the QRCode + * @maixpy maix.image.QRCode.payload + */ + std::string payload() { + return _payload; + } + + /** + * @brief get QRCode version + * @return return version of the QRCode + * @maixpy maix.image.QRCode.version + */ + int version() { + return _version; + } + + /** + * @brief get QRCode error correction level + * @return return error correction level of the QRCode + * @maixpy maix.image.QRCode.ecc_level + */ + int ecc_level() { + return _ecc_level; + } + + /** + * @brief get QRCode mask + * @return return mask of the QRCode + * @maixpy maix.image.QRCode.mask + */ + int mask() { + return _mask; + } + + /** + * @brief get QRCode dataType + * @return return mask of the QRCode + * @maixpy maix.image.QRCode.data_type + */ + int data_type() { + return _data_type; + } + + /** + * @brief get QRCode eci + * @return return data of the QRCode + * @maixpy maix.image.QRCode.eci + */ + int eci() { + return _eci; + } + + /** + * @brief check QRCode is numeric + * @return return true if the result type of the QRCode is numeric + * @maixpy maix.image.QRCode.is_numeric + */ + bool is_numeric() { + return _data_type == 1; + } + + /** + * @brief check QRCode is alphanumeric + * @return return true if the result type of the QRCode is alphanumeric + * @maixpy maix.image.QRCode.is_alphanumeric + */ + bool is_alphanumeric() { + return _data_type == 2; + } + + /** + * @brief check QRCode is binary + * @return return true if the result type of the QRCode is binary + * @maixpy maix.image.QRCode.is_binary + */ + bool is_binary() { + return _data_type == 4; + } + + /** + * @brief check QRCode is kanji + * @return return true if the result type of the QRCode is kanji + * @maixpy maix.image.QRCode.is_kanji + */ + bool is_kanji() { + return _data_type == 8; + } + }; // class QRCode + + /** + * AprilTag class + * @maixpy maix.image.AprilTag + */ + class AprilTag + { + private: + int _x; + int _y; + int _w; + int _h; + int _id; + std::vector> _corners; + int _family; + float _cx; + float _cy; + float _rotation; + float _decision_margin; + int _hamming; + float _goodness; + float _x_translation; + float _y_translation; + float _z_translation; + float _x_rotation; + float _y_rotation; + float _z_rotation; + public: + /** + * AprilTag constructor + * + * @param rect Inlucdes the top-left corner and the width and height of the rectangle. format is {x, y, w, h}, type is std::vector + * @param corners Includes the four corners of the rectangle. format is {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}, type is std::vector> + * @param id The id of the AprilTag + * @param famliy The family of the AprilTag + * @param centroid_x The x coordinate of the center of the AprilTag + * @param centroid_y The y coordinate of the center of the AprilTag + * @param rotation The rotation of the AprilTag + * @param decision_margin The decision_margin of the AprilTag + * @param hamming The hamming of the AprilTag + * @param goodness The goodness of the AprilTag + * @param x_translation The x_translation of the AprilTag + * @param y_translation The y_translation of the AprilTag + * @param z_translation The z_translation of the AprilTag + * @param x_rotation The x_rotation of the AprilTag + * @param y_rotation The y_rotation of the AprilTag + * @param z_rotation The z_rotation of the AprilTag + * @maixpy maix.image.AprilTag.__init__ + */ + AprilTag(std::vector &rect, std::vector> &corners, int id, int famliy, float centroid_x, float centroid_y, float rotation, float decision_margin, int hamming, float goodness, float x_translation, float y_translation, float z_translation, float x_rotation, float y_rotation, float z_rotation) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _corners = corners; + _id = id; + _family = famliy; + _cx = centroid_x; + _cy = centroid_y; + _rotation = rotation; + _decision_margin = decision_margin; + _hamming = hamming; + _goodness = goodness; + _x_translation = x_translation; + _y_translation = y_translation; + _z_translation = z_translation; + _x_rotation = x_rotation; + _y_rotation = y_rotation; + _z_rotation = z_rotation; + } + + ~AprilTag(){}; + + /** + * Subscript operator + * @param index + * [0] Returns the apriltag’s bounding box x coordinate + * [1] Returns the apriltag’s bounding box y coordinate + * [2] Returns the apriltag’s bounding box w coordinate + * [3] Returns the apriltag’s bounding box h coordinate + * [4] Returns the apriltag’s id + * [5] Returns the apriltag’s family + * [6] Not support + * [7] Not support + * [8] Not support + * [9] Not support + * [10] Returns the apriltag’s hamming + * [11] Not support + * [12] Not support + * [13] Not support + * [14] Not support + * [15] Not support + * [16] Not support + * [17] Not support + * @return int& + * @maixpy maix.image.AprilTag.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: return _id; + case 5: return _family; + case 6: throw err::Exception("Not support this index, try to use cxf() method"); + case 7: throw err::Exception("Not support this index, try to use cyf() method"); + case 8: throw err::Exception("Not support this index, try to use rotation() method"); + case 9: throw err::Exception("Not support this index, try to use decision_margin() method"); + case 10: return _hamming; + case 11: throw err::Exception("Not support this index, try to use goodness() method"); + case 12: throw err::Exception("Not support this index, try to use x_translation() method"); + case 13: throw err::Exception("Not support this index, try to use y_translation() method"); + case 14: throw err::Exception("Not support this index, try to use z_translation() method"); + case 15: throw err::Exception("Not support this index, try to use x_rotation() method"); + case 16: throw err::Exception("Not support this index, try to use y_rotation() method"); + case 17: throw err::Exception("Not support this index, try to use z_rotation() method"); + default:throw std::out_of_range("Apriltag index out of range"); + } + } + + /** + * @brief get coordinate of AprilTag + * @return return the coordinate of the AprilTag. + * @maixpy maix.image.AprilTag.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of AprilTag + * @return return the rectangle of the AprilTag. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.AprilTag.rect + */ + std::vector rect() { + return {_x, _y, _w, _h}; + } + + /** + * @brief get x of AprilTag + * @return return x of the AprilTag, type is int + * @maixpy maix.image.AprilTag.x + */ + int x() { + return _x; + } + + /** + * @brief get y of AprilTag + * @return return y of the AprilTag, type is int + * @maixpy maix.image.AprilTag.y + */ + int y() { + return _y; + } + + /** + * @brief get w of AprilTag + * @return return w of the AprilTag, type is int + * @maixpy maix.image.AprilTag.w + */ + int w() { + return _w; + } + + /** + * @brief get h of AprilTag + * @return return h of the AprilTag, type is int + * @maixpy maix.image.AprilTag.h + */ + int h() { + return _h; + } + + /** + * @brief get id of AprilTag + * @return return id of the AprilTag, type is int + * @maixpy maix.image.AprilTag.id + */ + int id() { + return _id; + } + + /** + * @brief get family of AprilTag + * @return return family of the AprilTag, type is int + * @maixpy maix.image.AprilTag.family + */ + int family() { + return _family; + } + + /** + * @brief get cx of AprilTag + * @return return cx of the AprilTag, type is int + * @maixpy maix.image.AprilTag.cx + */ + int cx() { + return (int)_cx; + } + + /** + * @brief get cxf of AprilTag + * @return return cxf of the AprilTag, type is float + * @maixpy maix.image.AprilTag.cxf + */ + float cxf() { + return _cx; + } + + /** + * @brief get cy of AprilTag + * @return return cy of the AprilTag, type is int + * @maixpy maix.image.AprilTag.cy + */ + int cy() { + return (int)_cy; + } + + /** + * @brief get cyf of AprilTag + * @return return cyf of the AprilTag, type is float + * @maixpy maix.image.AprilTag.cyf + */ + float cyf() { + return _cy; + } + + /** + * @brief get rotation of AprilTag + * @return return rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief Get decision_margin of AprilTag + * @return Returns the quality of the apriltag match (0.0 - 1.0) where 1.0 is the best. + * @maixpy maix.image.AprilTag.decision_margin + */ + float decision_margin() { + return _decision_margin; + } + + /** + * @brief get hamming of AprilTag + * @return Returns the number of accepted bit errors for this tag. + * return 0, means 0 bit errors will be accepted. + * 1 is TAG25H7, means up to 1 bit error may be accepted + * 2 is TAG25H9, means up to 3 bit errors may be accepted + * 3 is TAG36H10, means up to 3 bit errors may be accepted + * 4 is TAG36H11, means up to 4 bit errors may be accepted + * 5 is ARTOOLKIT, means 0 bit errors will be accepted + * @maixpy maix.image.AprilTag.hamming + */ + int hamming() { + return _hamming; + } + + /** + * @brief get goodness of AprilTag + * @return return goodness of the AprilTag, type is float + * Note: This value is always 0.0 for now. + * @maixpy maix.image.AprilTag.goodness + */ + float goodness() { + return _goodness; + } + + /** + * @brief get x_translation of AprilTag + * @return return x_translation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.x_translation + */ + float x_translation() { + return _x_translation; + } + + /** + * @brief get y_translation of AprilTag + * @return return y_translation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.y_translation + */ + float y_translation() { + return _y_translation; + } + + /** + * @brief get z_translation of AprilTag + * @return return z_translation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.z_translation + */ + float z_translation() { + return _z_translation; + } + + /** + * @brief get x_rotation of AprilTag + * @return return x_rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.x_rotation + */ + float x_rotation() { + return _x_rotation; + } + + /** + * @brief get y_rotation of AprilTag + * @return return y_rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.y_rotation + */ + float y_rotation() { + return _y_rotation; + } + + /** + * @brief get z_rotation of AprilTag + * @return return z_rotation of the AprilTag, type is float + * @maixpy maix.image.AprilTag.z_rotation + */ + float z_rotation() { + return _z_rotation; + } + }; + + /** + * DataMatrix class + * @maixpy maix.image.DataMatrix + */ + class DataMatrix + { + private: + int _x; + int _y; + int _w; + int _h; + std::vector> _corners; + std::string _payload; + float _rotation; + int _rows; + int _columns; + int _capacity; + int _padding; + public: + /** + * DataMatrix constructor + * + * @param rect Inlucdes the top-left corner and the width and height of the rectangle. format is {x, y, w, h}, type is std::vector + * @param corners Includes the four corners of the rectangle. format is {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}, type is std::vector> + * @param payload The payload of the DataMatrix + * @param rotation The rotation of the DataMatrix + * @param rows The rows of the DataMatrix + * @param columns The columns of the DataMatrix + * @param capacity The capacity of the DataMatrix + * @param padding The padding of the DataMatrix + * @maixpy maix.image.DataMatrix.__init__ + */ + DataMatrix(std::vector &rect, std::vector> &corners, std::string &payload, float rotation, int rows, int columns, int capacity, int padding) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _corners = corners; + _payload = payload; + _rotation = rotation; + _rows = rows; + _columns = columns; + _capacity = capacity; + _padding = padding; + } + + ~DataMatrix(){}; + + /** + * Subscript operator + * @param index + * [0] get x of DataMatrix + * [1] get y of DataMatrix + * [2] get w of DataMatrix + * [3] get h of DataMatrix + * [4] Not support this index, try to use payload() method + * [5] Not support this index, try to use rotation() method + * [6] get rows of DataMatrix + * [7] get columns of DataMatrix + * [8] get capacity of DataMatrix + * [9] get padding of DataMatrix + * @return int& + * @maixpy maix.image.DataMatrix.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: throw err::Exception("Not support this index, try to use payload() method"); + case 5: throw err::Exception("Not support this index, try to use ratation() method"); + case 6: return _rows; + case 7: return _columns; + case 8: return _capacity; + case 9: return _padding; + default:throw std::out_of_range("DataMatrix index out of range"); + } + } + + /** + * @brief get coordinate of DataMatrix + * @return return the coordinate of the DataMatrix. + * @maixpy maix.image.DataMatrix.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of DataMatrix + * @return return the rectangle of the DataMatrix. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.DataMatrix.rect + */ + std::vector rect() { + return {_x, _y, _w, _h}; + } + + /** + * @brief get x of DataMatrix + * @return return x of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.x + */ + int x() { + return _x; + } + + /** + * @brief get y of DataMatrix + * @return return y of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.y + */ + int y() { + return _y; + } + + /** + * @brief get w of DataMatrix + * @return return w of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.w + */ + int w() { + return _w; + } + + /** + * @brief get h of DataMatrix + * @return return h of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.h + */ + int h() { + return _h; + } + + /** + * @brief get payload of DataMatrix + * @return return payload of the DataMatrix, type is std::string + * @maixpy maix.image.DataMatrix.payload + */ + std::string payload() { + return _payload; + } + + /** + * @brief get rotation of DataMatrix + * @return return rotation of the DataMatrix, type is float + * @maixpy maix.image.DataMatrix.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief get rows of DataMatrix + * @return return rows of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.rows + */ + int rows() { + return _rows; + } + + /** + * @brief get columns of DataMatrix + * @return return columns of the DataMatrix, type is int + * @maixpy maix.image.DataMatrix.columns + */ + int columns() { + return _columns; + } + + /** + * @brief get capacity of DataMatrix + * @return returns how many characters could fit in this data matrix, type is int + * @maixpy maix.image.DataMatrix.capacity + */ + int capacity() { + return _capacity; + } + + /** + * @brief get padding of DataMatrix + * @return returns how many unused characters are in this data matrix, type is int + * @maixpy maix.image.DataMatrix.padding + */ + int padding() { + return _padding; + } + }; + + /** + * BarCode class + * @maixpy maix.image.BarCode + */ + class BarCode { + private: + int _x; + int _y; + int _w; + int _h; + std::vector> _corners; + std::string _payload; + int _type; + float _rotation; + int _quality; + public: + /** + * BarCode constructor + * + * @param rect Inlucdes the top-left corner and the width and height of the rectangle. format is {x, y, w, h}, type is std::vector + * @param corners Includes the four corners of the rectangle. format is {{x0, y0}, {x1, y1}, {x2, y2}, {x3, y3}}, type is std::vector> + * @param payload The payload of the BarCode + * @param type The type of the BarCode + * @param rotation The rotation of the BarCode + * @param quality The quality of the BarCode + * @maixpy maix.image.BarCode.__init__ + */ + BarCode(std::vector &rect, std::vector> &corners, std::string &payload, int type, float rotation, int quality) + { + _x = rect[0]; + _y = rect[1]; + _w = rect[2]; + _h = rect[3]; + _corners = corners; + _payload = payload; + _type = type; + _rotation = rotation; + _quality = quality; + } + + ~BarCode(){}; + + /** + * Subscript operator + * @param index + * [0] get x of BarCode + * [1] get y of BarCode + * [2] get w of BarCode + * [3] get h of BarCode + * [4] Not support this index, try to use payload() method + * [5] get type of BarCode + * [6] Not support this index, try to use rotation() method + * [7] get quality of BarCode + * @return int& + * @maixpy maix.image.BarCode.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _x; + case 1: return _y; + case 2: return _w; + case 3: return _h; + case 4: throw err::Exception("Not support this index, try to use payload() method"); + case 5: return _type; + case 6: throw err::Exception("Not support this index, try to use rotation() method"); + case 7: return _quality; + default:throw std::out_of_range("BarCode index out of range"); + } + } + + /** + * @brief get coordinate of BarCode + * @return return the coordinate of the BarCode. + * @maixpy maix.image.BarCode.corners + */ + std::vector> corners() { + return _corners; + } + + /** + * @brief get rectangle of BarCode + * @return return the rectangle of the BarCode. format is {x, y, w, h}, type is std::vector + * @maixpy maix.image.BarCode.rect + */ + std::vector rect() { + return {_x, _y, _w, _h}; + } + + /** + * @brief get x of BarCode + * @return return x of the BarCode, type is int + * @maixpy maix.image.BarCode.x + */ + int x() { + return _x; + } + + /** + * @brief get y of BarCode + * @return return y of the BarCode, type is int + * @maixpy maix.image.BarCode.y + */ + int y() { + return _y; + } + + /** + * @brief get w of BarCode + * @return return w of the BarCode, type is int + * @maixpy maix.image.BarCode.w + */ + int w() { + return _w; + } + + /** + * @brief get h of BarCode + * @return return h of the BarCode, type is int + * @maixpy maix.image.BarCode.h + */ + int h() { + return _h; + } + + /** + * @brief get payload of BarCode + * @return return payload of the BarCode, type is std::string + * @maixpy maix.image.BarCode.payload + */ + std::string payload() { + return _payload; + } + + /** + * @brief get type of BarCode + * @return return type of the BarCode, type is int + * @maixpy maix.image.BarCode.type + */ + int type() { + return _type; + } + + /** + * @brief get rotation of BarCode + * @return return rotation of the BarCode, type is float. FIXME: always return 0.0 + * @maixpy maix.image.BarCode.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief get quality of BarCode + * @return return quality of the BarCode, type is int + * @maixpy maix.image.BarCode.quality + */ + int quality() { + return _quality; + } + }; + + /** + * Statistics class + * @maixpy maix.image.Statistics + */ + class Statistics { + private: + int _l_mean; + int _l_median; + int _l_mode; + int _l_std_dev; + int _l_min; + int _l_max; + int _l_lq; + int _l_uq; + + int _a_mean; + int _a_median; + int _a_mode; + int _a_std_dev; + int _a_min; + int _a_max; + int _a_lq; + int _a_uq; + + int _b_mean; + int _b_median; + int _b_mode; + int _b_std_dev; + int _b_min; + int _b_max; + int _b_lq; + int _b_uq; + + image::Format _format; + public: + /** + * Statistics constructor + * + * @param format The statistics source image format + * @param l_statistics The statistics of the L channel. format is {mean, median, mode, std_dev, min, max, lq, uq}, type is std::vector + * @param a_statistics The statistics of the A channel. format is {mean, median, mode, std_dev, min, max, lq, uq}, type is std::vector + * @param b_statistics The statistics of the B channel. format is {mean, median, mode, std_dev, min, max, lq, uq}, type is std::vector + * @maixpy maix.image.Statistics.__init__ + */ + Statistics(image::Format format, std::vector &l_statistics, std::vector &a_statistics, std::vector &b_statistics) + { + err::check_bool_raise((l_statistics.size() == 8) || (a_statistics.size() == 8) || (b_statistics.size() == 8), "statistics size must be 8"); + + _l_mean = l_statistics[0]; + _l_median = l_statistics[1]; + _l_mode = l_statistics[2]; + _l_std_dev = l_statistics[3]; + _l_min = l_statistics[4]; + _l_max = l_statistics[5]; + _l_lq = l_statistics[6]; + _l_uq = l_statistics[7]; + + _a_mean = a_statistics[0]; + _a_median = a_statistics[1]; + _a_mode = a_statistics[2]; + _a_std_dev = a_statistics[3]; + _a_min = a_statistics[4]; + _a_max = a_statistics[5]; + _a_lq = a_statistics[6]; + _a_uq = a_statistics[7]; + + _b_mean = b_statistics[0]; + _b_median = b_statistics[1]; + _b_mode = b_statistics[2]; + _b_std_dev = b_statistics[3]; + _b_min = b_statistics[4]; + _b_max = b_statistics[5]; + _b_lq = b_statistics[6]; + _b_uq = b_statistics[7]; + + _format = format; + } + Statistics(){}; + ~Statistics(){}; + + /** + * Subscript operator + * @param index array index + * @return int& + * @maixpy maix.image.Statistics.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: return _l_mean; + case 1: return _l_median; + case 2: return _l_mode; + case 3: return _l_std_dev; + case 4: return _l_min; + case 5: return _l_max; + case 6: return _l_lq; + case 7: return _l_uq; + case 8: return _a_mean; + case 9: return _a_median; + case 10: return _a_mode; + case 11: return _a_std_dev; + case 12: return _a_min; + case 13: return _a_max; + case 14: return _a_lq; + case 15: return _a_uq; + case 16: return _b_mean; + case 17: return _b_median; + case 18: return _b_mode; + case 19: return _b_std_dev; + case 20: return _b_min; + case 21: return _b_max; + case 22: return _b_lq; + case 23: return _b_uq; + default:throw std::out_of_range("Statistics index out of range"); + } + } + + /** + * @brief get format of Statistics source image + * @return return format of the Statistics source image, type is image::Format + * @maixpy maix.image.Statistics.format + */ + image::Format format() { + return _format; + } + + /** + * @brief get L channel mean + * @return return L channel mean, type is int + * @maixpy maix.image.Statistics.l_mean + */ + int l_mean() { + return _l_mean; + } + + /** + * @brief get L channel median + * @return return L channel median, type is int + * @maixpy maix.image.Statistics.l_median + */ + int l_median() { + return _l_median; + } + + /** + * @brief get L channel mode + * @return return L channel mode, type is int + * @maixpy maix.image.Statistics.l_mode + */ + int l_mode() { + return _l_mode; + } + + /** + * @brief get L channel std_dev + * @return return L channel std_dev, type is int + * @maixpy maix.image.Statistics.l_std_dev + */ + int l_std_dev() { + return _l_std_dev; + } + + /** + * @brief get L channel min + * @return return L channel min, type is int + * @maixpy maix.image.Statistics.l_min + */ + int l_min() { + return _l_min; + } + + /** + * @brief get L channel max + * @return return L channel max, type is int + * @maixpy maix.image.Statistics.l_max + */ + int l_max() { + return _l_max; + } + + /** + * @brief get L channel lq + * @return return L channel lq, type is int + * @maixpy maix.image.Statistics.l_lq + */ + int l_lq() { + return _l_lq; + } + + /** + * @brief get L channel uq + * @return return L channel uq, type is int + * @maixpy maix.image.Statistics.l_uq + */ + int l_uq() { + return _l_uq; + } + + /** + * @brief get A channel mean + * @return return A channel mean, type is int + * @maixpy maix.image.Statistics.a_mean + */ + int a_mean() { + return _a_mean; + } + + /** + * @brief get A channea median + * @return return A channel median, type is int + * @maixpy maix.image.Statistics.a_median + */ + int a_median() { + return _a_median; + } + + /** + * @brief get A channel mode + * @return return A channel mode, type is int + * @maixpy maix.image.Statistics.a_mode + */ + int a_mode() { + return _a_mode; + } + + /** + * @brief get A channel std_dev + * @return return A channel std_dev, type is int + * @maixpy maix.image.Statistics.a_std_dev + */ + int a_std_dev() { + return _a_std_dev; + } + + /** + * @brief get A channel min + * @return return A channel min, type is int + * @maixpy maix.image.Statistics.a_min + */ + int a_min() { + return _a_min; + } + + /** + * @brief get A channel max + * @return return A channel max, type is int + * @maixpy maix.image.Statistics.a_max + */ + int a_max() { + return _a_max; + } + + /** + * @brief get A channel lq + * @return return A channel lq, type is int + * @maixpy maix.image.Statistics.a_lq + */ + int a_lq() { + return _a_lq; + } + + /** + * @brief get A channel uq + * @return return A channel uq, type is int + * @maixpy maix.image.Statistics.a_uq + */ + int a_uq() { + return _a_uq; + } + + /** + * @brief get B channel mean + * @return return B channel mean, type is int + * @maixpy maix.image.Statistics.b_mean + */ + int b_mean() { + return _b_mean; + } + + /** + * @brief get B channea median + * @return return B channel median, type is int + * @maixpy maix.image.Statistics.b_median + */ + int b_median() { + return _b_median; + } + + /** + * @brief get B channel mode + * @return return B channel mode, type is int + * @maixpy maix.image.Statistics.b_mode + */ + int b_mode() { + return _b_mode; + } + + /** + * @brief get B channel std_dev + * @return return B channel std_dev, type is int + * @maixpy maix.image.Statistics.b_std_dev + */ + int b_std_dev() { + return _b_std_dev; + } + + /** + * @brief get B channel min + * @return return B channel min, type is int + * @maixpy maix.image.Statistics.b_min + */ + int b_min() { + return _b_min; + } + + /** + * @brief get B channel max + * @return return B channel max, type is int + * @maixpy maix.image.Statistics.b_max + */ + int b_max() { + return _b_max; + } + + /** + * @brief get B channel lq + * @return return B channel lq, type is int + * @maixpy maix.image.Statistics.b_lq + */ + int b_lq() { + return _b_lq; + } + + /** + * @brief get B channel uq + * @return return B channel uq, type is int + * @maixpy maix.image.Statistics.b_uq + */ + int b_uq() { + return _b_uq; + } + }; + + + /** + * Displacement class + * @maixpy maix.image.Displacement + */ + class Displacement { + private: + float _x_translation; + float _y_translation; + float _rotation; + float _scale; + float _response; + public: + Displacement(){}; + /** + * Displacement constructor + * + * @param x_translation The x_translation of the Displacement + * @param y_translation The y_translation of the Displacement + * @param rotation The rotation of the Displacement + * @param scale The scale of the Displacement + * @param response The response of the Displacement + * @maixpy maix.image.Displacement.__init__ + */ + Displacement(float x_translation, float y_translation, float rotation, float scale, float response) + { + _x_translation = x_translation; + _y_translation = y_translation; + _rotation = rotation; + _scale = scale; + _response = response; + } + + ~Displacement(){}; + + /** + * Subscript operator + * @param index array index + * @return int& + * @maixpy maix.image.Displacement.__getitem__ + */ + int &__getitem__(int index) + { + switch (index) { + case 0: throw err::Exception("Not support this index, try to use x_translation() method"); + case 1: throw err::Exception("Not support this index, try to use y_translation() method"); + case 2: throw err::Exception("Not support this index, try to use rotation() method"); + case 3: throw err::Exception("Not support this index, try to use scale() method"); + case 4: throw err::Exception("Not support this index, try to use response() method"); + default:throw std::out_of_range("Displacement index out of range"); + } + } + + /** + * @brief get x_translation of Displacement + * @return return x_translation of the Displacement, type is float + * @maixpy maix.image.Displacement.x_translation + */ + float x_translation() { + return _x_translation; + } + + /** + * @brief get y_translation of Displacement + * @return return y_translation of the Displacement, type is float + * @maixpy maix.image.Displacement.y_translation + */ + float y_translation() { + return _y_translation; + } + + /** + * @brief get rotation of Displacement + * @return return rotation of the Displacement, type is float + * @maixpy maix.image.Displacement.rotation + */ + float rotation() { + return _rotation; + } + + /** + * @brief get scale of Displacement + * @return return scale of the Displacement, type is float + * @maixpy maix.image.Displacement.scale + */ + float scale() { + return _scale; + } + + /** + * @brief get response of Displacement + * @return return response of the Displacement, type is float + * @maixpy maix.image.Displacement.response + */ + float response() { + return _response; + } + }; + + /** + * LBPKeyPoint class + * @maixpy maix.image.LBPKeyPoint + */ + class LBPKeyPoint { + private: + std::valarray data; + public: + LBPKeyPoint(){}; + + /** + * LBPKeyPoint constructor + * + * @param data The data of the LBPKeyPoint + * @maixpy maix.image.LBPKeyPoint.__init__ + */ + LBPKeyPoint(std::valarray &data) + { + this->data = data; + } + + ~LBPKeyPoint(){}; + + std::valarray get_data() { + return data; + } + }; + + /** + * KeyPoint class + * @maixpy maix.image.KeyPoint + */ + class KeyPoint { + private: + uint16_t _x; + uint16_t _y; + uint16_t _score; + uint16_t _octave; + uint16_t _angle; + uint16_t _matched; + std::vector desc; + public: + /** + * KeyPoint constructor + * + * @param x The x of the KeyPoint + * @param y The y of the KeyPoint + * @param score The score of the KeyPoint + * @param octave The octave of the KeyPoint + * @param angle The angle of the KeyPoint + * @param matched The matched of the KeyPoint + * @param desc The desc of the KeyPoint + * @maixpy maix.image.KeyPoint.__init__ + */ + KeyPoint(uint16_t x, uint16_t y, uint16_t score, uint16_t octave, uint16_t angle, uint16_t matched, std::vector &desc) + { + _x = x; + _y = y; + _score = score; + _octave = octave; + _angle = angle; + _matched = matched; + this->desc = desc; + } + + ~KeyPoint(){}; + }; + + /** + * KPTMatch class + * @maixpy maix.image.KPTMatch + */ + class KPTMatch { + private: + int cx; + int cy; + int x; + int y; + int w; + int h; + int score; + int theta; + int match; + public: + KPTMatch() {} + /** + * KPTMatch constructor + * + * @param cx The cx of the KPTMatch + * @param cy The cy of the KPTMatch + * @param x The x of the KPTMatch + * @param y The y of the KPTMatch + * @param w The w of the KPTMatch + * @param h The h of the KPTMatch + * @param score The score of the KPTMatch + * @param theta The theta of the KPTMatch + * @param match The match of the KPTMatch + * @maixpy maix.image.KPTMatch.__init__ + */ + KPTMatch(int cx, int cy, int x, int y, int w, int h, int score, int theta, int match) + { + this->cx = cx; + this->cy = cy; + this->x = x; + this->y = y; + this->w = w; + this->h = h; + this->score = score; + this->theta = theta; + this->match = match; + } + + ~KPTMatch(){}; + }; + + /** + * ORBKeyPoint class + * @maixpy maix.image.ORBKeyPoint + */ + class ORBKeyPoint { + private: + std::vector data; + int threshold; + bool normalized; + public: + ORBKeyPoint(){}; + /** + * ORBKeyPoint constructor + * + * @param data The data of the ORBKeyPoint + * @param threshold The threshold of the ORBKeyPoint + * @param normalized The normalized of the ORBKeyPoint + * @maixpy maix.image.ORBKeyPoint.__init__ + */ + ORBKeyPoint(std::vector &data, int threshold, bool normalized) + { + this->data = data; + this->threshold = threshold; + this->normalized = normalized; + } + + ~ORBKeyPoint(){}; + + /** + * @brief get data of ORBKeyPoint + * @return return data of the ORBKeyPoint, type is std::vector + * @maixpy maix.image.ORBKeyPoint.get_data + */ + std::vector get_data() { + return data; + } + }; + + /** + * HaarCascade class + * @maixpy maix.image.HaarCascade + */ + class HaarCascade { + private: + int std; // Image standard deviation. + int step; // Image scanning factor. + float threshold; // Detection threshold. + float scale_factor; // Image scaling factor. + int n_stages; // Number of stages in the cascade. + int n_features; // Number of features in the cascade. + int n_rectangles; // Number of rectangles in the cascade. + std::vector window; // Detection window size. + // image::Image *img; // Grayscale image. + std::vector sum; // Integral image. + // sum:[0] width, [1] height, [2] y_offs, [3] x_ratio, [4] y_ratio, [5] uint32_t **data, [6] uint32_t **swap + std::vector ssq; // Squared integral image. + // ssq:[0] width, [1] height, [2] y_offs, [3] x_ratio, [4] y_ratio, [5] uint32_t **data, [6] uint32_t **swap + uint8_t *stages_array; // Number of features per stage. + int16_t *stages_thresh_array; // Stages thresholds. + int16_t *tree_thresh_array; // Features threshold (1 per feature). + int16_t *alpha1_array; // Alpha1 array (1 per feature). + int16_t *alpha2_array; // Alpha2 array (1 per feature). + int8_t *num_rectangles_array; // Number of rectangles per features (1 per feature). + int8_t *weights_array; // Rectangles weights (1 per rectangle). + int8_t *rectangles_array; // Rectangles array. + public: + /** + * HaarCascade constructor + * + * @param data The data of the HaarCascade + * @param threshold The threshold of the HaarCascade + * @param normalized The normalized of the HaarCascade + * @maixpy maix.image.HaarCascade.__init__ + */ + HaarCascade() + { + + } + + ~HaarCascade(){}; + }; +} diff --git a/vision/components/vision/include/maix_vision.hpp b/vision/components/vision/include/maix_vision.hpp new file mode 100644 index 0000000..4040ff5 --- /dev/null +++ b/vision/components/vision/include/maix_vision.hpp @@ -0,0 +1,4 @@ +#include "maix_image.hpp" +#include "maix_display.hpp" +#include "maix_camera.hpp" +#include "maix_video.hpp" diff --git a/vision/components/vision/include_private/maix_image_util.hpp b/vision/components/vision/include_private/maix_image_util.hpp new file mode 100644 index 0000000..c7c85dd --- /dev/null +++ b/vision/components/vision/include_private/maix_image_util.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "maix_image.hpp" +#include "omv.hpp" + +namespace maix::image +{ + /** + * Convert image to openmv image format + * @param image image + * @param imlib_image openmv image + * @return + */ + extern void convert_to_imlib_image(image::Image *image, image_t *imlib_image); + extern void _convert_to_lab_thresholds(std::vector> &in, list_t *out); +} + diff --git a/vision/components/vision/port/maixcam/maix_camera_mmf.hpp b/vision/components/vision/port/maixcam/maix_camera_mmf.hpp new file mode 100644 index 0000000..7de5bd8 --- /dev/null +++ b/vision/components/vision/port/maixcam/maix_camera_mmf.hpp @@ -0,0 +1,317 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + + +#pragma once + +#include +#include "maix_err.hpp" +#include "maix_basic.hpp" +#include "maix_log.hpp" +#include "maix_image.hpp" +#include "maix_time.hpp" +#include "maix_camera_base.hpp" +#include "kvm_mmf.hpp" +#include + +static void try_deinit_mmf() +{ + static uint8_t is_called = 0; + if (!is_called) { + mmf_try_deinit(true); + is_called = 1; + } +} + +static void signal_handle(int signal) +{ + const char *signal_msg = NULL; + switch (signal) { + case SIGILL: signal_msg = "SIGILL"; break; + case SIGTRAP: signal_msg = "SIGTRAP"; break; + case SIGABRT: signal_msg = "SIGABRT"; break; + case SIGBUS: signal_msg = "SIGBUS"; break; + case SIGFPE: signal_msg = "SIGFPE"; break; + case SIGKILL: signal_msg = "SIGKILL"; break; + case SIGSEGV: signal_msg = "SIGSEGV"; break; + default: signal_msg = "UNKNOWN"; break; + } + + maix::log::error("Trigger signal, code:%s(%d)!\r\n", signal_msg, signal); + try_deinit_mmf(); + exit(1); +} + +// FIXME: move this function to port/maix_vision_maixcam.cpp ? +static __attribute__((constructor)) void maix_vision_register_signal(void) +{ + signal(SIGILL, signal_handle); + signal(SIGTRAP, signal_handle); + signal(SIGABRT, signal_handle); + signal(SIGBUS, signal_handle); + signal(SIGFPE, signal_handle); + signal(SIGKILL, signal_handle); + signal(SIGSEGV, signal_handle); + + maix::util::register_exit_function(try_deinit_mmf); +} + +namespace maix::camera +{ + class CameraCviMmf final : public CameraBase + { + public: + CameraCviMmf(const std::string device, int width, int height, image::Format format, int buff_num) + { + this->device = device; + this->format = format; + this->width = width; + this->height = height; + this->buffer_num = buff_num; + this->ch = -1; + + if (0 != mmf_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf init failed"); + } + + if (0 != mmf_vi_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf vi init failed"); + } + } + + CameraCviMmf(const std::string device, int ch, int width, int height, image::Format format, int buff_num) + { + this->device = device; + this->format = format; + this->width = width; + this->height = height; + this->buffer_num = buff_num; + this->ch = ch; + + if (0 != mmf_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf init failed"); + } + + if (0 != mmf_vi_init()) { + err::check_raise(err::ERR_RUNTIME, "mmf vi init failed"); + } + } + + ~CameraCviMmf() + { + mmf_del_vi_channel(this->ch); + if (0 != mmf_vi_deinit()) { + err::check_raise(err::ERR_RUNTIME, "mmf vi init failed"); + } + mmf_try_deinit(true); + } + + err::Err open(int width, int height, image::Format format, int buff_num) + { + if (format == image::FMT_GRAYSCALE) { + format = image::FMT_YVU420SP; + } + int ch = mmf_get_vi_unused_channel(); + if (ch < 0) { + log::error("camera open: mmf get vi channel failed"); + return err::ERR_RUNTIME; + } + if (0 != mmf_add_vi_channel(ch, width, height, mmf_invert_format_to_mmf(format))) { + log::error("camera open: mmf add vi channel failed"); + return err::ERR_RUNTIME; + } + + this->ch = ch; + this->width = (width == -1) ? this->width : width; + this->height = (height == -1) ? this->height : height; + this->align_width = mmf_vi_aligned_width(this->ch); + this->align_need = (this->width % this->align_width == 0) ? false : true; // Width need align only + return err::ERR_NONE; + } // open + + bool is_support_format(image::Format format) + { + if(format == image::Format::FMT_RGB888 || format == image::Format::FMT_BGR888 + || format == image::Format::FMT_YVU420SP|| format == image::Format::FMT_GRAYSCALE) + return true; + return false; + } + + void close() + { + if (mmf_vi_chn_is_open(this->ch) == true) { + if (0 != mmf_del_vi_channel(this->ch)) { + log::error("mmf del vi channel failed"); + } + } + } // close + + // read + image::Image *read(void *buff = NULL, size_t buff_size = 0) + { + // printf("Func: maix_image.cpp read\r\n"); + image::Image *img = NULL; + + void *buffer = NULL; + int buffer_len = 0, width = 0, height = 0, format = 0; + + if (0 == mmf_vi_frame_pop(this->ch, &buffer, &buffer_len, &width, &height, &format)) { + if (buffer == NULL) { + mmf_vi_frame_free(this->ch); + printf("mmf_vi_frame_free error\r\n"); + return NULL; + } + if(buff) + { + if(buff_size < (size_t)buffer_len) + { + log::error("camera read: buff size not enough, need %d, but %d", buffer_len, buff_size); + mmf_vi_frame_free(this->ch); + return NULL; + } + img = new image::Image(width, height, this->format, (uint8_t*)buff, buff_size, false); + } + else + { + img = new image::Image(this->width, this->height, this->format); + } + void *image_data = img->data(); + switch (img->format()) { + case image::Format::FMT_GRAYSCALE: + if (this->align_need) { + for (int h = 0; h < this->height; h ++) { + memcpy((uint8_t *)image_data + h * this->width, (uint8_t *)buffer + h * width, this->width); + } + } else { + memcpy(image_data, buffer, this->width * this->height); + } + break; + case image::Format::FMT_BGR888: // fall through + case image::Format::FMT_RGB888: + if (this->align_need) { + for (int h = 0; h < this->height; h++) { + memcpy((uint8_t *)image_data + h * this->width * 3, (uint8_t *)buffer + h * width * 3, this->width * 3); + } + } else { + memcpy(image_data, buffer, this->width * this->height * 3); + } + break; + case image::Format::FMT_YVU420SP: + if (this->align_need) { + for (int h = 0; h < this->height * 3 / 2; h ++) { + memcpy((uint8_t *)image_data + h * this->width, (uint8_t *)buffer + h * width, this->width); + } + } else { + memcpy(image_data, buffer, this->width * this->height * 3 / 2); + } + break; + default: + printf("unknown format\n"); + delete img; + mmf_vi_frame_free(this->ch); + printf("switch (img->format()\r\n"); + return NULL; + } + mmf_vi_frame_free(this->ch); + // printf("mmf_vi_frame_free\r\n"); + return img; + } + + return img; + } // read + + camera::CameraCviMmf *add_channel(int width, int height, image::Format format, int buff_num) + { + int new_channel = mmf_get_vi_unused_channel(); + if (new_channel < 0) { + log::error("Support not more channel!\r\n"); + return NULL; + } + return new camera::CameraCviMmf(this->device, new_channel, width, height, format, buff_num); + } + + void clear_buff() + { + + } + + bool is_opened() { + return mmf_vi_chn_is_open(this->ch); + } + + int get_ch_nums() { + return 1; + } + + int get_channel() { + return this->ch; + } + + int hmirror(int en) + { + bool out; + if (en == -1) { + mmf_get_vi_hmirror(this->ch, &out); + } else { + bool need_open = false; + if (this->is_opened()) { + this->close(); + need_open = true; + } + + mmf_set_vi_hmirror(this->ch, en); + + if (need_open) { + err::check_raise(this->open(this->width, this->height, this->format, this->buffer_num), "Open failed"); + } + out = en; + } + + return out; + } + + int vflip(int en) + { + bool out; + if (en == -1) { + mmf_get_vi_vflip(this->ch, &out); + } else { + bool need_open = false; + if (this->is_opened()) { + this->close(); + need_open = true; + } + + mmf_set_vi_vflip(this->ch, en); + + if (need_open) { + err::check_raise(this->open(this->width, this->height, this->format, this->buffer_num), "Open failed"); + } + out = en; + } + return out; + } + + private: + std::string device; + image::Format format; + int fd; + int ch; + uint32_t raw_format; + std::vector buffers; + std::vector buffers_len; + int buffer_num; + int queue_id; // user directly used buffer id + int width; + int height; + void *buff; + bool buff_alloc; + int align_width; + bool align_need; + }; + +} // namespace maix::camera diff --git a/vision/components/vision/src/maix_camera.cpp b/vision/components/vision/src/maix_camera.cpp new file mode 100644 index 0000000..8dab65c --- /dev/null +++ b/vision/components/vision/src/maix_camera.cpp @@ -0,0 +1,425 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + + +#include "maix_camera.hpp" +#include +#ifdef PLATFORM_LINUX + #include "maix_camera_v4l2.hpp" +#endif +#ifdef PLATFORM_MAIXCAM + #include "maix_camera_mmf.hpp" +#endif + +namespace maix::camera +{ + static bool set_regs_flag = false; + + std::vector list_devices() + { + // find to /dev/video* + std::vector devices; + std::string path = "/dev"; + DIR *dir = opendir(path.c_str()); + if (dir == NULL) + { + return devices; + } + struct dirent *ptr; + while ((ptr = readdir(dir)) != NULL) + { + if (ptr->d_type == DT_CHR) + { + std::string name = ptr->d_name; + if (name.find("video") != std::string::npos) + { + devices.push_back( path + "/" + name ); + } + } + } + closedir(dir); + // sort devices with name + std::sort(devices.begin(), devices.end()); + // print devices + for (size_t i = 0; i < devices.size(); i++) + { + log::debug("find device: %s\n", devices[i].c_str()); + } + return devices; + } + + void set_regs_enable(bool enable) { + set_regs_flag = enable; + } + + err::Err Camera::show_colorbar(bool enable) + { + // only set variable now + // should control camera to show colorbar + _show_colorbar = enable; + return err::ERR_NONE; + } + + static void generate_colorbar(image::Image &img) + { + int width = img.width(); + int height = img.height(); + int step = width / 8; + int color_step = 255 / 7; + int color = 0; + uint8_t colors[8][3] = { + {255, 255, 255}, + {255, 0, 0}, + {255, 127, 0}, + {255, 255, 0}, + {0, 255, 0}, + {0, 0, 255}, + {143, 0, 255}, + {0, 0, 0}, + }; + for (int i = 0; i < 8; i++) + { + image::Color _color(colors[i][0], colors[i][1], colors[i][2], 0, image::FMT_RGB888); + img.draw_rect(i * step, 0, step, height, _color, -1); + color += color_step; + } + } +#ifdef PLATFORM_LINUX + static char * _get_device(const char *device) + { + if (device) + { + return (char *)device; + } + else + { + std::vector devices = list_devices(); + err::check_bool_raise(devices.size() > 0, "No camera device"); + return (char *)devices[0].c_str(); + } + } +#endif + + Camera::Camera(int width, int height, image::Format format, const char *device, int fps, int buff_num, bool open) + { + err::Err e; + err::check_bool_raise(_check_format(format), "Format not support"); + + if (format == image::Format::FMT_RGB888 && width * height * 3 > 640 * 640 * 3) { + log::warn("Note that we do not recommend using large resolution RGB888 images, which can take up a lot of memory!\r\n"); + } + + _width = (width == -1) ? 640 : width; + _height = (height == -1) ? 480 : height; + _format = format; + _fps = (fps == -1) ? 30 : fps; + _buff_num = buff_num; + + _show_colorbar = false; + _open_set_regs = set_regs_flag; + _impl = NULL; + +#ifdef PLATFORM_LINUX + _device = _get_device(device); + _impl = new CameraV4L2(_device, _width, _height, _format, _buff_num); +#endif + +#ifdef PLATFORM_MAIXCAM + _device = ""; + _impl = new CameraCviMmf(_device, _width, _height, _format, _buff_num); +#endif + + if (open) { + e = this->open(_width, _height, _format, _buff_num); + + // err::check_raise(e, "camera open failed"); + } + } + + Camera::Camera(const char *device, CameraBase *base, int width, int height, image::Format format, int fps, int buff_num, bool open) + { + err::Err e; + err::check_bool_raise(_check_format(format), "Format not support"); + + _width = (width == -1) ? 640 : width; + _height = (height == -1) ? 480 : height; + _format = format; + _fps = (fps == -1) ? 30 : fps; + _buff_num = buff_num; + + _show_colorbar = false; + _open_set_regs = set_regs_flag; + _impl = base; + + if (open) { + e = this->open(_width, _height, _format, _buff_num); + err::check_raise(e, "camera open failed"); + } + } + + Camera::~Camera() + { + if (this->is_opened()) { + this->close(); + } +#ifdef PLATFORM_LINUX + delete (CameraV4L2*)_impl; +#endif + +#ifdef PLATFORM_MAIXCAM + delete (CameraCviMmf*)_impl; +#endif + } + + void Camera::restart(int width, int height, image::Format format, const char *device, int fps, int buff_num, bool open) + { + if (this->is_opened()) { + this->close(); + } +#ifdef PLATFORM_LINUX + delete (CameraV4L2*)_impl; +#endif + +#ifdef PLATFORM_MAIXCAM + delete (CameraCviMmf*)_impl; +#endif + + err::Err e; + err::check_bool_raise(_check_format(format), "Format not support"); + + if (format == image::Format::FMT_RGB888 && width * height * 3 > 640 * 640 * 3) { + log::warn("Note that we do not recommend using large resolution RGB888 images, which can take up a lot of memory!\r\n"); + } + + _width = (width == -1) ? 640 : width; + _height = (height == -1) ? 480 : height; + _format = format; + _fps = (fps == -1) ? 30 : fps; + _buff_num = buff_num; + + _show_colorbar = false; + _open_set_regs = set_regs_flag; + _impl = NULL; + +#ifdef PLATFORM_LINUX + _device = _get_device(device); + _impl = new CameraV4L2(_device, _width, _height, _format, _buff_num); +#endif + +#ifdef PLATFORM_MAIXCAM + _device = ""; + _impl = new CameraCviMmf(_device, _width, _height, _format, _buff_num); +#endif + + if (open) { + e = this->open(_width, _height, _format, _buff_num); + + // err::check_raise(e, "camera open failed"); + } + } + + int Camera::get_ch_nums() + { + return 2; + } + + int Camera::get_channel() + { + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return _impl->get_channel(); + } + + bool Camera::_check_format(image::Format format) { + if (format == image::FMT_RGB888 || format == image::FMT_BGR888 + || format == image::FMT_RGBA8888 || format == image::FMT_BGRA8888 + || format == image::FMT_YVU420SP || format == image::FMT_GRAYSCALE) { + return true; + } else { + return false; + } + } + + err::Err Camera::open(int width, int height, image::Format format, int fps, int buff_num) + { + if (_impl == NULL) + return err::Err::ERR_RUNTIME; + + int width_tmp = (width == -1) ? _width : width; + int height_tmp = (height == -1) ? _height : height; + image::Format format_tmp = (format == image::FMT_INVALID) ? _format : format; + int fps_tmp = (fps == -1) ? 30 : fps; + int buff_num_tmp =( buff_num == -1) ? _buff_num : buff_num; + + err::check_bool_raise(_check_format(format_tmp), "Format not support"); + + if (this->is_opened()) { + if (width == width_tmp && height == height_tmp && format == format_tmp && fps == fps_tmp && buff_num == buff_num_tmp) { + return err::ERR_NONE; + } + this->close(); // Get new param, close and reopen + } + + _width = width_tmp; + _height = height_tmp; + _fps = fps_tmp; + _buff_num = buff_num_tmp; + _format = format_tmp; + _format_impl = _format; + if(!_impl->is_support_format(_format)) + { + if(_impl->is_support_format(image::FMT_RGB888)) + _format_impl = image::FMT_RGB888; + else if(_impl->is_support_format(image::FMT_BGR888)) + _format_impl = image::FMT_BGR888; + else if(_impl->is_support_format(image::FMT_YVU420SP)) + _format_impl = image::FMT_YVU420SP; + else if(_impl->is_support_format(image::FMT_YUV420SP)) + _format_impl = image::FMT_YUV420SP; + else if(_impl->is_support_format(image::FMT_RGBA8888)) + _format_impl = image::FMT_RGBA8888; + else if(_impl->is_support_format(image::FMT_BGRA8888)) + _format_impl = image::FMT_BGRA8888; + else if(_impl->is_support_format(image::FMT_GRAYSCALE)) + _format_impl = image::FMT_GRAYSCALE; + else + return err::ERR_ARGS; + } + + return _impl->open(_width, _height, _format_impl, _buff_num);; + } + + void Camera::close() + { + if (this->is_closed()) + return; + + _impl->close(); + } + + camera::Camera *Camera::add_channel(int width, int height, image::Format format, int fps, int buff_num, bool open) + { + err::check_bool_raise(_check_format(format), "Format not support"); + + int width_tmp = (width == -1) ? _width : width; + int height_tmp = (height == -1) ? _height : height; + image::Format format_tmp = (format == image::Format::FMT_INVALID) ? _format : format; + int fps_tmp = (fps == -1) ? _fps : fps; + int buff_num_tmp = buff_num == -1 ? _buff_num : buff_num; + + Camera *cam = NULL; + if (_impl) { + CameraBase *cam_base = _impl->add_channel(width_tmp, height_tmp, format_tmp, buff_num); + err::check_bool_raise(cam_base, "Unable to add a new channel. Please check the maximum number of supported channels."); + cam = new Camera(_device.c_str(), cam_base, width_tmp, height_tmp, format_tmp, fps_tmp, buff_num_tmp, open); + } + return cam; + } + + bool Camera::is_opened() + { + if (_impl == NULL) + return false; + + return _impl->is_opened(); + } + + image::Image *Camera::read(void *buff, size_t buff_size, bool block) + { + if (!this->is_opened()) { + // err::Err e = open(_width, _height, _format, _buff_num); + // err::check_raise(e, "open camera failed"); + return NULL; + } + + if (_show_colorbar) { + image::Image *img = new image::Image(_width, _height); + generate_colorbar(*img); + err::check_null_raise(img, "camera read failed"); + return img; + } else { + // it's better all done by impl to faster read, but if impl not support, we have to convert it + if(_format_impl == _format) + { + image::Image *img = _impl->read(buff, buff_size); + // err::check_null_raise(img, "camera read failed"); + return img; + } + else + { + image::Image *img = _impl->read(); + image::Image *img2 = img->to_format(_format, buff, buff_size); + delete img; + // err::check_null_raise(img2, "camera read failed"); + return img2; + } + } + } + + void Camera::clear_buff() + { + if (_impl == NULL) + return; + _impl->clear_buff(); + } + + void Camera::skip_frames(int num) + { + if (_impl == NULL) + return; + for(int i = 0; i < num; i++) + { + image::Image *img = _impl->read(); + delete img; + } + } + + err::Err Camera::set_resolution(int width, int height) + { + err::Err e; + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (this->is_opened()) { + this->close(); + } + + _width = width; + _height = height; + e = this->open(_width, _height, _format, _buff_num); + err::check_raise(e, "camera open failed"); + return err::ERR_NONE; + } + + int Camera::hmirror(int value) { + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return _impl->hmirror(value); + } + + int Camera::vflip(int value) { + if (_impl == NULL) + return err::ERR_NOT_INIT; + + if (!this->is_opened()) { + return err::ERR_NOT_OPEN; + } + + return _impl->vflip(value); + } +} + diff --git a/vision/components/vision/src/maix_image.cpp b/vision/components/vision/src/maix_image.cpp new file mode 100644 index 0000000..9405b4a --- /dev/null +++ b/vision/components/vision/src/maix_image.cpp @@ -0,0 +1,1654 @@ +/** + * @author neucrack@sipeed, lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#include "maix_image.hpp" +#include "opencv2/opencv.hpp" +#include "opencv2/freetype.hpp" +#include +#include +#include +#include +#include +#ifdef PLATFORM_MAIXCAM +#include "kvm_mmf.hpp" +#endif + +namespace maix::image +{ + + static void _get_cv_format_color(image::Format _format, const image::Color &color_in, int *ch_format, cv::Scalar &cv_color); + + static uint8_t _get_char_size(const uint8_t c) + { + if ((c & 0x80) == 0x00) { + // 0xxxxxxx: 1 byte character + return 1; + } else if ((c & 0xE0) == 0xC0) { + // 110xxxxx: 2 byte character + return 2; + } else if ((c & 0xF0) == 0xE0) { + // 1110xxxx: 3 byte character + return 3; + } else if ((c & 0xF8) == 0xF0) { + // 11110xxx: 4 byte character + return 4; + } else { + // Invalid UTF-8 start byte, default to 1 + return 1; + } + } + + + image::Image *load(const char *path, image::Format format) + { + cv::Mat mat; + if (format == image::FMT_BGR888 || format == image::FMT_RGB888) + { + mat = cv::imread(path); + if (mat.empty()) + return nullptr; + if (format == image::FMT_RGB888) + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + } + else + { + mat = cv::imread(path, cv::IMREAD_UNCHANGED); + if (mat.empty()) + return nullptr; + if (mat.channels() == 1) + { + switch (format) + { + case image::FMT_GRAYSCALE: + break; + case image::FMT_RGB888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2RGB); + break; + case image::FMT_RGBA8888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2RGBA); + break; + case image::FMT_BGR888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2BGR); + break; + case image::FMT_BGRA8888: + cv::cvtColor(mat, mat, cv::COLOR_GRAY2BGRA); + break; + default: + log::error("load image failed, can't convert grayscale to format %d\n", format); + return nullptr; + } + } + else if (mat.channels() == 3) + { + switch (format) + { + case image::FMT_GRAYSCALE: + cv::cvtColor(mat, mat, cv::COLOR_BGR2GRAY); + break; + case image::FMT_RGB888: + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); + break; + case image::FMT_RGBA8888: + cv::cvtColor(mat, mat, cv::COLOR_BGR2RGBA); + break; + case image::FMT_BGR888: + break; + case image::FMT_BGRA8888: + cv::cvtColor(mat, mat, cv::COLOR_BGR2BGRA); + break; + default: + log::error("load image failed, can't convert bgr to format %d\n", format); + return nullptr; + } + } + else if (mat.channels() == 4) + { + switch (format) + { + case image::FMT_GRAYSCALE: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2GRAY); + break; + case image::FMT_RGB888: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2RGB); + break; + case image::FMT_RGBA8888: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2RGBA); + break; + case image::FMT_BGR888: + cv::cvtColor(mat, mat, cv::COLOR_BGRA2BGR); + break; + case image::FMT_BGRA8888: + break; + default: + log::error("load image failed, can't convert bgra to format %d\n", format); + return nullptr; + } + } + else + { + log::error("load image failed, channels not support: %d\n", mat.channels()); + return nullptr; + } + } + image::Image *img = new image::Image(mat.cols, mat.rows, format); + memcpy(img->data(), mat.data, mat.cols * mat.rows * mat.channels()); + return img; + } + + image::Image *from_bytes(int width, int height, image::Format format, Bytes *data, bool copy) + { + // _create_image(width, height, format, data->data, data->size(), copy); + return new image::Image(width, height, format, data->data, data->size(), copy); + } + + static std::map> fonts_info; + static std::map fonts_size_info; + static std::string curr_font_name = "hershey_plain"; + static int curr_font_id = cv::FONT_HERSHEY_PLAIN; // -1 if user custom font, else opencv's HersheyFonts id + + static void add_default_fonts(std::map> &fonts_info) + { + if (!fonts_info.empty()) + return; + fonts_info["hershey_simplex"] = cv::Ptr(); + fonts_info["hershey_plain"] = cv::Ptr(); + fonts_info["hershey_duplex"] = cv::Ptr(); + fonts_info["hershey_complex"] = cv::Ptr(); + fonts_info["hershey_triplex"] = cv::Ptr(); + fonts_info["hershey_complex_small"] = cv::Ptr(); + fonts_info["hershey_script_simplex"] = cv::Ptr(); + } + + static int get_default_fonts_id(const std::string &name) + { + if (name == "hershey_simplex") + return cv::FONT_HERSHEY_SIMPLEX; + else if (name == "hershey_plain") + return cv::FONT_HERSHEY_PLAIN; + else if (name == "hershey_duplex") + return cv::FONT_HERSHEY_DUPLEX; + else if (name == "hershey_complex") + return cv::FONT_HERSHEY_COMPLEX; + else if (name == "hershey_triplex") + return cv::FONT_HERSHEY_TRIPLEX; + else if (name == "hershey_complex_small") + return cv::FONT_HERSHEY_COMPLEX_SMALL; + else if (name == "hershey_script_simplex") + return cv::FONT_HERSHEY_SCRIPT_SIMPLEX; + else + return -1; + } + + err::Err load_font(const std::string &name, const char *path, int size) + { + add_default_fonts(fonts_info); + // use opencv to load freetype font and store object in global variable + cv::Ptr ft2 = cv::freetype::createFreeType2(); + if (!ft2) + { + log::error("load font failed\n"); + return err::ERR_ARGS; + } + ft2->loadFontData(path, 0); + fonts_info[name] = ft2; + fonts_size_info[name] = size; + return err::ERR_NONE; + } + + err::Err set_default_font(const std::string &name) + { + // if name in fonts_info + if (fonts_info.find(name) == fonts_info.end()) + { + log::error("font %d not load\n", name.c_str()); + return err::ERR_ARGS; + } + curr_font_name = name; + curr_font_id = get_default_fonts_id(name); + return err::ERR_NONE; + } + + std::vector *fonts() + { + std::vector *fonts = new std::vector; + add_default_fonts(fonts_info); + for (auto &font : fonts_info) + { + fonts->push_back(font.first); + } + return fonts; + } + + static void _get_text_size(cv::Size &size, const std::string &text, const std::string &font_name, int font_id, float scale, int thickness) + { + int baseLine = 0; + if (font_id == -1) + { + int font_height = fonts_size_info[font_name]; + cv::Ptr ft2 = fonts_info[font_name]; + if (ft2 == cv::Ptr()) + { + log::error("font %d not load\n", font_name.c_str()); + throw std::runtime_error("font not load"); + } + size = ft2->getTextSize(text, scale * font_height, thickness, &baseLine); + if (thickness > 0) + baseLine += thickness; + size.height = size.height + baseLine; + } + else + { + size = cv::getTextSize(text, font_id, scale, thickness > 0 ? thickness : -thickness, &baseLine); + baseLine += baseLine > 0 ? 0 : -thickness; + size.height += baseLine; + } + } + + static void _put_text(cv::Mat &img, const std::string &text, const cv::Point &point, + const cv::Scalar &color, float scale, int thickness, const std::string &font_name, int font_id) + { + if (font_id == -1) + { + cv::Ptr ft2 = fonts_info[font_name]; + if (ft2 == cv::Ptr()) + { + log::error("font %d not load\n", font_name.c_str()); + throw std::runtime_error("font not load"); + } + // point from left top to left center + const std::string text_tmp(text, 0, 1); + cv::Point point_tmp(point.x, point.y + ft2->getTextSize(text, scale * fonts_size_info[font_name], thickness, nullptr).height); + ft2->putText(img, text, point_tmp, scale * fonts_size_info[font_name], color, thickness, cv::LINE_AA, true); + } + else + { + // left top corner to point left bottom corner by first char height + const std::string text_tmp(text, 0, 1); + int baseLine = 0; + cv::Point point_tmp(point.x, point.y + cv::getTextSize(text_tmp, font_id, scale, thickness > 0 ? thickness : -thickness, &baseLine).height); + cv::putText(img, text, point_tmp, font_id, scale, color, thickness > 0 ? thickness : -thickness, cv::LINE_AA, false); + } + } + + void Image::_create_image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy) + { + _format = format; + _width = width; + _height = height; + if (width <= 0 || height <= 0) + throw err::Exception(err::ERR_ARGS, "image width and height should > 0"); + + if (_format > Format::FMT_COMPRESSED_MIN) { + if (!data || data_size < 0) + throw err::Exception(err::ERR_ARGS,"image data and data_size are incorrect"); + _data_size = data_size; + } else { + // not use data_size, uncompressed image only use fiexed size. + int size_calc = width * height * image::fmt_size[format]; + if(data_size > 0 && data_size != size_calc) + { + log::error("data_size not match image content size, data_size: %d, image content size: %d\n", data_size, size_calc); + throw err::Exception(err::ERR_ARGS, "data_size not match image content size"); + } + _data_size = size_calc; + } + + if (!data) + { + _actual_data = malloc(_data_size + 0x1000); + if (!_actual_data) + throw err::Exception(err::ERR_NO_MEM, "malloc image data failed"); + _data = (void *)(((uint64_t)_actual_data + 0x1000) & ~0xFFF); + // log::debug("malloc image data\n"); + _is_malloc = true; + } + else + { + if(!copy) + { + _data = data; + _actual_data = _data; + _is_malloc = false; + } + else + { + _actual_data = malloc(_data_size + 0x1000); + if (!_actual_data) + throw std::bad_alloc(); + _data = (void *)(((uint64_t)_actual_data + 0x1000) & ~0xFFF); + memcpy(_data, data, _data_size); + // log::debug("malloc image data\n"); + _is_malloc = true; + } + } + } + + Image::Image(int width, int height, image::Format format, uint8_t *data, int data_size, bool copy) + { + _create_image(width, height, format, data, data_size, copy); + } + + Image::Image(int width, int height, image::Format format) + // Image::Image(int width, int height, image::Format format, Bytes *data, bool copy) + { + // if(!data) + // { + _create_image(width, height, format, nullptr, 0, false); + // return; + // } + // _create_image(width, height, format, data->data, data->size(), copy); + } + + Image::~Image() + { + if (_is_malloc) + { + // log::debug("free image data\n"); + free(_actual_data); + _actual_data = NULL; + _data = NULL; + } + } + + void Image::operator=(const image::Image &img) + { + _format = img._format; + _width = img._width; + _height = img._height; + _data = malloc(_width * _height * image::fmt_size[_format]); + if (!_data) + throw std::bad_alloc(); + memcpy(_data, img._data, _width * _height * image::fmt_size[_format]); + } + + std::string Image::__str__() + { + char buf[128]; + sprintf(buf, "Image(%d, %d, %s), data size: %d", _width, _height, fmt_names[_format].c_str(), (int)(_width * _height * fmt_size[_format])); + return std::string(buf); + } + + Bytes *Image::to_bytes(bool copy) + { + if(copy) + return new Bytes((uint8_t *)_data, _data_size, true, true); + return new Bytes((uint8_t *)_data, _data_size, false, false); + } + + tensor::Tensor *Image::to_tensor(bool chw, bool copy) + { + void *data = _data; + std::vector shape; + tensor::Tensor *t = nullptr; + if (_format == image::FMT_GRAYSCALE) + shape = {_height, _width}; + else if (_format < image::FMT_YUV422SP) + shape = chw ? (std::vector){(int)image::fmt_size[_format], _height, _width} : (std::vector){_height, _width, (int)image::fmt_size[_format]}; + else if (_format == image::FMT_YUV422SP || _format == image::FMT_YUV422P) + shape = chw ? (std::vector){2, _height, _width} : (std::vector){_height, _width, 2}; + else if (_format == image::FMT_YUV420SP || _format == image::FMT_YUV420P || _format == image::FMT_YVU420SP || _format == image::FMT_YVU420P) + shape = {(int)(_height * 1.5), _width}; + else + throw std::runtime_error("not support format"); + if (copy) + { + t = new tensor::Tensor(shape, tensor::UINT8, nullptr); + memcpy(t->data(), data, t->size_int() * tensor::dtype_size[t->dtype()]); + } + else + { + t = new tensor::Tensor(shape, tensor::UINT8, data); + } + return t; + } + + image::Image *Image::to_format(const image::Format &format, void *buff, size_t buff_size) + { + if (_format == format) + { + log::error("convert format failed, already the format %d\n", format); + throw err::Exception(err::ERR_ARGS, "convert format failed, already the format"); + } + + cv::Mat src(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + cv::ColorConversionCodes cvt_code; + if(format == image::FMT_JPEG) // compress + { + switch (_format) { +#ifdef PLATFORM_MAIXCAM + case image::FMT_GRAYSCALE: + { + image::Image *img = nullptr; + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)_data, _width, _height, mmf_invert_format_to_mmf(image::FMT_GRAYSCALE), 95)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + return img; + break; + } + case image::FMT_YVU420SP: + { + image::Image *img = nullptr; + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)_data, _width, _height, mmf_invert_format_to_mmf(image::FMT_YVU420SP), 95)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + return img; + break; + } +#endif + default: + { +#ifdef PLATFORM_MAIXCAM + image::Image *p_img = nullptr; + image::Image *img = nullptr; + bool src_alloc = false; + if(_format != Format::FMT_RGB888) + { + p_img = to_format(image::FMT_RGB888); + src_alloc = true; + } else { + p_img = this; + } + + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)p_img->data(), _width, _height, mmf_invert_format_to_mmf(image::FMT_RGB888), 80)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + + if (src_alloc) + { + delete p_img; + } + return img; +#else + image::Image *p_img = nullptr; + cv::Mat *p_src = &src; + bool src_alloc = false; + if(_format != Format::FMT_BGR888 && _format != Format::FMT_BGRA8888) + { + p_img = to_format(image::FMT_BGR888); + p_src = new cv::Mat(p_img->_height, p_img->_width, CV_8UC((int)image::fmt_size[image::FMT_BGR888]), p_img->_data); + src_alloc = true; + } + cv::InputArray input(*p_src); + std::vector jpeg_buff; + std::vector params; + params.push_back(cv::IMWRITE_JPEG_QUALITY); + params.push_back(95); + cv::imencode(".jpg", input, jpeg_buff, params); + image::Image *img; + if(buff) + { + if(buff_size < jpeg_buff.size()) + { + log::error("convert format failed, buffer size not enough\n"); + throw err::Exception(err::ERR_ARGS, "convert format failed, buffer size not enough"); + } + memcpy(buff, jpeg_buff.data(), jpeg_buff.size()); + img = new image::Image(src.cols, src.rows, format, (uint8_t*)buff, jpeg_buff.size(), false); + } + else + img = new image::Image(src.cols, src.rows, format, (uint8_t*)jpeg_buff.data(), jpeg_buff.size(), true); + if(src_alloc) + { + delete p_img; + delete p_src; + } + return img; +#endif + break; + } + } + + } + else if(format == image::FMT_PNG) + { + image::Image *p_img = nullptr; + cv::Mat *p_src = &src; + bool src_alloc = false; + if(_format != Format::FMT_BGR888 && _format != Format::FMT_BGRA8888) + { + p_img = to_format(image::FMT_BGRA8888); + p_src = new cv::Mat(p_img->_height, p_img->_width, CV_8UC((int)image::fmt_size[image::FMT_BGRA8888]), p_img->_data); + src_alloc = true; + } + cv::InputArray input(*p_src); + std::vector png_buff; + std::vector params; + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(3); + cv::imencode(".png", input, png_buff, params); + image::Image *img; + if(buff) + { + if(buff_size < png_buff.size()) + { + log::error("convert format failed, buffer size not enough\n"); + throw err::Exception(err::ERR_ARGS, "convert format failed, buffer size not enough"); + } + memcpy(buff, png_buff.data(), png_buff.size()); + img = new image::Image(src.cols, src.rows, format, (uint8_t*)buff, png_buff.size(), false); + } + else + img = new image::Image(src.cols, src.rows, format, png_buff.data(), png_buff.size(), true); + if(src_alloc) + { + delete p_img; + delete p_src; + } + return img; + } + switch (_format) + { + case image::FMT_RGB888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_RGB2GRAY; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_RGB2BGR; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_RGB2RGBA; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_RGB2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_RGB2YUV_YV12; + break; + default: + log::error("convert format failed, can't convert rgb to format %d\n", format); + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_BGR888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_BGR2GRAY; + break; + case image::FMT_RGB888: + cvt_code = cv::COLOR_BGR2RGB; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_BGR2RGBA; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_BGR2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_RGBA8888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_RGBA2GRAY; + break; + case image::FMT_RGB888: + cvt_code = cv::COLOR_RGBA2RGB; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_RGBA2BGR; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_RGBA2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_BGRA8888: + switch (format) + { + case image::FMT_GRAYSCALE: + cvt_code = cv::COLOR_BGRA2GRAY; + break; + case image::FMT_RGB888: + cvt_code = cv::COLOR_BGRA2RGB; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_BGRA2BGR; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_BGRA2RGBA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + case image::FMT_GRAYSCALE: + switch (format) + { + case image::FMT_RGB888: + cvt_code = cv::COLOR_GRAY2RGB; + break; + case image::FMT_BGR888: + cvt_code = cv::COLOR_GRAY2BGR; + break; + case image::FMT_RGBA8888: + cvt_code = cv::COLOR_GRAY2RGBA; + break; + case image::FMT_BGRA8888: + cvt_code = cv::COLOR_GRAY2BGRA; + break; + case image::FMT_YVU420SP: + cvt_code = cv::COLOR_BGR2YUV_YV12; + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + break; + default: + throw err::Exception(err::ERR_NOT_IMPL, "not support format"); + } + image::Image *img; + if(buff) + { + if(buff_size < image::fmt_size[format]) + { + log::error("convert format failed, buffer size not enough, need %d, but %d\n", image::fmt_size[format], buff_size); + throw err::Exception(err::ERR_ARGS, "convert format failed, buffer size not enough"); + } + img = new image::Image(src.cols, src.rows, format, (uint8_t*)buff, src.cols * src.rows * image::fmt_size[format], false); + } + else + img = new image::Image(src.cols, src.rows, format); + + if (_format == image::FMT_GRAYSCALE && format == image::FMT_YVU420SP) { + cv::Mat dst(src.rows * 3 / 2, src.cols, CV_8UC((int)image::fmt_size[format]), img->data()); + int nv_len = src.cols * src.rows / 2; + int offset = src.cols * src.rows; + memcpy(dst.data, src.data, src.cols * src.rows); + memset(dst.data + offset, 128, nv_len); + } else if (format == image::FMT_YVU420SP) { + cv::Mat dst(src.rows * 3 / 2, src.cols, CV_8UC((int)image::fmt_size[format]), img->data()); + cv::cvtColor(src, dst, cvt_code); + int nv_len = src.cols * src.rows / 2; + int half_len = nv_len / 2; + int offset = src.cols * src.rows; + uint8_t *nv21 = (uint8_t *)img->data(); + uint8_t *uv_temp = (uint8_t *)malloc(nv_len); + err::check_null_raise(uv_temp, "malloc uv_temp failed"); + memcpy(uv_temp, nv21 + offset, nv_len); + for (int i = 0; i < half_len; i ++) { + nv21[offset + i * 2] = uv_temp[i]; // V + nv21[offset + i * 2 + 1] = uv_temp[half_len + i]; // U + } + if (uv_temp) + free(uv_temp); + } else if (_format == image::FMT_RGB888 && format == image::FMT_GRAYSCALE) { + int height = _width; + int width = _height; + uint8_t *src = (uint8_t *)_data; + uint8_t *dst = (uint8_t *)img->data(); + for (int i = 0; i < height; i ++) { + for (int j = 0; j < width; j ++) { + dst[i * width + j] = (src[(i * width + j) * 3 + 0] * 38 + src[(i * width + j) * 3 + 1] * 75 + src[(i * width + j) * 3 + 2] * 15) >> 7; + } + } + } else { + cv::Mat dst(src.rows, src.cols, CV_8UC((int)image::fmt_size[format]), img->data()); + cv::cvtColor(src, dst, cvt_code); + } + + return img; + } + + + image::Image *Image::to_format(const image::Format &format) + { + return to_format(format, nullptr, 0); + } + + image::Image *Image::to_jpeg(int quality) + { + image::Format format = image::Format::FMT_JPEG; + cv::Mat src(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + switch (_format) { + case image::FMT_YVU420SP: + { +#ifdef PLATFORM_MAIXCAM + image::Image *img = nullptr; + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)_data, _width, _height, mmf_invert_format_to_mmf(image::FMT_YVU420SP), quality)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + return img; +#endif + break; + } + default: + { +#ifdef PLATFORM_MAIXCAM + image::Image *p_img = nullptr; + image::Image *img = nullptr; + bool src_alloc = false; + if(_format != Format::FMT_RGB888) + { + p_img = to_format(image::FMT_RGB888); + src_alloc = true; + } else { + p_img = this; + } + + if (!mmf_enc_jpg_push_with_quality(0, (uint8_t *)p_img->data(), _width, _height, mmf_invert_format_to_mmf(image::FMT_RGB888), quality)) { + uint8_t *data; + int data_size; + if (!mmf_enc_jpg_pop(0, &data, &data_size)) { + img = new image::Image(_width, _height, format, data, data_size, true); + mmf_enc_jpg_free(0); + } + } + + if (src_alloc) + { + delete p_img; + } + return img; +#else + image::Image *p_img = nullptr; + cv::Mat *p_src = &src; + bool src_alloc = false; + if(_format != Format::FMT_BGR888 && _format != Format::FMT_BGRA8888) + { + p_img = to_format(image::FMT_BGR888); + p_src = new cv::Mat(p_img->_height, p_img->_width, CV_8UC((int)image::fmt_size[image::FMT_BGR888]), p_img->_data); + src_alloc = true; + } + cv::InputArray input(*p_src); + std::vector jpeg_buff; + std::vector params; + params.push_back(cv::IMWRITE_JPEG_QUALITY); + params.push_back(60); // 压缩率 + cv::imencode(".jpg", input, jpeg_buff, params); + image::Image *img; + img = new image::Image(src.cols, src.rows, format, (uint8_t*)jpeg_buff.data(), jpeg_buff.size(), true); + if(src_alloc) + { + delete p_img; + delete p_src; + } + return img; +#endif + break; + } + } + + return nullptr; + } + + image::Image *Image::draw_image(int x, int y, image::Image &img) + { + image::Format fmt = img.format(); + if (!(fmt == image::FMT_GRAYSCALE || fmt == image::FMT_RGB888 || fmt == image::FMT_BGR888 || + fmt == image::FMT_RGBA8888 || fmt == image::FMT_BGRA8888)) + throw std::runtime_error("image format not support"); + // check format + if (image::fmt_size[img.format()] > image::fmt_size[_format]) + throw std::runtime_error("image format not match"); + cv::Mat src(img.height(), img.width(), CV_8UC((int)image::fmt_size[img.format()]), img.data()); + cv::Mat dst(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + cv::Rect rect(x, y, img.width(), img.height()); + if (_format == fmt && image::fmt_size[fmt] < 4) + { + src.copyTo(dst(rect)); + } + else + { + // convert format + bool img_alloc = false; + image::Image *img_new = nullptr; + if (_format != fmt) + { + img_new = img.to_format(_format); + if (!img_new) + { + return img_new; + } + img_alloc = true; + } + else + img_new = &img; + cv::Mat src_new(img_new->height(), img_new->width(), CV_8UC((int)image::fmt_size[_format]), img_new->data()); + if (image::fmt_size[_format] < 4) + { + src_new.copyTo(dst(rect)); + } + else // merge two BGRA images + { + for (int y = 0; y < src_new.rows; y++) + { + if(y + rect.y >= dst.rows) + break; + for (int x = 0; x < src_new.cols; x++) + { + if(x + rect.x >= dst.cols) + break; + cv::Vec4b &pixel = src_new.at(y, x); + // ignore transparent pixel + if (pixel[3] == 0) + continue; + cv::Vec4b &pixel_dst = dst.at(y + rect.y, x + rect.x); + // alpha is 255, just copy + if (pixel[3] == 255) + { + pixel_dst[0] = pixel[0]; + pixel_dst[1] = pixel[1]; + pixel_dst[2] = pixel[2]; + pixel_dst[3] = pixel[3]; + } + // alpha > 0 and < 255, blend + else + { + // TODO: optimize blend algorithm + pixel_dst[0] = (uint32_t)((pixel[0] * pixel[3] + pixel_dst[0] * (255 - pixel[3]))) >> 8; + pixel_dst[1] = (uint32_t)((pixel[1] * pixel[3] + pixel_dst[1] * (255 - pixel[3]))) >> 8; + pixel_dst[2] = (uint32_t)((pixel[2] * pixel[3] + pixel_dst[2] * (255 - pixel[3]))) >> 8; + pixel_dst[3] = 255 - (255 - pixel[3]) * (255 - pixel_dst[3]); + } + } + } + } + if (img_alloc) + delete img_new; + } + return this; + } + + image::Image *Image::draw_rect(int x, int y, int w, int h, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::Rect rect(x, y, w, h); + if( color.alpha != 1 && (_format == Format::FMT_RGBA8888 || _format == Format::FMT_BGRA8888)) + { + cv::Mat roi = img(rect); + cv::Mat color_mat(roi.size(), roi.type(), cv_color); + cv::addWeighted(color_mat, color.alpha, roi, 1 - color.alpha, 0, roi); + } + else + cv::rectangle(img, rect, cv_color, thickness); + return this; + } + + image::Image *Image::draw_line(int x1, int y1, int x2, int y2, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::line(img, cv::Point(x1, y1), cv::Point(x2, y2), cv_color, thickness); + return this; + } + + image::Image *Image::draw_circle(int x, int y, int radius, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::circle(img, cv::Point(x, y), radius, cv_color, thickness); + return this; + } + + image::Image *Image::draw_ellipse(int x, int y, int a, int b, float angle, float start_angle, float end_angle, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::ellipse(img, cv::Point(x, y), cv::Size(a, b), angle, start_angle, end_angle, cv_color, thickness); + return this; + } + + image::Image *image::Image::draw_string(int x, int y, const std::string &text, const image::Color &color, float scale, int thickness, + bool wrap, int wrap_space, const std::string &font) + { + int ch_format = 0; + cv::Scalar cv_color; + add_default_fonts(fonts_info); + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::Point point(x, y); + const std::string *final_font = &curr_font_name; + int final_font_id = curr_font_id; + // if cont is not empty, use it as font path + if (!font.empty()) + { + // if name in fonts_info + if (fonts_info.find(font) == fonts_info.end()) + { + log::error("font %d not load\n", font.c_str()); + throw std::runtime_error("font not load"); + } + final_font = &font; + final_font_id = get_default_fonts_id(font); + } + // auto wrap if text width > image width + if (!wrap) + { + _put_text(img, text, point, cv_color, scale, thickness, *final_font, final_font_id); + } + else + { + cv::Size size; + _get_text_size(size, text, *final_font, final_font_id, scale, thickness); + int text_width = size.width; + int text_height = size.height; + int text_max_width = _width - x; + bool wrap_now = false; + if(strstr(text.c_str(), "\n") != NULL || strstr(text.c_str(), "\r") != NULL) + wrap_now = true; + + // auto wrap + if (wrap_now || text_width > text_max_width) + { + // loop to calculate text width, if reach to image width, draw text on image, and move to next line go on + size_t idx = 0; + std::string text_tmp; + cv::Size size_tmp; + uint8_t char_size = 1; + bool last_is_r = false; + wrap_now = false; + while (idx < text.length()) + { + char c = text[idx]; + wrap_now = false; + if(c == '\r') + { + last_is_r = true; + wrap_now = true; + } + else if(c == '\n') + { + if(last_is_r) + { + last_is_r = false; + ++idx; + continue; + } + wrap_now = true; + last_is_r = false; + } + else + { + last_is_r = false; + } + if(wrap_now) + { + _put_text(img, text_tmp, point, cv_color, scale, thickness, *final_font, final_font_id); + point.x = x; + point.y += text_height + wrap_space; + text_tmp.clear(); + if (point.y + text_height >= _height) + break; + ++idx; + continue; + } + char_size = _get_char_size(c); + text_tmp += text.substr(idx, char_size); + cv::Size size_tmp; + _get_text_size(size_tmp, text_tmp, *final_font, final_font_id, scale, thickness); + if (size_tmp.width >= text_max_width) + { + if (size_tmp.width > text_max_width) + { + text_tmp.erase(text_tmp.length() - char_size, char_size); + } + _put_text(img, text_tmp, point, cv_color, scale, thickness, *final_font, final_font_id); + point.x = x; + point.y += text_height + wrap_space; + text_tmp.clear(); + if (point.y + text_height >= _height) + break; + if (size_tmp.width > text_max_width) + continue; + } + idx += char_size; + } + // draw last line + if (!text_tmp.empty()) + { + _put_text(img, text_tmp, point, cv_color, scale, thickness, *final_font, final_font_id); + } + } + else + { + _put_text(img, text, point, cv_color, scale, thickness, *final_font, final_font_id); + } + } + return this; + } + + image::Image *Image::draw_cross(int x, int y, const image::Color &color, int size, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::line(img, cv::Point(x - size, y), cv::Point(x + size, y), cv_color, thickness); + cv::line(img, cv::Point(x, y - size), cv::Point(x, y + size), cv_color, thickness); + return this; + } + + image::Image *Image::draw_arrow(int x0, int y0, int x1, int y1, const image::Color &color, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + cv::Point start(x0, y0); + cv::Point end(x1, y1); + cv::arrowedLine(img, start, end, cv_color, thickness); + return this; + } + + image::Image *Image::draw_edges(std::vector> corners, const image::Color &color, int size, int thickness, bool fill) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + + if (corners.size() < 4) + { + throw std::runtime_error("corners size must >= 4"); + return nullptr; + } + + if (fill) { + thickness = -1; + } + + cv::Point topLeft(corners[0][0], corners[0][1]); + cv::Point bottomRight(corners[2][0], corners[2][1]); + cv::rectangle(img, topLeft, bottomRight, cv_color, thickness); + return this; + } + + image::Image *Image::draw_keypoints(std::vector keypoints, const image::Color &color, int size, int thickness) + { + int ch_format = 0; + cv::Scalar cv_color; + _get_cv_format_color(_format, color, &ch_format, cv_color); + cv::Mat img(_height, _width, ch_format, _data); + + if (keypoints.size() < 2 || keypoints.size() % 2 != 0) { + throw std::runtime_error("keypoints size must >= 2 and multiple of 2"); + return nullptr; + } + for(size_t i=0; idata()); + _cv_rgb_nv21(resize_rgb, dst, width, height); + } else { + dst = cv::Mat(height, width, pixel_num, ret->data()); + cv::resize(img, dst, cv::Size(width, height), 0, 0, inter_method); + } + } + else if (object_fit == image::Fit::FIT_CONTAIN) + { + cv::Mat tmp; + float scale = std::min((float)width / _width, (float)height / _height); + cv::resize(img, tmp, cv::Size(), scale, scale, inter_method); + dst = cv::Mat(cv_dst_h, width, pixel_num, ret->data()); + cv::Rect rect((width - tmp.cols) / 2, (height - tmp.rows) / 2, tmp.cols, tmp.rows); + tmp.copyTo(dst(rect)); + // fill black color + if (tmp.cols < width) + { + cv::Mat black(height, (width - tmp.cols) / 2, pixel_num, cv::Scalar(0, 0, 0)); + // left black area + cv::Rect rect(0, 0, (width - tmp.cols) / 2, height); + black.copyTo(dst(rect)); + // right black area + rect.x = (width - tmp.cols) / 2 + tmp.cols; + black.copyTo(dst(rect)); + if (rect.width != width - tmp.cols - rect.width) + { + // one line + rect.x = dst.cols - 1; + rect.width = 1; + cv::Mat black2(height, 1, pixel_num, cv::Scalar(0, 0, 0)); + black2.copyTo(dst(rect)); + } + } + if (tmp.rows < height) + { + cv::Mat black((height - tmp.rows) / 2, width, pixel_num, cv::Scalar(0, 0, 0)); + // top black area + cv::Rect rect(0, 0, width, (height - tmp.rows) / 2); + black.copyTo(dst(rect)); + // bottom black area + rect.y = (height - tmp.rows) / 2 + tmp.rows; + black.copyTo(dst(rect)); + if (rect.height != height - tmp.rows - rect.height) + { + // one line + rect.y = dst.rows - 1; + rect.height = 1; + cv::Mat black2(1, width, pixel_num, cv::Scalar(0, 0, 0)); + black2.copyTo(dst(rect)); + } + } + } + else if (object_fit == image::Fit::FIT_COVER) + { + cv::Mat tmp; + float scale = std::max((float)width / _width, (float)height / _height); + cv::resize(img, tmp, cv::Size(), scale, scale, inter_method); + dst = cv::Mat(cv_dst_h, width, pixel_num, ret->data()); + cv::Rect rect((tmp.cols - width) / 2, (tmp.rows - height) / 2, width, height); + tmp(rect).copyTo(dst); + } + else + { + throw std::runtime_error("not support object fit"); + } + return ret; + } + + image::Image *Image::affine(std::vector src_points, std::vector dst_points, int width, int height, image::ResizeMethod method) + { + if (width < 0 && height < 0) + { + throw std::runtime_error("width and height can't both be -1"); + } + int pixel_num = _get_cv_pixel_num(_format); + /// calculate size if width or height is -1 + if (width == -1) + { + width = height * _width / _height; + } + else if (height == -1) + { + height = width * _height / _width; + } + image::Image *ret = new image::Image(width, height, _format); + ; + cv::Mat img(_height, _width, pixel_num, _data); + cv::Mat dst(height, width, pixel_num, ret->data()); + cv::Point2f srcTri[3]; + cv::Point2f dstTri[3]; + for (int i = 0; i < 3; i++) + { + srcTri[i] = cv::Point2f(src_points[i * 2], src_points[i * 2 + 1]); + dstTri[i] = cv::Point2f(dst_points[i * 2], dst_points[i * 2 + 1]); + } + cv::Mat warp_mat = cv::getAffineTransform(srcTri, dstTri); + cv::warpAffine(img, dst, warp_mat, dst.size(), (cv::InterpolationFlags)method); + return ret; + } + + image::Image *Image::copy() + { + image::Image *ret = new image::Image(_width, _height, _format); + ; + memcpy(ret->data(), _data, _width * _height * image::fmt_size[_format]); + return ret; + } + + image::Image *Image::crop(int x, int y, int w, int h) + { + image::Image *ret = new image::Image(w, h, _format); + ; + int pixel_num = _get_cv_pixel_num(_format); + cv::Mat img(_height, _width, pixel_num, _data); + cv::Mat dst(h, w, pixel_num, ret->data()); + cv::Rect rect(x, y, w, h); + img(rect).copyTo(dst); + return ret; + } + + image::Image *Image::rotate(float angle, int width, int height, image::ResizeMethod method) + { + int pixel_num = _get_cv_pixel_num(_format); + if (width < 0 && height < 0) + { + if (angle == 90 || angle == 270) + { + width = _height; + height = _width; + } + } + /// calculate size if width or height is -1 + if (width < 0) + { + // new_H = int(w * fabs(sin(radians(angle))) + h * fabs(cos(radians(angle)))) + width = _width * fabs(sin(angle * M_PI / 180)) + _height * fabs(cos(angle * M_PI / 180)); + } + if (height < 0) + { + // new_W = int(h * fabs(sin(radians(angle))) + w * fabs(cos(radians(angle)))) + height = _height * fabs(sin(angle * M_PI / 180)) + _width * fabs(cos(angle * M_PI / 180)); + } + image::Image *ret = new image::Image(width, height, _format); + ; + cv::Mat img(_height, _width, pixel_num, _data); + cv::Mat dst(height, width, pixel_num, ret->data()); + cv::Point2f center((float)_width / 2.0, (float)_height / 2.0); + cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, 1.0); + // M[0, 2] += (width - w) / 2 + // M[1, 2] += (height - h) / 2 + rot_mat.at(0, 2) += (width - _width) / 2.0; + rot_mat.at(1, 2) += (height - _height) / 2.0; + cv::warpAffine(img, dst, rot_mat, dst.size(), (cv::InterpolationFlags)method); + return ret; + } + + err::Err Image::save(const char *path, int quality) + { + if (_height <= 0 || _width <= 0) + { + log::error("save image failed, image size is invalid\n"); + return err::ERR_ARGS; + } + cv::Mat img(_height, _width, CV_8UC((int)image::fmt_size[_format]), _data); + std::vector params; + if (quality >= 0 && quality <= 100) + { + params.push_back(cv::IMWRITE_JPEG_QUALITY); + params.push_back(quality); + } + else if (quality >= -1 && quality <= 9) + { + params.push_back(cv::IMWRITE_PNG_COMPRESSION); + params.push_back(quality); + } + else + { + return err::ERR_ARGS; + } + log::debug("save image to %s\n", path); + // not not dir exists, create it + std::string dir = path; + size_t pos = dir.find_last_of('/'); + if (pos != std::string::npos) + { + dir = dir.substr(0, pos); + if (!fs::exists(dir)) + { + if (fs::mkdir(dir) < 0) + { + log::error("create dir %s failed\n", dir.c_str()); + return err::ERR_IO; + } + } + } + // save, img may RGB format + if (_format == image::FMT_RGB888) + { + cv::cvtColor(img, img, cv::COLOR_RGB2BGR); + } + else if (_format == image::FMT_RGBA8888) + { + cv::cvtColor(img, img, cv::COLOR_RGBA2BGRA); + } + cv::imwrite(path, img, params); + // convert back + if (_format == image::FMT_RGB888) + { + cv::cvtColor(img, img, cv::COLOR_RGB2BGR); + } + else if (_format == image::FMT_RGBA8888) + { + cv::cvtColor(img, img, cv::COLOR_RGBA2BGRA); + } + return err::ERR_NONE; + } + + image::Size string_size(std::string text, float scale, int thickness, const std::string &font) + { + const std::string *final_font = &curr_font_name; + int final_font_id = curr_font_id; + // if cont is not empty, use it as font path + if (!font.empty()) + { + // if name in fonts_info + if (fonts_info.find(font) == fonts_info.end()) + { + log::error("font %d not load\n", font.c_str()); + throw std::runtime_error("font not load"); + } + final_font = &font; + final_font_id = get_default_fonts_id(font); + } + cv::Size size; + _get_text_size(size, text, *final_font, final_font_id, scale, thickness); + return Size(size.width, size.height); + } + + std::vector resize_map_pos(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w, int h) { + float scale_x = static_cast(w_out) / w_in; + float scale_y = static_cast(h_out) / h_in; + std::vector result; + + if (fit == image::Fit::FIT_FILL) { + x = static_cast(x * scale_x); + y = static_cast(y * scale_y); + if (w > 0) { + w = static_cast(w * scale_x); + } + if (h > 0) { + h = static_cast(h * scale_y); + } + } else if (fit == image::Fit::FIT_CONTAIN) { + float scale = std::min(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast((w_out - w_in * scale) / 2 + x * scale); + y = static_cast(y * scale); + } else { + x = static_cast(x * scale); + y = static_cast((h_out - h_in * scale) / 2 + y * scale); + } + if (w > 0) { + w = static_cast(w * scale); + h = static_cast(h * scale); + } + } else if (fit == image::Fit::FIT_COVER) { + float scale = std::max(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast(x * scale); + y = static_cast((h_out - h_in * scale) / 2 + y * scale); + } else { + x = static_cast((w_out - w_in * scale) / 2 + x * scale); + y = static_cast(y * scale); + } + if (w > 0) { + w = static_cast(w * scale); + h = static_cast(h * scale); + } + } else { + throw err::Exception(err::ERR_ARGS, "Unsupported fit mode"); + } + + // Populate the result vector + result.push_back(x); + result.push_back(y); + if (w > 0 && h > 0) { + result.push_back(w); + result.push_back(h); + } + return result; + } + + std::vector resize_map_pos_reverse(int w_in, int h_in, int w_out, int h_out, image::Fit fit, int x, int y, int w, int h) { + float scale_x = static_cast(w_out) / w_in; + float scale_y = static_cast(h_out) / h_in; + std::vector result; + + if (fit == image::Fit::FIT_FILL) { + x = static_cast(x / scale_x); + y = static_cast(y / scale_y); + if (w > 0) { + w = static_cast(w / scale_x); + } + if (h > 0) { + h = static_cast(h / scale_y); + } + } else if (fit == image::Fit::FIT_CONTAIN) { + float scale = std::min(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast((x - (w_out - w_in * scale) / 2) / scale); + y = static_cast(y / scale); + } else { + x = static_cast(x / scale); + y = static_cast((y - (h_out - h_in * scale) / 2) / scale); + } + if (w > 0) { + w = static_cast(w / scale); + h = static_cast(h / scale); + } + } else if (fit == image::Fit::FIT_COVER) { + float scale = std::max(scale_x, scale_y); + if (w_out > h_out) { + x = static_cast(x / scale); + y = static_cast((y - (h_out - h_in * scale) / 2) / scale); + } else { + x = static_cast((x - (w_out - w_in * scale) / 2) / scale); + y = static_cast(y * scale); + } + if (w > 0) { + w = static_cast(w / scale); + h = static_cast(h / scale); + } + } else { + throw err::Exception(err::ERR_ARGS, "Unsupported fit mode"); + } + + // Populate the result vector + result.push_back(x); + result.push_back(y); + if (w > 0 && h > 0) { + result.push_back(w); + result.push_back(h); + } + return result; + } + + void _get_cv_format_color(image::Format _format, const image::Color &color_in, int *ch_format, cv::Scalar &cv_color) + { + bool color_alloc = false; + image::Color *color = (image::Color *)&color_in; + if (color->format != _format) + { + color = color->to_format2(_format); + color_alloc = true; + } + switch (_format) + { + case image::FMT_RGB888: + *ch_format = CV_8UC3; + cv_color = cv::Scalar(color->r, color->g, color->b); + break; + case image::FMT_BGR888: + *ch_format = CV_8UC3; + cv_color = cv::Scalar(color->b, color->g, color->r); + break; + case image::FMT_GRAYSCALE: + *ch_format = CV_8UC1; + cv_color = cv::Scalar(color->gray); + break; + case image::FMT_BGRA8888: + *ch_format = CV_8UC4; + cv_color = cv::Scalar(color->b, color->g, color->r, color->alpha * 255); + break; + case image::FMT_RGBA8888: + *ch_format = CV_8UC4; + cv_color = cv::Scalar(color->r, color->g, color->b, color->alpha * 255); + break; + default: + throw std::runtime_error("not support format"); + break; + } + if (color_alloc) + delete color; + } + + int Image::_get_cv_pixel_num(image::Format &format) + { + int pixel_num = 0; + switch (_format) + { + case image::FMT_RGB888: + case image::FMT_BGR888: + pixel_num = CV_8UC3; + break; + case image::FMT_GRAYSCALE: + pixel_num = CV_8UC1; + break; + case image::FMT_BGRA8888: + case image::FMT_RGBA8888: + pixel_num = CV_8UC4; + break; + case image::FMT_RGB565: + case image::FMT_BGR565: + pixel_num = CV_8UC2; + break; + default: + throw std::runtime_error("not support format"); + break; + } + return pixel_num; + } + + std::vector Image::_get_available_roi(std::vector roi, std::vector other_roi) { + int src_x, src_y, src_w, src_h; + + if (other_roi.size() == 0) { + src_x = 0; + src_y = 0; + src_w = _width; + src_h = _height; + } else if (other_roi.size() == 4) { + src_x = other_roi[0]; + src_y = other_roi[1]; + src_w = other_roi[2]; + src_h = other_roi[3]; + } else { + throw err::Exception("other_roi size must be 4"); + } + + if (roi.size() == 0) { + return std::vector{0, 0, _width, _height}; + } else if (roi.size() != 4) { + throw err::Exception("roi size must be 4"); + } + + if (roi[2] <= 0 || roi[3] <= 0) { + throw err::Exception("roi width and height must > 0"); + } + + if ((roi[0] >= (src_x + src_w)) || (roi[1] >= (src_y + src_h)) || (roi[0] + roi[2] <= src_x) || (roi[1] + roi[3] <= src_y)) { + throw err::Exception("roi does not overlap on the image!"); + } + + int x = std::max(roi[0], src_x); + int y = std::max(roi[1], src_y); + int w = std::min(roi[0] + roi[2], src_x + src_w) - x; + int h = std::min(roi[1] + roi[3], src_y + src_h) - y; + return std::vector{x, y, w, h}; + } +} // namespace maix::image diff --git a/vision/components/vision/src/maix_image_ops.cpp b/vision/components/vision/src/maix_image_ops.cpp new file mode 100644 index 0000000..dd3eb17 --- /dev/null +++ b/vision/components/vision/src/maix_image_ops.cpp @@ -0,0 +1,1209 @@ +/** + * @author lxowalle@sipeed + * @copyright Sipeed Ltd 2023- + * @license Apache 2.0 + * @update 2023.9.8: Add framework, create this file. + */ + +#include "maix_image.hpp" +#include "maix_image_util.hpp" +#include "maix_err.hpp" +#include +#include + +namespace maix::image { + void convert_to_imlib_image(image::Image *image, image_t *imlib_image) { + if (!image || !imlib_image) { + return; + } + + pixformat_t imlib_format; + switch (image->format()) { + case Format::FMT_GRAYSCALE: + imlib_format = PIXFORMAT_GRAYSCALE; + break; + case Format::FMT_RGB565: + imlib_format = PIXFORMAT_RGB565; + break; + case Format::FMT_BGR888: // fall through + case Format::FMT_RGB888: + imlib_format = PIXFORMAT_RGB888; + break; + default: + log::error("convert_to_imlib_image format not support: %d", image->format()); + return; + } + + image_init(imlib_image, image->width(), image->height(), imlib_format, image->data_size(), image->data()); + } + + image::Image *Image::mean_pool(int x_div, int y_div, bool copy) { + err::check_bool_raise(x_div > 0 && x_div <= _width && y_div > 0 && y_div <= _height, "mean pool get invalid param"); + + image_t src_img, out_img; + uint8_t *buffer = NULL; + convert_to_imlib_image(this, &src_img); + + out_img.w = src_img.w / x_div; + out_img.h = src_img.h / y_div; + out_img.pixfmt = src_img.pixfmt; + if (copy) { + buffer = (uint8_t *)malloc(out_img.w * out_img.h * image::fmt_size[_format]); + if (!buffer) { + log::error("mean pool malloc failed\r\n"); + return nullptr; + } + out_img.pixels = buffer; + } else { + out_img.pixels = src_img.pixels; + } + + imlib_mean_pool(&src_img, &out_img, x_div, y_div); + + if (copy) { + image::Image *dst = new image::Image(out_img.w, out_img.h, _format, out_img.pixels, -1, true); + return dst; + } else { + _width = out_img.w; + _height = out_img.h; + } + return this; + } + + image::Image *Image::midpoint_pool(int x_div, int y_div, double bias, bool copy) { + if (x_div <= 0 || x_div > _width || y_div <= 0 || y_div > _height) { + log::warn("midpoint pool invalid div: %d, %d", x_div, y_div); + return nullptr; + } + + if (!(_format == Format::FMT_RGB888 || _format == Format::FMT_BGR888 || _format == Format::FMT_GRAYSCALE)) { + log::warn("midpoint pool not support format: %d", _format); + return nullptr; + } + + image::Image *dst; + int _dst_width = _width / x_div; + int _dst_height = _height / y_div; + if (copy) { + dst = new image::Image(_dst_width, _dst_height, _format); + } else { + dst = this; + } + + bias *= 256; + int min_bias = 256 - bias; + int max_bias = bias; + + switch (_format) { + case Format::FMT_GRAYSCALE: + for (int y = 0, yy = _dst_height, yyy = (_height % y_div) / 2; y < yy; y++) { + for (int x = 0, xx = _dst_width, xxx = (_width % x_div) / 2; x < xx; x++) { + int min = 255, max = 0; + uint8_t *src_data = (uint8_t *)_data; + uint8_t *dst_data = (uint8_t *)dst->data(); + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = src_data[(yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)]; + min = std::min(min, pixel); + max = std::max(max, pixel); + } + } + dst_data[y * _dst_width + x] = (min_bias * min + max_bias * max) >> 8; + } + } + break; + case Format::FMT_RGB888: // fall through + case Format::FMT_BGR888: + for (int y = 0, yy = _dst_height, yyy = (_height % y_div) / 2; y < yy; y++) { + for (int x = 0, xx = _dst_width, xxx = (_width % x_div) / 2; x < xx; x++) { + int v0_min = 255, v0_max = 0; + int v1_min = 255, v1_max = 0; + int v2_min = 255, v2_max = 0; + uint8_t *src_data = (uint8_t *)_data; + uint8_t *dst_data = (uint8_t *)dst->data(); + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int v0 = src_data[((yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)) * 3]; + int v1 = src_data[((yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)) * 3 + 1]; + int v2 = src_data[((yyy + (y * y_div) + i) * _width + (xxx + (x * x_div) + j)) * 3 + 2]; + v0_min = std::min(v0_min, v0); + v0_max = std::max(v0_max, v0); + v1_min = std::min(v1_min, v1); + v1_max = std::max(v1_max, v1); + v2_min = std::min(v2_min, v2); + v2_max = std::max(v2_max, v2); + } + } + + dst_data[(y * _dst_width + x) * 3] = ((v0_min * min_bias) + (v0_max * max_bias)) >> 8; + dst_data[(y * _dst_width + x) * 3 + 1] =((v1_min * min_bias) + (v1_max * max_bias)) >> 8; + dst_data[(y * _dst_width + x) * 3 + 2] = ((v2_min * min_bias) + (v2_max * max_bias)) >> 8; + } + } + break; + default: + // should not be here + break; + } + + if (!copy) { + _width = _dst_width; + _height = _dst_height; + } + return dst; + } + + image::Image *Image::compress(int quality) { + return to_jpeg(quality); + } + + err::Err image_zero(image::Image &src, image::Image &mask, bool invert) { + image_t src_img, mask_img; + convert_to_imlib_image(&src, &src_img); + convert_to_imlib_image(&mask, &mask_img); + imlib_zero(&src_img, &mask_img, invert); + + return err::Err::ERR_NONE; + } + + image::Image *Image::clear(image::Image *mask) { + if (!mask) { + memset(_data, 0, _data_size); + } else { + image_zero(*this, *mask, false); + } + + return this; + } + + image::Image *Image::mask_rectange(int x, int y, int w, int h) { + int use_default_setting = 0; + if (x < 0 || y < 0 || w < 0 || h < 0) { + use_default_setting = 1; + } + + if (use_default_setting) { + x = _width / 4; + y = _height / 4; + w = _width / 2; + h = _height / 2; + } + + draw_rect(x, y, w, h, image::COLOR_BLACK, -1); + return this; + } + + image::Image *Image::mask_circle(int x, int y, int radius) { + int use_default_setting = 0; + if (x < 0 || y < 0 || radius < 0) { + use_default_setting = 1; + } + + if (use_default_setting) { + x = _width / 2; + y = _height / 2; + radius = std::min(_width, _height) / 2; + } + + draw_circle(x, y, radius, image::COLOR_BLACK, -1); + return this; + } + + image::Image *Image::mask_ellipse(int x, int y, int radius_x, int radius_y, float rotation_angle_in_degrees) { + int use_default_setting = 0; + if (x < 0 || y < 0 || radius_x < 0 || radius_y < 0) { + use_default_setting = 1; + } + + if (use_default_setting) { + x = _width / 2; + y = _height / 2; + radius_x = _width / 2; + radius_y = _height / 2; + rotation_angle_in_degrees = 0; + } + + draw_ellipse(x, y, radius_x, radius_y, rotation_angle_in_degrees, 0, 360, image::COLOR_BLACK, -1); + return this; + } + + image::Image *Image::binary(std::vector> thresholds, bool invert, bool zero, image::Image *mask, bool to_bitmap, bool copy) { + err::check_bool_raise(thresholds.size() != 0, "You need to set thresholds"); + err::check_bool_raise(to_bitmap == false, "Parameter to_bitmap is not supported"); + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + image_t src_img, mask_img, out_img; + image::Image *dst = nullptr; + if (copy) { + dst = new image::Image(_width, _height, _format); + } else { + dst = this; + } + + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(dst, &out_img); + if (mask) { + convert_to_imlib_image(mask, &mask_img); + } + + imlib_binary(&out_img, &src_img, &thresholds_list, invert, zero, mask ? &mask_img : NULL); + list_free(&thresholds_list); + + return dst; + } + + image::Image *Image::invert() { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_data = (uint32_t *)_data; + for (int i = 0; i < u32_len; i ++) { + u32_data[i] = ~u32_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_data[i] = ~remain_data[i]; + } + + return this; + } + + image::Image *Image::b_and(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_and(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] & u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] & remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::b_nand(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_nand(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] & ~u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] & ~remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::b_or(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_or(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] | u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] | remain_other_data[i]; + } + } + + + return this; + } + + image::Image *Image::b_nor(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_nor(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] | ~u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] | ~remain_other_data[i]; + } + } + + + return this; + } + + image::Image *Image::b_xor(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_xor(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] ^ u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] ^ remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::b_xnor(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + + err::check_bool_raise(other != NULL && other->data() != NULL, "Other image is null"); + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + + if (mask) { + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + err::check_bool_raise(_width == mask->width() && _height == mask->height(), "Mask image size is not match source image"); + convert_to_imlib_image(mask, &mask_img); + imlib_b_xnor(&src_img, NULL, other ? &other_img : NULL, 0, mask ? &mask_img : NULL); + } else { + int remain_len = _data_size % 4; + int u32_len = (_data_size - remain_len) >> 2; + uint8_t *remain_src_data = (uint8_t *)((uint8_t *)_data + (u32_len << 2)); + uint32_t *u32_src_data = (uint32_t *)_data; + uint8_t *remain_other_data = (uint8_t *)((uint8_t *)other->data() + (u32_len << 2)); + uint32_t *u32_other_data = (uint32_t *)other->data(); + for (int i = 0; i < u32_len; i ++) { + u32_src_data[i] = u32_src_data[i] ^ ~u32_other_data[i]; + } + + for (int i = 0; i < remain_len; i ++) { + remain_src_data[i] = remain_src_data[i] ^ ~remain_other_data[i]; + } + } + + return this; + } + + image::Image *Image::awb(bool max) { + image_t src_img; + Image *rgb565_img = nullptr; + if (_format == image::FMT_RGB888 || _format == image::FMT_BGR888) { + rgb565_img = to_format(image::FMT_RGB565); + convert_to_imlib_image(rgb565_img, &src_img); + } else { + log::warn("awb not support format: %d", _format); + return this; + } + + imlib_awb(&src_img, max); + + if (_format == image::FMT_RGB888 || _format == image::FMT_BGR888) { + Image *rgb888_img = rgb565_img->to_format(image::FMT_RGB888); + memcpy(_data, rgb888_img->data(), _data_size); + delete rgb888_img; + delete rgb565_img; + } + return this; + } + + image::Image *Image::ccm(std::vector &matrix) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + + float ccm[12] = {}; + float *matrix_data = (float *)matrix.data(); + size_t len = matrix.size(); + if (len != 9 && len != 12) { + log::error("ccm matrix size not match: %d", len); + return this; + } + + for (size_t i = 0; i < len; i ++) { + ccm[i] = matrix_data[i]; + } + + imlib_ccm(&src_img, ccm, len == 12); + return this; + } + + image::Image *Image::gamma(double gamma, double contrast, double brightness) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + + imlib_gamma(&src_img, gamma, contrast, brightness); + return this; + } + + image::Image *Image::gamma_corr(double gamma, double contrast, double brightness) { + return this->gamma(gamma, contrast, brightness); + } + + image::Image *Image::negate(void) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_negate(&src_img); + return this; + } + + image::Image *Image::replace(image::Image *other, bool hmirror, bool vflip, bool transpose, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (other) { + err::check_bool_raise(_format == other->format(), "Other image format is not match source image"); + err::check_bool_raise(_width == other->width() && _height == other->height(), "Other image size is not match source image"); + convert_to_imlib_image(other, &other_img); + } + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + } + + if (other && mask) { + imlib_replace(&src_img, NULL, &other_img, 0, hmirror, vflip, transpose, &mask_img); + } else if (other && !mask) { + imlib_replace(&src_img, NULL, &other_img, 0, hmirror, vflip, transpose, NULL); + } else if (!other && mask) { + imlib_replace(&src_img, NULL, &src_img, 0, hmirror, vflip, transpose, &mask_img); + } else { + imlib_replace(&src_img, NULL, &src_img, 0, hmirror, vflip, transpose, NULL); + } + + _width = src_img.w; + _height = src_img.h; + + return this; + } + + image::Image *Image::set(image::Image *other, bool hmirror, bool vflip, bool transpose, image::Image *mask) { + return this->replace(other, hmirror, vflip, transpose, mask); + } + + image::Image *Image::add(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_add(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_add(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::sub(image::Image *other, bool reverse, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_sub(&src_img, NULL, &other_img, 0, reverse, &mask_img); + } else { + imlib_sub(&src_img, NULL, &other_img, 0, reverse, NULL); + } + return this; + } + + image::Image *Image::mul(image::Image *other, bool invert, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_mul(&src_img, NULL, &other_img, 0, invert, &mask_img); + } else { + imlib_mul(&src_img, NULL, &other_img, 0, invert, NULL); + } + return this; + } + + image::Image *Image::div(image::Image *other, bool invert, bool mod, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_div(&src_img, NULL, &other_img, 0, invert, mod, &mask_img); + } else { + imlib_div(&src_img, NULL, &other_img, 0, invert, mod, NULL); + } + return this; + } + + image::Image *Image::min(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_min(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_min(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::max(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_max(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_max(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::difference(image::Image *other, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_difference(&src_img, NULL, &other_img, 0, &mask_img); + } else { + imlib_difference(&src_img, NULL, &other_img, 0, NULL); + } + return this; + } + + image::Image *Image::blend(image::Image *other, int alpha, image::Image *mask) { + image_t src_img, other_img, mask_img; + convert_to_imlib_image(this, &src_img); + convert_to_imlib_image(other, &other_img); + + if (alpha < 0 || alpha > 256) { + log::error("alpha value not valid: %d", alpha); + return this; + } + + float alpha_f = alpha / 256.0; + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_blend(&src_img, NULL, &other_img, 0, alpha_f, &mask_img); + } else { + imlib_blend(&src_img, NULL, &other_img, 0, alpha_f, NULL); + } + return this; + } + + image::Image *Image::histeq(bool adaptive, int clip_limit, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (adaptive) { + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_clahe_histeq(&src_img, clip_limit, &mask_img); + } else { + imlib_clahe_histeq(&src_img, clip_limit, NULL); + } + + } else { + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_histeq(&src_img, &mask_img); + } else { + imlib_histeq(&src_img, NULL); + } + } + return this; + } + + image::Image *Image::mean(int size, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_mean_filter(&src_img, size, threshold, offset, invert, &mask_img); + } else { + imlib_mean_filter(&src_img, size, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::median(int size, double percentile, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_median_filter(&src_img, size, percentile, threshold, offset, invert, &mask_img); + } else { + imlib_median_filter(&src_img, size, percentile, threshold, offset, invert, NULL); + } + + return this; + } + + image::Image *Image::mode(int size, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_mode_filter(&src_img, size, threshold, offset, invert, &mask_img); + } else { + imlib_mode_filter(&src_img, size, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::midpoint(int size, double bias, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_midpoint_filter(&src_img, size, bias, threshold, offset, invert, &mask_img); + } else { + imlib_midpoint_filter(&src_img, size, bias, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::morph(int size, std::vector kernel, float mul, float add, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + int *kernel_data = (int *)kernel.data(); + size_t len = kernel.size(); + + err::check_bool_raise(len != 0, "You need to config values of kernel"); + err::check_bool_raise(len == ((size * 2) + 1) * ((size * 2) + 1), "Kernel size not match"); + + int m = 0; + for (size_t i = 0; i < len; i++) { + m += kernel_data[i]; + } + if (m == 0) { + m = 1; + } + + if (mul < 0) { + mul = 1.0f / m; + } + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_morph(&src_img, size, kernel_data, mul, add, threshold, offset, invert, &mask_img); + } else { + imlib_morph(&src_img, size, kernel_data, mul, add, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::gaussian(int size, bool unsharp, float mul, float add, bool threshold, int offset, bool invert, image::Image *mask) { + std::vector pascal; + std::vector kernel; + int m = 0; + int k_2 = size * 2; + int n = k_2 + 1; + + pascal.resize(n); + pascal[0] = 1; + for (int i = 0; i < k_2; i ++) { + pascal[i + 1] = (pascal[i] * (k_2 - i)) / (i + 1); + } + + kernel.resize(n * n); + for (int i = 0; i < n; i ++) { + for (int j = 0; j < n; j ++) { + int temp = pascal[i] * pascal[j]; + kernel[(i * n) + j] = temp; + m += temp; + } + } + + if (unsharp == false) { + kernel[((n / 2) * n) + (n / 2)] -= m * 2; + m = -m; + } + + if (mul < 0) { + mul = 1.0f / m; + } + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, &mask_img); + } else { + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, NULL); + } + + return this; + } + + image::Image *Image::laplacian(int size, bool sharpen, float mul, float add, bool threshold, int offset, bool invert, image::Image *mask) { + std::vector pascal; + std::vector kernel; + int m = 0; + int k_2 = size * 2; + int n = k_2 + 1; + + pascal.resize(n); + pascal[0] = 1; + for (int i = 0; i < k_2; i ++) { + pascal[i + 1] = (pascal[i] * (k_2 - i)) / (i + 1); + } + + kernel.resize(n * n); + for (int i = 0; i < n; i ++) { + for (int j = 0; j < n; j ++) { + int temp = pascal[i] * pascal[j]; + kernel[(i * n) + j] = -temp; + m += temp; + } + } + + kernel[((n / 2) * n) + (n / 2)] += m; + m = kernel[((n / 2) * n) + (n / 2)]; + + if (sharpen == false) { + kernel[((n / 2) * n) + (n / 2)] += m; + } + + if (mul < 0) { + mul = 1.0f / m; + } + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, &mask_img); + } else { + imlib_morph(&src_img, size, kernel.data(), mul, add, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::bilateral(int size, double color_sigma, double space_sigma, bool threshold, int offset, bool invert, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_bilateral_filter(&src_img, size, color_sigma, space_sigma, threshold, offset, invert, &mask_img); + } else { + imlib_bilateral_filter(&src_img, size, color_sigma, space_sigma, threshold, offset, invert, NULL); + } + return this; + } + + image::Image *Image::linpolar(bool reverse) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_logpolar(&src_img, true, reverse); + return this; + } + + image::Image *Image::logpolar(bool reverse) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_logpolar(&src_img, false, reverse); + return this; + } + + image::Image *Image::lens_corr(double strength, double zoom, double x_corr, double y_corr) { + if (_width % 2 || _height % 2) { + log::error("lens_corr image size must be even"); + return this; + } + + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_lens_corr(&src_img, strength, zoom, x_corr, y_corr); + return this; + } + + image::Image *Image::rotation_corr(double x_rotation, double y_rotation, double z_rotation, double x_translation, double y_translation, double zoom, double fov, std::vector corners) { + image_t src_img; + convert_to_imlib_image(this, &src_img); + imlib_rotation_corr(&src_img, x_rotation, y_rotation, z_rotation, x_translation, y_translation, zoom, fov, (float *)corners.data()); + return this; + } + + std::map> Image::get_histogram(std::vector> thresholds, bool invert, std::vector roi, int bins, int l_bins, int a_bins, int b_bins, image::Image *difference) { + std::map> result = std::map>(); + image_t src_img, *other_img = NULL; + convert_to_imlib_image(this, &src_img); + if (difference) { + other_img = (image_t *)malloc(sizeof(image_t)); + if (!other_img) { + log::error("malloc image_t failed"); + return result; + } + convert_to_imlib_image(difference, other_img); + } + + rectangle_t roi_rect; + std::vector avail_roi = _get_available_roi(roi); + roi_rect.x = avail_roi[0]; + roi_rect.y = avail_roi[1]; + roi_rect.w = avail_roi[2]; + roi_rect.h = avail_roi[3]; + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + histogram_t hist; + switch (_format) { + case image::FMT_GRAYSCALE: + // bins must be greater than 1 + bins = bins >= 2 ? bins : COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1; + hist.LBinCount = bins; + hist.ABinCount = 0; + hist.BBinCount = 0; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + case image::FMT_RGB888: + bins = bins >= 2 ? bins : COLOR_L_MAX - COLOR_L_MIN + 1; + l_bins = l_bins >= 2 ? l_bins : bins; + a_bins = a_bins >= 2 ? a_bins : COLOR_A_MAX - COLOR_A_MIN + 1; + b_bins = b_bins >= 2 ? b_bins : COLOR_B_MAX - COLOR_B_MIN + 1; + hist.LBinCount = l_bins; + hist.ABinCount = a_bins; + hist.BBinCount = b_bins; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = (float *)malloc(hist.ABinCount * sizeof(float)); + hist.BBins = (float *)malloc(hist.BBinCount * sizeof(float)); + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + default: + log::error("format not support: %d", _format); + return result; + } + + std::vector l_bins_data(hist.LBins, hist.LBins + hist.LBinCount); + std::vector a_bins_data(hist.ABins, hist.ABins + hist.ABinCount); + std::vector b_bins_data(hist.BBins, hist.BBins + hist.BBinCount); + result["L"] = l_bins_data; + result["A"] = a_bins_data; + result["B"] = b_bins_data; + + list_free(&thresholds_list); + if (difference && other_img) free(other_img); + if (hist.LBins) free(hist.LBins); + if (hist.ABins) free(hist.ABins); + if (hist.BBins) free(hist.BBins); + return result; + } + + image::Statistics Image::get_statistics(std::vector> thresholds, bool invert, std::vector roi, int bins, int l_bins, int a_bins, int b_bins, image::Image *difference) { + image::Statistics result = image::Statistics(); + image_t src_img, *other_img = NULL; + convert_to_imlib_image(this, &src_img); + if (difference) { + other_img = (image_t *)malloc(sizeof(image_t)); + if (!other_img) { + log::error("malloc image_t failed"); + return result; + } + convert_to_imlib_image(difference, other_img); + } + + rectangle_t roi_rect; + std::vector avail_roi = _get_available_roi(roi); + roi_rect.x = avail_roi[0]; + roi_rect.y = avail_roi[1]; + roi_rect.w = avail_roi[2]; + roi_rect.h = avail_roi[3]; + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + histogram_t hist = {0}; + switch (_format) { + case image::FMT_GRAYSCALE: + // bins must be greater than 1 + bins = bins >= 2 ? bins : COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1; + hist.LBinCount = bins; + hist.ABinCount = 0; + hist.BBinCount = 0; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + case image::FMT_RGB888: + bins = bins >= 2 ? bins : COLOR_L_MAX - COLOR_L_MIN + 1; + l_bins = l_bins >= 2 ? l_bins : bins; + a_bins = a_bins >= 2 ? a_bins : COLOR_A_MAX - COLOR_A_MIN + 1; + b_bins = b_bins >= 2 ? b_bins : COLOR_B_MAX - COLOR_B_MIN + 1; + hist.LBinCount = l_bins; + hist.ABinCount = a_bins; + hist.BBinCount = b_bins; + hist.LBins = (float *)malloc(hist.LBinCount * sizeof(float)); + hist.ABins = (float *)malloc(hist.ABinCount * sizeof(float)); + hist.BBins = (float *)malloc(hist.BBinCount * sizeof(float)); + imlib_get_histogram(&hist, &src_img, &roi_rect, &thresholds_list, invert, other_img); + break; + default: + log::error("format not support: %d", _format); + return result; + } + + statistics_t stats = {0}; + imlib_get_statistics(&stats, (pixformat_t)src_img.pixfmt, &hist); + + std::vector l_statistics = {stats.LMean, stats.LMedian, stats.LMode, stats.LSTDev, stats.LMin, stats.LMax, stats.LLQ, stats.LUQ}; + std::vector a_statistics = {stats.AMean, stats.AMedian, stats.AMode, stats.ASTDev, stats.AMin, stats.AMax, stats.LLQ, stats.AUQ}; + std::vector b_statistics = {stats.BMean, stats.BMedian, stats.BMode, stats.BSTDev, stats.BMin, stats.BMax, stats.LLQ, stats.BUQ}; + result = image::Statistics( _format, + l_statistics, + a_statistics, + b_statistics); + + list_free(&thresholds_list); + if (difference && other_img) free(other_img); + if (hist.LBins) free(hist.LBins); + if (hist.ABins) free(hist.ABins); + if (hist.BBins) free(hist.BBins); + return result; + } + + std::vector Image::get_regression(std::vector> thresholds, bool invert, std::vector roi, int x_stride, int y_stride, int area_threshold, int pixels_threshold, bool robust) { + std::vector lines = std::vector(); + image_t src_img; + if (_format != image::FMT_GRAYSCALE && _format != image::FMT_RGB888 && _format != image::FMT_RGB565) { + log::error("get_regression only support GRAYSCALE RGB888 RGB565 format!\n"); + } + convert_to_imlib_image(this, &src_img); + + rectangle_t roi_rect; + std::vector avail_roi = _get_available_roi(roi); + roi_rect.x = avail_roi[0]; + roi_rect.y = avail_roi[1]; + roi_rect.w = avail_roi[2]; + roi_rect.h = avail_roi[3]; + + list_t thresholds_list; + list_init(&thresholds_list, sizeof(color_thresholds_list_lnk_data_t)); + _convert_to_lab_thresholds(thresholds, &thresholds_list); + + find_lines_list_lnk_data_t out; + bool res = imlib_get_regression(&out, &src_img, &roi_rect, x_stride, + y_stride, &thresholds_list, invert, area_threshold, pixels_threshold, robust); + if (true == res) { + Line line = Line(out.line.x1, out.line.y1, out.line.x2, out.line.y2, out.magnitude, out.theta, out.rho); + lines.push_back(line); + } + + list_free(&thresholds_list); + return lines; + } + + image::Image *Image::flood_fill(int x, int y, float seed_threshold, float floating_threshold, image::Color color , bool invert, bool clear_background, image::Image *mask) { + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_flood_fill(&src_img, x, y, seed_threshold, floating_threshold, color.hex(), invert, clear_background, &mask_img); + } else { + imlib_flood_fill(&src_img, x, y, seed_threshold, floating_threshold, color.hex(), invert, clear_background, NULL); + } + return this; + } + + image::Image *Image::erode(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "erode size must be greater than 0"); + err::check_bool_raise(threshold == -1 || threshold >= 0, "erode threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (threshold == -1) { + threshold = ((size * 2) + 1) * ((size * 2) + 1) - 1; + } + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_erode(&src_img, size, threshold, &mask_img); + } else { + imlib_erode(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::dilate(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "dilate size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "dilate threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_dilate(&src_img, size, threshold, &mask_img); + } else { + imlib_dilate(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::open(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "open size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "open threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_open(&src_img, size, threshold, &mask_img); + } else { + imlib_open(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::close(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "close size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "close threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_close(&src_img, size, threshold, &mask_img); + } else { + imlib_close(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::top_hat(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "top_hat size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "top_hat threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_top_hat(&src_img, size, threshold, &mask_img); + } else { + imlib_top_hat(&src_img, size, threshold, NULL); + } + return this; + } + + image::Image *Image::black_hat(int size, int threshold, image::Image *mask) { + err::check_bool_raise(size > 0, "black_hat size must be greater than 0"); + err::check_bool_raise(threshold >= 0, "black_hat threshold must be greater than or equal to 0"); + + image_t src_img, mask_img; + convert_to_imlib_image(this, &src_img); + + if (mask) { + convert_to_imlib_image(mask, &mask_img); + imlib_black_hat(&src_img, size, threshold, &mask_img); + } else { + imlib_black_hat(&src_img, size, threshold, NULL); + } + return this; + } +} \ No newline at end of file